A Detailed Django Tutorial: Blog Basics Part II

 

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.).

Table of Contents

  1. Installation

    1. Development Methodologies
  2. Getting Started

    1. Creating Our First Application
  3. Django Admin
  4. Running the Development Server

    1. Customizing Model Display
  5. Public Facing

      1. Generic Views
      2. Url Function
    1. Order By
    2. Templates

      1. Base.html
      2. Listing page
      3. Detail page
    3. Comments

      1. Listing page
      2. Detail Page
      3. Final Result
    4. Wrapping Up



    Django Admin

    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.

    I want a custom-tailored admin site!

    That’s possible, either by customizing the Django admin interface or using your own views to generate a custom site. If most users will be interacting with some kind of admin-like frontend, it’s best to custom tailor an interface for them, but that’s out of the scope of this tutorial.


    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)),
    )

    Django admin urls.py breakdown

    from django.conf.urls.defaults import *

    This imports all the functionality that would be needed for urls patterns, including the patterns function.

    from django.contrib import admin

    This imports an AdminSite instance which represents a full admin backend site. It’s possible to customize (through inheritance) the AdminSite for more customization, but that requires you to create your own instance.

    admin.autodiscover()

    This searches all INSTALLED_APPS for admin.py which can be used to customize model-specific details for the admin interface. This will be useful since we’ll be adding an admin.py to our posts application to support the admin interface.

    urlpatterns = patterns(”,

    This stores the url pattern mapping in the variable, urlpatterns, which is read by Django. The first parameter for patterns is the prefix module for all views defined in the mappings. The remaining parameters are tuples which are url-to-function mappings. The url patterns are always matched in order that it was given.

    (r’^admin/’, include(admin.site.urls)),

    Maps all urls starting with admin/ to be sent to another urls.py defined in admin.site.urls to check for a match. The specific urls are not needed to be known on your end. The r in front of the regular expression indicates a raw string. This means escaping using \ is not allowed and all backslashes are treated as is. In Python, regular expressions are usually expressed in raw strings.

     

    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.

     



    Running the Development Server

    Django comes with a development server bundled. Simply type python manage.py runserver to initialize the server. Simple as that.

    Django runserver Command

    Django’s runserver is only a primitive server and shouldn’t be used for production purposes since it isn’t optimized for speed or designed with security in mind. Instead it allows rapid development which includes automatically reloading your models, views, and urls when you make changes.

    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!

    Our admin is up and running!

    Adding a new post interface.

    Customizing Model Display

    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)

    Django admin.py Breakdown

    from django.contrib import admin

    Identical to the line in urls.py, imports AdminSite instance.

    from models import Post

    Imports our Post model in models.py.

    class PostAdmin(admin.ModelAdmin):

    Creates a PostAdmin class that inherits the ModelAdmin class. Standard naming convention  is <model_name>Admin for these classes.

    pass

    Tells python that we have nothing in this class (an empty code block).

    admin.site.register(Post, PostAdmin)

    Registers the Post model, using PostAdmin for model details to the AdminSite instance.


    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

    Share and Enjoy:
    • Print
    • Digg
    • Sphinn
    • del.icio.us
    • Facebook
    • Mixx
    • Google Bookmarks
    • Add to favorites
    • Design Float
    • DZone
    • email
    • FriendFeed
    • PDF
    • Propeller
    • Reddit
    • RSS
    • StumbleUpon
    • Twitter

    Related posts:

    1. A Detailed Django Tutorial: Blog Basics Part I
    2. A Detailed Django Tutorial: Blog Basics Part IV
    3. A Detailed Django Tutorial: Blog Basics Part III
    4. Django Python Framework
    5. Ruby on Rails To Do List Tutorial


    Written by Brenley Dueck

     

    5 Responses to “A Detailed Django Tutorial: Blog Basics Part II”

    1. Django Blog Tutorial | Blog of Jeff Says:

      April 3rd, 2009 at 2:49 am

      [...] Part 2 [...]

    2. Spy Says:

      April 24th, 2009 at 9:29 am

      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

    3. Jeff Says:

      June 14th, 2009 at 5:24 pm

      If I recall correctly, this was changed in some svn revision.

      Just wondering, are you using SVN or 1.0?

    4. Jack Says:

      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?

    5. lizzie Says:

      October 8th, 2009 at 12:31 pm

      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?

    Leave a Reply

    XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

     
    connect with me!