Skip to content

Commit 172b6bb

Browse files
authored
Merge pull request #32 from tailsdotcom/master
Add support for composite slices
2 parents 888e262 + 16f33ec commit 172b6bb

File tree

4 files changed

+233
-8
lines changed

4 files changed

+233
-8
lines changed

prismic/fragments.py

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,24 @@ def as_html(self, link_resolver):
178178
html.append("""<section data-field="%s">""" % key)
179179
html.append(self.fragment_to_html(fragment, link_resolver))
180180
html.append("""</section>""")
181+
181182
return ''.join(html)
182183

184+
def __getitem__(self, name):
185+
return self.fragments[name]
186+
187+
def __iter__(self):
188+
return iter(self.fragments)
189+
190+
def keys(self):
191+
return self.fragments.keys()
192+
193+
def items(self):
194+
return self.fragments.items()
195+
196+
def values(self):
197+
return self.fragments.values()
198+
183199
# Links
184200

185201
class Link(FragmentElement):
@@ -224,13 +240,21 @@ def __init__(self, value):
224240
def as_html(self, documentlink_resolver, html_serializer=None):
225241
"""Get the DocumentLink as html.
226242
227-
:param documentlink_resolver: A resolver function will be called with :class:`prismic.fragments.Fragment.DocumentLink <prismic.fragments.Fragment.DocumentLink>` object as argument. Resolver function should return a string, the local url to the document.
243+
:param documentlink_resolver: A resolver function will be called with
244+
:class:`prismic.fragments.Fragment.DocumentLink <prismic.fragments.Fragment.DocumentLink>` object as
245+
argument. Resolver function should return a string, the local url to the document.
228246
"""
229-
return """<a href="%(link)s">%(slug)s</a>""" % {"link": self.get_url(documentlink_resolver), "slug": self.slug}
247+
return """<a href="%(link)s">%(slug)s</a>""" % {
248+
"link": self.get_url(documentlink_resolver),
249+
"slug": self.slug
250+
}
230251

231252
def get_url(self, documentlink_resolver=None):
232253
if not hasattr(documentlink_resolver, '__call__'):
233-
raise Exception("documentlink_resolver should be a callable object, but it's: %s" % type(documentlink_resolver))
254+
raise Exception(
255+
"documentlink_resolver should be a callable object, but it's: %s"
256+
% type(documentlink_resolver)
257+
)
234258
return documentlink_resolver(self)
235259

236260
def get_document_id(self):
@@ -468,6 +492,8 @@ def as_html(self, link_resolver):
468492
html.append(group_doc.as_html(link_resolver))
469493
return "\n".join(html)
470494

495+
def __iter__(self):
496+
return iter(self.value)
471497

472498
class Slice(FragmentElement):
473499

@@ -486,6 +512,48 @@ def as_html(self, link_resolver):
486512
"body": self.value.as_html(link_resolver)
487513
}
488514

515+
class CompositeSlice(FragmentElement):
516+
517+
def __init__(self, slice_type, slice_label, elt):
518+
self.slice_type = slice_type
519+
self.slice_label = slice_label
520+
self.repeat = []
521+
self.non_repeat = {}
522+
523+
_repeat = elt.get('repeat')
524+
_non_repeat = elt.get('non-repeat')
525+
526+
if any(_repeat):
527+
self.repeat = self.parse_repeat(_repeat)
528+
529+
if _non_repeat:
530+
self.non_repeat = self.parse_non_repeat(_non_repeat)
531+
532+
@staticmethod
533+
def parse_repeat(repeat):
534+
return Fragment.Group(repeat)
535+
536+
@staticmethod
537+
def parse_non_repeat(non_repeat):
538+
return Fragment.Group([non_repeat])
539+
540+
def as_html(self, link_resolver):
541+
classes = ['slice']
542+
if self.slice_label:
543+
classes.append(self.slice_label)
544+
545+
body = ""
546+
if self.non_repeat:
547+
body += self.non_repeat.as_html(link_resolver)
548+
549+
if self.repeat:
550+
body += self.repeat.as_html(link_resolver)
551+
552+
return '<div data-slicetype="%(slice_type)s" class="%(classes)s">%(body)s</div>' % {
553+
"slice_type": self.slice_type,
554+
"classes": ' '.join(classes),
555+
"body": body
556+
}
489557

490558
class SliceZone(FragmentElement):
491559

