Commits

Anonymous committed 77474b9

Add DailyActivity model for analyzing Cohorts.

Comments (0)

Files changed (8)

django_lean/lean_retention/middleware.py

 from django.core import mail
 from django.db import transaction
 
-from django_lean.lean_retention.models import LastActivity, SignIn
+from django_lean.lean_retention.models import (DailyActivity, LastActivity,
+                                               SignIn)
 from django_lean.lean_retention import signals
+from django_lean.utils import get_current_site
 
 
-class TrackSigninMiddleware(object):
-    """
-    Tracks when users have last signed in.
-
-    To use, install in settings.MIDDLEWARE_CLASSES before
-    `TransactionMiddleware`, to prevent failures due to multiple
-    requests racing to update `LastActivity` objects.
-
-    MIDDLEWARE_CLASSES = (
-        'django_lean.lean_retention.middleware.TrackSigninMiddleware',
-        'django.middleware.transaction.TransactionMiddleware',
-    )
-    """
+class BaseTrackingMiddleware(object):
+    def _track(self, request, response):
+        raise NotImplementedError()
+        
     def medium(self, request, response):
         return 'Default'
 
         if request.is_ajax():
             # Ignore AJAX calls.
             return response
+        if request.user.is_anonymous():
+            # Ignore anonymous users.
+            return response
+        return self._track(request=request, response=response)
+
+
+class TrackRetentionMiddleware(BaseTrackingMiddleware):
+    """
+    Tracks which days a user has used the application.
+
+    To use, install in settings.MIDDLEWARE_CLASSES before
+    `TransactionMiddleware`, to prevent failures due to multiple
+    requests racing to update `DailyActivity` objects.
+
+    MIDDLEWARE_CLASSES = (
+        'django_lean.lean_retention.middleware.TrackRetentionMiddleware',
+        'django.middleware.transaction.TransactionMiddleware',
+    )
+    """
+    def _track(self, request, response):
+        # Has the user done anything today?
+        activity, created = DailyActivity.objects.stamp(
+            user=request.user, site=get_current_site(),
+            medium=self.medium(request=request, response=response)
+        )
+        if created:
+            # Send a signal as the user has become active today
+            signals.new_day.send(daily_activity=activity,
+                                 request=request, sender=self.__class__)
+        return response
+
+
+class TrackSigninMiddleware(BaseTrackingMiddleware):
+    """
+    Tracks when users have last signed in.
+
+    To use, install in settings.MIDDLEWARE_CLASSES before
+    `TransactionMiddleware`, to prevent failures due to multiple
+    requests racing to update `LastActivity` objects.
+
+    MIDDLEWARE_CLASSES = (
+        'django_lean.lean_retention.middleware.TrackSigninMiddleware',
+        'django.middleware.transaction.TransactionMiddleware',
+    )
+    """
+    def _track(self, request, response):
+        # Figure out when the user last had any activity
         user = request.user
-        if user.is_anonymous():
-            # Anonymous users can't sign in.
-            return response
-        # Figure out when the user last had any activity
         now = datetime.now()
-        medium = self.medium(request, response)
+        site = get_current_site()
+        medium = self.medium(request=request, response=response)
         activity, created = LastActivity.objects.get_or_create(
-            user=user, medium=medium, defaults={'datetime': now}
+            user=user, site=site, medium=medium, defaults={'datetime': now}
         )
         past = activity.datetime
         if (now - past) > timedelta(seconds=1):
 
     def create_sign_in(self, user, medium, datetime):
         """Log the fact that the user has become active again."""
