Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# CHANGELOG

## Unreleased
### Added
* Added support for `StructuredText` inline Entry include resolution.

## v1.10.1
### Added
Expand Down
17 changes: 9 additions & 8 deletions contentful/content_type_field_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,8 @@ class StructuredTextField(BasicField):
"""

def _coerce_link(self, value, includes=None, errors=None, resources=None, default_locale='en-US', locale=None):
if 'data' not in value or 'target' not in value['data']:
return value

if not('target' in value['data'] and value['data']['target']['sys']['type'] == 'Link'):
return value
if value['data']['target']['sys']['type'] != 'Link':
return value['data']

if unresolvable(value['data']['target'], errors):
return None
Expand Down Expand Up @@ -184,8 +181,9 @@ def _coerce_block(self, value, includes=None, errors=None, resources=None, defau
return value

invalid_nodes = []
coerced_nodes = {}
for index, node in enumerate(value['content']):
if node['nodeClass'] == 'block' and 'data' in node:
if node.get('data', None) and node['data'].get('target', None):
link = self._coerce_link(
node,
includes=includes,
Expand All @@ -198,8 +196,8 @@ def _coerce_block(self, value, includes=None, errors=None, resources=None, defau
node['data'] = link
else:
invalid_nodes.append(index)
elif node['nodeClass'] == 'block' and 'content' in node:
node['content'] = self._coerce_block(
if node.get('content', None):
coerced_nodes[index] = self._coerce_block(
node,
includes=includes,
errors=errors,
Expand All @@ -208,6 +206,9 @@ def _coerce_block(self, value, includes=None, errors=None, resources=None, defau
locale=locale
)

for node_index, coerced_node in coerced_nodes.items():
value['content'][node_index] = coerced_node

for node_index in invalid_nodes:
del value['content'][node_index]

Expand Down
122 changes: 122 additions & 0 deletions fixtures/fields/structured_text_lists_with_embeds.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer 6256b8ef7d66805ca41f2728271daf27e8fa6055873b802a813941a0fe696248]
Connection: [keep-alive]
Content-Type: [application/vnd.contentful.delivery.v1+json]
User-Agent: [python-requests/2.12.1]
X-Contentful-User-Agent: [sdk contentful.py/1.10.1; platform python/3.6.3; os
macOS/16.7.0;]
method: GET
uri: https://cdn.contentful.com/spaces/jd7yc4wnatx3/environments/master/content_types
response:
body:
string: !!binary |
H4sIAAAAAAAAA91VPW/CMBDd+RXIc0Eh0DbKRis6VV3I1IrBxIfk4jipbSgp4r/Xdr6cqKhUReqH
F3xn3727y3tm3+v3kcwlCvt7vdWGyjPQFpoKgXOkfYcLc0elCjPt960l1zTThmcNRhOqtDXyCpsq
SEzCJ5uwSNtBsUgyw7GBqm4UTqcW47DOqqZ7ytfIYDZLo/N1VNY8txk7Fygx7TyT6zyevHKsdmPT
VbUO9d72WSxUxECyBEKAOBnrUm5TroAri+wExgKwAjI180C+NwoGXjDw/WjkhZNJ6F0Pg+Dq0Q3Y
ZORrAcC3VKQ80eAnza5oJcFSgeiO5tS5zhzMT2cnYEslTblhRHm5Hi0iVGYM53cUmP0sy5TkdVWI
48Ryb9YdPCIgY0EzVeRFTcjKZGrYZr5fi09F+y0Yc6eGunELsCfVUOZ5skxZa2SIpTFm9E1TIuyv
MJPgcg0JeNlQceRQd46X7MhhqhWkaVNlbThZ7hb2txzjH1aUVBFIdSY9jcNLbxh4/sl6+jDgf+nJ
KqgiZU3yeXvs31VTC6SlpgcX/vepqVHrud4IJTax2mjNR7BzeW3+Fn/2rdAvxqJ36L0DMymmQeEH
AAA=
headers:
Accept-Ranges: [bytes]
Access-Control-Allow-Headers: ['Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature']
Access-Control-Allow-Methods: ['GET,HEAD,OPTIONS']
Access-Control-Allow-Origin: ['*']
Access-Control-Expose-Headers: [Etag]
Access-Control-Max-Age: ['86400']
Age: ['0']
Cache-Control: [max-age=0]
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['458']
Content-Type: [application/vnd.contentful.delivery.v1+json]
Contentful-Api: [cda_cached]
Date: ['Wed, 12 Sep 2018 10:07:49 GMT']
ETag: [W/"df23ab55e257a1f3eb634ed5ee9759ec"]
Server: [Contentful]
Vary: [Accept-Encoding]
Via: [1.1 varnish]
X-Cache: [MISS]
X-Cache-Hits: ['0']
X-Content-Type-Options: [nosniff]
X-Contentful-Region: [us-east-1]
X-Contentful-Request-Id: [ad7e4567861099ccf465ff7866201ef8]
X-Served-By: [cache-hhn1549-HHN]
X-Timer: ['S1536746869.280947,VS0,VE366']
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [Bearer 6256b8ef7d66805ca41f2728271daf27e8fa6055873b802a813941a0fe696248]
Connection: [keep-alive]
Content-Type: [application/vnd.contentful.delivery.v1+json]
User-Agent: [python-requests/2.12.1]
X-Contentful-User-Agent: [sdk contentful.py/1.10.1; platform python/3.6.3; os
macOS/16.7.0;]
method: GET
uri: https://cdn.contentful.com/spaces/jd7yc4wnatx3/environments/master/entries?sys.id=6NGLswCREsGA28kGouScyY
response:
body:
string: !!binary |
H4sIAAAAAAAAA+0ay27bOPCerxB0XruS67i2b0GQDYLNtmidoGgXPTASHavWwyvRbowi/14+RJqk
SEppjcBNrUNiUcOZ4XBeHM73E8/zq23lT73v+Cd+QdsVxG/+WVmCrY/HHv8iMKhAIMXjIX2rlskK
vwT0JU2yBJFPAXtPEMwIwv8oQoZWo0IpVSsQEVIcgg1KvJABOsh5uk7ypU9o7h5MPV/e1DzPKEYN
IInJcr7Gb7bR8FsO0MNrsir+PIrfdJ3s8dmc0dvL6+rb+YeL6vJsMF5eFutZtP0k4ReMXeSo3Mof
ohICBOMzIhd/EITjXjDpheFNMJkOg+kw7J++Dj/LE9ar2DBhcBMG02A4DUf9yUSdAPNNUhZ5BnNC
o12GbEkZqBAsdRF1le+FRLNVhiXcJFVS5Ji7gSTaqMgR5rnesna+u/J2LuE1akCFbmCF2vc+LSKQ
UhOAee92xicI9fDnCUzjncUQlfFzkNEphIR3lWOlhN4MredzD+SxR/S2knf7roi3+q7VgsHDzHC4
MsoiorSwogAyWVLYWm1tKMhnHQ1FlYESM4YpflFFVuPbgHTNnIFHzMxDhaepTg2YFzHkRojgA7JD
naegIhR9CqaYMsHVWJOFcS6DBgK6rHWZEBILhFbV9NWr+6K4T2E/KrImQSNJjMIlSkLDJE5K2y1S
CiLEyhgzCosCdhKrgGwRLYHbeTvyxh7z1sukF9i9lkQBjJxSLgXthOp+U85dN9Ytvp3o7KzsRw81
xWoISdmbFSjBfQlWiwZXqnDusG9ZqrLR5KKr1T6N3eo3qAY9i8LPigx6/68LHBo9q5+g7ByI6tt3
VnApVN+wuwRItzm3KlHhHLQa/ZYWutBznlrFXJv3+5um0+I7WT0B0n0SGeNPh3BH6YiQt86LMoYl
tn9yTGgouoK7sw+gJJQwZM4uOHbdJPl4wzTFMmVW3D6hyYvFLxBAEyNGJhR/mCYV6lnF1xpyKOFm
vmfaZnv4ocv86ZhxgIoJ8gItYOkdFZQr/VFBHa7PR6C8h/rRWxYdtRC5tKJ/5O91tWE4KYv59Wbz
cP4uufr3KhllyzFInB4SE3Acj3V6SqmEVSyMxyc2z+Sa3F8MPoVNaPUTBMzkfoSE3DmHABNRxim1
7rklQfx8cQVmdzCOYdzD5ZFy22Nho+mna5kq4c4VYswYbOJ2e3wqkDavT4Bs6CmCP2I3X0SW8KRT
sIicPZKgNEywNS855tvMtPdqO/OkxBVJa7LIKNZerrVyokB3KDcReFscMaa51D0cc21DWG71ujat
OUSHXkEcQ44nQJt5GE3jhZ4ADarOvMz+E+zTz39P3g//GSUfZ5+ubgfvbqvgFrxvxCnu5Dgb9d2i
4e5RhcSeS76LPCbY7B7ZWeYV/v5ZgskxwVY0tuUyhcMe6nHpz0uw68rp4afX7gNei95107cux3I9
9WzEVYXUgV8RdJUpvWIzBrU9CrZZUzCln7uDvjHKm6bQgNBeHeZVrwhfPRvXSvEUd19hRDtwiPCa
d9IESNcRMtbQE4pNFJfYFbdnpb1HOT/p/G13iKqxGIpFshSU1StriYtoTfuN5O1XcQsQwTlHzf6T
v5SAn+RRuo6h1HnGMqZd+8tOPwxlVGP3GEZrLri6y6StPWSEXWcXmapHSiGje3XX2k2GyRv6yca9
AW0PG55OB0F/ND6V+8nwFENHmT5lpE2x95TZJevoK8OTnOVpLV/e9bMpai/rpipZqcOMtiXyh5+V
DT1m9nV059TeaSb0hGeaWq+HxKO6EkvPmXJPZ+o7wwTrVjL/oq4ee+GOqLA9Lp0XZVIdznO/YFKT
/pvwiSZlmnI0Kc83Hp0V4z9wkxoYTIra8hf89/Hk8eQHimwRyEctAAA=
headers:
Accept-Ranges: [bytes]
Access-Control-Allow-Headers: ['Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature']
Access-Control-Allow-Methods: ['GET,HEAD,OPTIONS']
Access-Control-Allow-Origin: ['*']
Access-Control-Expose-Headers: [Etag]
Access-Control-Max-Age: ['86400']
Age: ['0']
Cache-Control: [max-age=0]
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['1409']
Content-Type: [application/vnd.contentful.delivery.v1+json]
Contentful-Api: [cda_cached]
Date: ['Wed, 12 Sep 2018 10:07:49 GMT']
ETag: [W/"ad6896d8b4604d1307178a506ce1b102"]
Server: [Contentful]
Vary: [Accept-Encoding]
Via: [1.1 varnish]
X-Cache: [MISS]
X-Cache-Hits: ['0']
X-Content-Type-Options: [nosniff]
X-Contentful-Region: [us-east-1]
X-Contentful-Request-Id: [f59d940c76009e6aea7642be6cee10d5]
X-Served-By: [cache-hhn1520-HHN]
X-Timer: ['S1536746870.776895,VS0,VE197']
status: {code: 200, message: OK}
version: 1
55 changes: 50 additions & 5 deletions tests/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from contentful.client import Client
from contentful.content_type_cache import ContentTypeCache
from contentful.errors import EntryNotFoundError
from contentful.utils import ConfigurationException, NotSupportedException
from contentful.utils import ConfigurationException
from contentful.entry import Entry


Expand Down Expand Up @@ -111,7 +111,7 @@ def test_entry_incoming_references(self):
def test_entry_incoming_references_with_query(self):
client = Client('cfexampleapi', 'b4c0n73n7fu1', content_type_cache=False)
entry = client.entry('nyancat')
entries = entry.incoming_references(client, { 'content_type': 'cat', 'select': ['fields.name'] })
entries = entry.incoming_references(client, {'content_type': 'cat', 'select': ['fields.name']})
self.assertEqual(len(entries), 1)
self.assertEqual(str(entries[0]), "<Entry[cat] id='happycat'>")
self.assertEqual(entries[0].fields(), {'name': 'Happy Cat'})
Expand All @@ -136,7 +136,10 @@ def test_client_asset(self):
client = Client('cfexampleapi', 'b4c0n73n7fu1', content_type_cache=False)
asset = client.asset('nyancat')

self.assertEqual(str(asset), "<Asset id='nyancat' url='//images.contentful.com/cfexampleapi/4gp6taAwW4CmSgumq2ekUm/9da0cd1936871b8d72343e895a00d611/Nyan_cat_250px_frame.png'>")
self.assertEqual(
str(asset),
"<Asset id='nyancat' url='//images.contentful.com/cfexampleapi/4gp6taAwW4CmSgumq2ekUm/9da0cd1936871b8d72343e895a00d611/Nyan_cat_250px_frame.png'>"
)

@vcr.use_cassette('fixtures/client/locales_on_environment.yaml')
def test_client_locales_on_environment(self):
Expand All @@ -151,14 +154,22 @@ def test_client_assets(self):
client = Client('cfexampleapi', 'b4c0n73n7fu1', content_type_cache=False)
assets = client.assets()

self.assertEqual(str(assets[0]), "<Asset id='1x0xpXu4pSGS4OukSyWGUK' url='//images.contentful.com/cfexampleapi/1x0xpXu4pSGS4OukSyWGUK/cc1239c6385428ef26f4180190532818/doge.jpg'>")
self.assertEqual(
str(assets[0]),
"<Asset id='1x0xpXu4pSGS4OukSyWGUK' url='//images.contentful.com/cfexampleapi/1x0xpXu4pSGS4OukSyWGUK/cc1239c6385428ef26f4180190532818/doge.jpg'>"
)

@vcr.use_cassette('fixtures/client/sync.yaml')
def test_client_sync(self):
client = Client('cfexampleapi', 'b4c0n73n7fu1', content_type_cache=False)
sync = client.sync({'initial': True})

self.assertEqual(str(sync), "<SyncPage next_sync_token='w5ZGw6JFwqZmVcKsE8Kow4grw45QdybCnV_Cg8OASMKpwo1UY8K8bsKFwqJrw7DDhcKnM2RDOVbDt1E-wo7CnDjChMKKGsK1wrzCrBzCqMOpZAwOOcOvCcOAwqHDv0XCiMKaOcOxZA8BJUzDr8K-wo1lNx7DnHE'>")
self.assertEqual(
str(sync),
"<SyncPage next_sync_token='{0}'>".format(
'w5ZGw6JFwqZmVcKsE8Kow4grw45QdybCnV_Cg8OASMKpwo1UY8K8bsKFwqJrw7DDhcKnM2RDOVbDt1E-wo7CnDjChMKKGsK1wrzCrBzCqMOpZAwOOcOvCcOAwqHDv0XCiMKaOcOxZA8BJUzDr8K-wo1lNx7DnHE'
)
)
self.assertEqual(str(sync.items[0]), "<Entry[1t9IbcfdCk6m04uISSsaIK] id='5ETMRzkl9KM4omyMwKAOki'>")

@vcr.use_cassette('fixtures/client/sync_environments.yaml')
Expand Down Expand Up @@ -398,3 +409,37 @@ def test_structured_text_field(self):
expected_entry_occurrances -= 1
embedded_entry_index += 1
self.assertEqual(expected_entry_occurrances, 0)

@vcr.use_cassette('fixtures/fields/structured_text_lists_with_embeds.yaml')
def test_structured_text_field_with_embeds_in_lists(self):
client = Client(
'jd7yc4wnatx3',
'6256b8ef7d66805ca41f2728271daf27e8fa6055873b802a813941a0fe696248',
gzip_encoded=False
)

entry = client.entry('6NGLswCREsGA28kGouScyY')

# Hyperlink data is conserved
self.assertEqual(entry.body['content'][0], {
'data': {},
'content': [
{'marks': [], 'value': 'A link to ', 'nodeType': 'text', 'nodeClass': 'text'},
{
'data': {'uri': 'https://google.com'},
'content': [{'marks': [], 'value': 'google', 'nodeType': 'text', 'nodeClass': 'text'}],
'nodeType': 'hyperlink',
'nodeClass': 'inline'
},
{'marks': [], 'value': '', 'nodeType': 'text', 'nodeClass': 'text'}
],
'nodeType': 'paragraph',
'nodeClass': 'block'
})

# Unordered lists and ordered lists can contain embedded entries
self.assertEqual(entry.body['content'][3]['nodeType'], 'unordered-list')
self.assertEqual(str(entry.body['content'][3]['content'][2]['content'][0]['data']), "<Entry[embedded] id='49rofLvvxCOiIMIi6mk8ai'>")

self.assertEqual(entry.body['content'][4]['nodeType'], 'ordered-list')
self.assertEqual(str(entry.body['content'][4]['content'][2]['content'][0]['data']), "<Entry[embedded] id='5ZF9Q4K6iWSYIU2OUs0UaQ'>")