diff --git a/README.rst b/README.rst index 9314600..b352ea9 100644 --- a/README.rst +++ b/README.rst @@ -23,3 +23,14 @@ Next, include the ``personalisation`` and ``wagtail.contrib.modeladmin`` app in ] Make sure that ``django.contrib.sessions.middleware.SessionMiddleware`` has been added in first, this is a prerequisite for this project. + +Changing segments adapter +------------------------- +To change the segments adapter, first make a new one based on the ``BaseSegmentsAdapter`` + +.. code-block:: python + + class YourSegmentsAdapter(BaseSegmentsAdapter): + # Add your own logic here + +Add the ``PERSONALISATION_SEGMENTS_ADAPTER`` setting to your settings.py and choose your own adapter. diff --git a/src/personalisation/adapters.py b/src/personalisation/adapters.py new file mode 100644 index 0000000..5891c5a --- /dev/null +++ b/src/personalisation/adapters.py @@ -0,0 +1,99 @@ +import time + +from django.db.models import F + +from personalisation.models import AbstractBaseRule, Segment + + +class BaseSegmentsAdapter(object): + def setup(self): + return None + + def get_all_segments(self): + return None + + def get_segment(self): + return None + + def add(self): + return None + + def refresh(self): + return None + + def check_segment_exists(self): + return None + + def _test_rules(self, rules, request): + if len(rules) > 0: + for rule in rules: + result = rule.test_user(request) + + if result is False: + return False + + return True + return False + + class Meta: + abstract = True + + +class SessionSegmentsAdapter(BaseSegmentsAdapter): + def setup(self, request): + self.request = request + + self.request.session.setdefault('segments', []) + + def get_all_segments(self): + return self.request.session['segments'] + + def get_segment(self, segment_id): + return next(item for item in self.request.session['segments'] if item.id == segment_id) + + def add(self, segment): + def check_if_segmented(item): + """Check if the user has been segmented""" + return any(seg['encoded_name'] == item.encoded_name for seg in self.request.session['segments']) + + if not check_if_segmented(segment): + segdict = { + "encoded_name": segment.encoded_name(), + "id": segment.pk, + "timestamp": int(time.time()), + "persistent": segment.persistent, + } + self.request.session['segments'].append(segdict) + + def refresh(self): + current_segments = self.request.session['segments'] + persistent_segments = Segment.objects.filter(persistent=True) + + current_segments = [item for item in current_segments if + any(seg.pk for seg in persistent_segments) == item['id']] + + self.request.session['segments'] = current_segments + + segments = Segment.objects.filter(status='enabled').prefetch_related('rules') + + for segment in segments: + rules = AbstractBaseRule.__subclasses__() + segment_rules = [] + for rule in rules: + segment_rules += segments.rules.filter(segment=segment, pk=rule.pk) + result = self._test_rules(segment_rules, self.request) + + if result: + self.add(segment) + + + for seg in self.request.session['segments']: + segment = Segment.objects.get(pk=seg['id']) + segment.visit_count = F('visit_count') + 1 + segment.save() + + def check_segment_exists(self, segment): + segments = self.request.session['segments'] + + return any(item for item in segments if segment.pk == item.id) + diff --git a/src/personalisation/app_settings.py b/src/personalisation/app_settings.py new file mode 100644 index 0000000..a64ceec --- /dev/null +++ b/src/personalisation/app_settings.py @@ -0,0 +1,6 @@ +from django.conf import settings +from django.utils.module_loading import import_string + + +segments_adapter = import_string(getattr(settings, 'PERSONALISATION_SEGMENTS_ADAPTER', 'personalisation.adapters.SessionSegmentsAdapter'))() + diff --git a/src/personalisation/wagtail_hooks.py b/src/personalisation/wagtail_hooks.py index 33c5db5..f6c0f42 100644 --- a/src/personalisation/wagtail_hooks.py +++ b/src/personalisation/wagtail_hooks.py @@ -13,6 +13,7 @@ from wagtail.wagtailadmin.widgets import ( from wagtail.wagtailcore import hooks from personalisation import admin_urls +from personalisation.app_settings import segments_adapter from personalisation.models import (AbstractBaseRule, PersonalisablePage, Segment) from personalisation.utils import impersonate_other_page @@ -78,81 +79,16 @@ def set_visit_count(page, request, serve_args, serve_kwargs): @hooks.register('before_serve_page') def segment_user(page, request, serve_args, serve_kwargs): - if 'segments' not in request.session: - request.session['segments'] = [] + segments_adapter.setup(request) + segments_adapter.refresh() - current_segments = request.session['segments'] - persistent_segments = Segment.objects.filter(persistent=True) - - current_segments = [item for item in current_segments if any(seg.pk for seg in persistent_segments) == item['id']] - - request.session['segments'] = current_segments - - segments = Segment.objects.all().filter(status='enabled') - - for segment in segments: - rules = AbstractBaseRule.__subclasses__() - segment_rules = [] - for rule in rules: - queried_rules = rule.objects.filter(segment=segment) - for result in queried_rules: - segment_rules.append(result) - result = _test_rules(segment_rules, request, segment.match_any) - - if result: - _add_segment_to_user(segment, request) - - if request.session['segments']: - logger.info("User has been added to the following segments: {}" - .format(request.session['segments'])) - - for seg in request.session['segments']: - segment = Segment.objects.get(pk=seg['id']) - segment.visit_count = segment.visit_count + 1 - segment.save() - - -def _test_rules(rules, request, match_any=False): - """Test whether the user matches a segment's rules'""" - if len(rules) > 0: - for rule in rules: - result = rule.test_user(request) - if match_any: - if result is True: - return result - - elif result is False: - return False - if not match_any: - return True - return False - - -def _add_segment_to_user(segment, request): - """Save the segment in the user session""" - - def check_if_segmented(segment): - """Check if the user has been segmented""" - for seg in request.session['segments']: - if seg['encoded_name'] == segment.encoded_name(): - return True - return False - - if not check_if_segmented(segment): - segdict = { - "encoded_name": segment.encoded_name(), - "id": segment.pk, - "timestamp": int(time.time()), - "persistent": segment.persistent, - } - request.session['segments'].append(segdict) @hooks.register('before_serve_page') def serve_variation(page, request, serve_args, serve_kwargs): user_segments = [] - for segment in request.session['segments']: + for segment in segments_adapter.get_all_segments(): try: user_segment = Segment.objects.get(pk=segment['id'], status='enabled')