Either have a global, versioned settings.py file which imports a local (non-versioned) configuration:
import settings_local
which has a versioned template settings_local.py.template, or use the reverse approach - have a common settings file, e.g. common_settings.py and then a non-versioned settings.py which imports the common stuff. The latter seems to be the preferred way.
Figure out project root using either:
PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
or:
PROJECT_ROOT = os.path.realpath(os.path.dirname(__file__))
both forms seems to be actively used and they are pretty much equivalent.
To get full file paths, use:
os.path.join(PROJECT_ROOT, 'dir1', 'myfile.txt')
You probably always want to have the detailed information about errors in templates. This is independent of the DEBUG setting:
TEMPLATE_DEBUG = True
You may want to use HttpOnly cookies:
SESSION_COOKIE_PATH = '/; HttpOnly'
SESSION_COOKIE_HTTPONLY = True
Changed in version Django: 1.4
SESSION_COOKIE_HTTPONLY is True by default in Django 1.4+
For multilingual sites use:
USE_I18N = True
You might also want:
USE_L10N = True
Language definitions:
gettext = lambda s: s
LANGUAGES = (
('sv', gettext('Swedish')),
('en', gettext('English')),
)
If you use a global (per-project) template folder you need:
TEMPLATE_DIRS = (os.path.join(PROJECT_ROOT, 'templates'),)
South migrations - you might want to use the following settings:
SKIP_SOUTH_TESTS = True,
SOUTH_TESTS_MIGRATE = False
Django Debug Toolbar - make sure to configure it according to the docs
Django Sentry - the preferred way to catch exceptions and log messages. It has been split into Sentry and Raven so now both are needed. Note that because Sentry/Raven are meant to replace Django’s default mechanism and also to integrate deeply into the framework, some attention is needed during configuration. Also note that there were (still are?) unsolved problems like this one. But still, Sentry/Raven is probably one of the best such tools out there.
Create the database using the following command:
CREATE DATABASE CHARACTER SET UTF8;
To convert an existing table with different encoding, use:
ALTER TABLE tab CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Note that CONVERT TO is critical to do the actual encoding conversion.
Make sure your tables use the InnoDB engine. You can make sure that it is so by adding this line to your database configuration:
'OPTIONS': {'init_command': 'SET storage_engine=INNODB',}
More. Note that MySQL 5.5 (and probably 5.1) have already set InnoDB as the default engine).
You can make the InnoDB engine the default one in my.cnf file (if you’re on MySQL <= 5.0), and you don’t even have to modify the global my.cnf but use a custom config file for your Django project.
In-memory database for tests, and also this. Rewritten in a cleaner way:
stop mysql
mount -t tmpfs -o size=400M tmpfs /tmp/ramdisk/
cp /var/lib/mysql /tmp/ramdisk/
mount --bind /tmp/ramdisk/ /var/lib/mysql
start mysql
Speed tuning:
- http://www.mysqlperformanceblog.com/2010/02/28/maximal-write-througput-in-mysql/
- http://www.stereoplex.com/blog/speeding-up-django-unit-test-runs-with-mysql
- http://www.stereoplex.com/blog/speeding-up-django-unit-test-runs-with-mysql
- http://www.mysqlperformanceblog.com/2007/11/01/innodb-performance-optimization-basics/
- http://www.mysqlperformanceblog.com/2007/11/03/choosing-innodb_buffer_pool_size/
- http://www.mysqlperformanceblog.com/2006/09/29/what-to-tune-in-mysql-server-after-installation/
- http://www.mysqlperformanceblog.com/2007/11/01/innodb-performance-optimization-basics/#comment-364739
- Disable logging, slow-logging, binary log etc.
Watch out for problems:
- http://stackoverflow.com/questions/2235318/how-do-i-deal-with-this-race-condition-in-django/2235624#2235624
- http://stackoverflow.com/questions/2221247/why-doesnt-this-loop-display-an-updated-object-count-every-five-seconds/2221400#2221400
- http://www.no-ack.org/2010/07/mysql-transactions-and-django.html
- http://www.no-ack.org/2011/05/broken-transaction-management-in-mysql.html
- QuerySet.get_or_create() is clumsy anyway
Smart handling of forms in views (Credits go to PyDanny&Co). Instead of this:
def my_view(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
form.hooray()
return HttpResponseRedirect('/success/')
else:
form = MyForm()
return render_to_response('my_template.html', {'form': form})
do this:
def my_view(request):
form = MyForm(request.POST or None)
if form.is_valid():
form.hooray()
return HttpResponseRedirect('/success/')
return render_to_response('my_template.html', {'form': form})
The catch here is that form.is_valid() returns False for unbound forms.
When converting models.py into a Python package, make sure that models there have app_label set in their Meta:
class Meta:
app_label = 'app-name'
Without this trick Django won’t see the models.
form.Form.has_changed() - checks if form data is different than the initial data
django.utils.html.linebreaks(...) - converts newlines into \<p\> and \<br\> tags
django.utils.html.urlize(...) - safely converts URLs into clickable links. This is a hard task otherwise:
model.Meta.order_with_respect_to - adds an additional field to the model, purely for ordering purposes. The code behind this feature:
- https://github.com/django/django/blob/1.3.2/django/db/models/base.py#L227
- https://github.com/django/django/blob/1.3.2/django/db/models/base.py#L532
- https://github.com/django/django/blob/1.3.2/django/db/models/base.py#L603
- https://github.com/django/django/blob/1.3.2/django/db/models/base.py#L860
- https://github.com/django/django/blob/1.3.2/django/db/models/options.py#L114
- https://github.com/django/django/blob/1.3.2/django/db/models/fields/proxy.py
Check the difference between Model.objects.filter(a__x=1, a__y=2) and Model.objects.filter(a__x=1).filter(a__y=2)
A neat trick with aggregation and filtering - if .filter() precedes .annotate() then the annotation is applied only to the filtered elements.
A good practice is to design your URL structure so that it more or less follows the de facto standard convention. Note that this is mostly about “ordnung”, not about being RESTful. It’s very hard, if not impossible, to write a RESTful service - and if you violate any of the REST principles, you’re not RESTful anymore. So just accept that and follow whatever is reasonable.
Still not convinced that REST is not what it appears to be (i.e. a way of naming URLs)? Check these resources (in random order): S.O. thread #1, Roy Fielding’s article, S.O. thread #2, Example of RESTful web service design.
Specifically, Django sessions are not RESTful so to speak (check these: [1], [2], [3]). But they are great otherwise, so why not use them? Web development is not a purity contest!
Still, adopting parts of the REST philosophy is a good idea. Some readings: [1], [2], [3], [4].
Get lost, my website is RESTful!!!!! collapses if only it uses HTML forms. For illustration - let’s imagine that we want to add books to a catalog. To create a new book resource you POST data to /books/ collection. If there is any error, you can get one of the HTTP error codes. If the new book resource is created, you get #201 response.
Now, that’s not how it works in Django (or any other web framework)! In Django, if there is any form validation error, a normal (i.e. #200) response is returned, just with some additional HTML markup for presenting errors to the user. And even if the new book resource is created, a #302 redirect is returned. Moreover, you POST to the very same URL which you get the form from - and not to the /books/ collection!
Why do we have here such a big deviation from how it should look like in a RESTful case?
The answer is simple - the HTML form is kind of a separate application, a user interface to the server-side service - in the old days it would just be a standalone program. It’s simply a coincidence (or signum temporis) that now it’s a part of the same web application.
The moment we abandon the POST-REDIRECT-GET paradigm, and start POSTing forms to the backend using AJAX requests, we have a much cleaner separation of the user interface part and the underlying RESTful (or pseudo-RESTful) service. Only that the application is hooked to an URL in the same URL space..
So what to do about that? Just treat forms as non-RESTful parts, separate applications that happen to live in the same house. Use a consistent URL naming for them, like /books/1/edit, and don’t think about them more.
Some back up for what I’ve written above: [1], [2], [3], [4], [5].
Which HTTP error codes to use? Here’s the answer. Ok ok, I know :-)
But seriously, there are some rules that are worth following.
HttpResponseBadRequest [400] seems to be a good choice when Django view is reached but request parameters are invalid. Here are some good discussions on that.
HttpResponseForbidden [403] seems like a good choice to indicate that authentication is needed in a situation when redirection to the login page doesn’t make sense - e.g. for AJAX requests. Note that there is also 401 code, but it is meant to be used for the purposes of HTTP authentication, and not a custom one. (A nice discussion)
Use an asset manager. There is one shipped with Django 1.3+ (django.contrib.staticfiles) but it’s not too powerful
Pick your favourite one from django-pluggables
A pretty great one is (was?) django-mediagenerator (Hopefully someone will maintain it)
Your picked assed manager should be able to:
Combine & minimize CSS and JS scripts, preferably using YUI Compressor and/or Google Closure Compiler
Version the assets, i.e. give them unique names like sitescripts.1fhdysjnry46.js - this is required to efficiently cache them
Now, you want your web server to serve the assets with one of these headers:
Expires: (now + 1 year) Cache-Control: public, max-age=31536000plus this one:
Last-Modified: {{ date }}Thanks to the above headers, the browser caches the assets for up to one year - and in case it wants to check if an asset has changed, it sends a conditional request (using If-Modified-Since header) that makes it possible for the web server to reply with 304 Not Modified status code.
In Apache one need to add something like this to the virtual host definition (after making sure that the relevant modules are loaded):
<Directory /my/project/dir/_generated_media> ExpiresActive On ExpiresDefault "access plus 1 year" Header merge Cache-Control "public" Header unset Etag FileETag None </Directory>That’s basically all - for static assets there is no need to worry about things like proxy caches storing sensitive data etc.
Ah, one more thing - you probably want to have Keep-Alive on for static assets, but it’s not that good for your Django application. So better think about some nginx. Useful link
There’s probably no single setup suitable for all your views (pages)
So let me just give you a few links:
- Caching in IE9 Take a look at Vary-related issues, HTTPS caching, redirect caching etc.. It’s not trivial to set it all up properly.
- Controlling HTTP caching from Django
- django.utils.cache module
Because of all these things to consider, if you don’t have enough manpower to handle it properly, I think that it’s not that unreasonable to just disable HTTP caching using something like this (idea borrowed from Google Docs):
response['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
response['Expires'] = 'Fri, 01 Jan 2010 00:00:00 GMT'
Otherwise you would have to make sure that there’s no leak of sensitive data, no old content is presented to users etc. (Btw using must-revalidate causes the back button in the browser to refresh (reload) the page when pressed.)
Apache is a mature and stable piece of software...
...but it’s also a complex one. It’s not that hard to leave a security hole or misconfigure it:
- MPM vs Prefork
- mod_wsgi embedded vs daemon mode
- Are you sure /etc/passwd is not exposed? I’m never sure :) Apache “thinks” in terms of files and folders so there might be a way (i.e. URL) to access sensitive data.
- http://stackoverflow.com/questions/6248772/should-django-python-apps-be-stored-in-the-web-server-document-root/6249943#6249943
- http://stackoverflow.com/questions/5021424/mod-wsgi-daemon-mode-wsgiapplicationgroup-and-python-interpreter-separation
nginx is simpler and is the preferred server for static assets anyway.
Btw use KeepAlive=0 for wsgi apps (to not run out of connections) vs KeepAlive=1 for static assets (to speed up serving them)