Django | Mitch Fournier - Part 2

Tag: django

Django 1.2 Tutorials: Django by Example

Looking for some good Django 1.2 tutorials? Check out Django by Example by “andreai.avk”. I just discovered them myself via the django-users group on googlegroups.com.

When I searched a few months ago for good (and current) Django tutorials, I never ran across this site. Hopefully adding a couple links will help bump it up the Google rankings. The simple blog tutorial is currently ranked #103 for the search “django 1.2 tutorials”, but no other page from the site is in the top 500.

Topics covered include: Django admin customization, comment notification and moderation, thumbnail creation, searching and filtering, and automated testing. Current demos include a To-Do App, a Simple Blog, a Photo Organizing and Sharing App and a Simple Forum.

Let the link juice trickle.

Django Documentation for the iPhone

Have you ever searched the App Store for Django apps? Don’t bother, as of this post there is only one that will come up: Django Documentation. Since it was only 99 cents, I bought it a few weeks back and I wouldn’t recommend that you do the same. It is simply the online Django documentation “formatted” for the iPhone and iPod Touch.

Guess what? If you use your iPhone to browse to the Django documentation page with your free Mobile Safari app you’ll also get the Django documentation “formatted” for you iPhone or iPod Touch, and you’ll also get the ability to bookmark pages, easily go forward and back in your page history and zoom in and out on text.

For the past couple weeks I’ve had the paid app sitting side-by-side with a Safari bookmark on my iPhone:

Paid app on far right, standard bookmark to the left of it, new bookmark to the left of that

My only problem with adding the Safari bookmark to the home screen? The ugly default icon it creates. For some reason, it *really* bothers me. Sure, it’s an accurate representation of the page it bookmarks, but it doesn’t make for a very good icon.

What I have done — and you can see it immediately to the left of the Safari bookmark — is create a front page to the standard Django docs so when you bookmark it, it makes a presentable icon suitable for the iPhone. The downside is that when you click on this icon, you go to that front page and then have to click the huge “dj” button to get to the docs. For me, however, this is worth it.

If you care about the appearance of your iPhone icons and want to have the Django docs on your own phone, do the following:

  1. open Mobile Safari
  2. browse to http://be73.com
  3. click the “+” icon on the bottom of the screen
  4. click the “Add to Home Screen” button
  5. click “Add”

Done. May my fellow OCD suffers rest easily tonight.

UPDATE: In the comments Phillip Bosch points out that I can use a more standard <link rel=”apple-touch-icon” href=”django-icon.png”> to produce an even better icon. Using this, I have iframed the django docs, so clicking on the icon goes directly there while still producing a nice iPhone/touch-friendly icon. Thanks Phillip!

Show a Custom 403 Forbidden Error Page in Django

Creating a custom 404 Page Not Found error page is so easy in Django (all you do is put your own template named “404.html” at the root of your templates directory) that I naturally assumed doing the same for a 403 Forbidden error page would be just as easy. Unfortunately it is not.

After searching around for quite a while last night, I found bits and pieces that I have modified slightly and republished below in a unambiguous step-by-step tutorial (see the “Source and Other Resources” section at the end of the post for a few of the source posts).

The method posted below leverages some custom Django middleware code. Please, if you have a better, more elegant solution I’d love to hear about it in the comments.

