Skip to content

Commit

Permalink
Add basic personalisation blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
tm-kn committed May 31, 2017
1 parent 2651eb0 commit 1c201b7
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 12 deletions.
146 changes: 134 additions & 12 deletions src/wagtail_personalisation/blocks.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,78 @@
from __future__ import absolute_import, unicode_literals

from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _

from wagtail.wagtailcore import blocks
from wagtail.wagtailimages.blocks import ImageChooserBlock

from wagtail_personalisation.adapters import get_segment_adapter
from wagtail_personalisation.models import Segment


def list_segment_choices():
"""Get a list of segment choices visible in the admin site when editing
BasePersonalisedStructBlock and its derived classes."""
yield (-1, _('Visible to everyone'))

for pk, name in Segment.objects.values_list('pk', 'name'):
yield pk, name


class PersonalisedStructBlock(blocks.StructBlock):
"""Struct block that allows personalisation per block."""

class BasePersonalisedStructBlock(blocks.StructBlock):
"""Base class for personalised struct blocks."""
segment = blocks.ChoiceBlock(
choices=list_segment_choices,
required=False, label=_("Personalisation segment"),
help_text=_("Only show this content block for users in this segment"))

def __init__(self, *args, **kwargs):
"""Instantiate personalised struct block.
The arguments are the same as for the blocks.StructBlock constructor and
one addtional one.
Keyword Arguments:
render_fields: List with field names to be rendered or None to use
the default block rendering.
"""
render_fields = kwargs.pop('render_fields',
self._meta_class.render_fields)
super(BasePersonalisedStructBlock, self).__init__(*args, **kwargs)

if isinstance(render_fields, tuple):
render_fields = list(render_fields)

if render_fields is not None \
and not isinstance(render_fields, list):
raise ValueError('"render_fields" has to be a list or None.')
elif isinstance(render_fields, list) \
and not set(render_fields).issubset(self.child_blocks):
raise ValueError('"render_fields" has to contain name(s) of the '
'specified blocks.')
else:
setattr(self._meta_class, 'render_fields', render_fields)


def is_visible(self, value, request):
"""Check whether user should see the block based on their segments.
:param value: The value from the block.
:type value: dict
:returns: True if user should see the block.
:rtype: bool
"""
if int(value['segment']) == -1:
return True

if value['segment']:
for segment in get_segment_adapter(request).get_segments():
if int(segment['id']) == int(value['segment']):
return True

return False

def render(self, value, context=None):
"""Only render this content block for users in this segment.
Expand All @@ -31,14 +84,83 @@ def render(self, value, context=None):
:rtype: blocks.StructBlock or empty str
"""
request = context['request']
adapter = get_segment_adapter(request)
user_segments = adapter.get_segments()
if not self.is_visible(value, context['request']):
return ""

if self._meta_class.render_fields is None:
return super(BasePersonalisedStructBlock, self).render(
value, context)

if isinstance(self._meta_class.render_fields, list):
render_value = ''
for field_name in self._meta_class.render_fields:
if hasattr(value.bound_blocks[field_name], 'render_as_block'):
block_value = value.bound_blocks[field_name] \
.render_as_block(context=context)
else:
block_value = force_text(value[field_name])

if block_value != 'None':
render_value += block_value

return render_value

raise RuntimeError('"render_fields" is neither "None" or "list" '
'during rendering.')

class Meta:
"""
Setting render field will define which field gets rendered.
Please use a name of the field. If none, it will render the whole block.
"""
render_fields = None


class PersonalisedStructBlock(BasePersonalisedStructBlock):
"""Struct block that allows personalisation per block."""

class Meta:
label = _('Personalised Block')
render_fields = None


class PersonalisedRichTextBlock(BasePersonalisedStructBlock):
"""Rich text block that allows personalisation."""
rich_text = blocks.RichTextBlock(label=_('Rich Text'))

class Meta:
icon = blocks.RichTextBlock._meta_class.icon
label = _('Personalised Rich Text')
render_fields = ['rich_text']


