Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge commit 'upstream/master'

Conflicts:
	README.txt
  • Loading branch information...
commit fcf18712723b23a92c0c4e1d768e648d057051f0 2 parents 8115969 + bdba3ee
Ross Poulton authored March 08, 2012
5  .gitignore
... ...
@@ -0,0 +1,5 @@
  1
+*.pyc
  2
+*.egg-info
  3
+build
  4
+dist
  5
+MANIFEST
4  AUTHORS.txt
@@ -3,4 +3,6 @@ django-app-metrics was originally created by Frank Wiles <frank@revsys.com>
3 3
 Thanks to all of our contributors! 
4 4
 
5 5
 Ross Poulton <ross@rossp.org> 
6  
-
  6
+Flavio Curella <flavio@storymarket.com>
  7
+Jacob Burch <jacobburch@revsys.com>
  8
+Jannis Leidel <jezdez@enn.io>
2  MANIFEST.in
... ...
@@ -0,0 +1,2 @@
  1
+include README.rst
  2
+recursive-include app_metrics/templates *
92  README.rst
Source Rendered
... ...
@@ -0,0 +1,92 @@
  1
+Overview
  2
+========
  3
+
  4
+django-app-metrics allows you to capture and report on various events in your
  5
+applications.  You simply define various named metrics and record when they
  6
+happen.  These might be certain events that may be immediatey useful, for
  7
+example 'New User Signups', 'Downloads', etc.
  8
+
  9
+Or they might not prove useful until some point in the future.  But if you
  10
+begin recording them now you'll have great data later on if you do need it.
  11
+
  12
+For example 'Total Items Sold' isn't an exciting number when you're just
  13
+launching when you only care about revenue, but being able to do a contest
  14
+for the 1 millionth sold item in the future you'll be glad you were tracking
  15
+it.
  16
+
  17
+You then group these individual metrics into a MetricSet, where you define
  18
+how often you want an email report being sent, and to which User(s) it should
  19
+be sent.
  20
+
  21
+Requirements
  22
+============
  23
+
  24
+Celery_ and `django-celery`_ must be installed, however if you do not wish to
  25
+actually use Celery you can simply set ``CELERY_ALWAYS_EAGER = True`` in your
  26
+settings and it will behave as if Celery was not configured.
  27
+
  28
+.. _Celery: http://celeryproject.org/
  29
+.. _`django-celery`: http://ask.github.com/django-celery/
  30
+
  31
+Usage
  32
+=====
  33
+
  34
+::
  35
+
  36
+  from app_metrics.utils import create_metric, metric
  37
+
  38
+  # Create a new metric to track
  39
+  my_metric = create_metric(name='New User Metric', slug='new_user_signup')
  40
+
  41
+  # Create a MetricSet which ties a metric to an email schedule and sets
  42
+  # who should receive it
  43
+  my_metric_set = create_metric_set(name='My Set',
  44
+                                    metrics=[my_metric],
  45
+                                    email_recipients=[user1, user2])
  46
+
  47
+  # Increment the metric by one
  48
+  metric('new_user_signup')
  49
+
  50
+  # Increment the metric by some other number
  51
+  metric('new_user_signup', 4)
  52
+
  53
+  # Aggregate metric items into daily, weekly, monthly, and yearly totals
  54
+  # It's fairly smart about it, so you're safe to run this as often as you
  55
+  # like
  56
+  manage.py metrics_aggregate
  57
+
  58
+  # Send email reports to users
  59
+  manage.py metrics_send_mail
  60
+
  61
+Backends
  62
+========
  63
+
  64
+``app_metrics.backends.db`` (Default) - This backend stores all metrics and
  65
+aggregations in your database. NOTE: Every call to ``metric()`` generates a
  66
+database write, which may decrease your overall performance is you go nuts
  67
+with them or have a heavily traffic site.
  68
+
  69
+``app_metrics.backends.mixpanel`` - This backend allows you to pipe all of
  70
+your calls to ``metric()`` to Mixpanel. See the `Mixpanel documentation`_
  71
+for more information on their API.
  72
+
  73
+.. _`Mixpanel documentation`: http://mixpanel.com/docs/api-documentation
  74
+
  75
+Settings
  76
+========
  77
+
  78
