Organize django applications in packages and modules

When you create a django application you have one module to add your model classes the models.py and one module to add your views the views.py (also called actions in other web frameworks), and there are possibly other modules like forms.py and admin.py. This is perfectly fine when you have a small application with a couple of classes in your models.py and few functions  in your views.py. However, this files start to become unmanageable when adding more and more code. For example, in one of my projects the views.py is more than 600 lines and the models.py contains more than 20 classes. For that reason I decided to split the models.py and put each class in its won module, and also organize the views.py in more modules depending on related functionality.

To split the views.py in more modules it’s a no brain task, it is simply a python module and there is nothing that can prevent you from doing so. However, the models.py needs more attention and few more steps to accomplish the task since this module is used by the manage.py module.

Lets take an example and organize the code so it is more manageable.
Here’s how my application looks like at first, there is only one module for all model classes and one module for all views (actions):

This is how the models.py looks like
Note that the code example is from the book “Learning Website Development with Django”:

from django.db import models
from django.contrib.auth.models import User

class Link(models.Model):
    url = models.URLField(unique = True)

    def __str__(self):
        return self.url

class Bookmark(models.Model):
    title = models.CharField(max_length = 200)
    user = models.ForeignKey(User)
    link = models.ForeignKey(Link)

    def __str(self):
        return "%s, %s" % (self.user.username, self.link.url)

class Tag(models.Model):
    name = models.CharField(max_length = 64, unique = True)
    bookmarks = models.ManyToManyField(Bookmark)

    def __str__(self):
        return self.name

class SharedBookmark(models.Model):
    bookmark    = models.ForeignKey(Bookmark, unique = True)
    date        = models.DateTimeField(auto_now_add = True)
    votes       = models.IntegerField(default = 1)
    users_voted = models.ManyToManyField(User)

    def __str__(self):
        return '%s, %s' % self.bookmark, self.votes

This is only a small part of the views.py module, in the real example there are more than 200 lines of code:

from django.core.exceptions import ObjectDoesNotExist
from django.template import Context
from django.template import RequestContext
from django.template.loader import get_template
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.contrib.auth import logout
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.shortcuts import render_to_response
from django.shortcuts import get_object_or_404
from bookmarks.forms import *
from bookmarks.models import * 

def main_page(request):
    shared_bookmarks = SharedBookmark.objects.order_by('-date')[:10]
    variables = RequestContext(request,
                {'shared_bookmarks': shared_bookmarks})
    return render_to_response('main_page.html', variables)

@login_required()
def user_page(request, username):
    user = get_object_or_404(User, username=username)
    bookmarks = user.bookmark_set.order_by('-id')
    variables = RequestContext(request,
                {'bookmarks': bookmarks,
                 'username': username,
                 'show_tags': True,
                 'show_edit': username == request.user.username, })
    return render_to_response('user_page.html', variables)

def logout_page(request):
    logout(request)
    return HttpResponseRedirect('/')

def register_page(request):
    form = RegistrationForm(request.GET)
    if request.method == 'POST':
        form = RegistrationForm(request.POST)
    if form.is_valid():
        user = User.objects.create_user(username=form.cleaned_data['username'],
                                        password=form.cleaned_data['password1'],
                                        email=form.cleaned_data['email'])
        return HttpResponseRedirect('/register/success/')
    else:
        form = RegistrationForm()
        variables = RequestContext(request, {'form': form})
    return render_to_response('registration/register.html', variables)

So I will take a more Java approach and create two packages one for the model and one for the views:

As I said, there is no problem whatsoever to organize the views.py into more modules, the authentication.py module is an example of the separated functionality:

from django.template import RequestContext
from django.template.loader import get_template
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.contrib.auth import logout
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.shortcuts import render_to_response
from django.shortcuts import get_object_or_404

@login_required()
def user_page(request, username):
    user = get_object_or_404(User, username=username)
    bookmarks = user.bookmark_set.order_by('-id')
    variables = RequestContext(request,
                {'bookmarks': bookmarks,
                 'username': username,
                 'show_tags': True,
                 'show_edit': username == request.user.username,})
    return render_to_response('user_page.html', variables)

def logout_page(request):
    logout(request)
    return HttpResponseRedirect('/')

Separating model classes and placing them into their won module follows, for brevity only the Bookmark class will be shown but all the other classes are separated in similar way. The difference now is that we have added a class inside Bookmark class called Meta with the field called app_label where we define the name of the application, if you do not add this field then the separation will not work, there is a workaround which requires to modify the the manage.py. Note that the Meta class can be used for a lot more things, like the ordering of objects for example. Here is the bookmark module:

from django.db import models
from django.contrib.auth.models import User

from media.bookmarks import *

class Bookmark(models.Model):
    title = models.CharField(max_length = 200)
    user = models.ForeignKey(User)
    link = models.ForeignKey(Link)

    def __str(self):
        return "%s, %s" % (self.user.username, self.link.url)

    class Meta:
        app_label = 'bookmarks'

Another thing that should be done is to add the following import lines in the __init__.py, this way the manage.py can find your classes and you can import your model classes as you did previously:

from bookmarks import Bookmark
from link import Link
from sharedbookmark import SharedBookmark
from tag import Tag

The same for the views, I use the * here since I want to import every function from the modules but you can define the function names there:

from authentication import *
from registration import *
from search import *

Of course the admin.py and the forms.py modules can be organized in the same way.

The concept of django applications is to be small and focused on certain and related functionality so that they can be plugged in different projects without tweaking and maybe that is the reason that the code is organized in a simple modules instead of packages.¬† But I think this way the application looks cleaner and more manageable even for the small ones, or maybe I have been exposed to Java for a long time and it looks more like a Java application…

Enjoy!

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s