Today we’ll be learning the gist of Django, a pythonic web framework. In the spirit of a stereotypical web framework tutorial, we’ll produce a primitive blog with the minimal amount of code possible. Yellow background color text indicates text to be entered into a command line/terminal and blue indicates code. If you’ve followed my django screencast, you’ll be familiar with the most of what this tutorial covers. However, I’ll explain the code in this tutorial in more depth than I did in the screencast. If you like to know what everything does, this article is for you.
If you’ve never dealt with (or even seen) python code before, I recommend skimming through python’s official tutorial. The most notable difference between python and most other languages is that tabs/spaces are used to indicate code blocks. So an if statement would look something like this:
if myvariable == True:
print “myvariable is True”
print “Always printed”
One other thing to note is that boolean variables are True and False (case-sensitive). It’s also a good idea to dust off your terminal / command line skills (cd, ls, etc.).
We could go implementing the typical CRUD operations. But creating HTML pages and implementing the code to create, read, update, and delete our models with proper validation and error checking is laborious. Definitely not the best place to spend time in the journalism world where Django was created. Deadlines are in hours, not in days or weeks. To solve this problem, Django creators implemented the famous admin interface, which minimizes the amount of code we need to write for the entire process.
Django’s admin isn’t enabled by default, so we’ll enabled it. In the settings.py, add the following in INSTALLED_APPS, but above posts:
‘django.contrib.admin’,
To get something like:
INSTALLED_APPS = (
’django.contrib.auth’,
’django.contrib.contenttypes’,
’django.contrib.sites’,
’django.contrib.admin’,
’posts’,
)
Which will include the Django admin application. Don’t forget to run python manage.py syncdb!
Next, we’ll map a url to the admin functions. Open your urls.py and uncomment all the admin comments to get something like this:
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns(”,
(r’^admin/’, include(admin.site.urls)),
)
Since we’re here let’s talk about the urls.py. It maps incoming urls to python functions. Incoming urls always have the http://, domain name, port, and starting / removed beforehand. So http://localhost/admin/ would match the url for the admin interface above.
It’s also noted that admin and admin/ are different urls and are treated like wise. But if the first isn’t matched, a slash is appended and rechecked for matches. In short, admin will redirect to admin/, which will be a match.
URLs are entered as a tuple (immutable array) starting with the regular expression to match followed by the function to map to. An optional element in the tuple can be a dictionary of parameters & values. The second element in the tuple can either be a string module path to the function or the function directly. If a string is used, the prefix module path is added before the string before the lookup occurs (see urls.py breakdown).
Include loads a another set of urls (identical to urls.py), removes whatever matched the incoming url (in this case, admin/ and checks for matches with the included urls.
There’s also another way of defining urls which we’ll encounter later on.
Now we have to tell Django’s admin that our model should be editable by the user in the admin interface. We can do this by creating a new file in our posts application named admin.py. Alternatively, we could add the code from admin.py into our models.py, but that causes errors if django.contrib.admin isn’t added to our INSTALLED_APPS. Admin.py is only loaded if admin.autodiscover() is called, which usually indicates that the project has the admin application installed.
Now add the following code to admin.py.
from django.contrib import admin
from models import Post
# new code will go here later on
admin.site.register(Post)
This is the minimum to register your model to the admin interface. Now we should preview our handy work.
Django comes with a development server bundled. Simply type python manage.py runserver to initialize the server. Simple as that.
Visit the given url (defaults to http://localhost:8000) and you’ll notice a page not found error. That’s fine since we didn’t define any mapping to the base url. Instead, go to http://localhost:8000/admin/. Viola!


Adding a new post interface.
Majority of the time you’ll probably want to customize it a bit further that this. You do so by extending the ModelAdmin class. Replace admin.py with the following:
from django.contrib import admin
from models import Post
class PostAdmin(admin.ModelAdmin):
pass
admin.site.register(Post, PostAdmin)
Nothing changed! That’s because the previous code did the same exact thing as this, but now we can further customize the PostAdmin class. First, we’ll customize the fields to exclude the user input (we’ll make it automatically assigned to the current user later). Replace pass with:
fields = (‘title’, ‘body’)
list_display = (‘title’, ‘author’, ‘pub_date’)
date_hierarchy = ‘pub_date’
fields restricts the displayed fields to the ones listed for add/edit forms. Alternatively, exclude can be used identically. list_display specifies which fields (or functions) to display in the list view of posts. If this isn’t specified, it defaults to the result of __unicode__ in the model. date_hierarchy allows the user to drill down by a specific date field.
Now if we try to add something we get an error that the author field needs to be filled. We can do this by overriding the save_model method which allows us to add values before saving. So add this after date_hierarchy.
def save_model(self, request, obj, form, change):
if not change:
obj.author = request.user
obj.save()
This simply checks if the instance model is a new model. If it is, then the author is set to the currently logged in user before saving. request is the http response object which is normally passed to a view. It contains various information about a particular http request by a client. The django.contrib.auth application adds the user field to the request object to provide information about the current user. obj represents the instance of the particular model we’re going to save. form represents a Form instance which encapsulates the displaying, validating, and preparing forms and its data. Finally, change indicates if the model has been saved before (True) or is a new record to be created in the database (False).
Now if we add a post, whatever you user you logged in as becomes the author.
The field pub_date doesn’t quite cut it to be displayed. Django automatically converts the underscore to a space, but we want a difference alias. Simply open up models.py and change pub_date and up_date to:
pub_date = models.DateTimeField(‘Date Published’, auto_now_add=True)
up_date = models.DateTimeField(‘Date Updated’, auto_now=True)
This simply changes the display name in the admin interface to use. This can be done for any field. Now we get it to display what we want ![]()

We will be continuing Part III tomorrow so stay tuned by subscribing to the RSS Feed
[...] Part 2 [...]
Hi,
I have had a problem with urlpatterns, in my opinion instead of
(r’^admin/’, include(admin.site.urls)),
should be
(r’^admin/(.*)’, admin.site.root),
Regards,
Spy
If I recall correctly, this was changed in some svn revision.
Just wondering, are you using SVN or 1.0?
September 5th, 2009 at 6:50 am
I get the following error when I try to save a post:
IntegrityError at /admin/posts/post/add/
posts_post.author_id may not be NULL
Any idea what could be causing that?
I have the same error as Jack, here’s the traceback:
Traceback:
File “/var/lib/python-support/python2.6/django/core/handlers/base.py” in get_response
86. response = callback(request, *callback_args, **callback_kwargs)
File “/var/lib/python-support/python2.6/django/contrib/admin/sites.py” in root
157. return self.model_page(request, *url.split(‘/’, 2))
File “/var/lib/python-support/python2.6/django/views/decorators/cache.py” in _wrapped_view_func
44. response = view_func(request, *args, **kwargs)
File “/var/lib/python-support/python2.6/django/contrib/admin/sites.py” in model_page
176. return admin_obj(request, rest_of_url)
File “/var/lib/python-support/python2.6/django/contrib/admin/options.py” in __call__
191. return self.add_view(request)
File “/var/lib/python-support/python2.6/django/db/transaction.py” in _commit_on_success
238. res = func(*args, **kw)
File “/var/lib/python-support/python2.6/django/contrib/admin/options.py” in add_view
494. self.save_model(request, new_object, form, change=False)
File “/var/lib/python-support/python2.6/django/contrib/admin/options.py” in save_model
376. obj.save()
File “/var/lib/python-support/python2.6/django/db/models/base.py” in save
311. self.save_base(force_insert=force_insert, force_update=force_update)
File “/var/lib/python-support/python2.6/django/db/models/base.py” in save_base
383. result = manager._insert(values, return_id=update_pk)
File “/var/lib/python-support/python2.6/django/db/models/manager.py” in _insert
138. return insert_query(self.model, values, **kwargs)
File “/var/lib/python-support/python2.6/django/db/models/query.py” in insert_query
894. return query.execute_sql(return_id)
File “/var/lib/python-support/python2.6/django/db/models/sql/subqueries.py” in execute_sql
309. cursor = super(InsertQuery, self).execute_sql(None)
File “/var/lib/python-support/python2.6/django/db/models/sql/query.py” in execute_sql
1734. cursor.execute(sql, params)
File “/var/lib/python-support/python2.6/django/db/backends/util.py” in execute
19. return self.cursor.execute(sql, params)
File “/var/lib/python-support/python2.6/django/db/backends/sqlite3/base.py” in execute
168. return Database.Cursor.execute(self, query, params)
Exception Type: IntegrityError at /admin/posts/post/add/
Exception Value: posts_post.author_id may not be NULL
Any suggestions?
Twitter
Follow me on Twitter to keep up to date!
RSS Feed
Keep up with all of our updates by subscribing to our RSS feed!
FaceBook
Join our group on Facebook and become a fan of us!