Small Django tips from one newbie to another

January 12, 2007

I am a Django newbie: I have gotten my feet wet exploring Django and have started building my first serious application with it. I am having more fun with Django than I have had with any other software tool in a long time, and I would like to share a few things I have learned along the way so far, that might help other newbies. If you are a more experienced Django-neer reading this and notice areas for improvement or outright mistakes, I would be very grateful for the feedback.

Contents


A minor “Gotcha” encountered when customizing the Admin application.

If you are creating an application that customizes the templates in the provided “admin” application, such as adding your own header, etc., then in the settings.py file in your site root, make sure that your application appears first in the INSTALLED_APPS directive. Otherwise, Django will find and use the default admin templates first before finding any updates you have made, and your changes won’t appear.


Make configuration paths relative to make moving applications easier.

In your site’s settings.py module (in your site root), TEMPLATE_DIRS takes absolute paths. Here is a way to dynamically determine the absolute path to the application directory so you only have to specify relative paths within settings.py:

import os
dirname = os.path.dirname(globals()["__file__"])
TEMPLATE_DIRS = (
    os.path.join(dirname, 'application_directory/templates'),
)

Obviously, replace “application_directory” for the name of your application’s directory.


Evolving models (database schema) during development.

As you develop your application, you will likely change your data model and migrating existing data is a concern. There are a number of discussions about this on the Django developers list, and I found this wiki entry particular informative.

But there may be an easier way. Your database is most likely to undergo frequent and significant changes early in the development of a new application, when you may be developing on a sandbox with no real data. (If you are unit testing, or just want to see something interesting displayed in your templates, take the time to write code to populate the database with test data.) In this situation, simply blowing away the database and recreating it may be much more convenient.

Django’s manage.py module makes managing your projects easier, and with the help of a little shell script, you can refresh your database with one command:

#! /bin/bash
# I could make this more general
# by passing the name of the django project dir 

# 1) trash the old table definitions
python manage.py sqlclear application_name | mysql -uusername -ppassword site_name

# 2) create new table definitions
python manage.py syncdb

Obviously, you will need to replace your mysql username and password as well as the Django site_name and application_name for your project. Otherwise, this just lets Django generate SQL drop commands for the older model code that are then piped to MySQL, then Django recreates the database from scratch using the new model code.


Unit testing in Django.

I am a big believer in Test Driven Development (TDD), so when I began to build my first real application in Django, I investigated how to best unit test in this environment, and in particular, how to test against the current incarnation of the data model so I could easily and reliably clean up the database afterward. I was excited to find this blog post by Ian Maurer that showed that this was not only possible, but easy (following his recipe) with some unique opportunities for writing clearer, more comprehensive unit tests.

Basically, you can create unit tests for Django that instantiate a separate instance of Django, overriding the normal settings (those specified in settings.py, such as the database connection information) to point to an in memory instance of an Sqlite database. Basically, every test run creates a mock database on the fly so the latest model code can be tested without touching the real development database and possibly corrupting any existing data. This is a trememdous convenience, although it requires that you install Sqlite and its Python bindings. Alternately, you could point it at another MySQL configuration, but using Sqlite is more elegant.

I encourage you to read Ian’s post thoroughly. The code is a little dated at this point (I follow the Django documentation recommendation of having a cron job that updates the Django code from the SVN repository each day), so I used his last blog update as a start and only slightly modified it to work with the current version of Django.

Save the following code in a Python module (source file) called mockDatabase.py:

import sys
import os

def enable_inmemory_testing_db(appName):
  try:

    # Change Settings
    from django.conf import settings, global_settings

    INSTALLED_APPS = (appName,)
    settings.configure(default_settings=global_settings, INSTALLED_APPS = INSTALLED_APPS, DATABASE_NAME=':memory:', DATABASE_ENGINE='sqlite3')

    # Reload DB module
    from django import db
    reload(db)

    # Install Models
    from django.core import management

    # disable the "create a super user" question
    from django.contrib.auth.management import create_superuser
    from django.contrib.auth import models as auth_app
    from django.db.models import signals
    from django.dispatch import dispatcher
    dispatcher.disconnect(create_superuser, sender=auth_app, signal=signals.post_syncdb)

    management.syncdb()

  except Exception, e:
    print e

The try/catch block will allow you to call this code in both individual test suites and test suites without seeing ugly messages warning you that it has already been called, which I decided was irrelevant in this context. Ian mentions that he based this approach on Django’s own unit tests, which can therefore be used as a guide for improving this code in the future.

Its important to call this code as soon as possible, before importing any other Django modules, or Django will fire off the real application settings in the settings.py file before you have a chance to implement the test settings. So, in an individual test suite module, the first lines should be:

  import application
  from application.test.mockDatabase import enable_inmemory_testing_db
  enable_inmemory_testing_db('application')

…where “application” is the application name of course. In a suite, like an AllTests.py module, it might look like this:

import unittest

class AllTests(unittest.TestSuite):

  def suite(self):

    import application
    from application.test.mockDatabase import enable_inmemory_testing_db
    enable_inmemory_testing_db('application')

    from application.test.common_test_data import common_test_dataTest
    suite1 = unittest.makeSuite(common_test_dataTest)

    suite = unittest.TestSuite((suite1,))
    return suite

allTests = AllTests()
unittest.TextTestRunner( verbosity=2 ).run( allTests.suite() )

