├── prelaunch
├── __init__.py
├── templatetags
│ ├── __init__.py
│ └── prelaunch_tags.py
├── templates
│ └── prelaunch
│ │ ├── confirmation_email_subject.txt
│ │ ├── confirmation_email.txt
│ │ └── inclusion_tags
│ │ └── prelaunch_form.html
├── admin.py
├── forms.py
├── middleware.py
├── settings.py
├── models.py
└── shorten.py
├── .gitignore
├── setup.py
└── README.rst
/prelaunch/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/prelaunch/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | django_prelaunch.egg-info
2 | build/
3 | dist/
4 | *.pyc
5 |
--------------------------------------------------------------------------------
/prelaunch/templates/prelaunch/confirmation_email_subject.txt:
--------------------------------------------------------------------------------
1 | Thanks for your beta application!
2 |
--------------------------------------------------------------------------------
/prelaunch/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from models import PrelaunchSubscriber
4 |
5 | admin.site.register(PrelaunchSubscriber)
6 |
--------------------------------------------------------------------------------
/prelaunch/templates/prelaunch/confirmation_email.txt:
--------------------------------------------------------------------------------
1 | Thank you for applying to our closed beta!
2 |
3 | We will be sending out beta invites soon. To jump to the front of the line for an invite, just send some of your friends/followers to also apply for the beta, using the following url:
4 |
5 | {{ http_referer }}?{{ referrer_parameter }}={{ referrer_code }}
6 |
7 | Thanks,
8 |
9 | The Django Prelaunch team
10 |
--------------------------------------------------------------------------------
/prelaunch/templates/prelaunch/inclusion_tags/prelaunch_form.html:
--------------------------------------------------------------------------------
1 | {% if not email_hash %}
2 |
6 | {% else %}
7 | Congratulations, you are a winner! hash: {{ email_hash }}
8 |
9 |

