Permalink
Browse files

+ Initial import.

-----
ToDo:
Move Session garbage to a Object.
  • Loading branch information...
0 parents commit 04f1b8d11ba68abbea6bfe908b1bd64ffebd26c9 @johnboxall committed May 17, 2009
Showing with 178 additions and 0 deletions.
  1. 0 __init__.py
  2. +16 −0 admin.py
  3. +23 −0 loaders.py
  4. +72 −0 middleware.py
  5. +66 −0 models.py
  6. 0 tests.py
  7. +1 −0 views.py
No changes.
@@ -0,0 +1,16 @@
+from django.contrib import admin
+from ab.models import Experiment, Test
+
+class TestInline(admin.TabularInline):
+ model = Test
+
+# class TestAdmin(admin.ModelAdmin):
+# list_display = ("name", "hits", "conversion",)
+
+class ExperimentAdmin(admin.ModelAdmin):
+# list_display = ("name", "hits", "conversion",)
+ inlines = (TestInline,)
+
+
+admin.site.register(Experiment, ExperimentAdmin)
+# admin.site.register(Test, TestAdmin)
@@ -0,0 +1,23 @@
+from django.template.loaders.filesystem import load_template_source as default_template_loader
+from django.template import TemplateDoesNotExist
+
+from ab.middleware import get_current_request
+from ab.models import Experiment
+
+def load_template_source(template_name, template_dirs=None,
+ template_loader=default_template_loader):
+ """If an Experiment exists for this template use template_loader to load it."""
+ try:
+ # @@@ This (c|sh)ould be a cached call.
+ experiment = Experiment.objects.get(template_name=template_name)
+ except Experiment.DoesNotExist:
+ raise TemplateDoesNotExist, template_name
+
+ request = get_current_request()
+ test_template_name = experiment.get_test_template_for_request(request)
+
+ return default_template_loader(test_template_name,
+ template_dirs=template_dirs)
+load_template_source.is_usable = True
+
+
@@ -0,0 +1,72 @@
+try:
+ from threading import local
+except ImportError:
+ from django.utils._threading_local import local
+
+from ab.models import Experiment, Test
+
+
+_thread_locals = local()
+def get_current_request():
+ return getattr(_thread_locals, 'request', None)
+
+"""
+Things to keep in mind:
+* Only record goal conversions when a Experiment is active
+* Can only record a conversion once
+
+what about only running a test X times and then defaulting to the best performer?
+
+
+!!! the session junk should be abstracted to some kind of model thing. SessionBackend.is_active etc.
+
+"""
+
+
+# @@@ How will caching effect all this???
+class ABMiddleware:
+ def process_request(self, request):
+ """
+ Puts the request object in local thread storage.
+ Also checks whether we've reached a A/B test goal.
+ """
+ _thread_locals.request = request
+
+ # We can only do this if a Experiment is active.
+
+
+ print request.session.keys()
+
+ if "ab_active" in request.session:
+ experiments = Experiment.objects.all()
+ for experiment in experiments:
+
+ print request.path
+ print experiment.goal
+
+ if request.path == experiment.goal:
+
+ print 'yes'
+
+ # @@@ Also
+
+
+ key = "ab_%s" % experiment.template_name
+ if key in request.session and "converted" not in request.session[key]:
+ print request.session[key]
+ test_id = request.session[key]["id"]
+ test = Test.objects.get(pk=test_id)
+ test.conversions = test.conversions + 1
+ test.save()
+
+ request.session[key]["converted"] = 1
+ request.session.modified = True
+ print request.session[key]
+
+
+
+
+
+
+
+
@@ -0,0 +1,66 @@
+from django.db import models
+
+
+# @@@ How to remember tests are active???
+
+
+class Experiment(models.Model):
+ """
+
+ """
+ # @@@ unique=True ??? Does that make sense???
+ name = models.CharField(max_length=255, unique=True)
+ template_name = models.CharField(max_length=255, unique=True,
+ help_text="Example: 'registration/signup.html'. The template to replaced.")
+ goal = models.CharField(max_length=255, unique=True,
+ help_text="Example: '/signup/complete/'. The path where the goal is converted.")
+
+ def __unicode__(self):
+ return self.name
+
+ def get_session_key(self):
+ return "ab_%s" % self.template_name
+
+ def get_test_template_for_request(self, request):
+ """
+ Given a request return one of the templates from it's Tests.
+ Tests are sticky - if a viewer saw a Test before, they should
+ see the same test.
+ """
+ key = self.get_session_key()
+ if key in request.session:
+ return request.session[key]["template"]
+
+ # Pick a Test to show.
+ tests = self.test_set.all()
+ # @@@ This hash will probably make the django pony cry.
+ test = tests[request.session.accessed % len(tests)]
+
+ # Record this unique hit to the Test.
+ test.hits = test.hits + 1
+ test.save()
+
+ # Make the Test sticky.
+ if key not in request.session:
+ print 'here'
+ request.session[key] = {"id": test.id, "template": test.template_name}
+
+
+ request.session["ab_active"] = True
+
+
+ return test.template_name
+
+
+class Test(models.Model):
+ """
+
+ """
+ experiment = models.ForeignKey(Experiment)
+ template_name = models.CharField(max_length=255,
+ help_text="Example: 'registration/signup_1.html'. The template to be tested.")
+ hits = models.IntegerField(blank=True, default=0)
+ conversions = models.IntegerField(blank=True, default=0)
+
+ def __unicode__(self):
+ return self.template_name
No changes.
@@ -0,0 +1 @@
+# Create your views here.

0 comments on commit 04f1b8d

Please sign in to comment.