+``APP_METRICS_BACKEND`` - Defaults to 'app_metrics.backends.db' if not defined.
  79
+
  80
+``APP_METRICS_MIXPANEL_TOKEN`` - Your Mixpanel.com API token
  81
+
  82
+``APP_METRICS_MIXPANEL_URL`` - Allow overriding of the API URL end point
  83
+
  84
+``METRICS_SEND_ZERO_ACTIVITY`` - Prevent e-amils being sent when there's been 
  85
+no activity today (i.e. during testing). Defaults to `True`.
  86
+
  87
+TODO
  88
+====
  89
+
  90
+- Improve text and HTML templates to display trending data well
  91
+- Create redis backend for collection and aggregation
  92
+
67  README.txt
... ...
@@ -1,67 +0,0 @@
1  
-
2  
-Overview
3  
-========
4  
-
5  
-django-app-metrics is a in-development project to capture and report on various
6  
-possible events in your application.  You simply define various named metrics
7  
-and record when they happen.  These might be certain events that may be
8  
-immediatey useful, for example 'New User Signups', 'Downloads', etc. 
9  
-
10  
-Or they might not prove useful until some point in the future.  But if you 
11  
-begin recording them now you'll have great data later on when you do need it. 
12  
-For example 'Total Items Sold' isn't an exciting number when you're just
13  
-launching when you only care about revenue, but being able to do a contest 
14  
-for the 1 millionth sold item in the future you'll be glad you were tracking 
15  
-it. 
16  
-
17  
-You then group these individual metrics into a MetricSet, where you define
18  
-how often you want an email report being sent, and to which User(s) it should 
19  
-be sent. 
20  
-
21  
-Settings 
22  
-========
23  
-
24  
-APP_METRIC_BACKEND='app-metrics.backends.db' currently our only backend.
25  
-The plan is to add in redis and celery capture and aggregation as well in the
26  
-future. NOTE: Every call to metric() generates a data write, which may decrease
27  
-your overall performance is you go nuts with them or have a large site. 
28  
-
29  
-Set `METRICS_SEND_ZERO_ACTIVITY` to `False` in your settings.py file to stop
30  
-django-app-metrics from sending you e-mails on days when there's been no activity.
31  
-
32  
-Usage 
33  
-=====
34  
-
35  
-  from app_metrics.utils import create_metric, metric
36  
-
37  
-  # Create a new metric to track 
38  
-  my_metric = create_metric(name='New User Metric', slug='new_user_signup')
39  
-
40  
-  # Create a MetricSet which ties a metric to an email schedule and sets
41  
-  # who should receive it 
42  
-  my_metric_set = create_metric_set(name='My Set', 
43  
-                                    metrics=[my_metric], 
44  
-                                    email_recipients=[user1, user2])
45  
-
46  
-  # Increment the metric by one 
47  
-  metric('new_user_signup') 
48  
-
49  
-  # Increment the metric by some other number 
50  
-  metric('new_user_signup', 4) 
51  
-
52  
-  # Aggregate metric items into daily, weekly, monthly, and yearly totals 
53  
-  # It's fairly smart about it, so you're safe to run this as often as you
54  
-  # like 
55  
-  manage.py metrics_aggregate 
56  
-
57  
-  # Send email reports to users 
58  
-  manage.py metrics_send_mail 
59  
-
60  
-TODO
61  
-====
62  
-
63  
-    - Improve text and HTML templates to display trending data well 
64  
-    - Create redis backend for collection and aggregation 
65  
-    - Create celery backend for collection and aggregation 
66  
-
67  
-
2  app_metrics/__init__.py
... ...
@@ -1 +1 @@
1  
-VERSION = (0, 1, 0)
  1
+VERSION = (0, 3, 0)
12  app_metrics/backends/db.py
... ...
@@ -1,9 +1,5 @@
1  
-from app_metrics.models import Metric, MetricItem 
2  
-
3  
-def metric(slug=None, num=1): 
4  
-    """ Record our metric in the database """ 
5  
-    met = Metric.objects.get(slug=slug)
6  
-
7  
-    new_metric = MetricItem(metric=met, num=num)
8  
-    new_metric.save() 
  1
+from app_metrics.tasks import db_metric_task 
9 2
 
  3
+def metric(slug, num=1, **kwargs): 
  4
