Better experiments management.
authorRadek Czajka <rczajka@rczajka.pl>
Mon, 14 Mar 2022 15:51:23 +0000 (16:51 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Mon, 14 Mar 2022 15:51:23 +0000 (16:51 +0100)
src/catalogue/models/book.py
src/catalogue/views.py
src/experiments/base.py [new file with mode: 0644]
src/experiments/experiments.py [new file with mode: 0644]
src/experiments/middleware.py
src/experiments/templates/experiments/main_switch.html
src/experiments/views.py
src/wolnelektury/settings/custom.py
src/wolnelektury/templates/piwik/tracking_code.html

index 9e0f7c8..97677bd 100644 (file)
@@ -226,6 +226,11 @@ class Book(models.Model):
     def isbn_mobi(self):
         return self.get_extra_info_json().get('isbn_mobi')
 
+    def is_accessible_to(self, user):
+        if not self.preview:
+            return True
+        Membership = apps.get_model('club', 'Membership')
+        return Membership.is_active_for(user)
 
     def save(self, force_insert=False, force_update=False, **kwargs):
         from sortify import sortify
index 62d34bb..9da3ebb 100644 (file)
@@ -3,6 +3,7 @@
 #
 from collections import OrderedDict
 import random
+import re
 
 from django.conf import settings
 from django.template.loader import render_to_string
@@ -286,9 +287,14 @@ def book_detail(request, slug):
     except Book.DoesNotExist:
         return pdcounter_views.book_stub_detail(request, slug)
 
+    new_layout = request.EXPERIMENTS['layout']
+    # Not for preview books.
+    if new_layout.value and not book.is_accessible_to(request.user):
+        new_layout.override(None)
+    
     return render(
         request,
-        'catalogue/2021/book_detail.html' if request.EXPERIMENTS['layout'] == 'new' else 'catalogue/book_detail.html',
+        'catalogue/2021/book_detail.html' if new_layout.value else 'catalogue/book_detail.html',
         {
             'book': book,
             'book_children': book.children.all().order_by('parent_number', 'sort_key'),
diff --git a/src/experiments/base.py b/src/experiments/base.py
new file mode 100644 (file)
index 0000000..3430df2
--- /dev/null
@@ -0,0 +1,42 @@
+import hashlib
+from django.conf import settings
+
+
+class Experiment:
+    slug = None
+    name = 'experiment'
+    explicit = False
+    size = 0
+
+    def qualify(self, request):
+        return True
+
+    def __init__(self, request):
+        self.value = self.get_value(request)
+
+    def override(self, value):
+        self.value = value
+        
+    def get_value(self, request):
+        overrides = getattr(settings, 'EXPERIMENTS_OVERRIDES', {})
+        slug = self.slug
+        if slug in overrides:
+            return overrides[slug]
+
+        if self.qualify(request) is False:
+            return None
+
+        cookie_value = request.COOKIES.get(f'EXPERIMENT_{slug}')
+        if cookie_value is not None:
+            if cookie_value == 'on':
+                return True
+            elif cookie_value == 'off':
+                return False
+
+        number = int(
+            hashlib.md5(
+                (slug + request.META['REMOTE_ADDR']).encode('utf-8')
+            ).hexdigest(),
+            16
+        ) % 10e6 / 10e6
+        return number < self.size
diff --git a/src/experiments/experiments.py b/src/experiments/experiments.py
new file mode 100644 (file)
index 0000000..e06d881
--- /dev/null
@@ -0,0 +1,19 @@
+import re
+from .base import Experiment
+
+
+class NewLayout(Experiment):
+    slug = 'layout'
+    name = 'Nowy layout strony'
+
+    def qualify(self, request):
+        if re.search(
+                'iphone|mobile|androidtouch',
+                request.META['HTTP_USER_AGENT'],
+                re.IGNORECASE):
+            return False
+
+
+experiments = [
+    NewLayout,
+]
index 080aee2..73e93e4 100644 (file)
@@ -1,38 +1,12 @@
-import hashlib
-from django.conf import settings
+from .experiments import experiments
 
 
 def experiments_middleware(get_response):
     def middleware(request):
         exps = {}
 
-        overrides = getattr(settings, 'EXPERIMENTS_OVERRIDES', {})
-        for exp in settings.EXPERIMENTS:
-            slug = exp['slug']
-            if slug in overrides:
-                exps[slug] = overrides[slug]
-                continue
-
-            cookie_value = request.COOKIES.get(f'EXPERIMENT_{slug}')
-            if cookie_value is not None:
-                for cohort in exp.get('cohorts', []):
-                    if cohort['value'] == cookie_value:
-                        exps[slug] = cookie_value
-                        break
-
-            if slug not in exps:
-                number = int(
-                    # TODO sth else?
-                    hashlib.md5(
-                        (slug + request.META['REMOTE_ADDR']).encode('utf-8')
-                    ).hexdigest(),
-                    16
-                ) % 10e6 / 10e6
-                for cohort in exp.get('cohorts', []):
-                    number -= cohort.get('size', 1)
-                    if number < 0:
-                        exps[slug] = cohort['value']
-                        break
+        for exp in experiments:
+            exps[exp.slug] = exp(request)
 
         request.EXPERIMENTS = exps
         response = get_response(request)
index 0ff0d88..fbdf863 100644 (file)
@@ -4,21 +4,20 @@
 {% block body %}
   <img src="https://upload.wikimedia.org/wikipedia/commons/c/c8/MH1Asimulator.JPG" style="width:100">
 
-  {% for exp in experiments %}
-    <div class="experiment" data-slug="{{ exp.config.slug }}">
-      {{ exp.config.name }}
-      {% for cohort in exp.config.cohorts %}
-        <button
-            {% if exp.value == cohort.value %}
-            disabled class="active"
-            {% endif %}
-            data-value="{{ cohort.value }}">{{ cohort.name }}</button>
-      {% endfor %}
+  {% for exp in request.EXPERIMENTS.values %}
+    <div class="experiment" data-slug="{{ exp.slug }}">
+      {{ exp.name }}
+      <button
+          {% if exp.value %}
+          disabled class="active"
+          {% endif %}
+          data-value="on">włączony</button>
+      <button
+          {% if not exp.value %}
+          disabled class="active"
+          {% endif %}
+          data-value="off">wyłączony</button>
     </div>
-
-    <script>
-    </script>
-
   {% endfor %}
 {% endblock %}
 
index 71c514a..001b32a 100644 (file)
@@ -1,17 +1,5 @@
 from django.views.generic import TemplateView
-from django.conf import settings
 
 
 class MainSwitchView(TemplateView):
     template_name = 'experiments/main_switch.html'
-
-    def get_context_data(self):
-        ctx = super().get_context_data()
-        ctx['experiments'] = [
-            {
-                "config": conf,
-                "value": self.request.EXPERIMENTS.get(conf['slug'])
-            }
-            for conf in settings.EXPERIMENTS
-        ]
-        return ctx
index 08937d9..68a2153 100644 (file)
@@ -39,18 +39,6 @@ CLUB_PAYU_POS = '300746'
 CLUB_PAYU_RECURRING_POS = '300746'
 CLUB_APP_HOST = None
 
-
-EXPERIMENTS = [
-    {
-        "name": "Eksperymentalny układ strony utworu",
-        "slug": "layout",
-        "cohorts": [
-            {"size": 0,   "value": "new", "name": "eksperymentalny układ", "explicit": True},
-            {             "value": "old", "name": "stary układ"},
-        ],
-    },
-]
-
 MESSAGING_MIN_DAYS = 2
 
 NEWSLETTER_PHPLIST_SUBSCRIBE_URL = None
index 53be0f3..b70225e 100644 (file)
@@ -1,8 +1,8 @@
 <!-- Matomo -->
 <script type="text/javascript">
   var _paq = window._paq || [];
-  {% for experiment, value in request.EXPERIMENTS.items %}
-    _paq.push(['setCustomVariable', '{{ forloop.counter }}', "{{ experiment }}", "{{ value }}", "visit"]);
+  {% for slug, experiment in request.EXPERIMENTS.items %}
+    _paq.push(['setCustomVariable', '{{ forloop.counter }}', "{{ slug }}", "{{ experiment.value|yesno:"on,off," }}", "visit"]);
   {% endfor %}
   _paq.push(['trackPageView']);
   _paq.push(['enableLinkTracking']);