Skip to content

Commit 90c8c05

Browse files
committed
Add basic personalisation blocks
1 parent e6fac5f commit 90c8c05

1 file changed

Lines changed: 134 additions & 12 deletions

File tree

src/wagtail_personalisation/blocks.py

Lines changed: 134 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,78 @@
11
from __future__ import absolute_import, unicode_literals
22

3+
from django.utils.encoding import force_text
34
from django.utils.translation import ugettext_lazy as _
5+
46
from wagtail.wagtailcore import blocks
7+
from wagtail.wagtailimages.blocks import ImageChooserBlock
58

69
from wagtail_personalisation.adapters import get_segment_adapter
710
from wagtail_personalisation.models import Segment
811

912

1013
def list_segment_choices():
14+
"""Get a list of segment choices visible in the admin site when editing
15+
BasePersonalisedStructBlock and its derived classes."""
16+
yield (-1, _('Visible to everyone'))
17+
1118
for segment in Segment.objects.all():
1219
yield (segment.pk, segment.name)
1320

1421

15-
class PersonalisedStructBlock(blocks.StructBlock):
16-
"""Struct block that allows personalisation per block."""
17-
22+
class BasePersonalisedStructBlock(blocks.StructBlock):
23+
"""Base class for personalised struct blocks."""
1824
segment = blocks.ChoiceBlock(
1925
choices=list_segment_choices,
2026
required=False, label=_("Personalisation segment"),
2127
help_text=_("Only show this content block for users in this segment"))
2228

29+
def __init__(self, *args, **kwargs):
30+
"""Instantiate personalised struct block.
31+
32+
The arguments are the same as for the blocks.StructBlock constructor and
33+
one addtional one.
34+
35+
Keyword Arguments:
36+
render_fields: List with field names to be rendered or None to use
37+
the default block rendering.
38+
"""
39+
render_fields = kwargs.pop('render_fields',
40+
self._meta_class.render_fields)
41+
super(BasePersonalisedStructBlock, self).__init__(*args, **kwargs)
42+
43+
if isinstance(render_fields, tuple):
44+
render_fields = list(render_fields)
45+
46+
if render_fields is not None \
47+
and not isinstance(render_fields, list):
48+
raise ValueError('"render_fields" has to be a list or None.')
49+
elif isinstance(render_fields, list) \
50+
and not set(render_fields).issubset(self.child_blocks):
51+
raise ValueError('"render_fields" has to contain name(s) of the '
52+
'specified blocks.')
53+
else:
54+
setattr(self._meta_class, 'render_fields', render_fields)
55+
56+
57+
def is_visible(self, value, request):
58+
"""Check whether user should see the block based on their segments.
59+
60+
:param value: The value from the block.
61+
:type value: dict
62+
:returns: True if user should see the block.
63+
:rtype: bool
64+
65+
"""
66+
if int(value['segment']) == -1:
67+
return True
68+
69+
if value['segment']:
70+
for segment in get_segment_adapter(request).get_segments():
71+
if int(segment['id']) == int(value['segment']):
72+
return True
73+
74+
return False
75+
2376
def render(self, value, context=None):
2477
"""Only render this content block for users in this segment.
2578
@@ -31,14 +84,83 @@ def render(self, value, context=None):
3184
:rtype: blocks.StructBlock or empty str
3285
3386
"""
34-
request = context['request']
35-
adapter = get_segment_adapter(request)
36-
user_segments = adapter.get_segments()
87+
if not self.is_visible(value, context['request']):
88+
return ""
89+
90+
if self._meta_class.render_fields is None:
91+
return super(BasePersonalisedStructBlock, self).render(
92+
value, context)
93+
94+
if isinstance(self._meta_class.render_fields, list):
95+
render_value = ''
96+
for field_name in self._meta_class.render_fields:
97+
if hasattr(value.bound_blocks[field_name], 'render_as_block'):
98+
block_value = value.bound_blocks[field_name] \
99+
.render_as_block(context=context)
100+
else:
101+
block_value = force_text(value[field_name])
102+
103+
if block_value != 'None':
104+
render_value += block_value
105+
106+
return render_value
107+
108+
raise RuntimeError('"render_fields" is neither "None" or "list" '
109+
'during rendering.')
110+
111+
class Meta:
112+
"""
113+
Setting render field will define which field gets rendered.
114+
Please use a name of the field. If none, it will render the whole block.
115+
"""
116+
render_fields = None
117+
118+
119+
class PersonalisedStructBlock(BasePersonalisedStructBlock):
120+
"""Struct block that allows personalisation per block."""
121+
122+
class Meta:
123+
label = _('Personalised Block')
124+
render_fields = None
125+
126+
127+
class PersonalisedRichTextBlock(BasePersonalisedStructBlock):
128+
"""Rich text block that allows personalisation."""
129+
rich_text = blocks.RichTextBlock(label=_('Rich Text'))
130+
131+
class Meta:
132+
icon = blocks.RichTextBlock._meta_class.icon
133+
label = _('Personalised Rich Text')
134+
render_fields = ['rich_text']
135+
136+
137+
class PersonalisedTextBlock(BasePersonalisedStructBlock):
138+
"""Text block that allows personalisation."""
139+
text = blocks.TextBlock(label=_('Mutli-line Text'))
140+
141+
class Meta:
142+
icon = blocks.TextBlock._meta_class.icon
143+
label = _('Personalised Multi-line Text')
144+
render_fields = ['text']
145+
146+
147+
class PersonalisedCharBlock(BasePersonalisedStructBlock):
148+
"""Char block that allows personalisation."""
149+
char = blocks.CharBlock(label=_('Text'))
150+
151+
class Meta:
152+
icon = blocks.CharBlock._meta_class.icon
153+
label = _('Personalised Single-line Text')
154+
render_fields = ['char']
155+
156+
157+
class PersonalisedImageChooserBlock(BasePersonalisedStructBlock):
158+
"""Image chooser block that allows personalisation."""
159+
image = ImageChooserBlock(label=_('Image'))
160+
161+
class Meta:
162+
icon = ImageChooserBlock._meta_class.icon
163+
label = _('Personalised Image')
164+
render_fields = ['image']
37165

38-
if value['segment']:
39-
for segment in user_segments:
40-
if segment['id'] == int(value['segment']):
41-
return super(PersonalisedStructBlock, self).render(
42-
value, context)
43166

44-
return ""

0 commit comments

Comments
 (0)