Django Site of the Week: Ooh-Ga-Boo-Ga

For the second Django SOTW, I had a chat to Bruce Kroeze about one of his Satchmo projects, Ooh-Ga-Boo-Gah. It's a great match of cool design and technical wizardry.

For the un-initiated, Satchmo is one of Django's 'killer apps', providing an e-commerce platform that enables Django-powered shopping sites to be built with ease. Bruce is on the core development team and was able to provide us with a great insight into the project.

You can read the interview now.

Note: Due to Christmas and New Years, there will be no Django SOTW next week. We'll return on January 3rd, 2009. Have a great holiday season!

Dynamic ModelForms in Django

For an ongoing project I am implementing basic advertising functionality, where I define a number of positions in the page and advertisers can self-serve to create an advertisement to fit those positions.

Each 'Position' can have different attributes turned on or off. For example, a 'sidebar' ad may permit an image and link text, however a 'footer' ad may only contain link text. When the user creates their own ad, I wanted a single form that morphed itself based on the Position being used so that fields were enabled or disabled, and made mandatory as required.

To get started, here are excerpts from the two models I'll use to demonstrate how I achieved this:

from django.db import models

class Position(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField()
    has_title = models.BooleanField(blank=True, null=True)
    has_summary = models.BooleanField(blank=True, null=True)
    has_link = models.BooleanField(blank=True, null=True)
    has_image = models.BooleanField(blank=True, null=True)

class Advertisement(models.Model):
    position = models.ForeignKey(Position)
    internal_name = models.CharField(max_length=150)
    image = models.ImageField(upload_to=ad_image_path, blank=True, null=True)
    link_url = models.URLField(blank=True, null=True)
    link_text = models.CharField(max_length=100, blank=True, null=True)
    ad_text = models.CharField(max_length=100, blank=True, null=True)

The trick here is to ensure that when creating an Advertisement, the image, link_url, link_text and ad_text fields are turned on and off based on the related Position.

First things first: I created a ModelForm instance for my Advertisement model:

class AdvertisementForm(forms.ModelForm):
    class Meta:
        model = Advertisement

This basic form shows all fields from the Advertisement model to the user, which wasn't exactly what I wanted. My final AdvertisementForm class made use of the __init__() method to remove irrelevant fields, and make any remaining fields required.

class AdvertisementForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):

        self.position = kwargs['position']
        del kwargs['position']
        super(AdvertisementForm, self).__init__(*args, **kwargs)

        if not self.position.has_title:
            del self.fields['link_text']
            self.fields['link_text'].required = True

        if not self.position.has_summary:
            del self.fields['ad_text']
            self.fields['ad_text'].required = True

        if not self.position.has_link:
            del self.fields['link_url']
            self.fields['link_url'].required = True

        if not self.position.has_image:
            del self.fields['image']
            self.fields['image'].required = True

    class Meta:
        model = Advertisement
        exclude = ('position',)

You'll notice that I am using the position keyword argument to the form to determine which fields to show and make required. Obviously this turns the usage of this form into a two-step process: Firstly the user must select a position, then the relevant form is displayed to them based on their selection. This means that the in-view usage is slightly different to how we usually use forms in Django:

