A Django project typically consists of many applications declared in
INSTALLED_APPS. Django applications should follow the Unix philosopy of, “Do one thing and do it well.” , with a focus on being small and modular, mirroring Django’s “loose coupling” design philosophy .
James Bennett’s Reusable Apps talk at the first DjangoCon is an excellent primer on the subject of building good Django applications.
The only requirement of a Django application is that it provides a
models.py file. In practice, however, Django applications are made up of many different files. When building your own applications, follow common file naming conventions. Start with the framework Django provides via
manage.py startapp <foo> and build out from there as needed.
What lives in each of these files should be self-explanatory. Let’s dive into some of the meatier ones though.
Make ‘em Fat¶
A common pattern in MVC-style programming is to build thick/fat models and thin controllers. For Django this translates to building models with lots of small methods attached to them and views which use those methods to keep their logic as minimal as possible. There are lots of benefits to this approach.
- DRY: Rather than repeating the same logic in multiple views, it is defined once on the model.
- Testable: Breaking up logic into small methods on the model makes your code easier to unit test.
- Readable: By giving your methods friendly names, you can abstract ugly logic into something that is easily readable and understandable.
For a good example of a fat model in Django, look at the definition of
Similar to models, it’s good practice to abstract common logic into methods on a manager. More specifically, you’ll probably want a chainable method that you can use on any queryset. This involves some boilerplate that I always forget, so here’s an example for (mostly) cutting and pasting:
import datetime from django.db import models from django.db.models.query import QuerySet class PostQuerySet(QuerySet): def live(self): """Filter out posts that aren't ready to be published""" now = datetime.datetime.now() return self.filter(date_published__lte=now, status="published") class PostManager(models.Manager): def get_query_set(self): return PostQuerySet(self.model) def __getattr__(self, attr, *args): # see https://code.djangoproject.com/ticket/15062 for details if attr.startswith("_"): raise AttributeError return getattr(self.get_query_set(), attr, *args) class Post(models.Model): # field definitions... objects = PostManager()
This code will let you call our new method live both directly on the manager
Post.objects.live() and chain it on a queryset
Post.objects.filter(category="tech").live(). At the time of writing, there is an open bug to make this less painful.