@@ -494,15 +562,24 @@ def __init__(self, value):
494562
for elt in value:
495563
slice_type = elt['slice_type']
496564
slice_label = elt.get('slice_label')
497-
fragment = Fragment.from_json(elt['value'])
498-
self.slices.append(Fragment.Slice(slice_type, slice_label, fragment))
565+
566+
# Old style slice
567+
if 'value' in elt:
568+
fragment = Fragment.from_json(elt['value'])
569+
self.slices.append(Fragment.Slice(slice_type, slice_label, fragment))
570+
else:
571+
Fragment.CompositeSlice(slice_type, slice_label, elt)
572+
self.slices.append(Fragment.CompositeSlice(slice_type, slice_label, elt))
499573

500574
def as_html(self, link_resolver):
501575
html = []
502576
for slice in self.slices:
503577
html.append(slice.as_html(link_resolver))
504578
return "\n".join(html)
505579

580+
def __iter__(self):
581+
return iter(self.slices)
582+
506583

507584
class StructuredText(object):
508585

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name='prismic',
11-
version='1.4.2',
11+
version='1.5.0',
1212
description='Prismic.io development kit',
1313
author='The Prismic.io Team',
1414
author_email='contact@prismic.io',

tests/test_prismic.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from prismic.exceptions import InvalidTokenError, AuthorizationNeededError, InvalidURLError
1010
from .test_prismic_fixtures import fixture_api, fixture_search, fixture_groups, \
1111
fixture_structured_lists, fixture_empty_paragraph, fixture_store_geopoint, fixture_image_links, \
12-
fixture_spans_labels, fixture_block_labels, fixture_custom_html, fixture_slices
12+
fixture_spans_labels, fixture_block_labels, fixture_custom_html, fixture_slices, fixture_composite_slices
1313
import time
1414
import json
1515
import logging
@@ -37,6 +37,7 @@ def setUp(self):
3737
self.fixture_spans_labels = json.loads(fixture_spans_labels)
3838
self.fixture_custom_html = json.loads(fixture_custom_html)
3939
self.fixture_slices = json.loads(fixture_slices)
40+
self.fixture_composite_slices = json.loads(fixture_composite_slices)
4041

4142
self.api = prismic.Api(self.fixture_api, self.token, ShelveCache("prismictest"), None)
4243

@@ -409,14 +410,25 @@ def test_slicezone(self):
409410
self.maxDiff = 10000
410411
doc = prismic.Document(self.fixture_slices)
411412
slices = doc.get_slice_zone("article.blocks")
412-
slices_html =slices.as_html(PrismicTestCase.link_resolver)
413+
slices_html = slices.as_html(PrismicTestCase.link_resolver)
413414
expected_html = (
414415
"""<div data-slicetype="features" class="slice"><section data-field="illustration"><img src="https://wroomdev.s3.amazonaws.com/toto/db3775edb44f9818c54baa72bbfc8d3d6394b6ef_hsf_evilsquall.jpg" alt="" width="4285" height="709" /></section>"""
415416
"""<section data-field="title"><span class="text">c'est un bloc features</span></section></div>\n"""
416417
"""<div data-slicetype="text" class="slice"><p>C'est un bloc content</p></div>""")
417418
# Comparing len rather than actual strings because json loading is not in a deterministic order for now
418419
self.assertEqual(len(expected_html), len(slices_html))
419420

421+
def test_composite_slices(self):
422+
self.maxDiff = 1000
423+
doc = prismic.Document(self.fixture_composite_slices)
424+
slices = doc.get_slice_zone("test.body")
425+
slices_html = slices.as_html(PrismicTestCase.link_resolver)
426+
expected_html = """<div data-slicetype="slice-a" class="slice"><section data-field="non-repeat-text"><p>Slice A non-repeat text</p></section><section data-field="non-repeat-title"><h1>Slice A non-repeat title</h1></section><section data-field="repeat-text"><p>Repeatable text A</p></section><section data-field="repeat-title"><h1>Repeatable title A</h1></section>
427+
<section data-field="repeat-text"><p>Repeatable text B</p></section><section data-field="repeat-title"><h1>Repeatable title B</h1></section></div>
428+
<div data-slicetype="slice-b" class="slice"><section data-field="image"><img src="https://prismic-io.s3.amazonaws.com/tails/014c1fe46e3ceaf04b7cc925b2ea7e8027dc607a_mobile_header_tp.png" alt="" width="800" height="500" /></section><section data-field="title"><h1>Slice A non-repeat title</h1></section></div>"""
429+
# Comparing len rather than actual strings because json loading is not in a deterministic order for now
430+
self.assertEqual(len(expected_html), len(slices_html))
431+
420432
def test_image_links(self):
421433
self.maxDiff = 10000
422434
text = prismic.fragments.StructuredText(self.fixture_image_links.get('value'))
@@ -516,6 +528,7 @@ def test_geopoint_near(self):
516528
.query(predicates.near('my.store.coordinates', 40.689757, -74.0451453, 15))
517529
self.assertEqual(f.data['q'], ['[[:d = geopoint.near(my.store.coordinates, 40.689757, -74.0451453, 15)]]'])
518530

