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/
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.
- 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'),
)
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)
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.
Put your forms in django-APP/APP/forms.py
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.
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).
Put your translations in APP/locale/{{language_code}}/django.po
Put your middleware in APP/middleware.py
Put your context processors in APP/context_processors.py
Pour your feeds in APP/feeds.py
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.
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/
# 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
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
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 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
"""
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',
...]
- 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
- 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)
Django Pluggable App : https://bitbucket.org/bearstech/djangopluggableapp