Reusable Django Apps
This document helps you create reusable Django applications.
References:
- http://ericholscher.com/projects/django-conventions/app/
- http://www.b-list.org/weblog/2007/mar/27/reusable-django-apps/
Start an application
First of all, you can use Paster to create the app skeleton. http://packages.python.org/DjangoPluggableApp/
But maybe it would be better to use distutils2 from now on.
Reusable and pluggable Models
Declare your models in settings
- Declare base model as abstract
- Inherit from base model to provide the default model
- Declare used model in settings
- Use get_model() to load model from settings
- Your application uses the model returned by get_model()
# __init__.py
from library.settings import load_settings
apply_settings()
# settings.py
LIBRARY_BOOK_MODEL = 'library.default_models.Book'
def apply_settings():
"""Updates django.conf.settings with application settings."""
pass
# utils.py
def get_model(module_name):
"""Imports the model module_name."""
mod = __import__(module_name)
return mod
# models.py
from django.db import models
class BookBase(models.Model):
title = models.CharField(max_length=100)
class Meta:
abstract = True
# default_models.py
# Is it loaded with syncdb? Definitly no.
class Book(BookBase):
pass
# urls.py
from django.conf import settings
from library.utils import get_model
Book = get_model(settings.LIBRARY_BOOK_MODEL)
urlpatterns = ('',
url('^books/$', ListView.as_view(model=Book)), name='book_list'),
)
Add methods to managers
Put your managers in APP/managers.py
Extend QuerySet and EmptyQuerySet to be able to chain querysets like this:
Book.objects.filter(title__contains='foo').is_published()
See http://djangosnippets.org/snippets/2117/ or use the following pattern:
from django.db import models
from django.db.models.query import QuerySet, EmptyQuerySet
class BookEmptyQuerySet(EmptyQuerySet):
"""Specific EmptyQuerySet for Book model"""
def is_published(self, *args, **kwargs):
"""Always returns BookEmptyQuerySet."""
return self
class BookQuerySet(QuerySet):
"""Specific QuerySet for Book model"""
def is_published(self, is_published=True, *args, **kwargs):
"""Return books which are on loan if is_is_published is False."""
clone = self.filter(is_on_loan=not is_published, *args, **kwargs)
return clone
def none(self):
"""
Returns an empty QuerySet.
"""
return self._clone(klass=BookEmptyQuerySet)
class BookManager(models.Manager):
"""Specific manager for Book model"""
def get_empty_query_set(self):
return BookEmptyQuerySet(self.model, using=self._db)
def get_query_set(self):
return BookQuerySet(self.model, using=self._db)
def is_published(self, *args, **kwargs):
"""Proxy to queryset"""
return self.get_query_set().is_published(*args, **kwargs)
Importing inside the reusable app
The best solution for standalone apps is to write it with the assumption that they’ll live directly on the Python path, and so can use absolute imports stemming off the app name; e.g., APP/views.py should be able to assume that from APP import models will work. This also makes life easier for the end user of the app, because it’s quite a bit simpler to use a single installed copy of the app in multiple projects.
Forms
Put your forms in django-APP/APP/forms.py
Reusable Templates
Put your templates in django-APP/APP/templates/APP/template.html
Placed your templatetags in APP/templatetags/APP_tags.py Name your templatetags with the name of the APP they belongs to.
{% load APP_tags %}
There have also been a couple proposals for changing how Django looks for tag libraries, so that it’d become possible to do things like {% load myapp.foo %}, and that might be something worth looking into since it’d make this much easier.
Use blocks or snippets (?) for portions of content
As an example, book_list.html includes snippets/book_overview.html where the latter displays book title, summary... => Someone who wants to display the overview a custom template of his own can include the snippet. => Someone who wants to override the snippet for a project can do it in the project's templates directory (beware of the order of settings.TEMPLATE_LOADERS).
Locales
Put your translations in APP/locale/{{language_code}}/django.po
Middleware
Put your middleware in APP/middleware.py
Context Processors
Put your context processors in APP/context_processors.py
Feeds
Pour your feeds in APP/feeds.py
URLs
Use the permalink decorator when using get_absolute_url() in models.
.. coding-block:: python # urls.py (r'^people/(\d+)/$', 'people.views.details'), # models.py from django.db import models @models.permalink def get_absolute_url(self): return ('myapp.views.details', [str(self.id)])
When get_absolute_urls need to be hardcoded, this should be documentated so end-users will know that they need to use ABSOLUTE_URL_OVERRIDES.
Document your app
Placed in a docs directory at the same level as the APP directory : django-APP/docs/
You should have a look at Sphinx-Documentation
This tool helps you to make greats docs.
You can also link your DVCS with ReadTheDocs so they automatically generate an up-to-date web version of your doc.
Example: http://readthedocs.org/docs/django-floppyforms/en/latest/
Settings merge and default settings
# project/settings.py
MY_APP_CONFIG = {
'ENABLE_CHUCK_NORRIZ_MODE': True,
}
# APP/__init__.py
from django.conf import settings
app_settings = dict({
'FOO': 42,
'ENABLE_CHUCK_NORRIZ_MODE': False,
}, **getattr(settings, 'MY_APP_CONFIG', {}))
Then to use it just do load it:
# foo/bar.py
from APP import app_settings
print app_settings.get('FOO') # 42
print app_settings.get('ENABLE_CHUCK_NORRIZ_MODE') # True
http://blog.akei.com/post/4575980188/une-autre-facon-de-gerer-ses-settings-dapplication un autre exemple : https://bitbucket.org/benoitbryon/django-formrenderingtools/src/e6930c651fa3/djc/formrenderingtools/settings.py, notamment utilisé dans les tests : https://bitbucket.org/benoitbryon/django-formrenderingtools/src/e6930c651fa3/djc/formrenderingtools/tests.py#cl-41
Test your application
Unified way to run the tests
Buildout (djangorecipe)
standalone script (the Pinax way) https://github.com/pinax/pinax/blob/master/tests/runner.py
Pourquoi pas tout simplement manage.py test app ? (avec des settings spécifiques aux tests, genre ./manage.py test --settings=setting_test app ?)
Usually override Django TestCase
Test the models
The your model creation and deletion.
"""
Testing the creation and deletion of Model
>>> from APP.models import MyModel
>>> obj = MyModel.objects.create(title='Toto')
>>> MyModel.objects.all()
[<MyModel: Toto>]
>>> obj.delete()
"""
Put your fixtures in files prefixed with app name APP/fixtures/APP_testdata.json. Avoid initial_data.json.
Test your views
Test the response status_code from the django client mock. For non-regression.
"""
Testing the views
>>> from django.test import Client
>>> from django.core.urlresolvers import reverse
>>> client = Client()
>>> response = client.get(reverse('view_name'))
>>> response.status_code
200
"""
Packaging for Pypi
Package simple apps and write comments about it Do it again
Disutils2 tutorial : http://distutils2.notmyidea.org/tutorial.html
classifiers = ['Programming Language :: Python :: 2.5',
'Environment :: Web Environment',
'Framework :: Django',
...]
How to package (with distutils2)
- Install distutils2 (or use Python 3.3)
- Go on your app root
- Launch "pysetup run mkcfg" and follow the wizard
- Try to install it elsewhere
How to upload distribution
- Run "pysetup run sdist" (create an archive of your app)
- Run "pysetup run register" (to register the project on pypi)
- Run "pysetup run upload" (to send archive to pypi)
Links
Django Pluggable App : https://bitbucket.org/bearstech/djangopluggableapp