Create the Middleware

  1. Create a directory at the root of your project called “middleware”
  2. Add a file named “__init__.py” to this directory
  3. Create a file named “http.py” in this directory with the following contents:
  4. from django.conf import settings
    from django.http import HttpResponseForbidden
    from django.template import RequestContext,Template,loader,TemplateDoesNotExist
    from django.utils.importlib import import_module
    
    """
    # Middleware to allow the display of a 403.html template when a
    # 403 error is raised.
    """
    
    class Http403(Exception):
        pass
    
    class Http403Middleware(object):
        def process_exception(self, request, exception):
            from http import Http403
    
            if not isinstance(exception, Http403):
                # Return None so django doesn't re-raise the exception
                return None
    
            try:
                # Handle import error but allow any type error from view
                callback = getattr(import_module(settings.ROOT_URLCONF),'handler403')
                return callback(request,exception)
            except (ImportError,AttributeError):
                # Try to get a 403 template
                try:
                    # First look for a user-defined template named "403.html"
                    t = loader.get_template('403.html')
                except TemplateDoesNotExist:
                    # If a template doesn't exist in the projct, use the following hardcoded template
                    t = Template("""{% load i18n %}
                     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
                            "http://www.w3.org/TR/html4/strict.dtd">
                     <html>
                     <head>
                         <title>{% trans "403 ERROR: Access denied" %}</title>
                     </head>
                     <body>
                         <h1>{% trans "Access Denied (403)" %}</h1>
                         {% trans "We're sorry, but you are not authorized to view this page." %}
                     </body>
                     </html>""")
    
                # Now use context and render template
                c = RequestContext(request, {
                      'message': exception.message
                 })
    
                return HttpResponseForbidden(t.render(c))
    
  5. You should now have this:
    • /myproject/middleware/
      • __init__.py
      • http.py

Modify Your Project’s “settings.py”

  1. Add ” ‘myproject.middleware.http.Http403Middleware’, ” to your MIDDLEWARE_CLASSES

Create a Custom “403.html” page

  1. Put it at the root of your template directory
  2. Sample content: (note: assumes you’ve already defined a “base.html” template)
{% extends "base.html" %}
{% block title %} | Access Denied{% endblock %}

{% block content %}
<h1>Access Denied</h1>
<span>We're sorry, but you are not authorized to view this page (Error: 403)</span>
{% endblock content %}

Raise a 403 Error

  1. In the file where you want to raise the 403 add this at the top: (I used it in my project’s “view.py” file)
  2. from myproject.middleware.http import Http403
  3. Raise the 403
  4. if request.user.id != object.user.id:
        raise Http403

That’s it, you now have a Django template to handle 403 Forbidden errors. I’m sure there’s a way for your front-end, production web server to do the same, but I haven’t explored that yet.

Source and Other Resources

  1. A middleware solution is here (HT Felipe ‘chronos’ Prenholato)
  2. A middleware solution is here (HT Glen Zangirolami)
  3. A potential decorator solution is here (HT Magus)

Getting Started with virtualenv (Isolated Python Environments)

Like South, virtualenv is a helper utility that I put off using for too long. Looking back, it is so easy to get up and running (just like South, see below) that there is no reason for you to hold off like I did.

In a nutshell, virtualenv is a tool for creating isolated Python environments. This is particularly useful if you host multiple Django projects on a single dev box. As an example, virtualenv allows you to easily work on one site built on Django 1.1 and django-registration 0.7  and another one built on Django 1.2 with django-registration 0.8.

It is also invaluable if you want to deploy a Django project to a shared host where you don’t have root access to the main “site-packages” directory. Once you create a virtualenv for your project, an isolated copy of Python and “site-packages” is created which you own and can write to.

Basic virtualenv Start-up Steps

  1. sudo pip install virtualenv
    (or, sudo easy_install virtualenv if you don’t use pip)
    (or, easy_install --install-dir ~/site-packages/ virtualenv on a shared host)
  2. mkdir ~/virtualenvs   (a directory for your isolated environments)
  3. virtualenv ~/virtualenvs/mysite.com --no-site-packages
    (--no-site-packages isolates your environment from the main site-packages directory)
  4. cd ~/virtualenvs/mysite.com/bin
  5. source activate  (activates your new environment)

That’s it, you now have a dedicated Python environment for your mysite.com project with it’s own “site-packages” directory (~/virtualenvs/mysite.com/lib/python2.5/site-packages/) where you can install any version of Django or Django app without messing with your other projects. To exit your virtualenv just type “deactivate”.

A helper alias:

  1. vi ~/.bash_aliases
    alias ams='source ~/virtualenvs/mysite.com/bin/activate'
    
  2. source ~/.bash_aliases  (to activate the aliases)

