-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
django design patterns book completed - added agiliq theme
- Loading branch information
Showing
64 changed files
with
6,322 additions
and
13 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Sphinx build info version 1 | ||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. | ||
config: 3ca1cc8b4da783defdc657615ddaeb59 | ||
tags: fbb0d17656682115ca4d033fb2f83ba1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
================================================== | ||
Welcome to Django design patterns's documentation! | ||
================================================== | ||
|
||
Prerequisites | ||
------------------ | ||
|
||
1. You know Python. If you do not, try `Dive into Python <http://diveintopython.org/>`_ | ||
2. You know Django. You work with Django 1.0+. If you do not try `Djangobook <http://www.djangobook.com/>`_ | ||
|
||
Getting the docs | ||
---------------------- | ||
The documents are generated using `Sphinx <http://sphinx.pocoo.org/>`_. The code | ||
is available on `Github <http://github.com/agiliq/django-design-patterns/tree/master>`_. | ||
The generated Html is available on http://uswaretech.com/books/djangodesignpatterns/ | ||
|
||
To generate the docs, do `make html` within the top level folder of repo. | ||
|
||
License | ||
------------- | ||
This work is dual licensed under | ||
`Creative Commons Attribution-Share Alike 3.0 Unported License <http://creativecommons.org/licenses/by-sa/3.0/>`_ | ||
and | ||
`GNU Free Documentation License <http://en.wikipedia.org/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License>`_ | ||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
================= | ||
Forms | ||
================= | ||
|
||
Prefer ModelForm to Form | ||
-------------------------- | ||
ModelForm already know the correct UI widgets for your underlying Models. In | ||
most of the cases ModelForm would suffice instead of Forms. | ||
|
||
Some common scenarios | ||
|
||
Hiding some fields from ModelForm which are needed for a DB save. | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Eg, you want to create a profile for the logged in user.:: | ||
|
||
#in Forms.py | ||
class ProfileForm(forms.ModelForm): | ||
class Meta: | ||
model = Profile | ||
exclude = ['user',] | ||
|
||
#In Views: | ||
form = ProfileForm(request.POST) | ||
profile = form.save(commit = False) | ||
profile.user = request.user | ||
profile.save() | ||
|
||
Or:: | ||
|
||
#Todo test this | ||
class ProfileForm(forms.ModelForm): | ||
class Meta: | ||
model = Profile | ||
exclude =['user',] | ||
def __init__(self, user, *args, **kwargs) | ||
self.user = user | ||
super(ProfileForm, self).__init__(*args, **kwargs) | ||
|
||
def save(*args, **kwargs): | ||
self.instance.user = self.user | ||
super(ProfileForm, self).save(*args, **kwargs) | ||
|
||
|
||
Customizing widgets in ModelForm fields | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Sometimes you just need to override the widget of a field that's already on | ||
your ModelForm. Instead of duplicating the field definition (with `help_text`, | ||
`required`, `max_length`, etc). You can do this:: | ||
|
||
from django.contrib.admin.widgets import AdminFileWidget | ||
|
||
class ProfileForm(forms.ModelForm): | ||
class Meta: | ||
model = Profile | ||
exclude = 'user', | ||
|
||
def __init__(self, *args, **kwargs): | ||
super(ProfileForm, self).__init__(*args, **kwargs) | ||
# note that self.fields is available just after calling super's __init__ | ||
self.fields['picture'].widget = AdminFileWidget() | ||
|
||
|
||
Saving multiple Objects in one form | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
As:: | ||
|
||
class ProfileForm(forms.ModelForm): | ||
class Meta: | ||
model = Profile | ||
exclude = ['user',] | ||
|
||
class UserForm(forms.ModelForm): | ||
class Meta: | ||
model = User | ||
exclude = [...] | ||
|
||
#in views.py | ||
userform = UserForm(request.POST) | ||
profileform = ProfileForm(request.POST) | ||
if userform.is_valid() and profileform.is_valid(): | ||
#Only if both are valid together | ||
user = userform.save() | ||
profile = profileform.save(commit = False) | ||
profile.user = user | ||
profile.save() | ||
|
||
{# In templates #} | ||
<form ...> | ||
{{ userform }} | ||
{{ profileform }} | ||
<input type="submit" /> | ||
</form> | ||
|
||
|
||
|
||
|
||
Forms should know how to save themselves. | ||
--------------------------------------------- | ||
if your forms is a forms.ModelForm, it already knows how to save its data. If you | ||
write a forms.Form, it should have a `.save()`. This keeps things symmetrical with | ||
`ModelForms`, and allows you to do:: | ||
|
||
#in views.py | ||
def view_func(request): | ||
if request.method == 'POST': | ||
form = FormClass(request.POST) | ||
if form.is_valid(): | ||
obj = form.save() | ||
... | ||
... | ||
|
||
Instead of:: | ||
|
||
if form.is_valid(): | ||
#handle the saving in DB inside of views. | ||
|
||
The `.save()` should return a Model Object | ||
|
||
|
||
The form should know what to do with it's data | ||
------------------------------------------------ | ||
|
||
If you're building a contact form, or something like this, the goal of your form is | ||
to send an email. So this logic should stay in the form:: | ||
|
||
class ContactForm(forms.Form): | ||
subject = forms.CharField(...) | ||
message = forms.TextField(...) | ||
email = forms.EmailField(...) | ||
... | ||
|
||
def save(self): | ||
mail_admins(self.cleaned_data['subject'], self.cleaned_data['message']) | ||
|
||
I've used `save()`, and not `send()`, even when i'm not really saving anything. | ||
This is just a convention, people prefer to use `save()` to keep the same interface to | ||
ModelForms. But it doesn't really matter, call it whatever you want. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
.. Django design patterns documentation master file, created by sphinx-quickstart on Thu Jun 18 21:08:53 2009. | ||
You can adapt this file completely to your liking, but it should at least | ||
contain the root `toctree` directive. | ||
|
||
================= | ||
Index | ||
================= | ||
|
||
This is a collection of patterns which we have found occuring commonly with | ||
Django. All of these either make collaboration easier, coding simpler or code | ||
more maintainable. None of them are design patterns in the sense of GoF design | ||
patterns. We call them design patterns as none other seem closer or more | ||
convinient. | ||
|
||
These are guidelines, which need to be overriden (very commonly, in some cases). | ||
Use your judgement when using them. As PEP8 says, "Foolish consistency is the | ||
hobgoblin of small minds." | ||
|
||
Contents: | ||
|
||
Chapters | ||
=========== | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
|
||
|
||
|
||
contents | ||
urls | ||
models | ||
views | ||
forms | ||
templates | ||
workflow | ||
misc | ||
|
||
Indices and tables | ||
================== | ||
|
||
* :ref:`genindex` | ||
* :ref:`modindex` | ||
* :ref:`search` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
================= | ||
Misc | ||
================= | ||
|
||
settings.py and localsettings.py | ||
------------------------------------ | ||
The settings for your project which are a machine specific should be refactored | ||
out of settings.py into localsettings.py. In your settings.py, you should do:: | ||
|
||
try: | ||
from localsettings import * | ||
except ImportError: | ||
print 'localsettings could not be imported' | ||
pass #Or raise | ||
|
||
This should be at the end of settings.py, so that localsetting.py override | ||
settings in settings.py | ||
|
||
This file should not be checked in your repository. | ||
|
||
Use relative path in settings.py | ||
-------------------------------------- | ||
Instead of writing:: | ||
|
||
TEMPLATE_DIRS = '/home/user/project/templates' | ||
|
||
Do:: | ||
|
||
#settings.py | ||
import os | ||
|
||
CURRENT_DIR = os.path.dirname(__file__) | ||
TEMPLATE_DIRS = os.path.join(CURRENT_DIR, 'template') | ||
|
||
Apps should provide default values for settings they are trying to read. | ||
--------------------------------------------------------------------------- | ||
As far as possible, apps should have defaults for settings they are trying to | ||
read. Instead of:: | ||
|
||
DEFAULT_SORT_UP = settings.DEFAULT_SORT_UP | ||
|
||
Use:: | ||
|
||
DEFAULT_SORT_UP = getattr(settings, 'DEFAULT_SORT_UP' , '↑') | ||
|
||
|
||
|
||
Use templatetags when the output does not depend on the request | ||
------------------------------------------------------------------- | ||
In the sidebar, you want to show the 5 latest comments. You do not need | ||
the request to output this. Make it a templatetag. | ||
|
||
Import as if your apps are on your project path | ||
---------------------------------------------------- | ||
Instead of doing `from project.app.models import ModelClass` do `from app.models | ||
import ModelClass`. This makes you apps reusable as they are not tied to a project. | ||
|
||
Naming things | ||
----------------- | ||
|
||
Model class names should be singular, not plural.:: | ||
|
||
class Post(models.Model): | ||
... | ||
|
||
and not:: | ||
|
||
class Posts(models.Model): | ||
... | ||
|
||
Foreign key should use the name of the referenced class.:: | ||
|
||
class Post(models.Model): | ||
user = models.ForeignKey(User) | ||
|
||
Querysets should be plural, instances should be singular.:: | ||
|
||
posts = Post.objects.all() | ||
posts = Post.objects.filter(...) | ||
|
||
post = Post.object.get(pk = 5) | ||
post = Post.object.latest() | ||
|
||
Using pdb remotely | ||
------------------------ | ||
Sometimes you will hit bugs which show up on server but not on your local | ||
system. To handle these, you need to debug on the server. Doing `manage.py | ||
runserver` only allows local connections. To allow remote connections, use:: | ||
|
||
python manage.py runserver 0.0.0.0:8000 | ||
|
||
or:: | ||
|
||
python manage.py runserver 0:8000 | ||
|
||
|
||
So that your `pdb.set_trace()` which are on remote servers are hit when you access | ||
them from your local system. | ||
|
||
Do not use primary keys in urls | ||
----------------------------------- | ||
If you use PK in urls you are giving away sensitive information, for example, | ||
the number of entries in your table. It also makes it trivial to guess other urls. | ||
|
||
Use slugs in urls. This has the advantage of being both user and SEO | ||
friendly. | ||
|
||
If slugs do not make sense, instead use a CRC algorithm.:: | ||
|
||
class Customer(models.Model): | ||
name = models.CharField(max_length = 100) | ||
|
||
def get_absolute_url(self): | ||
import zlib | ||
#Use permalink in real case | ||
return '/customer/%s/' % zlib.crc32(self.pk) | ||
|
||
|
||
Code defensively in middleware and context processors. | ||
----------------------------------------------------------- | ||
Your middleware and context processors are going to be run for **all** requests. | ||
Have you handled all cases? | ||
|
||
def process_request(request): | ||
if user.is_authenticated(): | ||
profile = request.user.get_profile()#Hah, I create profiles during | ||
#registration so this is safe. | ||
... | ||
|
||
Or it is? What about users created via `manage.py createsuperuser`? With the | ||
above middleware, the default user can not access even the admin site. | ||
|
||
Hence handle all scenarios in middleware and context processors. This is one place | ||
where `try: .. except: ..` (bare except) blocks are acceptable. You do not want one | ||
middleware bringing down the entire site. |
Oops, something went wrong.