class PersonalisedTextBlock(BasePersonalisedStructBlock):
"""Text block that allows personalisation."""
text = blocks.TextBlock(label=_('Mutli-line Text'))

class Meta:
icon = blocks.TextBlock._meta_class.icon
label = _('Personalised Multi-line Text')
render_fields = ['text']


class PersonalisedCharBlock(BasePersonalisedStructBlock):
"""Char block that allows personalisation."""
char = blocks.CharBlock(label=_('Text'))

class Meta:
icon = blocks.CharBlock._meta_class.icon
label = _('Personalised Single-line Text')
render_fields = ['char']


class PersonalisedImageChooserBlock(BasePersonalisedStructBlock):
"""Image chooser block that allows personalisation."""
image = ImageChooserBlock(label=_('Image'))

class Meta:
icon = ImageChooserBlock._meta_class.icon
label = _('Personalised Image')
render_fields = ['image']

if value['segment']:
for segment in user_segments:
if segment['id'] == int(value['segment']):
return super(PersonalisedStructBlock, self).render(
value, context)

return ""
32 changes: 32 additions & 0 deletions tests/sandbox/pages/migrations/0003_personalisedfieldspage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-31 11:29
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import wagtail.wagtailcore.blocks
import wagtail.wagtailcore.fields
import wagtail.wagtailimages.blocks
import wagtail_personalisation.blocks


class Migration(migrations.Migration):

dependencies = [
('wagtailcore', '0033_remove_golive_expiry_help_text'),
('pages', '0002_auto_20170531_0915'),
]

operations = [
migrations.CreateModel(
name='PersonalisedFieldsPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
('body', wagtail.wagtailcore.fields.StreamField((('personalised_block', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('heading', wagtail.wagtailcore.blocks.CharBlock()), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock())))), ('personalised_block_template', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('heading', wagtail.wagtailcore.blocks.CharBlock()), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock())), label='Block with template', template='blocks/personalised_block_template.html')), ('personalised_rich_text_block', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('rich_text', wagtail.wagtailcore.blocks.RichTextBlock(label='Rich Text'))))), ('personalised_image', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(label='Image'))))), ('personalised_char', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('char', wagtail.wagtailcore.blocks.CharBlock(label='Text'))))), ('personalised_text', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('text', wagtail.wagtailcore.blocks.TextBlock(label='Mutli-line Text')))))))),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% load wagtailcore_tags %}

<div class="personalisation-block-template">
<p>This is a block with <strong>template</strong>.</p>
<h2>Heading: {{ value.heading }}</h2>
<div>
{{ value.paragraph|richtext }}
</div>
</div>
15 changes: 15 additions & 0 deletions tests/sandbox/templates/pages/page_with_personalisable_fields.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends "base.html" %}
{% load wagtailcore_tags %}

{% block body_class %}template-homepage{% endblock %}

{% block content %}
<h1>{{ page.title }}</h1>
{% for block in page.body %}
<section class="section-{{ block.block_type }}">
<p><em>Section for {{ block.block_type }}.</em></p>
{% include_block block %}
</section>
<hr>
{% endfor %}
{% endblock %}
36 changes: 36 additions & 0 deletions tests/unit/test_blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import absolute_import, unicode_literals

import pytest

from tests.factories.segment import SegmentFactory
from test.factories.pages import PersonalisedFieldsPageFactory
from tests.utils import render_template

@pytest.mark.django_db
def test_render_block(rf):
SegmentFactory(name='test', persistent=True)

request = rf.get('/')

request.session['segments'] = [{
"encoded_name": 'test',
"id": 1,
"timestamp": int(time.time()),
"persistent": True
}]

PersonalisedFieldsPageFactory(body=)

content = render_template("""
{% load wagtail_personalisation_tags %}
{% segment name='test' %}Test{% endsegment %}
""", request=request).strip()

assert content == "Test"

content = render_template("""
{% load wagtail_personalisation_tags %}
{% segment name='test2' %}Test{% endsegment %}
""", request=request).strip()

assert content == ""

0 comments on commit 1c201b7

Please sign in to comment.