├── 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 |
{% csrf_token %} 3 | {{ form.as_p }} 4 | 5 |
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 | --------------------------------------------------------------------------------