(I should point out that I am more accustomed to using JUnit within Eclipse, so my Python unit test code may not be optimal–if anyone reading this would like to leave suggestions for improvement, I would be most grateful.) The common_test_data module is where I isolated all model calls that populate the database with initial data for testing. I put its test class in the same module. It might look like this:

import application
from application.test.mockDatabase import enable_inmemory_testing_db
enable_inmemory_testing_db('application')

from application.models import ModelClass1, ModelClass2

def createCommonTestData():

    testObject = ModelClass1(name="test")
    ModelClass1.save()
    ...

def deleteCommonTestData():
    from django.db import models
    from django.core.management import _get_table_list, _get_installed_models
    models = _get_installed_models(_get_table_list())
    for model in models:
      model.objects.all().delete()

def refreshCommonTestData():
    deleteCommonTestData()
    createCommonTestData()

import unittest

class common_test_dataTest( unittest.TestCase ):

  def setUp( self ):
    createCommonTestData()

  def tearDown( self ):
    deleteCommonTestData()

  def testCommonTestData( self ):
    ...

if __name__ == "__main__":
    unittest.main()

Please excuse that I didn’t create a phony, canned set of model classes, but I think you get the idea of how to implement this in your own application, importing and instantiating real model classes. In particular, notice the deleteCommonTestData() method, which dynamically gets all table names and wipes out all data in them.

The last lines of the test suite and aggregator modules allows it to be run on the command line or in Eclipse/PyDev, although as I indicated, I am not sure which approach is best yet. Make sure that Eclipse has the project’s path in the PYTHONPATH (under the project’s properties).

As a Django newbie creating my first real application, I was surprised to see how often I was not unit testing because for many common types of Web application interfaces, I may only be specifying the models and doing small customizations of the admin application screens, or using a URL mapping strategy to invoke generic views (more on this below)–in short, not writing units to test but declaratively customizing what Django already provides. Which brings me to my final suggestions….


Delving deeper into Django.

Django, like Python itself gives you instant gratification via a shallow learning curve, but also grows with you, providing you with more powerful options as you learn the environment more deeply. If, like me, you are a Django newbie who is excited by what you have seen of Django’s possibilities so far, then the following resources might really blow your mind, as they begin to show the real power of Django.

Of all of the powerful tools Django makes available, perhaps the most underrated is generic views. Generic views provide you with common interface design patterns that you can easily configure and extend in your own application, such as displaying lists of items with paging and detail views. This blog post really made it clear to me how generic views combined with a thoughtful URL mapping strategy can result in really elegant, succinct code.

I also recently stumbled upon two blog posts showing how to organize Django views in directories and how to similarly organize your model code. If you are planning a complex application in Django, these posts are required reading.

Django has a lot of great documentation on the project Web site to get you started, and the quickly evolving Django book goes even further. Its a testament to how expressive Django and Python are that there is also a lot of great documentation freely available on the Web being produced by experienced Django developers in their blogs, so don’t forget this avenue. In particular, I am finding The B-List to be a consistent source of high quality Django insight. Interestingly, this site, including its blog functionality, appears to be Django driven and is a good example of an attractive, highly functional Django implementation.

I hope this post has been informative. Please feel free to leave comments pointing to other blog posts with Django tips you found helpful.


Resources.

11 Comments »

2007-01-12 09:21:55

[...] I stumbled across this post by stone mind that contained a number of helpful tips for those of us just getting started with Django. There’s also a bunch of links to some great resources too. Check it out. [...]

 
Comment by Amit Upadhyay
2007-03-12 22:41:52

There is an option dbshell in manage.py. It will read the databasename/password etc from settings.py, and open default shell for whatever db you have set.

 
Comment by Teresa
2007-09-05 20:14:18

Thanks so very much for taking your time to create this very useful and informative site. I have learned a lot from your site. Thanks!!5

Comment by admin
2007-09-06 02:16:16

I’m glad you find it informative, and thank you for taking the time to say so.

 
 
Comment by Michael
2007-11-07 10:56:22

Just learning Django and working with the models… strongly encourages me to learn TDD. This is a great start. Many thanks!

 
Comment by marty
2008-06-21 15:36:55

I was looking to start off with TDD as well to learn django properly. Thanks for this blog entry :)

On this though:
# 1) trash the old table definitions
python manage.py sqlclear application_name | mysql -uusername -ppassword site_name

I found I can use python manage.py reset [--interactive][appname ...]
It seems to be doing the same thing

 
Comment by jonathan
2008-07-13 17:07:26

Thanks for the post. You pointed to links on the information I was looking for. Testing is important to me.

 
Comment by Art
2008-12-15 13:16:15

Good post, especially thank you for thr database re-creation trick!

 
2009-05-16 09:07:15

[...] [upmod] [downmod] stone mind » Small Django tips from one newbie to another (www.stonemind.net) 0 points posted 10 months, 1 week ago by jeethu tags webdev tips django [...]

 
Comment by Phoebe Bright
2010-01-27 01:15:35

Great post - thanks for sharing your learning. Only just getting into TDD and glad I already use Django!

 
2010-02-20 00:25:09

[...] the original post: stonemind consulting » Small Django tips from one newbie to another Tags: asmith-at-agile, browser-privacy, could-not, django, fix-this, invalid-request, key-shown, [...]

 
Name (required)
E-mail (required - never shown publicly)
URI
Your Comment (smaller size | larger size)
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.