Archive for the ‘Tips and Tricks’ Category.

Pre-populate Django ModelForm with Specific Queryset

I just had a situation where I was trying to filter the queryset for a ModelMultipleChoiceField based on the currently logged-on user.  I was going crazy trawling through the Django docs and eventually Google.  It seemed like something which should be so simple, but there was no obvious way to do it.  Eventually I found the answer, and it IS simple!  As an example, let’s say you have the following two models as part of a simple photo gallery app:

?View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Photo(Model):
    name = CharField(max_length=100)
    caption = CharField(max_length = 150)
    user = ForeignKey(User)
    upload_timestamp = DateTimeField(auto_now_add=True)
    image = ImageField(upload_to="user_images/%Y/%m/%d/")
    thumbnail = ImageField(upload_to="user_images/%Y/%m/%d")
    def __unicode__(self):
        return self.name
 
class Album(Model):
    name = CharField(max_length=100)
    caption = CharField(max_length = 150)
    user = ForeignKey(User)
    photos = ManyToManyField(Photo, related_name="albums", blank=True)
    creation_timestamp = DateTimeField(auto_now_add=True)
    cover_photo = ForeignKey(Photo, related_name="cover_photos")
    def __unicode__(self):
        return self.name

We then define a ModelForm based on the Album model, which allows users to create albums with photos they’ve previously uploaded (pretend we’ve already made that possible). We only expose the “name”, “caption” and “photos” fields because we’ll fill in the others automatically as part of our view:

?View Code PYTHON
1
2
3
4
class AlbumCreationForm(ModelForm):
    class Meta:
        model = Album
        fields = ("name", "caption", "photos")

Now here’s the real magic. Ordinarily, when first showing the form (pre-POST) we would create it like this and pass it to the template:

?View Code PYTHON
form = AlbumCreationForm()

The problem here is that by default we’ll get all photo objects, i.e. the result of “Photo.objects.all()”. That’s a problem because in this case we just want to list the photos belonging to the current user. To do this, just add the following line:

?View Code PYTHON
form.fields["photos"].queryset = Photo.objects.filter(user=request.user)

It turns out that form fields can be accessed as a dictionary attached to the form instance, and that if the field is model-related, like “photos” in the example, you can update its queryset dynamically.

Here’s a partial view which uses the last two code samples, to provide some context:

?View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
def create_album(request):
    if request.method == "POST":
        # Process form.
    else:
        form = AlbumCreationForm()
        # Get just the photos belonging to this user.
        form.fields["photos"].queryset = Photo.objects.filter(user=request.user)
 
    template_vars = RequestContext(request, {
        "form": form,
        "user": request.user
    })
    return render_to_response("create_album.html", template_vars)

That’s it! Adding the one extra line to the view gives us the filtering we need.

CSS auto reload with Django templates

Have you ever been in the situation where you’ve updated your CSS file but users’ web browsers haven’t automatically loaded the new one? The reason is because many web browsers cache the stylesheet for faster future reloading. Obviously you don’t want to have to get your users to Ctrl-F5 every time you update your stylesheet so here’s a little tip for making this automatic in your Django templates.

Let’s say you have a template with the following line:

<link rel="stylesheet" type="text/css" href="/site_media/css/style.css" />

Every time the page loads the browser cache will think it’s seen the file before and reload the cached version from disk. One solution is to dynamically generate the link so that the browser thinks it is loading a new file every time. One way to do this without actually making a new file every time (you could just rename your CSS file every time you update it, but that’s painful) is to pretend you’re passing some parameters to the CSS file, like this:

<link rel="stylesheet" type="text/css" href="/site_media/css/style.css?12345" />

The trick is to generate a new fake parameter every time the page loads. Django templates make this easy by allowing us to generate a Unix timestamp (the number of seconds since a specific point in history) which we can attach to our link.

To do this we use the “now” template tag. In your template file, update the stylesheet line to the following:

<link rel="stylesheet" type="text/css" href="/site_media/css/style.css?{% now "U" %}" />

This will generate a line like the following every time the page is loaded:

<link rel="stylesheet" type="text/css" href="/site_media/css/style.css?1249948982" />

