diff --git a/docs/usage_guide.rst b/docs/usage_guide.rst index d88fe67..8afc158 100644 --- a/docs/usage_guide.rst +++ b/docs/usage_guide.rst @@ -47,6 +47,8 @@ personalised content. To do this, you can go one of two directions. 2. Create StreamField blocks only visible to your segment. +3. Create a template block only visible to your segment. + Method 1: Create a copy ^^^^^^^^^^^^^^^^^^^^^^^ @@ -59,13 +61,35 @@ You'll notice a new "Variants" dropdown button has appeared. Click the button and select the segment you'd like to create personalized content for. Once you've selected the segment, a copy of the page will be created with a -title that includes the segment. Don't worry, you'r visitors won't be able to +title that includes the segment. Don't worry, your visitors won't be able to see this title. You can change everything on this page you'd like. Visitors that are included in your segment, will automatically see the new page you've created for them. -Method 2: Create a block -^^^^^^^^^^^^^^^^^^^^^^^^ +Method 2: Create a StreamField block +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Method 3: Create a template block +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can add a template block that only shows its contents to users of a +specific segment. This is done using the "segment" block. + +When editing templates make sure to load the ``wagtail_personalisation_tags`` +tags library in the template:: + + {% load wagtail_personalisation_tags %} + +After that you can add a template block with the name of the segment you want +the content to show up for:: + + {% segment name="My Segment" %} +

Only users within "My Segment" see this!

+ {% endsegment %} + +The template block currently only supports one segment at a time. If you want +to target multiple segments you will have to make multiple blocks with the +same content. diff --git a/src/wagtail_personalisation/adapters.py b/src/wagtail_personalisation/adapters.py index 37ab022..6639f91 100644 --- a/src/wagtail_personalisation/adapters.py +++ b/src/wagtail_personalisation/adapters.py @@ -97,8 +97,11 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter): :rtype: wagtail_personalisation.models.Segment or None """ - return next(item for item in self.request.session['segments'] - if item.id == segment_id) + try: + return next(item for item in self.request.session['segments'] + if item['id'] == segment_id) + except StopIteration: + return None def add(self, segment): """Add a segment to the request session. diff --git a/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py new file mode 100644 index 0000000..00e166c --- /dev/null +++ b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py @@ -0,0 +1,58 @@ +from django import template +from django.template import TemplateSyntaxError + +from django.utils.safestring import mark_safe + +from wagtail_personalisation.models import Segment +from wagtail_personalisation.utils import parse_tag + +register = template.Library() + + +def do_segment(parser, token): + """Block that only shows content if user is in chosen segment.""" + # Parse the tag + tag_name, _, kwargs = parse_tag(token, parser) + + # If no segment is provided this block will raise an error + if set(kwargs.keys()) != {'name'}: + usage = '{% {tag_name} name="segmentname" %} ... {% end{tag_name} %}'.format(tag_name=tag_name) + raise TemplateSyntaxError("Usage: %s" % usage) + + nodelist = parser.parse(('endsegment',)) + parser.delete_first_token() + + return SegmentNode(nodelist, name=kwargs['name']) + + +register.tag('segment', do_segment) + + +class SegmentNode(template.Node): + """Node that only returns contents if user is in the segment. + + This node checks if the chosen segment exists and if the + user has been segmented in the chosen segment. + If not it will return nothing + + """ + def __init__(self, nodelist, name): + self.nodelist = nodelist + self.name = name + + def render(self, context): + # Check if segment exists + name = self.name.resolve(context) + segment = Segment.objects.filter(name=name).first() + if not segment: + return "" + + # Check if user has segment + user_segment = context['request'].segment_adapter.get_segment(segment_id=segment.pk) + if not user_segment: + return "" + + content = self.nodelist.render(context) + content = mark_safe(content) + + return content diff --git a/src/wagtail_personalisation/utils.py b/src/wagtail_personalisation/utils.py index d42876f..571d395 100644 --- a/src/wagtail_personalisation/utils.py +++ b/src/wagtail_personalisation/utils.py @@ -1,5 +1,6 @@ import time +from django.template.base import FilterExpression, kwarg_re from django.utils import timezone @@ -58,3 +59,37 @@ def count_active_days(enable_date, disable_date): return delta.days return 0 + + +def parse_tag(token, parser): + """Parses template tag for name, arguments and keyword arguments. + + :param token: Template token containing all the tag contents + :type token: django.template.base.Token + :param parser: Template parser + :type parser: django.template.base.Parser + :return: Tuple with tag name, arguments and keyword arguments + :rtype: tuple + + """ + # Split the tag content into words, respecting quoted strings. + bits = token.split_contents() + + # Pull out the tag name. + tag_name = bits.pop(0) + + # Parse the rest of the args, and build FilterExpressions from them so that + # we can evaluate them later. + args = [] + kwargs = {} + for bit in bits: + # Is this a kwarg or an arg? + match = kwarg_re.match(bit) + kwarg_format = match and match.group(1) + if kwarg_format: + key, value = match.groups() + kwargs[key] = FilterExpression(value, parser) + else: + args.append(FilterExpression(bit, parser)) + + return (tag_name, args, kwargs)