10 |
11 | {% endif %}
12 |
--------------------------------------------------------------------------------
/prelaunch/forms.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | from django import forms
3 | from django.core.mail import send_mail
4 | from django.shortcuts import render_to_response
5 | from django.template import loader, Context, RequestContext
6 |
7 | from django.conf import settings
8 | from settings import *
9 | from models import PrelaunchSubscriber
10 | from shorten import ShortCode
11 |
12 |
13 | class PrelaunchForm(forms.Form):
14 | email_address = forms.EmailField()
15 | prelaunch_referrer = forms.CharField(
16 | widget=forms.HiddenInput(),
17 | required=False)
18 |
19 |
--------------------------------------------------------------------------------
/prelaunch/middleware.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from settings import *
3 |
4 | class PrelaunchMiddleware(object):
5 | """ Adds a cookie to the response if a referrer parameter is present.
6 | """
7 |
8 | def process_response(self, request, response):
9 | """ Add or change the cookie.
10 | """
11 | prelaunch_parameter = getattr(settings,
12 | 'PRELAUNCH_PARAMETER_NAME',
13 | PRELAUNCH_PARAMETER_NAME)
14 |
15 | referrer = request.REQUEST.get(prelaunch_parameter, None)
16 | if referrer and not request.COOKIES.get('prelaunch_referrer'):
17 | response.set_cookie('prelaunch_referrer', referrer)
18 | return response
19 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name='django-prelaunch',
5 | description='A Django app to gather email addresses of people interested in your prelaunch-stage website.',
6 | version='0.1',
7 | packages = find_packages(),
8 | classifiers=[
9 | "Programming Language :: Python",
10 | "Topic :: Software Development :: Libraries :: Python Modules",
11 | "Framework :: Django",
12 | "Intended Audience :: Developers",
13 | "Environment :: Web Environment",
14 | "Development Status :: 4 - Beta",
15 | ],
16 | keywords='django',
17 | author='Johan Beyers',
18 | author_email='jbeyers@juizi.com',
19 | url='http://github.com/jbeyers/django-prelaunch',
20 | license='BSD',
21 | install_requires = []
22 | )
23 |
--------------------------------------------------------------------------------
/prelaunch/settings.py:
--------------------------------------------------------------------------------
1 | # The following settings are the defaults. If you want to override them, you
2 | # can copy them to your main settings.py and adjust as needed.
3 |
4 | # The numbers and letters allowed in the referral code. A string, with no
5 | # repeats. The string below consists of numbers and lower-case letters, with 0,
6 | # o, 1, i and l removed to make it easier to copy by hand without confusion.
7 | PRELAUNCH_DIGITS = '23456789abcdefghjkmnpqrstuvwxyz'
8 |
9 | # Offset the referral code with this amount. This is to prevent single-digit
10 | # or double-digit codes, if you care about that kind of thing.
11 | PRELAUNCH_OFFSET = 1200
12 |
13 | # The referral code typically looks something like this by default:
14 | # http://yourdomain.com/?referrer=3hg6sl
15 | # You can change the parameter name to something that does not clash with other
16 | # apps or looks nicer.
17 | PRELAUNCH_PARAMETER_NAME = 'referrer'
18 |
--------------------------------------------------------------------------------
/prelaunch/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 | from django.conf import settings
4 | from prelaunch.settings import *
5 | from shorten import ShortCode
6 |
7 | class PrelaunchSubscriber(models.Model):
8 | user = models.ForeignKey(User, blank=True, null=True)
9 | email = models.EmailField()
10 | referrer = models.ForeignKey('self', blank=True, null=True)
11 |
12 | def __unicode__(self):
13 | return u'%s' % self.email
14 |
15 | @property
16 | def shortcode(self):
17 | """ Get a shortcode for the user """
18 | prelaunch_digits = getattr(settings, 'PRELAUNCH_DIGITS',
19 | PRELAUNCH_DIGITS)
20 | prelaunch_offset = getattr(settings, 'PRELAUNCH_OFFSET',
21 | PRELAUNCH_OFFSET)
22 | shortcode = ShortCode(prelaunch_offset, prelaunch_digits)
23 | return shortcode.get_shortcode(self.id)
24 |
25 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | While developing the next Facebook, you want to get some buzz going, get beta-testers and start building your list of interested people. This app lets you do that.
2 |
3 | Prelaunch provides you with a couple of tools to put up a simple buzz-generating and information-gathering pre-launch page.
4 |
5 | Current functionality:
6 | ----------------------
7 |
8 | * A simple signup form asking for email.
9 | * A referral code for each signup, used to refer other people to the site.
10 | * Tracking of the referral code, so that you can identify prolific referrers.
11 | * An example of how to show a QR code image of the referral code, for easy distribution.
12 |
13 | Future planned functionality:
14 | ---------------------
15 |
16 | * Admin functionality to convert prelaunch emails to user accounts.
17 | * Hooks to automatically create user accounts after a specified number of referrals.
18 | * Analytics integration to track where the referrers come from.
19 | * Counting referrals that do not sign up.
20 | * Getting the domain from the sites framework and populating the welcome message using the domain.
21 |
22 | Some design considerations:
23 | ---------------------------
24 |
25 | We do not create user accounts immediately, since that reduces flexibility. Forcing the creation of user accounts bloats the user account system.
26 |
27 | The referral code should be as short as possible. We use a list of allowed characters. The code looks like a shorturl code. We remove easily-confused characters like 1 and l, 0 and O, etc. to make it easy to read and give out to other people.
28 |
29 | How to get started:
30 | -------------------
31 |
32 | Firstly, make django-prelaunch part of your django project by including it in your buildout.cfg or installing it in your django instance.
33 |
34 | Then, in your settings.py:
35 |
36 | * Make sure you include 'django.core.context_processors.request' in your context processors.
37 |
38 | * Add 'prelaunch' to your installed apps.
39 |
40 | Now you can add a prelaunch form in a template by doing the following:
41 |
42 | * add a {% load prelaunch_tags %} template tag to the template
43 | * Add a {% prelaunch_form %} tag where you want the form to appear.
44 |
45 | Customising the look and feel:
46 | ------------------------------
47 |
48 | You can customise the templates to your liking by copying them into the templates/prelaunch directory in your project and changing them.
49 |
50 | If you want to customise the settings in settings.py, just copy them to your own settings.py and customise.
51 |
--------------------------------------------------------------------------------
/prelaunch/shorten.py:
--------------------------------------------------------------------------------
1 | class ShortCode():
2 | """Translate from a shortcode string to integer and vice versa.
3 |
4 | >>> digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
5 | >>> hex = '0123456789ABCDEF'
6 | >>> alpha = map(chr, range(97, 123))
7 |
8 | No offset and direct digit substitution should be equal to str
9 | >>> coder = ShortCode(0, 'abcdgg')
10 | Traceback (most recent call last):
11 | ...
12 | Exception: Duplication in digits is not allowed.
13 |
14 | No offset and direct digit substitution should be equal to str
15 | >>> coder = ShortCode(0, digits)
16 | >>> coder.get_shortcode(12345)
17 | '12345'
18 |
19 | >>> coder.get_number('12345')
20 | 12345
21 |
22 | The offset gets added to the integer.
23 | >>> coder = ShortCode(100, digits)
24 | >>> coder.get_shortcode(12345)
25 | '12445'
26 |
27 | >>> coder.get_number('12345')
28 | 12245
29 |
30 | Using hex digits give us an integer to hex conversion.
31 | >>> hexer = ShortCode(0, hex)
32 | >>> hexer.get_shortcode(255)
33 | 'FF'
34 |
35 | >>> hexer.get_shortcode(256)
36 | '100'
37 |
38 | >>> alpher = ShortCode(0, alpha)
39 | >>> alpher.get_shortcode(0)
40 | 'a'
41 |
42 | >>> alpher.get_shortcode(1)
43 | 'b'
44 |
45 | >>> alpher.get_shortcode(25)
46 | 'z'
47 |
48 | >>> alpher.get_shortcode(26)
49 | 'ba'
50 |
51 | """
52 |
53 | def __init__(self, offset, digits):
54 | """ Get the shortcode from the number """
55 | # Duplication of digits is not allowed.
56 | if len(digits) != len(set(digits)):
57 | raise Exception('Duplication in digits is not allowed.')
58 |
59 | self.offset = offset
60 | self.digits = digits
61 |
62 |
63 | def get_shortcode(self, number):
64 | """ Get the shortcode from the number """
65 | still_more = 1
66 | alpha = []
67 | nom = number + self.offset
68 |
69 | # Divide and conquer
70 | while still_more:
71 | still_more, moddy = divmod(nom, len(self.digits ))
72 | alpha.append(self.digits[moddy])
73 | nom = nom/len(self.digits)
74 |
75 | # Reverse, join, return
76 | alpha.reverse()
77 | return ''.join(alpha)
78 |
79 |
80 | def get_number(self, shortcode):
81 | """ Get the number from the shortcode. """
82 | if not shortcode:
83 | raise Error
84 | base = len(self.digits)
85 | number = self.digits.index(shortcode[0])
86 | if len(shortcode) > 1:
87 | for digit in shortcode[1:]:
88 | number = number * base
89 | number += self.digits.index(digit)
90 | number -= self.offset
91 | if number <= 0:
92 | return None
93 | return number
94 |
95 |
96 | if __name__ == "__main__":
97 | import doctest
98 | doctest.testmod()
99 |
--------------------------------------------------------------------------------
/prelaunch/templatetags/prelaunch_tags.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.conf import settings
3 | from django.core.mail import send_mail
4 | from django.template import loader, Context
5 |
6 | from prelaunch.settings import *
7 | from prelaunch.models import PrelaunchSubscriber
8 | from prelaunch.shorten import ShortCode
9 | from prelaunch.forms import PrelaunchForm
10 |
11 | register = template.Library()
12 |
13 | @register.inclusion_tag('prelaunch/inclusion_tags/prelaunch_form.html', takes_context=True)
14 | def prelaunch_form(context):
15 | """
16 | The prelaunch form.
17 | """
18 | request = context['request']
19 | prelaunch_parameter = getattr(settings,
20 | 'PRELAUNCH_PARAMETER_NAME',
21 | PRELAUNCH_PARAMETER_NAME)
22 |
23 | prelaunch_offset = getattr(settings, 'PRELAUNCH_OFFSET', PRELAUNCH_OFFSET)
24 | prelaunch_digits = getattr(settings, 'PRELAUNCH_DIGITS', PRELAUNCH_DIGITS)
25 | shortcode = ShortCode(prelaunch_offset, prelaunch_digits)
26 | email_hash = ''
27 |
28 | if request.method == 'POST': # If the form has been submitted...
29 | form = PrelaunchForm(request.POST) # A form bound to the POST data
30 | if form.is_valid():
31 | referrer = None
32 | if form.cleaned_data['prelaunch_referrer']:
33 | referrer_id = shortcode.get_number(
34 | form.cleaned_data['prelaunch_referrer'])
35 | if referrer_id:
36 | referrer = PrelaunchSubscriber.objects.filter(
37 | id=referrer_id)
38 | if referrer:
39 | referrer = referrer[0]
40 |
41 | # Create a prelaunch user, if needed.
42 | subscriber = PrelaunchSubscriber.objects.filter(
43 | email=form.cleaned_data['email_address'])
44 | if not subscriber:
45 | subscriber = PrelaunchSubscriber(
46 | email=form.cleaned_data['email_address'],
47 | referrer=referrer)
48 | subscriber.save()
49 | else:
50 | subscriber = subscriber[0]
51 |
52 |
53 | email_hash = '%s?%s=%s' % (request.META['HTTP_REFERER'],
54 | prelaunch_parameter,
55 | subscriber.shortcode)
56 | # Send a welcoming/explanatory email
57 | c = Context({
58 | 'http_referer': request.META['HTTP_REFERER'],
59 | 'email_address': form.cleaned_data['email_address'],
60 | 'referrer_parameter': prelaunch_parameter,
61 | 'referrer_code': subscriber.shortcode,
62 | })
63 |
64 | # Ensure that we have a single line subject, since templates add a
65 | # linefeed automatically.
66 | subject = loader.get_template(
67 | 'prelaunch/confirmation_email_subject.txt').render(c)
68 | subject = subject.split('\n')[0]
69 |
70 | message = loader.get_template(
71 | 'prelaunch/confirmation_email.txt').render(c)
72 | email_address = form.cleaned_data['email_address']
73 | sender = ''
74 | if settings.ADMINS:
75 | sender = settings.ADMINS[0][1]
76 | recipients = [i[1] for i in settings.ADMINS]
77 | recipients.append(email_address)
78 |
79 | send_mail(subject, message, sender, recipients)
80 |
81 | else:
82 | prelaunch_referrer = request.COOKIES.get('prelaunch_referrer', '')
83 | if not prelaunch_referrer \
84 | and request.REQUEST.get(prelaunch_parameter, ''):
85 | prelaunch_referrer = request.REQUEST.get(prelaunch_parameter, '')
86 | form = PrelaunchForm(initial={'prelaunch_referrer': prelaunch_referrer})
87 |
88 | return {'form_target': request.path,
89 | 'email_hash': email_hash,
90 | 'form': form}
91 |
92 |
--------------------------------------------------------------------------------