Now you can run “ams” to quickly activate your “mysite.com” environment. For more in-depth information, check out the virtualenv documentation or a couple other informative blog posts.

Debian 5
Python 2.5.2
virtualenv 1.4.9

Getting Started with South (Django Database Migrations)

If you have worked on a project in Django, you have undoubtedly discovered that ‘syncdb’ is great at turning your ‘models.py’ files into real database tables but not so great at taking your modified models and altering your database with the new definitions. In fact, the Django documentation is pretty clear about this: “Syncdb will not alter existing tables”.

South is a Django project which solves this problem by providing “consistent, easy-to-use, database-agnostic migrations for Django applications.” Below are the most basic steps for getting South up and running in your project.

INSTALLING SOUTH

  1. pip install south     (if you’re lucky, otherwise RTFM)
  2. add ‘south’ to your project’s INSTALLED_APPS
  3. run ‘syncdb’     (before you create your own models)
  4. note: this is the last time you’ll run ‘syncdb’

YOUR FIRST MIGRATION

  1. create a new app and create your initial ‘models.py’ file for it
  2. add your app to your project’s INSTALLED_APPS
  3. run ‘python manage.py schemamigration myapp –initial’      (creates your initial migration, note: those are two dashes hyphens before initial)
  4. run ‘python manage.py migrate myapp’     (uses this initial migration to create your app’s DB tables)

MIGRATING A CHANGED MODEL

  1. modify your app’s models.py file    (e.g., add a new column somewhere)
  2. run ‘python manage.py schemamigration myapp –auto’    (creates a new migration, note: those are two dashes hyphens before auto)
  3. run ‘python manage.py migrate myapp’    (applies this new migration)

That’s it, the very bare bones steps to getting up and running with South. Obviously you’ll either need to run these commands in the same directory as ‘manage.py’ or change the commands to point to it.

One last thing…

AN OPTIONAL SHELL FUNCTION TO HELP OUT

  1. vi ~/.bash_aliases    (there’s probably a better home for this, but this is where I put mine)
  2. add:
    function mig() {
        python manage.py schemamigration "$@" --auto;
        python manage.py migrate "$@";
    }
  3. source .bash_aliases    (to activate the changes)
  4. now you can run ‘mig myapp’ whenever you change the model for ‘myapp’

If you want to be able to run this shell function anywhere, put the full path to your project’s ‘manage.py’ file in the function body. Otherwise, you’ll have to run it in the same directory as the relevant ‘manage.py’.

For South’s more advanced goodness, check out the documentation.

South 0.7
Django 1.2.1
Python 2.5.2.

Aptana Studio and “Undefined variable from import: DoesNotExist”

In my journey from Java hacker to a Django developer, I’ve test driven a bunch of Python IDEs (ranging from true dev tools like Aptana, Eclipse/PyDev, PyCharm to simple editors like Notepad++).

Recent I was adding some django-profile code I found via Scot Hacker’s very helpful “django-profiles: The Missing Manual“. When I added this bit of code for a custom form object:

    def __init__(self, *args, **kwargs):
        super(ProfileForm, self).__init__(*args, **kwargs)
        try:
            self.fields['email'].initial = self.instance.user.email
        except User.DoesNotExist:
            pass

Aptana would call out “DoesNotExist” with the error: “Undefined variable from import: DoesNotExist”. Thanks to Google and the DjangoBot I learned that this is because “DoesNotExist” is added by the metaclass.

Here’s the fix

  1. Open up Aptana Studio (I’m v2.0.3 btw)
  2. Open Window > Preferences > PyDev > Editor > Code Analysis
  3. Select the “Undefined” tab
  4. Add DoesNotExist at the end of the “Consider the following names as globals” list
  5. Apply and restart

Error gone. In the immortal words of ubernostrum: “Bah. It’s not like metaclasses are *that* hard to statically figure out.” I’ll take your word for that, James.