531+
519532
class TestCache(unittest.TestCase):
520533

521534
def setUp(self):

tests/test_prismic_fixtures.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,3 +1246,138 @@
12461246
}
12471247
}
12481248
"""
1249+
1250+
fixture_composite_slices = """
1251+
{
1252+
"alternate_languages": [],
1253+
"data": {
1254+
"test": {
1255+
"body": {
1256+
"type": "SliceZone",
1257+
"value": [
1258+
{
1259+
"non-repeat": {
1260+
"non-repeat-text": {
1261+
"type": "StructuredText",
1262+
"value": [
1263+
{
1264+
"spans": [],
1265+
"text": "Slice A non-repeat text",
1266+
"type": "paragraph"
1267+
}
1268+
]
1269+
},
1270+
"non-repeat-title": {
1271+
"type": "StructuredText",
1272+
"value": [
1273+
{
1274+
"spans": [],
1275+
"text": "Slice A non-repeat title",
1276+
"type": "heading1"
1277+
}
1278+
]
1279+
}
1280+
},
1281+
"repeat": [
1282+
{
1283+
"repeat-text": {
1284+
"type": "StructuredText",
1285+
"value": [
1286+
{
1287+
"spans": [],
1288+
"text": "Repeatable text A",
1289+
"type": "paragraph"
1290+
}
1291+
]
1292+
},
1293+
"repeat-title": {
1294+
"type": "StructuredText",
1295+
"value": [
1296+
{
1297+
"spans": [],
1298+
"text": "Repeatable title A",
1299+
"type": "heading1"
1300+
}
1301+
]
1302+
}
1303+
},
1304+
{
1305+
"repeat-text": {
1306+
"type": "StructuredText",
1307+
"value": [
1308+
{
1309+
"spans": [],
1310+
"text": "Repeatable text B",
1311+
"type": "paragraph"
1312+
}
1313+
]
1314+
},
1315+
"repeat-title": {
1316+
"type": "StructuredText",
1317+
"value": [
1318+
{
1319+
"spans": [],
1320+
"text": "Repeatable title B",
1321+
"type": "heading1"
1322+
}
1323+
]
1324+
}
1325+
}
1326+
],
1327+
"slice_label": null,
1328+
"slice_type": "slice-a",
1329+
"type": "Slice"
1330+
},
1331+
{
1332+
"non-repeat": {
1333+
"image": {
1334+
"type": "Image",
1335+
"value": {
1336+
"main": {
1337+
"alt": null,
1338+
"copyright": null,
1339+
"dimensions": {
1340+
"height": 500,
1341+
"width": 800
1342+
},
1343+
"url": "https://prismic-io.s3.amazonaws.com/tails/014c1fe46e3ceaf04b7cc925b2ea7e8027dc607a_mobile_header_tp.png"
1344+
},
1345+
"views": {}
1346+
}
1347+
},
1348+
"title": {
1349+
"type": "StructuredText",
1350+
"value": [
1351+
{
1352+
"spans": [],
1353+
"text": "Slice A non-repeat title",
1354+
"type": "heading1"
1355+
}
1356+
]
1357+
}
1358+
},
1359+
"repeat": [
1360+
{}
1361+
],
1362+
"slice_label": null,
1363+
"slice_type": "slice-b",
1364+
"type": "Slice"
1365+
}
1366+
]
1367+
}
1368+
}
1369+
},
1370+
"first_publication_date": "2017-10-10T11:30:08+0000",
1371+
"href": "http://tails.prismic.io/api/v1/documents/search?ref=WdyvQCsAAOgSj_r0&q=%5B%5B%3Ad+%3D+at%28document.id%2C+%22WdyvPCsAAOMSj_rf%22%29+%5D%5D",
1372+
"id": "WdyvPCsAAOMSj_rf",
1373+
"lang": "en-gb",
1374+
"last_publication_date": "2017-10-10T11:30:08+0000",
1375+
"linked_documents": [],
1376+
"slugs": [
1377+
"slice-a-non-repeat-title"
1378+
],
1379+
"tags": [],
1380+
"type": "test",
1381+
"uid": "test"
1382+
}
1383+
"""

0 commit comments

Comments
 (0)