Voila! Automatically-refreshing stylesheets. You don’t have to make any changes to your stylesheet to use this.

This is a little inefficient, and if you have a super busy site and a very big stylesheet this could add an extra fair bit of bandwidth usage to your server(s). To get around this you could just change the “now” tag usage to generate a daily stamp (see the “now” tag docs), e.g. style.css?20090712, in which case the client browser should only reload it once per day.

SourceForge drops IE 6 support

SourceForge has changed its appearance more often in the past 6 months than Joan Rivers’ face.  Not in small ways either.  I host Tubecaster on SourceForge and recently it seemed that every time I logged into the site they had completely re-themed.

Yesterday I was at work and trying to track down an Open Source hex editor so I thought I’d pop into SourceForge and browse their extensive software database.  Even though other Open Source project hosting services have been gaining a lot of momentum lately SourceForge is still the first place I turn to when looking for Open Source apps.  It still has the largest legacy range of software of any similar site.  Anyway, I happened to be using IE 6 because it was part of the particular build on the PC I was using and, to my surprise, up popped a little information bar at the top of the screen, stating:

Your browser isn’t supported, so some things might not work as expected.  Please upgrade to a newer version of IE, or to Firefox.

The web developer in me shrieked with joy, but the objective observer quickly overruled and made me think of all the corporates out there still on IE6.  I can certainly speak for most of the clients I’ve worked with at the company I work for.  Browser usage is a reasonably difficult metric to measure accurately in a big-picture sense, but I think it’s fair to say that we should still make some attempt at supporting it.  It seems that the good folks at SourceForge quite literally haven’t even tried their site in IE6, even once:

SourceForge.net in IE6 - Click to enlarge

SourceForge.net in IE6 - Click to enlarge

Compare that to IE 7 & 8 (it looks the same in both):

sourceforge_ie7_thumb

SourceForge.net in IE7 - Click to enlarge

I should point out that it is somewhat hypocritical of me to condemn this sort of behaviour, since I explicitly do not support any version of IE lower than 7 on the new Tubecaster site, but I would only do that on my own personal sites and never for a business or any other client.

Believe me when I say that I understand the pain of developing for IE6 – the constant hair pulling, beating and maiming of web standards, and horrors such as HasLayout – but I really believe that with the tools and knowledge available to us now, like these sites[1], which detail common known IE bugs and layout quirks, to Microsoft’s free Virtual PC 2007 and their free Virtual PC Windows XP IE images[2], there is little excuse for web developers to not make at least some effort to get their sites behaving reasonably well in IE6.

I’m not talking about getting everything pixel-perfect.  Nobody that uses your websites for their intended purposes is going to notice the 2px misalignment on that floating image, or slightly overweight sidebar.  As much as those extra pixels will scream at you, taunt you, every time you see them in IE6, just spare a thought for those users who, for one reason or another, cannot yet upgrade.

Don’t despair – Microsoft is well on the way to giving IE6 the boot, once and for all.

[1]:

[2]:

These images expire every few months, after which new ones are released.  They seem to be valid for about 3-4 months at a time.

Firefox Tip: More room on your Bookmarks Toolbar

I’m a huge fan of the Windows Quick Launch bar.  Mine is two lines high and contains 20 shortcuts, all of which I use almost every day.  Similarly, I have also become accustomed to using the Bookmarks Toolbar in Firefox.  Unfortunately even with a widescreen monitor the bar fills up quickly thanks to the horrendous amount of web browsing I do.

I’ve discovered a neat trick for making better use of the limited space available on the Bookmarks Toolbar: Favicons.  When you add a link to the Bookmarks Toolbar and have visited the link at least once (and let the page load fully) Firefox will retrieve the favicon from the website and attach it to the bookmark.  What you can do now is rename the bookmark (right-click -> Properties -> Name) and just remove the name altogether.  Thus:

Bookmarks Toolbar before iconisation

becomes…

bookmarks_toolbar_after

…reducing it to less than half the width!

Now you fit more than twice as many links on the toolbar!  Unfortunately there are still many sites that don’t publish favicons but most medium to large websites do.

-Wayne