Week 9 Miscellany

colorful graffiti

http://www.flickr.com/photos/unusual_image/3866046218/

Making views less repetitive

Typing this gets old after a while:

from django.shortcuts import render_to_response
from django.template import RequestContext

def some_view(request):
  # ... define something ...
  render_to_response('some_template.html', {'something': something},
                     context_instance=RequestContext(request))

Let's DRY that off

Let's wrap that up in a utility function so we don't have to type so much (I'll put it in a utils.py file in my project dir):

from django.shortcuts import render_to_response
from django.template import RequestContext

def render(template_name, context_dict, request):
  return render_to_response(template_name, context_dict,
                            context_instance=RequestContext(request))

What's that do?

Now my views.py can be a lot simpler:

from myproject.utils import render

def some_view(request):
  # ... define something ...
  render('some_template.html', {'something': something}, request)

Much nicer! (So much so that something like this will be included in Django 1.2).

Security update to Django 1.1

We should all upgrade our projects to Django 1.1.1, which has a critical security fix but is otherwise backwards-compatible with 1.1. First modify your requirements.txt file to specify Django==1.1.1, then with your virtualenv activated:

$ easy_install pip==dev
$ pip install -r requirements.txt

Mea culpa

facepalm

http://www.flickr.com/photos/senzanome/2265614466/

In our last episode...

syntax: glob
*.pyc
*~
*#
db.sqlite

Ooops.

Try this instead

syntax: glob
*.pyc
*~
*\#
db.sqlite

Also, the last line needs to be changed to whatever your database file is named (mydatabase, djangodb.sqlite, sqlite.db...).

And remember that the .hgignore file doesn't remove anything you already added: have to use hg rm for that (or just delete the file and then hg addremove).

What goes in? Code vs data

Code is your project. It goes in your repo.

Files that are part of your site design (i.e. banner images, logos, icons...) are part of your project. They go in your repo.

Data changes all the time. Your database, and things connected to your database data (i.e. uploaded files and images), do not go in your repo.

The middle ground

There is a middle ground: initial data that's required to be in the database for your project to function at all, and test data that just makes it more convenient to get the project up and running quickly for development at a new location.

Fixtures

The solution: fixtures. A text format (XML or JSON) dump of your data that can easily be loaded back into the database. Why fixtures and not just committing a sqlite db file?

  1. SQLite is only good for development. Once you deploy to a real database (Postgres, MySQL), you can't just commit the database file into your repo (you usually don't have access to it), you have to dump the data somehow.
  2. Your SQLite db file changes all the time as you're working on your site. Fixtures should only change when you need them to.
  3. Mercurial can't merge changes in a binary file (like the SQLite db). It can merge changes in a text file.

How?

First create a fixtures directory in your project dir next to settings.py etc (mkdir fixtures), and add FIXTURE_DIRS = (join(PROJECT_DIR, 'fixtures'),) in your settings.py so Django knows where to look for your fixtures.

Dumping data

python manage.py dumpdata --format=json --indent=4 
   appname appname2 > fixtures/test_data.json

Dumps all the data currently in your database, from the apps appname and appname2, into the file fixtures/test_data.json

You can list only one app, or as many as you want. You can also use appname.modelname to dump only the contents of a certain model: for instance I often use auth.user to dump a test user or two, without getting a bunch of other crud from the django.contrib auth app.

Loading data

python manage.py loaddata test_data  

This loads the data from the site_data.json fixture file into your database. If you do this when your database is not empty, it will overwrite models in your database with the same auto-ids (primary keys) as the ones in your fixture, and leave others alone.

Note that you don't have to give the path to the file (Django knows that from your FIXTURE_DIRS setting), and you can leave off the .json extension.

What about uploaded files/images?

If your models include an ImageField or FileField, you'll have a directory or two under media where those uploaded files go (determined by your upload_to parameter). These directories should be in your .hgignore so those uploaded files don't get committed to the repo: they are data, and they change constantly as you test your site in development; committing them will balloon your repo's size and make merges etc very difficult.

But if you dump some test data, you'll want to also save the images that go with it, otherwise that test data might be missing its images when you load it later. So copy those into your fixtures dir, too: cp -a media/uploaded_photos fixtures/. This stuff in the fixtures dir should be committed; the point is to have this test data with you wherever your repo goes.

Restoring the uploaded files.

So when you use python manage.py loaddata test_data to load your fixture into the database, you also want to restore the associated images back to their proper place under media/: cp -a fixtures/uploaded_photos media/

Wrapping it all up in scripts

Now you have two long-ish commands to run when you dump your fixture, and two more to restore it. Let's wrap those up in a couple of scripts...