From 20011f079db815bf45fb75e634832b44b93301d9 Mon Sep 17 00:00:00 2001 From: blurrah Date: Wed, 31 May 2017 11:50:45 +0200 Subject: [PATCH 1/5] adds simple block templatetag wip --- .../wagtail_personalisation_tags.py | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py 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..76b56a0 --- /dev/null +++ b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py @@ -0,0 +1,91 @@ +from django import template +from django.template import TemplateSyntaxError +from django.template.base import FilterExpression, kwarg_re +from django.utils.safestring import mark_safe + +from wagtail_personalisation.app_settings import segments_adapter +from wagtail_personalisation.models import Segment + +register = template.Library() + + +def parse_tag(token, parser): + """ + Generic template tag parser. + + Returns a three-tuple: (tag_name, args, kwargs) + + tag_name is a string, the name of the tag. + + args is a list of FilterExpressions, from all the arguments that didn't look like kwargs, + in the order they occurred, including any that were mingled amongst kwargs. + + kwargs is a dictionary mapping kwarg names to FilterExpressions, for all the arguments that + looked like kwargs, including any that were mingled amongst args. + + (At rendering time, a FilterExpression f can be evaluated by calling f.resolve(context).) + """ + # 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) + + +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): + 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 = segments_adapter.get_segment(segment_id=segment.pk) + if not user_segment: + return "" + + content = self.nodelist.render(context) + content = mark_safe(content) + + return content From a78290281b1778cd8ceb13d64b9fddee1ca85c5c Mon Sep 17 00:00:00 2001 From: blurrah Date: Wed, 31 May 2017 12:13:12 +0200 Subject: [PATCH 2/5] fixes adapter issues and fixes block for segments adapter --- src/wagtail_personalisation/adapters.py | 7 +++++-- .../templatetags/wagtail_personalisation_tags.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/wagtail_personalisation/adapters.py b/src/wagtail_personalisation/adapters.py index 55afea3..047f5b4 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 index 76b56a0..bc5a48a 100644 --- a/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py +++ b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py @@ -3,7 +3,6 @@ from django.template import TemplateSyntaxError from django.template.base import FilterExpression, kwarg_re from django.utils.safestring import mark_safe -from wagtail_personalisation.app_settings import segments_adapter from wagtail_personalisation.models import Segment register = template.Library() @@ -81,7 +80,7 @@ class SegmentNode(template.Node): return "" # Check if user has segment - user_segment = segments_adapter.get_segment(segment_id=segment.pk) + user_segment = context['request'].segment_adapter.get_segment(segment_id=segment.pk) if not user_segment: return "" From 23537ac29bca08a8827fa50412ba4660f506c9df Mon Sep 17 00:00:00 2001 From: blurrah Date: Wed, 31 May 2017 12:29:51 +0200 Subject: [PATCH 3/5] adds docstrings, moves parse_tag to utils and adds documentation --- docs/usage_guide.rst | 30 +++++++++-- .../wagtail_personalisation_tags.py | 50 ++++--------------- src/wagtail_personalisation/utils.py | 35 +++++++++++++ 3 files changed, 72 insertions(+), 43 deletions(-) diff --git a/docs/usage_guide.rst b/docs/usage_guide.rst index d88fe67..64db8ae 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 it's contents to users of a +specific segment. This is done using the "segment" block. + +When editing templates make sure to add the `wagtail_personalisation_tags` +tag to 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/templatetags/wagtail_personalisation_tags.py b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py index bc5a48a..3d2b570 100644 --- a/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py +++ b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py @@ -1,54 +1,17 @@ from django import template from django.template import TemplateSyntaxError -from django.template.base import FilterExpression, kwarg_re + from django.utils.safestring import mark_safe from wagtail_personalisation.models import Segment +from wagtail_personalisation.utils import parse_tag register = template.Library() -def parse_tag(token, parser): - """ - Generic template tag parser. - - Returns a three-tuple: (tag_name, args, kwargs) - - tag_name is a string, the name of the tag. - - args is a list of FilterExpressions, from all the arguments that didn't look like kwargs, - in the order they occurred, including any that were mingled amongst kwargs. - - kwargs is a dictionary mapping kwarg names to FilterExpressions, for all the arguments that - looked like kwargs, including any that were mingled amongst args. - - (At rendering time, a FilterExpression f can be evaluated by calling f.resolve(context).) - """ - # 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) - - 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) @@ -68,6 +31,13 @@ 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 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) From d3d4e7ec928345d2862fb6257727e11a0b1b25f5 Mon Sep 17 00:00:00 2001 From: blurrah Date: Wed, 31 May 2017 13:07:55 +0200 Subject: [PATCH 4/5] fixes a few code style issues and docs issues --- docs/usage_guide.rst | 8 ++++---- .../templatetags/wagtail_personalisation_tags.py | 10 ++++------ src/wagtail_personalisation/utils.py | 1 - 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/usage_guide.rst b/docs/usage_guide.rst index 64db8ae..8afc158 100644 --- a/docs/usage_guide.rst +++ b/docs/usage_guide.rst @@ -75,18 +75,18 @@ Method 2: Create a StreamField block Method 3: Create a template block ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You can add a template block that only shows it's contents to users of a +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 add the `wagtail_personalisation_tags` -tag to the template:: +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"} + {% segment name="My Segment" %}

Only users within "My Segment" see this!

{% endsegment %} diff --git a/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py index 3d2b570..00e166c 100644 --- a/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py +++ b/src/wagtail_personalisation/templatetags/wagtail_personalisation_tags.py @@ -10,9 +10,7 @@ register = template.Library() def do_segment(parser, token): - """Block that only shows content if user is in chosen segment. - - """ + """Block that only shows content if user is in chosen segment.""" # Parse the tag tag_name, _, kwargs = parse_tag(token, parser) @@ -33,9 +31,9 @@ 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 + 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): diff --git a/src/wagtail_personalisation/utils.py b/src/wagtail_personalisation/utils.py index 571d395..50d15d1 100644 --- a/src/wagtail_personalisation/utils.py +++ b/src/wagtail_personalisation/utils.py @@ -3,7 +3,6 @@ import time from django.template.base import FilterExpression, kwarg_re from django.utils import timezone - def impersonate_other_page(page, other_page): """Function to change the page metadata so the user gets to see the non-personalized path and page. From de2c7f99886ebf5cd345f863179d0947044c0e08 Mon Sep 17 00:00:00 2001 From: blurrah Date: Wed, 31 May 2017 14:01:11 +0200 Subject: [PATCH 5/5] fixes newline --- src/wagtail_personalisation/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wagtail_personalisation/utils.py b/src/wagtail_personalisation/utils.py index 50d15d1..571d395 100644 --- a/src/wagtail_personalisation/utils.py +++ b/src/wagtail_personalisation/utils.py @@ -3,6 +3,7 @@ import time from django.template.base import FilterExpression, kwarg_re from django.utils import timezone + def impersonate_other_page(page, other_page): """Function to change the page metadata so the user gets to see the non-personalized path and page.