Name

django-paid-accounts.

Goals

A common goal in web application development goes something like this.

  1. You want to allow users to create accounts.
  2. Users should get a subdomain for their account. This is most useful in a UGC webapp, as users prefer www.theirrsite.com as if they came to theirname.yoursite.com.
  3. Users should be able to get a domain for use with their account. So the users should be able to use
  4. You should be able to charge users for the account creation etc.

This is a application which makes achieving the above goals a breeze.

Installation

  1. easy_install django-sudomains
  2. Get it from here and put in your site-packages.Download here

After you have installed it import subdomains should work on your python shell.

License

This application is dual licensed under the following terms.

  1. This application is free for use in open source applications.
  2. For commercial use requires you should buy a license with the following terms.

Each license costs USD 400, and provides

  1. Lifetime upgrades.
  2. Lifetime email support.
  3. Use of library on unlimited sites and projects.

Please contact licenses@uswaretech.com to buy a license.

The problem

  1. Django url configuration mapping has no concept of the Domain or Subdomain being accessed.
  2. Django sites framework assumes one site per settings.py.
  3. There was no simple paypal library for use with Django.

Features

  1. You have access to the current subdomain/domain in the views.
  2. There is very well documented supporting machinary.
  3. Using Paypal requires nothing more than a simple Genric view.
  4. You can attach attach arbitrary settings to the Subdomains.

API

  1. A middleware which updates the request to Populate the current domain, and the current Subdomain.
  2. A request context which puploates the current domain and the current Subdomain in the templates.
  3. A decorator which allows you to redirect the user if they are not currently on a Subdomain.
  4. A decorator which allows you to redirect the user if they are not currently on the main domain.
  5. Paypal based account activation.

The starting point for using this is adding subdomain.middleware.GetSubdomainMiddleware to your settings.py.

We assume that our base domain is foo.tld.

The various cases are.

  1. You are on subdomain of your main domain www.foo.tld, this is in settings.MAIN_SITE.:

    request.main_site = True
    request.subdomain_text = 'www'
    request.subdomain = None
    
  2. You are on subdomain of your main domain bar.foo.tld, a Subdomain named bar exists in the DB.:

    request.main_site = True
    request.subdomain_text = 'bar'
    request.subdomain = Subdomain.object.get(subdomain_text = 'bar')
    
  3. You are on subdomain of your main domain baz.foo.tld, a Subdomain named baz does not exists in the DB.:

    request.main_site = False
    request.subdomain_text = 'baz'
    request.subdomain = None
    
  4. You are on another domain, www.notfoo.tld, and a Subdomain is mapped to www.notfoo.tld in the DB.:

    request.main_site = False
    request.subdomain_text = www.notfoo.tld
    request.subdomain = Subdomain.object.get(domain = 'www.notfoo.tld')
    
  5. You are on another domain, www.notfoonotbar.tld, and no Subdomain is mapped to www.notfoonotbar.tld in the DB.:

    request.main_site = False
    request.subdomain_text = www.notfoo.tld
    request.subdomain = None
    

The Subdomain class

Subdomain class has attributes:

class Subdomain:
    subdomain_text = models.CharField(max_length = 100, unique = True)
    domain = models.CharField(null = True, blank = True, max_length = 100, unique = True)
    name = models.CharField(max_length = 100)
    description = models.TextField()

    user = models.ForeignKey(User)
    created_on = models.DateTimeField(auto_now_add = 1)
    updated_on = models.DateTimeField(auto_now = 1)

For a subdomain bar.foo.tld. subdomain_text is ‘bar’, domain is None. A user has to asscociated to each Subdomain. This user should be generally treated as the owner of this subdomain.

Creating a new Subdomain:

Eg
Subdomain.objects.register_new_subdomain(subdomain_text = ‘foo2’, name=’Foo, the subdomain’, description = ‘Foo has not description’, user = user)

All the arguments in the previous call are required. There are two optional arguments.

domain: eg ‘bar.notfoo.tld’. Note that this is the FQDN without the trailing dot. No http:// is required or allowed. subdomain_callback: This must be callable, which takes Subdomain as the argument. This might be used as the profile creation callback.

register_new_subdomain returns the newly created and saved subdomain.

The Subdomain API:

get_absolute_url:

