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.
- Make configuration paths relative to make moving applications easier.
- Evolving models (database schema) during development.
- Unit testing in Django.
- Delving deeper into Django.
- Resources
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 osdirname = 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 definitionspython manage.py sqlclear application_name | mysql -uusername -ppassword site_name# 2) create new table definitionspython 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.
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 sysimport osdef 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 unittestclass 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 suiteallTests = 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 applicationfrom application.test.mockDatabase import enable_inmemory_testing_dbenable_inmemory_testing_db(‘application’)from application.models import ModelClass1, ModelClass2def 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 unittestclass 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….
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.
- Django Project Documentation
- Django Documentation Home
- Django Tutorial Part 2: Exploring the automatically-generated admin site.
- The Django Book
- Django Schema evolution
- Generic Views
- Django Unit Testing at Ian Maurer
- Django Tips: Get The Most Out Of Generic Views at The B-List
- Quick Tip: Django views ordered by file and directory at Coder Battery
- The adventure of ordering Django models at Coder Battery