diff --git a/ccvpn/settings.py b/ccvpn/settings.py index 99907e6..a4a4e58 100644 --- a/ccvpn/settings.py +++ b/ccvpn/settings.py @@ -57,6 +57,7 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.security.SecurityMiddleware', 'django.middleware.locale.LocaleMiddleware', 'lambdainst.middleware.ReferrerMiddleware', + 'lambdainst.middleware.CampaignMiddleware', ) ROOT_URLCONF = 'ccvpn.urls' diff --git a/lambdainst/admin.py b/lambdainst/admin.py index 74c41a8..d885033 100644 --- a/lambdainst/admin.py +++ b/lambdainst/admin.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext as _ from lambdainst.models import VPNUser, GiftCode, GiftCodeUser from . import core + def make_user_link(user): change_url = resolve_url('admin:auth_user_change', user.id) return '%s' % (change_url, user.username) @@ -31,8 +32,8 @@ class VPNUserInline(admin.StackedInline): fk_name = 'user' fields = ('notes', 'expiration', 'last_expiry_notice', 'notify_expiration', - 'trial_periods_given', 'referrer_a', 'last_vpn_auth') - readonly_fields = ('referrer_a', 'last_vpn_auth') + 'trial_periods_given', 'referrer_a', 'campaign', 'last_vpn_auth') + readonly_fields = ('referrer_a', 'last_vpn_auth', 'campaign') def referrer_a(self, object): if not object.referrer: diff --git a/lambdainst/middleware.py b/lambdainst/middleware.py index de06694..000ac4f 100644 --- a/lambdainst/middleware.py +++ b/lambdainst/middleware.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +import re from django.conf import settings from .models import User @@ -40,3 +41,41 @@ class ReferrerMiddleware(): secure=settings.SESSION_COOKIE_SECURE or None) return response + +class CampaignMiddleware(): + GET_FIELDS = ['pk_campaign', 'utm_campaign', 'utm_medium', 'utm_source'] + + def _get_name(self, request): + for f in self.GET_FIELDS: + if f in request.GET and request.GET[f]: + return request.GET[f] + if 'campaign' in request.COOKIES: + return request.COOKIES['campaign'] + return None + + def process_request(self, request): + name = self._get_name(request) + if not name: + return + + name = name.strip() + + if len(name) >= 64 or not re.match('^[a-zA-Z0-9_.:-]+$', name): + return + + request.session['campaign'] = name + + def process_response(self, request, response): + name = request.session.get('campaign') + if not name: + return response + + max_age = 365 * 24 * 60 * 60 + expires = (datetime.utcnow() + timedelta(seconds=max_age)) + expires = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + response.set_cookie('campaign', name, + max_age=max_age, + expires=expires, + domain=settings.SESSION_COOKIE_DOMAIN, + secure=settings.SESSION_COOKIE_SECURE or None) + return response diff --git a/lambdainst/migrations/0002_vpnuser_campaign.py b/lambdainst/migrations/0002_vpnuser_campaign.py new file mode 100644 index 0000000..ade03e7 --- /dev/null +++ b/lambdainst/migrations/0002_vpnuser_campaign.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-01 18:50 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lambdainst', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='vpnuser', + name='campaign', + field=models.CharField(blank=True, max_length=64, null=True), + ), + ] diff --git a/lambdainst/models.py b/lambdainst/models.py index 0bf225d..2badd17 100644 --- a/lambdainst/models.py +++ b/lambdainst/models.py @@ -41,6 +41,7 @@ class VPNUser(models.Model): referrer = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL, related_name='referrals') referrer_used = models.BooleanField(default=False) + campaign = models.CharField(blank=True, null=True, max_length=64) @property def is_paid(self): diff --git a/lambdainst/views.py b/lambdainst/views.py index 77e3c7d..c316d7e 100644 --- a/lambdainst/views.py +++ b/lambdainst/views.py @@ -85,6 +85,8 @@ def signup(request): except User.DoesNotExist: pass + user.vpnuser.campaign = request.session.get('campaign') + user.vpnuser.save() user.backend = 'django.contrib.auth.backends.ModelBackend'