+    """ Fire a celery task to record our metric in the database """ 
  5
+    db_metric_task.delay(slug, num, **kwargs) 
21  app_metrics/backends/mixpanel.py
... ...
@@ -0,0 +1,21 @@
  1
+# Backend to handle sending app metrics directly to mixpanel.com 
  2
+# See http://mixpanel.com/api/docs/ for more information on their API 
  3
+
  4
+from django.conf import settings 
  5
+from app_metrics.tasks import mixpanel_metric_task
  6
+from app_metrics.tasks import _get_token 
  7
+
  8
+def metric(slug, num=1, properties=None): 
  9
+    """
  10
+    Send metric directly to Mixpanel
  11
+
  12
+    - slug here will be used as the Mixpanel "event" string 
  13
+    - if num > 1, we will loop over this and send multiple 
  14
+    - properties are a dictionary of additional information you
  15
+      may want to pass to Mixpanel.  For example you might use it like: 
  16
+
  17
+      metric("invite-friends",
  18
+             properties={"method": "email", "number-friends": "12", "ip": "123.123.123.123"})
  19
+    """
  20
+    token = _get_token()
  21
+    mixpanel_metric_task.delay(slug, num, properties) 
5  app_metrics/management/commands/.gitignore
... ...
@@ -1,5 +0,0 @@
1  
-*.pyc 
2  
-backends/*.pyc 
3  
-management/*.pyc
4  
-management/commands/*.pyc
5  
-tests/*.pyc
9  app_metrics/management/commands/metrics_aggregate.py
@@ -3,7 +3,7 @@
3 3
 
4 4
 from app_metrics.models import Metric, MetricItem, MetricDay, MetricWeek, MetricMonth, MetricYear 
5 5
 
6  
-from app_metrics.utils import week_for_date, month_for_date, year_for_date
  6
+from app_metrics.utils import week_for_date, month_for_date, year_for_date, get_backend 
7 7
 
8 8
 class Command(NoArgsCommand): 
9 9
     help = "Aggregate Application Metrics" 
@@ -13,6 +13,13 @@ class Command(NoArgsCommand):
13 13
     def handle_noargs(self, **options): 
14 14
         """ Aggregate Application Metrics """ 
15 15
 
  16
+        backend = get_backend() 
  17
+
  18
+        # If using Mixpanel this command is a NOOP
  19
+        if backend == 'app_metrics.backends.mixpanel': 
  20
+            print "Useless use of metrics_aggregate when using Mixpanel backend"
  21
+            return 
  22
+
16 23
         # Aggregate Items
17 24
         items = MetricItem.objects.all() 
18 25
 
9  app_metrics/management/commands/metrics_send_mail.py
@@ -5,8 +5,8 @@
5 5
 from django.db.models import Q
6 6
 
7 7
 from app_metrics.reports import generate_report
8  
-
9 8
 from app_metrics.models import MetricSet, Metric 
  9
+from app_metrics.utils import get_backend 
10 10
 
11 11
 class Command(NoArgsCommand): 
12 12
     help = "Send Report E-mails" 
@@ -16,6 +16,13 @@ class Command(NoArgsCommand):
16 16
     def handle_noargs(self, **options): 
17 17
         """ Send Report E-mails """ 
18 18
 
  19
+        backend = get_backend() 
  20
+
  21
+        # This command is a NOOP if using the Mixpanel backend 
  22
+        if backend == 'app_metrics.backends.mixpanel': 
  23
+            print "Useless use of metrics_send_email when using Mixpanel backend."
  24
+            return 
  25
+
19 26
         # Determine if we should also send any weekly or monthly reports 
20 27
         today = datetime.date.today() 
21 28
         if today.weekday == 0: 
30  app_metrics/management/commands/move_to_mixpanel.py
... ...
@@ -0,0 +1,30 @@
  1
+from django.core.management.base import NoArgsCommand 
  2
+
  3
+from app_metrics.models import MetricItem
  4
+from app_metrics.backends.mixpanel import metric
  5
+
  6
+class Command(NoArgsCommand): 
  7
+    help = "Move MetricItems from the db backend to MixPanel" 
  8
+
  9
+    requires_model_validation = True 
  10
+
  11
+    def handle_noargs(self, **options): 
  12
+        """ Move MetricItems from the db backend to MixPanel" """ 
  13
+
  14
+        backend = get_backend() 
  15
+
  16
+        # If not using Mixpanel this command is a NOOP
  17
+        if backend != 'app_metrics.backends.mixpanel': 
  18
+            print "You need to set the backend to MixPanel"
  19
+            return 
  20
+
  21
+        items = MetricItem.objects.all() 
  22
+
  23
+        for i in items:
  24
+            properties = {
  25
+                'time': i.created.strftime('%s'),
  26
+            }
  27
+            metric(i.metric.slug, num=i.num, properties=properties)
  28
+
  29
+        # Kill off our items 
  30
+        items.delete() 
5  app_metrics/models.py
@@ -85,8 +85,9 @@ class Meta:
85 85
         verbose_name_plural = 'By Week Metrics' 
86 86
 
87 87
     def __unicode__(self): 
88  
-        return "'%s' for week %d of %d" % (self.metric.name, 
89  
-                                           self.created.strftime("%m"))
  88
+        return "'%s' for week %s of %s" % (self.metric.name, 
  89
+                                           self.created.strftime("%U"),
  90
+                                           self.created.strftime("%Y"))
90 91
 
91 92
 class MetricMonth(models.Model): 
92 93
     """ Aggregation of Metrics on monthly basis """ 
49  app_metrics/tasks.py
... ...
@@ -0,0 +1,49 @@
  1
+import base64
  2
+import json 
  3
+import urllib
  4
+import urllib2
  5
+
  6
+from django.conf import settings 
  7
+from django.core.exceptions import ImproperlyConfigured
  8
+from celery.decorators import task 
  9
+from app_metrics.models import Metric, MetricItem 
  10
+
  11
+
  12
+class MixPanelTrackError(Exception):
  13
+    pass
  14
+
  15
+@task 
  16
+def db_metric_task(slug, num=1, **kwargs): 
  17
+    met = Metric.objects.get(slug=slug)
  18
+
  19
+    new_metric = MetricItem.objects.create(metric=met, num=num)
  20
+
  21
+def _get_token(): 
  22
+    token = getattr(settings, 'APP_METRICS_MIXPANEL_TOKEN', None) 
  23
+
  24
+    if not token: 
  25
+        raise ImproperlyConfigured("You must define APP_METRICS_MIXPANEL_TOKEN when using the mixpanel backend.") 
  26
+    else: 
  27
+        return token 
  28
+
  29
+@task 
  30
+def mixpanel_metric_task(slug, num, properties=None, **kwargs):
  31
+
  32
+    token = _get_token()
  33
+    if properties == None: 
  34
+        properties = dict() 
  35
+
  36
+    if "token" not in properties: 
  37
+        properties["token"] = token 
  38
+
  39
+    url = getattr(settings, 'APP_METRICS_MIXPANEL_API_URL', "http://api.mixpanel.com/track/")
  40
+
  41
+    params = {"event": slug, "properties": properties}
  42
+    b64_data = base64.b64encode(json.dumps(params))
  43
+
  44
+    data = urllib.urlencode({"data": b64_data})
  45
+    req = urllib2.Request(url, data) 
  46
+    for i in range(num):
  47
+        response = urllib2.urlopen(req) 
  48
+        if response.read() == '0':
  49
+            raise MixPanelTrackError(u'MixPanel returned 0')
3  app_metrics/tests/__init__.py
... ...
@@ -1 +1,2 @@
1  
-from app_metrics.tests.tests import * 
  1
+from app_metrics.tests.base_tests import * 
  2
+from app_metrics.tests.mixpanel_tests import * 
12  app_metrics/tests/tests.py → app_metrics/tests/base_tests.py
@@ -12,10 +12,6 @@
12 12
 
13 13
 class MetricCreationTests(TestCase): 
14 14
    
15  
-    def setUp(self): 
16  
-        self.user1 = User.objects.create_user('user1', 'user1@example.com', 'user1pass')
17  
-        self.user2 = User.objects.create_user('user2', 'user2@example.com', 'user2pass')
18  
-
19 15
     def test_metric(self): 
20 16
 
21 17
         new_metric = create_metric(name='Test Metric Class',
@@ -34,8 +30,6 @@ def test_metric(self):
34 30
 class MetricAggregationTests(TestCase): 
35 31
 
36 32
     def setUp(self): 
37  
-        self.user1 = User.objects.create_user('user1', 'user1@example.com', 'user1pass')
38  
-        self.user2 = User.objects.create_user('user2', 'user2@example.com', 'user2pass')
39 33
         self.metric1 = create_metric(name='Test Aggregation1', slug='test_agg1')
40 34
         self.metric2 = create_metric(name='Test Aggregation2', slug='test_agg2')
41 35
 
@@ -83,8 +77,8 @@ class TrendingTests(TestCase):
83 77
     """ Test that our trending logic works """ 
84 78
 
85 79
     def setUp(self): 
86  
-        self.user1 = User.objects.create_user('user1', 'user1@example.com', 'user1pass')
87  
-        self.user2 = User.objects.create_user('user2', 'user2@example.com', 'user2pass')
  80
+        #self.user1 = User.objects.create_user('user1', 'user1@example.com', 'user1pass')
  81
+        #self.user2 = User.objects.create_user('user2', 'user2@example.com', 'user2pass')
88 82
         self.metric1 = create_metric(name='Test Trending1', slug='test_trend1')
89 83
         self.metric2 = create_metric(name='Test Trending2', slug='test_trend2')
90 84
 
@@ -194,7 +188,7 @@ def test_email(self):
194 188
         metric('test_trend1')
195 189
         metric('test_trend2')
196 190
         metric('test_trend2')
197  
-
  191
+        
198 192
         management.call_command('metrics_aggregate')
199 193
         management.call_command('metrics_send_mail')
200 194
 
32  app_metrics/tests/mixpanel_tests.py
... ...
@@ -0,0 +1,32 @@
  1
+from django.test import TestCase 
  2
+from django.conf import settings 
  3
+from django.core.exceptions import ImproperlyConfigured 
  4
+
  5
+from app_metrics.utils import * 
  6
+
  7
+class MixpanelMetricConfigTests(TestCase): 
  8
+
  9
+    def setUp(self): 
  10
+        self.old_backend = settings.APP_METRICS_BACKEND
  11
+        settings.APP_METRICS_BACKEND = 'app_metrics.backends.mixpanel'
  12
+
  13
+    def test_metric(self): 
  14
+        self.assertRaises(ImproperlyConfigured, metric, 'test_metric')
  15
+
  16
+    def tearDown(self): 
  17
+        settings.APP_METRICS_BACKEND = self.old_backend 
  18
+
  19
+class MixpanelCreationTests(TestCase): 
  20
+
  21
+    def setUp(self): 
  22
+        self.old_backend = settings.APP_METRICS_BACKEND
  23
+        self.old_token = settings.APP_METRICS_MIXPANEL_TOKEN 
  24
+        settings.APP_METRICS_BACKEND = 'app_metrics.backends.mixpanel'
  25
+        settings.APP_METRICS_MIXPANEL_TOKEN = 'foobar'
  26
+
  27
+    def test_metric(self): 
  28
+        metric('testing') 
  29
+
  30
+    def tearDown(self): 
  31
+        settings.APP_METRICS_BACKEND = self.old_backend 
  32
+        settings.APP_METRICS_MIXPANEL_TOKEN = self.old_token 
6  app_metrics/tests/settings.py
@@ -11,6 +11,12 @@
11 11
         'django.contrib.sites',
12 12
         'app_metrics',
13 13
         'app_metrics.tests',
  14
+        'djcelery',
14 15
 ]
15 16
 
16 17
 ROOT_URLCONF = 'app_metrics.tests.urls'
  18
+
  19
+CELERY_ALWAYS_EAGER = True 
  20
+
  21
+APP_METRICS_BACKEND = 'app_metrics.backends.db'
  22
+APP_METRICS_MIXPANEL_TOKEN = None 
30  app_metrics/utils.py
@@ -4,8 +4,17 @@
4 4
 
5 5
 from app_metrics.models import Metric, MetricSet, MetricItem 
6 6
 
  7
+def get_backend(): 
  8
+     return getattr(settings, 'APP_METRICS_BACKEND', 'app_metrics.backends.db')
  9
+
7 10
 def create_metric_set(name=None, metrics=None, email_recipients=None, no_email=False, send_daily=True, send_weekly=False, send_monthly=False): 
8 11
     """ Create a metric set """ 
  12
+
  13
+    # This should be a NOOP for the mixpanel backend 
  14
+    backend = get_backend()
  15
+    if backend == 'app_metrics.backends.mixpanel': 
  16
+        return 
  17
+
9 18
     try: 
10 19
         metric_set = MetricSet(
11 20
                             name=name, 
@@ -26,9 +35,14 @@ def create_metric_set(name=None, metrics=None, email_recipients=None, no_email=F
26 35
 
27 36
     return metric_set 
28 37
 
29  
-def create_metric(name=None, slug=None): 
  38
+def create_metric(name, slug): 
30 39
     """ Create a new type of metric to track """ 
31 40
 
  41
+    # Make this a NOOP for the mixpanel backend 
  42
+    backend = get_backend() 
  43
+    if backend == 'app_metrics.backends.mixpanel': 
  44
+        return 
  45
+
32 46
     # See if this metric already exists 
33 47
     existing = Metric.objects.filter(name=name, slug=slug) 
34 48
 
@@ -42,7 +56,7 @@ def create_metric(name=None, slug=None):
42 56
 class InvalidMetricsBackend(Exception): pass 
43 57
 class MetricError(Exception): pass 
44 58
 
45  
-def metric(slug=None, num=1):
  59
+def metric(slug, num=1, **kwargs):
46 60
     """ Increment a metric """ 
47 61
    
48 62
     backend_string = getattr(settings, 'APP_METRICS_BACKEND', 'app_metrics.backends.db')
@@ -54,20 +68,20 @@ def metric(slug=None, num=1):
54 68
         raise InvalidMetricsBackend("Could not load '%s' as a backend" % backend_string )
55 69
 
56 70
     try: 
57  
-        backend.metric(slug, num)
  71
+        backend.metric(slug, num, **kwargs)
58 72
     except Metric.DoesNotExist: 
59 73
         create_metric(slug=slug, name='Autocreated Metric')
60 74
 
61  
-def week_for_date(date=None): 
  75
+def week_for_date(date): 
62 76
     return date - datetime.timedelta(days=date.weekday())
63 77
 
64  
-def month_for_date(month=None): 
  78
+def month_for_date(month): 
65 79
     return month - datetime.timedelta(days=month.day-1)
66 80
 
67  
-def year_for_date(year=None): 
  81
+def year_for_date(year): 
68 82
     return datetime.date(year.year, 01, 01)
69 83
 
70  
-def get_previous_month(date=None): 
  84
+def get_previous_month(date): 
71 85
     if date.month == 1: 
72 86
         month_change = 12 
73 87
     else: 
@@ -76,6 +90,6 @@ def get_previous_month(date=None):
76 90
 
77 91
     return new.replace(month=month_change)
78 92
 
79  
-def get_previous_year(date=None): 
  93
+def get_previous_year(date): 
80 94
     new = date 
81 95
     return new.replace(year=new.year-1)
13  setup.py
@@ -4,7 +4,7 @@
4 4
 from app_metrics import VERSION
5 5
 
6 6
 
7  
-f = open(os.path.join(os.path.dirname(__file__), 'README.txt'))
  7
+f = open(os.path.join(os.path.dirname(__file__), 'README.rst'))
8 8
 readme = f.read()
9 9
 f.close()
10 10
 
@@ -15,8 +15,17 @@
15 15
     long_description=readme,
16 16
     author='Frank Wiles',
17 17
     author_email='frank@revsys.com',
18  
-    url='http://github.com/frankwiles/django-app-metrics/tree/master',
  18
+    url='https://github.com/frankwiles/django-app-metrics',
19 19
     packages=find_packages(),
  20
+    package_data={
  21
+        'app_metrics': [
  22
+            'templates/app_metrics/*',
  23
+        ]
  24
+    },
  25
+    install_requires = [
  26
+        'celery',
  27
+        'django-celery',
  28
+    ],
20 29
     classifiers=[
21 30
         'Development Status :: 4 - Beta',
22 31
         'Environment :: Web Environment',

0 notes on commit fcf1871

Please sign in to comment.
Something went wrong with that request. Please try again.