Add basic click & view tracking
parent
5b3b1a843d
commit
bf7cb1df3c
|
@ -1,5 +1,9 @@
|
||||||
|
import random
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
DISPLAY_CHOICES = (
|
DISPLAY_CHOICES = (
|
||||||
('doc', 'Documentation Pages'),
|
('doc', 'Documentation Pages'),
|
||||||
|
@ -50,9 +54,26 @@ class SupporterPromo(models.Model):
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
"A dict respresentation of this for JSON encoding"
|
"A dict respresentation of this for JSON encoding"
|
||||||
|
image_url = reverse(
|
||||||
|
'donate_view_proxy',
|
||||||
|
kwargs={'promo_id': self.pk, 'hash': random.random()}
|
||||||
|
)
|
||||||
|
link_url = reverse(
|
||||||
|
'donate_click_proxy',
|
||||||
|
kwargs={'promo_id': self.pk}
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
'id': self.analytics_id,
|
'id': self.analytics_id,
|
||||||
'text': self.text,
|
'text': self.text,
|
||||||
'link': self.link,
|
'link': link_url,
|
||||||
'image': self.image,
|
'image': image_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SupporterImpressions(models.Model):
|
||||||
|
promo = models.ForeignKey(SupporterPromo, related_name='impressions',
|
||||||
|
blank=True, null=True)
|
||||||
|
date = models.DateField(_('Date'))
|
||||||
|
offers = models.IntegerField(_('Offer'), default=0)
|
||||||
|
views = models.IntegerField(_('View'), default=0)
|
||||||
|
clicks = models.IntegerField(_('Clicks'), default=0)
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from django_dynamic_fixture import get
|
||||||
|
|
||||||
|
from .models import SupporterPromo
|
||||||
|
from readthedocs.projects.models import Project
|
||||||
|
|
||||||
|
|
||||||
|
class PromoTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.promo = get(SupporterPromo,
|
||||||
|
slug='promo-slug',
|
||||||
|
link='http://example.com',
|
||||||
|
image='http://media.example.com/img.png')
|
||||||
|
|
||||||
|
def test_clicks(self):
|
||||||
|
resp = self.client.get('http://testserver/sustainability/click/%s/' % self.promo.id)
|
||||||
|
self.assertEqual(resp._headers['location'][1], 'http://example.com')
|
||||||
|
promo = SupporterPromo.objects.get(pk=self.promo.pk)
|
||||||
|
impression = promo.impressions.first()
|
||||||
|
self.assertEqual(impression.clicks, 1)
|
||||||
|
|
||||||
|
def test_views(self):
|
||||||
|
resp = self.client.get('http://testserver/sustainability/click/%s/random_hash/' % self.promo.id)
|
||||||
|
self.assertEqual(resp._headers['location'][1], 'http://media.example.com/img.png')
|
||||||
|
promo = SupporterPromo.objects.get(pk=self.promo.pk)
|
||||||
|
impression = promo.impressions.first()
|
||||||
|
self.assertEqual(impression.views, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class FooterTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.promo = get(SupporterPromo,
|
||||||
|
live=True,
|
||||||
|
slug='promo-slug',
|
||||||
|
display_type='doc',
|
||||||
|
link='http://example.com',
|
||||||
|
image='http://media.example.com/img.png')
|
||||||
|
self.pip = get(Project, slug='pip')
|
||||||
|
|
||||||
|
def test_footer(self):
|
||||||
|
r = self.client.get(
|
||||||
|
'/api/v2/footer_html/?project=pip&version=latest&page=index'
|
||||||
|
)
|
||||||
|
resp = json.loads(r.content)
|
||||||
|
self.assertEqual(resp['promo_data']['link'], '/sustainability/click/%s/' % self.promo.pk)
|
|
@ -3,6 +3,7 @@ from django.conf.urls import url, patterns, include
|
||||||
from .views import DonateCreateView
|
from .views import DonateCreateView
|
||||||
from .views import DonateListView
|
from .views import DonateListView
|
||||||
from .views import DonateSuccessView
|
from .views import DonateSuccessView
|
||||||
|
from .views import click_proxy, view_proxy
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
|
@ -10,4 +11,6 @@ urlpatterns = patterns(
|
||||||
url(r'^$', DonateListView.as_view(), name='donate'),
|
url(r'^$', DonateListView.as_view(), name='donate'),
|
||||||
url(r'^contribute/$', DonateCreateView.as_view(), name='donate_add'),
|
url(r'^contribute/$', DonateCreateView.as_view(), name='donate_add'),
|
||||||
url(r'^contribute/thanks$', DonateSuccessView.as_view(), name='donate_success'),
|
url(r'^contribute/thanks$', DonateSuccessView.as_view(), name='donate_success'),
|
||||||
|
url(r'^click/(?P<promo_id>\d+)/(?P<hash>.+)/$', view_proxy, name='donate_view_proxy'),
|
||||||
|
url(r'^click/(?P<promo_id>\d+)/$', click_proxy, name='donate_click_proxy'),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
"""Donation views"""
|
"""Donation views"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db.models import F
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from vanilla import CreateView, ListView
|
from vanilla import CreateView, ListView
|
||||||
|
from redis import Redis, ConnectionError
|
||||||
|
import pytz
|
||||||
|
|
||||||
from readthedocs.payments.mixins import StripeMixin
|
from readthedocs.payments.mixins import StripeMixin
|
||||||
|
|
||||||
from .models import Supporter
|
from .models import Supporter, SupporterPromo
|
||||||
from .forms import SupporterForm
|
from .forms import SupporterForm
|
||||||
from .mixins import DonateProgressMixin
|
from .mixins import DonateProgressMixin
|
||||||
|
|
||||||
|
@ -54,3 +61,51 @@ class DonateListView(DonateProgressMixin, ListView):
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
return [self.template_name]
|
return [self.template_name]
|
||||||
|
|
||||||
|
|
||||||
|
def click_proxy(request, promo_id, redis=False):
|
||||||
|
promo = SupporterPromo.objects.get(pk=promo_id)
|
||||||
|
date = pytz.utc.localize(datetime.datetime.utcnow())
|
||||||
|
day = datetime.datetime(
|
||||||
|
year=date.year,
|
||||||
|
month=date.month,
|
||||||
|
day=date.day,
|
||||||
|
tzinfo=pytz.utc,
|
||||||
|
)
|
||||||
|
if redis:
|
||||||
|
redis = Redis.from_url(settings.BROKER_URL)
|
||||||
|
redis.incr('{slug}-{year}-{month}-{day}-clicks'.format(
|
||||||
|
slug=promo.analytics_id,
|
||||||
|
year=day.year,
|
||||||
|
month=day.month,
|
||||||
|
day=day.day,
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
impression, _ = promo.impressions.get_or_create(date=day)
|
||||||
|
impression.clicks = F('clicks') + 1
|
||||||
|
impression.save()
|
||||||
|
return redirect(promo.link)
|
||||||
|
|
||||||
|
|
||||||
|
def view_proxy(request, promo_id, hash, redis=False):
|
||||||
|
promo = SupporterPromo.objects.get(pk=promo_id)
|
||||||
|
date = pytz.utc.localize(datetime.datetime.utcnow())
|
||||||
|
day = datetime.datetime(
|
||||||
|
year=date.year,
|
||||||
|
month=date.month,
|
||||||
|
day=date.day,
|
||||||
|
tzinfo=pytz.utc,
|
||||||
|
)
|
||||||
|
if redis:
|
||||||
|
redis = Redis.from_url(settings.BROKER_URL)
|
||||||
|
redis.incr('{slug}-{year}-{month}-{day}-views'.format(
|
||||||
|
slug=promo.analytics_id,
|
||||||
|
year=day.year,
|
||||||
|
month=day.month,
|
||||||
|
day=day.day,
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
impression, _ = promo.impressions.get_or_create(date=day)
|
||||||
|
impression.views = F('views') + 1
|
||||||
|
impression.save()
|
||||||
|
return redirect(promo.image)
|
||||||
|
|
Loading…
Reference in New Issue