New Django site: polurls.com

polurls | the political blog aggregatorTwo of my great passions (OK…obsessions) are web tech and politics. It was just a matter of time before I mashed them together.

Recently, I’ve been diving head first into Django, looking to complement my Java-based toolset — honed via ParentShack.com and Sharenik.com — with some Python/Django ones.

Last week I launched polurls.com, a political blog aggregator which is not only my first live Django site but also the spawn of my politics and tech love. Think of it as the popurls of politics. A site which aggregates left-leaning political blogs on polurls.com/blue, right-leaning blogs on polurls.com/red and the whole spectrum of political blogs on polurls.com/purple.

My hope is that by showing conservative, progressive and centrist blogs side-by-side that polurls visitors will get a truly balanced take on the latest political news.

I’ve already found the site to be a very quick and interesting way to scan the latest political news. I’m eating my own dog food and loving the taste!

I’d love feedback either in the comments or on twitter (@mfournier or @polurls).

Specify a custom manager for the Django admin interface

As I was running through the weblog example in James Bennett’s excellent “Practical Django Projects (2nd Ed)” book I ran across a problem. In the book, we are asked to create a custom manager for the Entry model. Instead of pulling all entries, the custom manager only pull the entries that have been marked as LIVE (and ignores the ones that are marked HIDDEN or DRAFT).

This custom manager is set as the _default_manager (by defining it first in the Entry class), which is fine, except for the fact that the Django admin interface “defaults” to using the default manager of a class. In the admin, I want to see and edit ALL objects, not just the live ones.

To further complicate things, we create a custom tag that takes any type of content and displays the most recent elements of it. This tag is model agnostic and so uses the _default_manager of the model that it is looking at. So, for the Entry model, the default manager needs to remain the custom one which shows only LIVE results.

Luckily, there is a fairly simple way to tell the admin interface to not use the default manager. Simply change your ModelAdmin class in you admin.py file from something like this:

class EntryAdmin(admin.ModelAdmin):
    prepopulated_fields = { 'slug': ['title'] }

To something like this:

class EntryAdmin(admin.ModelAdmin):
     prepopulated_fields = { 'slug': ['title'] }
     def queryset(self, request):
         return Entry.objects

And make sure that your Entry model has its managers defined thusly:

# Give the Entry model two managers. NOTE: the first one is the default!
live = LiveEntryManager()   # _default_manager #
objects = models.Manager()

Now everywhere that you use Entry.live.all() or Entry._default_manager.all() You’ll pull only the LIVE results while the admin interface will show all of the LIVE, DRAFT and HIDDEN results.

(Let me know in the comments any unspeakable horrors this solution might stir up ;)

Errata: Practical Django Projects 2nd Edition (PDF)

UPDATE! Even better than listing out the individual errors, Phil Gyford has posted his working code for the examples in James Bennet’sPractical Django Projects 2nd Edition“. You can find it on bitbucket here. Thanks Phil!


Since a quick Google search failed to turn up these e-book errate for James Bennets informative “Practical Django Projects 2nd Edition”, I’ll compile my own list. Hopefully my frustration in overcoming these errors will save you from the same.

Chapter 4, page 66:

(r'^weblog/(?P<year>\d{4})/(?P<month>\w{3})/(?P<day>\d{2})/(P?<slug>[-\w]+)/$',
'coltrane.views.entry_detail'),

should be:

(r'^weblog/(?P<year>\d{4})/(?P<month>\w{3})/(?P<day>\d{2})/(?P<slug>[-\w]+)/$',
'coltrane.views.entry_detail'),

(note the “P?” vs. “?P” before <slug>)

Chapter 4, page 70

Author should mention that the following must be added to the top of urls.py once you switch to generic views:

from coltrane.models import Entry

Chapter 4, page 71 and 73

Each of the four urlpatterns which include:

weblog/(?P<year>\d{4}/

Should actually be:

weblog/(?P<year>\d{4})/

(note the “)” after the {4})

Web Analytics