If domain is None, subdomain is ‘bar’. Returns ‘bar.foo.tld’ If domain is ‘bar.notfoo.tld’ returns ‘bar.notfoo.tld’ (Notice no http://)

is_main_domain:

If domain is None, return True If domain is not None, return False

Subdomain settings:

Your subdomain would need some settings. Think of AUTH_PROFILE_MODULE for auth.User. So we have SUBDOMAIN_SETTINGS_MODULE in the settings.py. This setting must point to appname.ModelClass, which stores the settings for your Subdomain. The modelclass must have an attribute named subdomain as a ForeignKey to Subdomain.

Having this setting you can do something like,:

subdomain = Subdomain.objects.get(...)
subdomain.get_settings()

Context processors:

populate_subdomain

Just populates the objects populated into templates for ease of use. Variables populated,:

main_site
subdomain_text
subdomain

Which are the same as that populated by the middleware.

Decorators

ensure_has_subdomain:

Ensure that request has a Subdomain. Eg if the url is ‘bar.foo.tld’ a Subdomain must exist with ‘subdomain_text’ of ‘bar’. With custom domains, ‘foo.notfoo.tld’ a Subdomain must exist with domain of ‘foo.notfoo.tld’. If subdomain exists, the decorated view proceeds normally.

If no Subdomain exists, the decorator redirects to reverse(‘REGISTER_SUBDOMAIN_REDIRECT’).

For example, if you access ‘notbar.foo.tld’ and a Subdomain ‘notbar’ does not exist, this will redirect to ‘notbar.foo.tld/accounts/register/’ (or something similar).

ensure_is_main_subdomain:

If current subdomain_text is not in settings.MAIN_SUBDOMAIN, redirects to the first Subdomain listed in settings.MAIN_SUBDOMAIN.

eg settings.MAIN_SUBDOMAIN = [‘www’, ‘mainsite’, ‘home’]

If user navigates to ‘bar.foo.tld/bit/and/pieces/’ he will be redirected to ‘www.foo.tld/bit/and/pieces/’

If user navigates to ‘www.foo.tld/bit/and/pieces/’ the view function redirects normally.

Available settings:

Settings with example values.:

SUBDOMAIN_SETTINGS_MODULE = 'tstsubdomains.SubdomainSettings'
BASE_DOMAIN = 'foo.tld'
REGISTER_SUBDOMAIN_REDIRECT = 'registration_register'
MAIN_SUBDOMAIN = ['www', 'mainsite', 'home']

Paypal Integeration

Integrating with Paypal is very simple. There are essentially two generic views which you need to cutsomise and include.

The basic Paypal checkout pages can be created with:

#in views.py

class TestPaypalStart(PaypalStart):
    def __init__(self):
        super(TestPaypalStart, self).__init__()
        self.template = 'tstsubdomains/paypal_start.html'
        self.success_url = '/test/paypalresponse/'
        self.amount = 10

class TestPaypalResponse(PaypalResponse):
    def __init__(self):
        super(TestPaypalResponse, self).__init__()
        self.template_GET = 'tstsubdomains/paypal_response.html'
        self.redirect_url_GET_success = '/'
        self.redirect_url_GET_failure = '/'
        self.amount = 10

And:

#in urls.py

urlpatterns = patterns('tstsubdomains.views',
    url(r'^paypalstart/', TestPaypalStart(), name='test_paypalstart'),
    url(r'^paypalresponse/', TestPaypalResponse(), name='test_paypalresponse'),
)

A basic Paypal site can be built with just that much code, however, these Generic views provides lots of hooks to customise it exactly to your needs. All overridable things in this generic views have the same signature:

def get_success_url(self, request, *args, **kwargs): #Or other overidable functions.

[Understanding how Paypal works would be helpful at this point. www.uswaretech.com/blog/2008/11/using-paypal-with-django/ ]

Classes

PaypalStart : This class should be overriden and used as a callable in urls.py. This maps to the initial page where you show the pay with Paypal link.

PaypalResponse: This class should be overriden and used as a callable in urls.py. This maps to to page where you show the user’s details and collect money from the user.

Hooks - PaypalStart

Though a base PaypalStart derived class can be as simple as, there are a lot of hooks for power usage.:

class TestPaypalStart(PaypalStart):
    def __init__(self):
        super(TestPaypalStart, self).__init__()
        self.template = 'tstsubdomains/paypal_start.html'
        self.success_url = '/test/paypalresponse/'
        self.amount = 10

get_success_url: Overide this if you want to specify the success url, where Paypal redirects after creating a token. Default behaviour is to return self.success_url

get_failure_url: Override this if you want to specify the failure url, where Paypal redirects if user declines authorisation. Default behaviour is to return to current Url.

get_amount: Override this if you want to specify the amount. Default behaviour is to return self.amount.

get_template: Override this if you want to specify the template to use. Default behaviour is to return self.template.

process_request: Override this if you want to populate any extra variable in the template, and as a callback to do auxilarry processing. Default behaviour is to return an empty dictionary.

Hooks - PaypalResponse

PaypalResponse is class which allows you to show the user’s details and do checkout on your site.

In the simplest case it can be overriden as:

class TestPaypalResponse(PaypalResponse):
    def __init__(self):
        super(TestPaypalResponse, self).__init__()
        self.template_GET = 'tstsubdomains/paypal_response.html'
        self.redirect_url_GET_success = '/'
        self.redirect_url_GET_failure = '/'
        self.amount = 10

The overridable hooks are:

get_template_GET: Template to render during GET.

get_redirect_url_POST_success: The Url to redirect if Paypal allows a successful checkout.

get_redirect_url_POST_failure: The Url to redirect if Paypal checkout fails.

get_amount: Amount to checkout.

process_request_GET: Callback to do extra processing during GET, when we are showing details from Paypal account. Default behaviours is to return an empty dictionary.

process_request_POST: Callback to do extra processing during POST, when we do the actual checkout. This is where we do the the actual processing, for example, generate license key, etc.

An actual example

We are in the process of building a web application to demonstrate the capabilities of this web application.