def create_ad(request):
    if request.REQUEST.get('position', None):
        # This matches both request.GET and request.POST
        position = Position.objects.get(slug=request.REQUEST.get('position'))

        if request.method == 'POST':
            post_data = request.POST.copy()
            if position.has_image:
                form = AdvertisementForm(post_data, request.FILES, position=position)
                form = AdvertisementForm(post_data, position=position)

            if form.is_valid():
                ad =
                ad.position = position

                return HttpResponseRedirect(reverse('ad_listing'))
            form = AdvertisementForm(position=position)

        return render_to_response("create_ad.html", RequestContext(request, {
            'position': position,
            'form': form,
        positions = Position.objects.filter()
        return render_to_response("select_position.html", RequestContext(request, {
            'positions': positions,

This view shows the two-step process that's now involved for the user:

  1. They are shown select_position.html, a very simple template:

    <ul>{% for position in positions %}
        <li><a href='./?position={{ position.slug }}'>{{ position.title }}</a></li>{% endfor %}
  2. The user selects a position, and the slug is passed back into the view as a GET parameter.

  3. The view grabs the Position object from the database based on the slug

  4. The form is initialised, passing the position keyword paramater

  5. The form sets self.position to save the position within the AdvertisementForm instance, then deletes the parameter from the keyword options.

  6. Normal form initialisation then occurs (via the Super call)

  7. The position is then checked to see if the has_title paramater is set. If it is, then the link_text field is made mandatory. If it isn't, then the link_text field is deleted from the form.

  8. This happens again for the ad_text, link_url and image fields.

  9. The form is rendered to the user with a neat little loop within the create_ad.html template:

    <form method='post' action='./'{% if form.is_multipart %} enctype='multipart/form-data'{% endif %}>
        {% for field in form %}
            {% if field.is_hidden %}
                {{ field }}
            {% else %}
                <dt>{{ field.label_tag }}</dt>
                <dd>{{ field }}</dd>
                {% if field.help_text %}<dd class='help_text'>{{ field.help_text }}</dd>{% endif %}
                {% if field.errors %}<dd class='errors'>{{ field.errors }}</dd>{% endif %}
            {% endif %}
        {% endfor %}
        <dd><input type="submit" value="Add Item" /></dd>
    <input type="hidden" name="position" value="{{ position.slug }}" />
  10. When the user clicks 'Submit', the form initialisation is again run before the form is validated, using the POSTed 'position' variable.. This means that validation will be run against the modified form, not against the base form.

This is all pretty straightforward, but it seems to be missed by many people trying to build custom forms. It's very valuable to remember that if you modify a form in it's __init__() method, those modifications will be done to both the unbound form and then to the bound form, so any modifications will impact upon your form validation.


Django Site of the Week

Since I started DjangoSites over a year ago, the response has been fantastic. I used to approve a handful of websites a week, recently it's more like a half-dozen a day.

I've decided that a bunch of those websites are just awesome, and I wanted to have a chat with the owners of those sites and share their stories with the Django Community. Most larger or more unique stories have a story behind them, and the experiences of the team building them are vary from project to project.

Upon approaching the owners of a number of cool websites powered by Django, I received great feedback and enthusiastic responses. So far there are a few weeks of interviews ready to be published, with more on the way.

The first website we've featured is Disqus, a hosted comment engine that can be used on any blog or website for free. It's an interesting website, and Daniel Ha (one of the founders) was very helpful in answering my questions and helping to share his experiences with Django.

Each weekend, another interview will be published. You can subscribe to the RSS feed to be automatically alerted to new websites, however I suggest you visit the website to see the full content of the interviews along with screenshots and statistics that have been shared with us.

I would like to take this opportunity to say a big thank-you to everybody who has helped out so far with interviews, as it has made it much easier for me to undertake this project.

Lastly, I'm going to need a continuous supply of new material. If you find a Django-powered website that you really like, please let me know and I will contact the owners of the website. I also encourage you to leave comments on the website including any questions you would like answered in future interviews.


Business Cards!

I've had a few meetings recently relating to Django, so I figured I should have some of my own business cards. I went with a nice simple design with sparse information, although these ones are straight off a digital printer so colour quality isn't quite what I wanted. For the next batch (when I have more time up my sleeves) I'll go offset printing for sure.

I also picked up a Moleskine notebook, it's great for taking notes and keeping ideas & wireframes. Much easier than the A4 book I used to lug around. To begin with I baulked a little at the price, but I'm glad I went ahead and got it anyway.

Django Site of the Week: Suggestions Wanted

Over at DjangoSites there is a steady flow of new websites, with a handful showing up every single day. Something really cool is that a growing number of these are either commercial websites that are using Django to make money, or they are very serious in terms of code-base, development effort and online exposure.

Django is growing up, and is making an impression on the web at large: I've decided to interview the brains behind these websites to find out why they chose Django, how it assisted their development processes, and how it got in their way. Soon I'll be launching Django Site of the Week, and I want your input.

So far I've conducted quick interviews with a number of fantastic websites that are powered by Django. Some of these are commercial and charge their users for access, others are large-scale community projects, and others are a hybrid. I'm in the process of editing them into something useful for the community, with plans to launch Django SOTW within the next few weeks.

Each week, I aim to publish an interview with the creators of a Django-powered website that stands out from the other sites listed at DjangoSites. I'm looking for the real cream of the crop: websites that are more than a blog and a collection of generic views. Some of the websites you can expect to see interviews from in the first few weeks include EveryBlock, SuggestionBox and Disqus. These sites are all unique and have approached a problem in their own way, all of them using Django. The reasons for using Django and the issues they faced are interesting, and some background information should help aspiring entrepreneurs as they work towards releasing their own Django-based websites.

While I continue to prepare the websites, I would like any suggestions for websites to feature. If you would like to nominate a website to be the Django Site of the Week, or if you have a unique story to tell from your involvement in the development of a complex Django powered website, please email me at ross at this domain. I'd love to hear from you.

Jutda Helpdesk - A Django-Powered Ticket Tracker for Small Enterprise

I'm a firm believer in providing a great customer support experience to your customers, so when I needed a way to manage customer requests for WhisperGifts, I began putting together an in-house solution.

There are a number of great open-source helpdesk packages available, however most of them are written in PHP which is not installed on my server for various reasons. This caused me to begin to write one in my framework of choice, Django.

The website for Jutda Helpdesk can be found at, which includes documentation and links to the Google Code Page which is used for bug tracking and version control.

Some of the features include: * Full e-mail integration (it can pick up e-mails from your POP3 or IMAP mailboxes) Sends HTML & plain-text e-mails (templates controlled via admin screen) Multiple ticket queues, to use for different products or customer groups Pre-set replies for common ticket responses Built-in reporting & statistics Automatic ticket escalation with the ability to ignore weekends & public holidays Web-based API to allow 3rd party software integration

A full demo is available online where you can try out the features and get a feeling for what it's capable of.

There's still a little bit of work that needs to be done, but it's at a stage where you can use it publicly. I am using it already to manage support calls for Jutda Products including WhisperGifts and it hasn't given me any major grief so far.

If you want to contribute to the project, please submit it via Google Code and I'll review it. I'd also love to hear how you're using Jutda Helpdesk - I know there are at least a few installations out there from people who have found it via Google, so let me know and I'll give you a shout-out :)

How I Moved My Commercial Projects to Newforms-Admin

My projects were all running on an SVN checkout from late April 2007, after the Queryset-refactor branch was merged into trunk. This meant that I had to make a number of changes, on a public server, to incorporate modifications to file uploads, generic create/update views, and more.

In this post I'm only going to cover how I did the change to Newforms Admin, as the other changes were relatively simple for my projects.

I made these changes on a live server, whilst my projects were running. For the volume of changes I had to make, this was very straightforward. I use FastCGI, and because the FastCGI processes were already running I was able to modify the Python code without it taking effect until I restarted FastCGI.

As always, make sure you've got a backup that's easy to roll back to.

The first thing I did was create my files in each application. For example, for DjangoSites I removed this admin definition from my 'Website' model:

    class Admin:
        list_filter = ('verified', 'screenshot',)
        list_display = ('url', 'title', 'owner', 'created', 'verified', )
        search_fields = ('title',)

and moved it into websites/

from django.contrib import admin
from djangosites.websites.models import Website

class WebsiteAdmin(admin.ModelAdmin):
    list_filter = ('verified', 'screenshot',)
    list_display = ('url', 'title', 'owner', 'created', 'verified', )
    search_fields = ('title',), WebsiteAdmin)

It's useful to note that at this stage, if I restarted my Django FastCGI instances, nothing would be broken - but the Admin wouldn't work.

Next, my file had to be updated. Out with the old:

(r'^admin/', include('django.contrib.admin.urls')),

and in with the new:

from django.contrib import admin
urlpatterns = patterns('',

The last step to do before restarting FastCGI was to update Django. All i did was CD into my Django trunk folder and run svn up.

Finally, I restarted FastCGI and browsed to /admin/.

This is very straightforward, as most of my models have basic Admin requirements. For applications where you don't need any fancy Admin functionality at all, your can be even simpler:

from django.contrib import admin
from mysite.models import FirstModel, SecondModel, ThirdModel

for model in [FirstModel, SecondModel, ThirdModel]:

The only real gotcha with doing an svn up is that there have been a heap of backwards-incompatible changes made recently, so you really need to make sure you work through the list and make the required changes. The main one that tripped me up was on WhisperGifts where I still used oldforms in a few places. After moving these to newforms to match the rest of the site, it all worked a charm.

I recommend updating Django incrementally - eg update to 7476, make changes required for queryset-refactor, update to 7814, make changes required for file uploads, etc. This makes it easier to manage than one huge update.

Luckily as we approach Django 1.0 backwards-incompatible changes shouldn't be as regular, and most people should be able to stick to the official 1.0 release. This will be a godsend for those distributing applications, as they will be able to recommend a particular Django release and know it is stable and it works correctly.

Congratulations to everybody on the Django team who contributed to newforms-admin going live. I've only covered a raw conversion from the old admin to the new here, and haven't even looked at the new features that are available like application-specific admin screens and admin-level managers (to filter data at the admin level). This is a fantastic change to Django and everybody involved has put in a mammoth effort. Kudos to you all.

DjangoSites is 1337

It's kind of nerdy and geeky, but we've just passed the 1337th approved site on DjangoSites.

The lucky submission was Chinet, the website for a set of environmentally friendly disposable tableware.

From glancing at the websites as they're approved, I'm noticing the quality of design of many websites improving compared to the more 'tech-friendly' submissions when the project first began. It's a great sign that Django is reaching further into everyday website development, which can only mean good things.

Here's to another 1000+ sites!

DjangoSites Gets New Shoes

I launched DjangoSites publicly last June, and since then we've had over 1,250 websites submitted to what has become the ultimate directory of Django-powered websites.

The look and feel of the site didn't seem to have the same zing that many of the listed sites had though, so after a quick chat with the ever-creative Martin 'maddiin' Czura he was able to put together a new design that we both think is a great improvement.

The new design is already online at, with a few nifty changes as well as the colour scheme such as an improved navigation bar and less cluttered positioning of user account options.

At the same time, the website has been moved to a new VPS with KnownHost. I've been using KnownHost for over a year now and their VPS hosting is fantastic. The support from them has always been helpful, and their prices are very reasonable for what has been a very reliable service.

I updated the TTL for the domain a few weeks back, so by the time this post goes live the new IP address should be accessible to anybody with properly configured DNS servers. I encourage you to do a forced reload (using Ctrl-Reload in FireFox in Windows, I assume it's similar for other browsers) to ensure that the new stylesheet and graphics are loaded.

If you do find yourself getting to the blue design on the old server, you will not be able to submit sites, vote, or leave comments until your ISP realises there has been a change in IP address.

Don't forget, we now have OpenID support so you don't need a username and password to sign up. Since my announcement that OpenID was available a fortnight ago, there have been 44 signups of which 18 have used OpenID exclusively (not including users who added an OpenID to their existing account). It's fantastic to see this type of momentum behind OpenID - it would be interesting to see similar statistics from other websites.

As always, any problems or suggestions can be sent to me at djangosites at Enjoy!

DjangoSites Gets OpenID Support

OpenID is, in my opinion, critical in the success of an open web. It allows an individual to access websites without providing that website with a password, and it provides a single identity across non-homogeneous websites.

Generally speaking, it also makes the signup process for a website much simpler. Rather than the traditional method of finding a username that isn't yet in use, entering a password, verifying your e-mail address, then selling your firstborn, with a system such as OpenID you simply enter your OpenID Identifier (typically, but not necessarily the web address of your weblog) and click 'Signup'. You then verify your username and password with your OpenID Provider (the only username & password you should have to remember) who returns you to the original website with a token saying "Yep, this really is the guy who says he's"

There are plenty of descriptions of OpenID works, so I won't harp on about it too long. Lets just say that I think it's an important development in todays web.

Django got it's first dose of the OpenID 'syrup' from Simon Willison, who released his efforts as django_openidconsumer. This application lets you use OpenID on your website, however it doesn't provide any integration (yet) with the existing Django Authentication framework.

Soon after Simon's release, a number of people provided their own ways of integrating OpenID with Django's auth, including a messy codebase that I wrote. I personally didn't think any of these were ready for the prime-time, so until now I hadn't implemented OpenID on any public Django facing websites.

That changed recently when I came across django_authopenid, written by Benoit Chesneau. His code is much more complete than mine, and with a few minor changes (most of which I've submitted back to the project for others to use) I've been able to very easily add OpenID support to DjangoSites.

As of today, you can continue to use your existing username & password, or you can use OpenID. To use OpenID, simply log in using your OpenID and you'll be given the option to attach your OpenID to an existing account (if you've already signed up with a username & password) or create a new account tied directly to your OpenID (no password required!).

Once you're logged in, there are also a few new user features including easier access to your previously submitted sites, the ability to change your password, and the ability to delete your account entirely if you wish.

OpenID is starting to make more of an impact on various websites. Simon Willisons' Django People website already has OpenID support, and I hope others in the Django community follow his excellent lead.

Weblog Updates

My previous blog was my first ever Django application. It worked well, but it was beginning to feel a little clumsy.

For example, I had to write posts in HTML. Markdown would be much nicer. I also had separate applications to syndicate links, blog posts, photos, and more. It all felt like a bit of a dogs breakfast.

So, inspired by Jacob Kaplan-Moss' jellyroll, and other 'all in one' blog solutions I've seen lately, I've built a somewhat-complete tumblog system in Django. It lets me syndicate links from Magnolia, my blog posts, selected quotes, and soon, photos. Tags are shared across the board, and there is no real separation of content types to the end user.

The nice part from my point of view is that I can link to websites I find in my travels, adding commentary, without writing a whole blog post. It can also let me use the neat Magnolia bookmarklets to add links nice and quickly.

Lastly, I've published a more minimalist design and removed blog comments. Right now, I'm not one to write provocative blog posts that stir great discussion, so I doubt they'll really be missed. Any comments or thanks can be e-mailed to my first name at this domain.

For now I won't be releasing code to this site, as all of the fun parts (a common 'Item' model using generic foreign keys across content types, for example) can be found in jellyroll. I strongly suggest you take a look at it, as it is a great example of how to use generic foreign keys - a very powerful feature that I think is slightly under-estimated in Django.

WhisperGifts Sees the Light of Day

I got married in March 2007, within a few months of a number of friends. We all shared our trials and tribulations as we each prepared for our big events, and a number of common issues arose. One of these common points of confusion was the preparation of a bridal gift registry - a list of gifts that we would love to receive from our guests.

Being somewhat geeky, I started to put together an online bridal registry system that could not only be used by my fiance and I, but also by our friends and family. This case of scratching an itch turned into a service that would eventually become WhisperGifts, the first public offering of Jutda.

This service evolved over time into a solution suitable for the commercial prime-time and as of today is ready for public consumption at

Here's just some of the major benefits of WhisperGifts:

  • Allows your guests to shop at any store they like. Aunty Mary who works for MegaTVCorp can now buy you that 42-inch LCD that you want, without paying department store prices
  • Allows you to describe the types of items you want, rather than selecting a brand and model
  • Lets your guests browse for gifts in the comfort of their own home
  • Lets your guests easily see gifts sorted by category (Kitchenware, furniture, or outdoor living for example), within certain price brackets, or by priority
  • Can magically e-mail reminder messages to guests before the wedding
  • Can magically e-mail you a list of guests and their selections just after your wedding, helping with the process of sorting out who purchased what when it comes to writing thank-you notes.

More information on WhisperGifts is available at the WhisperGifts website and in a recent Jutda blog posting.

Soon I'll publish some Django-related technical details of the service, as elements of a commercial service built on Open Source software are often interesting to the community as a whole. As a teaser, here's a few built-in functions that were made much easier with Django. Stay tuned (or rather, subscribe to the RSS Feed) for implementation details over the coming weeks.

  • E-mailing of PDF lists of gifts / purchasers
  • Integration with PayPal to allow for upgrades to be purchased
  • A subdomain per-user, to allow for ease-of-communication
  • Built-in context-sensitive inline help and tips to maximise the benefit obtained by users, both free and paying

Jutda: Django-powered Solution Provider

In my recent catchup blog post I mentioned in passing a few projects including Jutda and WhisperGifts. Now, I'd like to formally introduce the former of these (with the latter coming very soon now)

When I'm not 'at work', I spend a significant amount of time working on other projects both independently and together with other talented developers. These days, all of these projects are powered by Django.

Jutda is the glue that will pull these projects together under a single name.

The name comes from the nearly-extinct Wagiman language, spoken by a small number of indigenous Australians. It means "to point" or "to show", which I've taken on as the ethos of my new company: We’re aiming to show the way to others, to make life easier through the use of elegant online solutions.

The ultimate aim is to create simple solutions for everyday problems. No bells, no whistles, just beautiful outcomes for ugly problems.

More information about Jutda can be seen on the About Jutda page, and in the initial blog post I published on the Jutda website.

The first project that's been finished and published is WhisperGifts, about which I'll write more in a future blog post. I'm very excited about the possibilities of this, and the impact it may have on couples in the process of organising their wedding festivities.

I've also got a few other projects in the pipeline, with no fixed feature-set or timeline. Projects that are well on their way to adulthood include:

  • A personal fixed asset register: Do you know the serial number of your television? The warranty details for your refrigerator? The phone number of the guy who borrowed your Playstation for his son to use over Christmas? What would you do if your house burnt down tomorrow, taking all of your warranty paperwork with it? I'm helping you sort out this mess.
  • A personal information manager: There's been many takes on these, but none of them suit my needs quite well enough. Why can't I keep track of pages of information along with photos, embedded maps, lists, enhanced details of my contacts, and more - all in one place?
  • An open-source ticket tracker / helpdesk system. We're currently using a Django-powered helpdesk system internally to manage WhisperGifts support queries, and we're in the process of cleaning it up and abstracting it out from our other systems so that we can publish it for everybody to see and use. It's written in Django, it has full e-mail integration, it has both public and private web interfaces, and it provides open access to your data. It rocks.

So that's Jutda. Although I'm not planning for it to take over and become a full-time job, I hope to get some useful tools out in the wild for everyday consumers, whilst contributing further code back to the Django community wherever possible. Wish me luck!

Overdue Catchup

I've had a very busy few months in every way conceivable - everything from my Django projects, to my day job, to life as a whole has been running in fast-forward. Here's a quick summary of the

DjangoSites is coming along very well, with 1040 sites listed as of this evening. The quantity and quality is ever-increasing, and more and more sites are being claimed. There are still well over 300 unclaimed sites - is yours listed there? If so, drop an email with your DjangoSites username to

After a server move late last year my Django OpenID project went offline for a little while. After a handful of requests from the blogosphere I've put it back online - see my original blog posting on the topic for more details, although I'm guessing that Simon Willison has something up his sleeve that'll trump my hack-job soon enougy.

Over my Christmas holidays I launched Jutda, the 'corporate' face for my upcoming web projects built with Django. The word Jutda is from the Wagiman language, a dialect spoken by an ever-shrinking Aboriginal tribe in the Northern Territory of Australia. It means show the way, which is something I hope to do with my projects. This is by no means my day job, rather a single name with which to pull together a number of after-hours projects. Hopefully the name starts to mean something within the Django community after a little while :)

The first project to be released by Jutda is WhisperGifts, a service to allow you to publish your wedding gift registry online with minimum fuss. I used it for my wedding almost a year ago with no problems, and it's been used by others before and since with great praise from happily married couples and their guests alike. Of course, it's all built with Django, with a whole host of neat features. When I have a spare evening I'll write up a few more details, but in the mean time check it out and let me know what you think.

Last but not least, I thought I'd bring attention to a simple Django application I wrote a while ago but never 'released' per-se. Django-forum is a simple Forum application for Django, allowing you to leverage your existing templates and user accounts to add discussion-forum capability to your existing project. At least a few people are using it, with a few patches coming from the community to add new functionality. I don't currently have any public sites running it, but keep your eyes peeled.

That's all of my news for now - I've got plenty more to share, however just a little more time is needed to give a few projects some more polish before I go public. Let's just hope my next update isn't another three-months away!


Want to see more? Check out the yearly archives below.