-        sign_in, _ = SignIn.objects.get_or_create(user=user, medium=medium,
+        sign_in, _ = SignIn.objects.get_or_create(site=get_current_site(),
+                                                  user=user, medium=medium,
                                                   datetime=datetime)
         return sign_in

django_lean/lean_retention/migrations/0002_daily_activity.py

+
+from south.db import db
+from django.db import models
+from django_lean.lean_retention.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding model 'DailyActivity'
+        db.create_table('lean_retention_dailyactivity', (
+            ('id', orm['lean_retention.dailyactivity:id']),
+            ('user', orm['lean_retention.dailyactivity:user']),
+            ('site', orm['lean_retention.dailyactivity:site']),
+            ('medium', orm['lean_retention.dailyactivity:medium']),
+            ('date', orm['lean_retention.dailyactivity:date']),
+            ('days', orm['lean_retention.dailyactivity:days']),
+        ))
+        db.send_create_signal('lean_retention', ['DailyActivity'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'DailyActivity'
+        db.delete_table('lean_retention_dailyactivity')
+        
+    
+    
+    models = {
+        'auth.group': {
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)"},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'db_index': 'True', 'max_length': '75', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'lean_retention.dailyactivity': {
+            'date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}),
+            'days': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'medium': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'site': ('django.db.models.fields.related.ForeignKey', [], {'default': "orm['sites.Site'].objects.get(pk=1L)", 'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'lean_retention.lastactivity': {
+            'Meta': {'unique_together': "(['user', 'site', 'medium'],)"},
+            'datetime': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'medium': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'site': ('django.db.models.fields.related.ForeignKey', [], {'default': "orm['sites.Site'].objects.get(pk=1L)", 'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'lean_retention.signin': {
+            'datetime': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'medium': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'site': ('django.db.models.fields.related.ForeignKey', [], {'default': "orm['sites.Site'].objects.get(pk=1L)", 'to': "orm['sites.Site']", 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'sites.site': {
+            'Meta': {'db_table': "'django_site'"},
+            'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        }
+    }
+    
+    complete_apps = ['lean_retention']

django_lean/lean_retention/models.py

+import datetime
+
 from django.contrib.auth.models import User
 from django.contrib.sites.models import Site
 from django.db import models
     site = models.ForeignKey(Site, verbose_name=_('site'),
                              blank=True, null=True,
                              default=get_current_site)
-    datetime = models.DateTimeField()
-    medium = models.CharField(max_length=255)
+    medium = models.CharField(max_length=255, verbose_name=_('medium'),
+                              help_text=_('Source of the activity.'))
 
     class Meta:
         abstract = True
 
     def __unicode__(self):
-        return "%r datetime='%s' medium='%s'" % (self.user,
-                                                 self.datetime,
-                                                 self.medium)
+        return "%r medium='%s'" % (self.user, self.medium)
 
 
-class LastActivity(BaseActivity):
+class DateTimeActivity(BaseActivity):
+    datetime = models.DateTimeField(verbose_name=_('date-time'),
+                                    help_text=_('Timestamp of activity'))
+
+    class Meta:
+        abstract = True
+
+    def __unicode__(self):
+        return ("%s datetime='%s'" %
+                (super(DateTimeActivity, self).__unicode__(),
+                 self.datetime))
+
+
+class DailyActivityManager(models.Manager):
+    def stamp(self, user, site, medium, date=None):
+        if date is None:
+            date = datetime.date.today()
+        days = self.model.days_since_signup(user=user, date=date)
+        return self.get_or_create(user=user, site=site, medium=medium,
+                                  date=date, defaults={'days': days})
+
+
+class DailyActivity(BaseActivity):
+    date = models.DateField(verbose_name=_('date'), db_index=True,
+                            help_text=_('Date of activity'))
+    days = models.PositiveIntegerField(verbose_name=_('day of life'),
+                                       db_index=True,
+                                       help_text=_('Days since sign up.'),)
+
+    objects = DailyActivityManager()
+
+    def __unicode__(self):
+        return "%s date='%s'" % (super(DailyActivity, self).__unicode__(),
+                                 self.date)
+
+    @classmethod
+    def days_since_signup(cls, user, date):
+        return (date - user.date_joined.date()).days
+
+
+
+class LastActivity(DateTimeActivity):
     class Meta:
         unique_together = ['user', 'site', 'medium']
 
 
-class SignIn(BaseActivity):
+class SignIn(DateTimeActivity):
     pass

django_lean/lean_retention/reports.py

+from datetime import date, datetime, time, timedelta
+
+from django.contrib.auth.models import User
+
+from django_lean.lean_retention.models import DailyActivity
+
+
+def sort_retention_periods(retention_periods):
+    result = list(sorted(set(p + 0 for p in retention_periods)))
+    if result and result[0] < 1:
+        raise ValueError('retention_periods must be greater than one day,'
+                         'not %s' % result[0])
+    return result
+
+
+class Period(object):
+    def __init__(self, cohort, start_day, end_day):
+        self.cohort = cohort
+        self.start_day = start_day
+        self.end_day = end_day
+        if self.start_day < 1:
+            raise ValueError("start day '%s' must be >= 1" % self.start_day)
+        if self.start_day >= self.end_day:
+            raise ValueError("start day '%s' must be before end day '%s'" %
+                             (self.start_day, self.end_day))
+        self._activities = None
+        self._users = None
+
+    def length(self):
+        return self.end_day - self.start_day
+
+    @property
+    def activities(self):
+        if self._activities is None:
+            self._activities = DailyActivity.objects.filter(
+                user__in=self.cohort.users,
+                days__range=(self.start_day, self.end_day - 1)
+            )
+        return self._activities
+
+    @property
+    def users(self):
+        if self._users is None:
+            self._users = User.objects.filter(
+                id__in=self.activities.values('user')
+            )
+        return self._users
+
+    @classmethod
+    def periods(cls, cohort, retention_periods):
+        last = 1
+        for period in sort_retention_periods(retention_periods):
+            yield cls(cohort=cohort, start_day=last, end_day=period)
+            last = period
+
+
+class Cohort(object):
+    def __init__(self, start_date, end_date, retention_periods,
+                 period_class=Period):
+        if hasattr(start_date, 'date'):
+            start_date = start_date.date() # Convert to a datetime.date
+        if hasattr(end_date, 'date'):
+            end_date = end_date.date()  # Convert to a datetime.date
+        self.start_date = start_date
+        self.end_date = end_date
+        if self.start_date > self.end_date:
+            raise ValueError("start date '%s' cannot be after end date '%s'" %
+                             (self.start_date, self.end_date))
+        self.retention_periods = sort_retention_periods(retention_periods)
+        self._Period = Period
+        self._periods = None
+        self._users = None
+
+    @property
+    def periods(self):
+        if self._periods is None:
+            self._periods = list(
+                self._Period.periods(cohort=self,
+                                     retention_periods=self.retention_periods)
+            )
+        return self._periods
+
+    @property
+    def users(self):
+        if self._users is None:
+            start = datetime.combine(self.start_date, time(0, 0, 0))
+            end = datetime.combine(self.end_date, time(23, 59, 59))
+            self._users = User.objects.filter(date_joined__range=(start, end))
+        return self._users
+
+    @classmethod
+    def cohorts(cls, end_date, length, retention_periods,
+                period_class=Period):
+        if hasattr(end_date, 'date'):
+            end_date = end_date.date()  # Convert to a datetime.date
+        # The end date falls on the last day of the shortest retention period
+        retention_periods = sort_retention_periods(retention_periods)
+        min_period = retention_periods[0] if retention_periods else 0
+        end_date -= timedelta(days=min_period)
+        # The start date 
+        one_day = timedelta(days=1)
+        start_date = end_date - timedelta(days=length) + one_day
+        # Generate the stream of cohorts, walking backwards
+        try:
+            while True:
+                yield cls(start_date=start_date, end_date=end_date,
+                          retention_periods=retention_periods)
+                # Walk backwards
+                start_date -= one_day
+                end_date -= one_day
+        except OverflowError:
+            raise StopIteration    # We cannot go further back in time

django_lean/lean_retention/signals.py

 
 # User has signed in after some inactivity
 sign_in = Signal(providing_args=['sign_in', 'request'])
+
+# User has triggered activity for the first time today
+new_day = Signal(providing_args=['daily_activity', 'request'])

django_lean/lean_retention/tests/test_reports.py

+from datetime import date, datetime, time, timedelta
+
+from django.contrib.auth.models import User
+from django.test import TestCase
+
+import mox
+
+from django_lean.lean_retention.models import DailyActivity
+from django_lean.lean_retention.reports import (sort_retention_periods,
+                                                Cohort, Period)
+from django_lean.utils import get_current_site
+
+
+class TestReportUtils(TestCase):
+    def test_sort_retention_periods(self):
+        self.assertEqual(sort_retention_periods([]), [])
+        self.assertEqual(sort_retention_periods([1, 2, 3]), [1, 2, 3])
+        self.assertEqual(sort_retention_periods([1, 1, 1]), [1])
+        self.assertEqual(sort_retention_periods([1, 2, 1]), [1, 2])
+        self.assertRaises(TypeError, sort_retention_periods, ['1', '2', '3'])
+        self.assertRaises(TypeError, sort_retention_periods, ['1', 2, 3])
+        self.assertRaises(ValueError, sort_retention_periods, [0, 1, 2])
+        self.assertRaises(ValueError, sort_retention_periods, [-1, 1, 2])
+
+
+class TestPeriod(TestCase):
+    def setUp(self):
+        self.mox = mox.Mox()
+        self.user = User.objects.create_user('user', 'user@example.com', 'user')
+        self.activity, _ = DailyActivity.objects.stamp(user=self.user,
+                                                       site=get_current_site(),
+                                                       medium='Default')
+        self.activity.days = 29
+        self.activity.save()
+
+    def test_create(self):
+        self.assertRaises(ValueError, Period,
+                          cohort=None, start_day=1, end_day=1)
+        self.assertRaises(ValueError, Period,
+                          cohort=None, start_day=30, end_day=1)
+        
+    def test_length(self):
+        period = Period(cohort=None, start_day=1, end_day=30)
+        self.assertEqual(period.length(), 29)
+
+    def test_activities(self):
+        cohort = self.mox.CreateMockAnything()
+        cohort.users = User.objects.all()
+        self.mox.ReplayAll()
+        self.assertEqual(
+            list(Period(cohort, start_day=1, end_day=30).activities),
+            [self.activity]
+        )
+        self.assertEqual(
+            list(Period(cohort, start_day=30, end_day=60).activities),
+            []
+        )
+        self.mox.VerifyAll()
+
+    def test_users(self):
+        cohort = self.mox.CreateMockAnything()
+        cohort.users = User.objects.all()
+        self.mox.ReplayAll()
+        self.assertEqual(list(Period(cohort, start_day=1, end_day=30).users),
+                         [self.user])
+        self.assertEqual(list(Period(cohort, start_day=30, end_day=60).users),
+                         [])
+        self.mox.VerifyAll()
+
+    def test_periods(self):
+        cohort = self.mox.CreateMockAnything()
+        cohort.users = User.objects.all()
+        self.mox.ReplayAll()
+        periods = list(Period.periods(cohort, [60, 30, 90]))
+        for period in periods:
+            self.assertEqual(period.cohort, cohort)
+        self.assertEqual(periods[0].start_day, 1)
+        self.assertEqual(periods[0].end_day, 30)
+        self.assertEqual(periods[1].start_day, 30)
+        self.assertEqual(periods[1].end_day, 60)
+        self.assertEqual(periods[2].start_day, 60)
+        self.assertEqual(periods[2].end_day, 90)
+        self.mox.VerifyAll()
+
+
+class TestCohort(TestCase):
+    def setUp(self):
+        midnight = time(0, 0, 0)
+        yesterday = datetime.combine(date.today() - timedelta(days=1), midnight)
+        day_before = datetime.combine(yesterday - timedelta(days=1), midnight)
+        self.user1 = User.objects.create_user('user1', 'user1@example.com',
+                                              'user1')
+        self.user1.date_joined = day_before
+        self.user1.save()
+        self.user2 = User.objects.create_user('user2', 'user2@example.com',
+                                              'user2')
+        self.user2.date_joined = yesterday
+        self.user2.save()
+
+    def test_create(self):
+        today = date.today()
+        yesterday = today - timedelta(days=1)
+        self.assertRaises(ValueError,
+                          Cohort, start_date=today, end_date=yesterday,
+                          retention_periods=[])
+        cohort = Cohort(start_date=yesterday, end_date=today,
+                        retention_periods=[2, 1, 3])
+        self.assertEqual(cohort.start_date, yesterday)
+        self.assertEqual(cohort.end_date, today)
+        self.assertEqual(cohort.retention_periods, [1, 2, 3])
+
+    def test_periods(self):
+        yesterday = date.today() - timedelta(days=1)
+        day_before = yesterday - timedelta(days=1)
+        cohort = Cohort(start_date=day_before, end_date=yesterday,
+                        retention_periods=[40, 20, 60])
+        self.assertEqual(cohort.periods[0].start_day, 1)
+        self.assertEqual(cohort.periods[0].end_day, 20)
+        self.assertEqual(cohort.periods[1].start_day, 20)
+        self.assertEqual(cohort.periods[1].end_day, 40)
+        self.assertEqual(cohort.periods[2].start_day, 40)
+        self.assertEqual(cohort.periods[2].end_day, 60)
+
+    def test_users(self):
+        yesterday = date.today() - timedelta(days=1)
+        day_before = yesterday - timedelta(days=1)
+        cohort = Cohort(start_date=day_before, end_date=yesterday,
+                        retention_periods=[])
+        self.assertEqual(set(cohort.users), set([self.user1, self.user2]))
+        cohort = Cohort(start_date=yesterday, end_date=yesterday,
+                        retention_periods=[])
+        self.assertEqual(set(cohort.users), set([self.user2]))
+
+    def test_cohorts(self):
+        yesterday = date.today() - timedelta(days=1)
+        retention_periods = [1]
+        cohorts = Cohort.cohorts(end_date=date.today(), length=2,
+                                 retention_periods=retention_periods)
+        # First cohort should have both users
+        cohort = cohorts.next()
+        self.assertEqual(set(cohort.users), set([self.user1, self.user2]))
+        self.assertEqual(cohort.start_date, yesterday - timedelta(days=1))
+        self.assertEqual(cohort.end_date, yesterday)
+        self.assertEqual(cohort.retention_periods, retention_periods)
+        # Next cohort should only have user1
+        cohort = cohorts.next()
+        self.assertEqual(set(cohort.users), set([self.user1]))
+        self.assertEqual(cohort.start_date, yesterday - timedelta(days=2))
+        self.assertEqual(cohort.end_date, yesterday - timedelta(days=1))
+        self.assertEqual(cohort.retention_periods, retention_periods)
+        # Next cohort should only have no users
+        cohort = cohorts.next()
+        self.assertEqual(set(cohort.users), set())
+        self.assertEqual(cohort.start_date, yesterday - timedelta(days=3))
+        self.assertEqual(cohort.end_date, yesterday - timedelta(days=2))
+        self.assertEqual(cohort.retention_periods, retention_periods)

django_lean/lean_retention/tests/test_retention.py

+from datetime import date, datetime, timedelta
+
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+
+from django_lean.lean_retention.models import DailyActivity
+from django_lean.lean_retention.tests.utils import TestCase
+
+
+class TestTrackRetentionMiddleware(TestCase):
+    middleware = [
+        'django.contrib.sessions.middleware.SessionMiddleware',
+        'django.contrib.auth.middleware.AuthenticationMiddleware',
+        'django_lean.lean_retention.middleware.TrackRetentionMiddleware',
+        'django.middleware.transaction.TransactionMiddleware',
+    ]
+    urls = 'django_lean.lean_retention.tests.urls'
+    
+    def setUp(self):
+        self.user = User.objects.create_user('user', 'user@example.com', 'user')
+
+    def test_anonymous(self):
+        response = self.client.get(reverse('home'))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(DailyActivity.objects.count(), 0)
+
+    def test_signup_today(self):
+        today = date.today()
+        self.assertTrue(self.client.login(username=self.user.username,
+                                          password=self.user.username))
+        response = self.client.get(reverse('home'))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(DailyActivity.objects.count(), 1)
+        activity = DailyActivity.objects.all()[0]
+        self.assertEqual(activity.user, self.user)
+        self.assertEqual(activity.medium, 'Default')
+        self.assertEqual(activity.date, today)
+        self.assertEqual(activity.days, 0)
+
+    def test_signup_yesterday(self):
+        today = date.today()
+        self.user.date_joined = datetime.now() - timedelta(days=1)
+        self.user.save()
+        self.assertTrue(self.client.login(username=self.user.username,
+                                          password=self.user.username))
+        response = self.client.get(reverse('home'))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(DailyActivity.objects.count(), 1)
+        activity = DailyActivity.objects.all()[0]
+        self.assertEqual(activity.user, self.user)
+        self.assertEqual(activity.medium, 'Default')
+        self.assertEqual(activity.date, today)
+        self.assertEqual(activity.days, 1)

django_lean/utils.py

 
 def get_current_site():
     if Site._meta.installed:
-        return Site.objects.get_current().id
+        return Site.objects.get_current()
     return None