JavaScript snippet: Remove base URL from link

I needed this function this morning so I thought I’d share it in case someone else does too.

?View Code JAVASCRIPT
function RemoveBaseUrl(url) {
    /*
     * Replace base URL in given string, if it exists, and return the result.
     *
     * e.g. "http://localhost:8000/api/v1/blah/" becomes "/api/v1/blah/"
     *      "/api/v1/blah/" stays "/api/v1/blah/"
     */
    var baseUrlPattern = /^https?:\/\/[a-z\:0-9.]+/;
    var result = "";
 
    var match = baseUrlPattern.exec(url);
    if (match != null) {
        result = match[0];
    }
 
    if (result.length > 0) {
        url = url.replace(result, "");
    }
 
    return url;
}

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.

Why I Love Django

I’ve used Django for a few small projects in the last year and have absolutely fallen in love with it. While I always insist that I’m not a language or framework zealot I will quite happily and unashamedly push the Python / Django team wherever possible. Here are some of the reasons why.

Pythonic. This is a term that has evolved with Python to basically mean compliance with the Zen of Python. Here is the Zen of Python in its entirety as provided by Python.org:

    Beautiful is better than ugly.
    Explicit is better than implicit.
    Simple is better than complex.
    Complex is better than complicated.
    Flat is better than nested.
    Sparse is better than dense.
    Readability counts.
    Special cases aren't special enough to break the rules.
    Although practicality beats purity.
    Errors should never pass silently.
    Unless explicitly silenced.
    In the face of ambiguity, refuse the temptation to guess.
    There should be one-- and preferably only one --obvious way to do it.
    Although that way may not be obvious at first unless you're Dutch.
    Now is better than never.
    Although never is often better than *right* now.
    If the implementation is hard to explain, it's a bad idea.
    If the implementation is easy to explain, it may be a good idea.
    Namespaces are one honking great idea -- let's do more of those!

It just so happens that this aligns perfectly with the way I think. This is what drew me so quickly and intimately to Python itself.  It’s one of those things that makes a language “feel right” to us.  Every time I read the Zen of Python I find myself nodding profusely and grinning cheekily.  Django is Pythonic.

No black magic. Django’s components provide everything you need, but don’t go away and do anything so mystical that you can’t quickly get a handle on what’s going on and what their thought process may be.

Fantastic, complete documentation. The documentation on the Django website is well-written, complete and up to date with abundant code examples.  I really can’t stress the importance of this enough.  Anyone who has spent hours trawling through useless, incoherent, incomplete documentation will immediately see the value in this.

It’s ready when it’s ready. The Django developers are not afraid of holding back on a release if it’s not ready.  Django 1.1 was released about 3 months later than it was intended and the developers had no shame in holding it back because it wasn’t quite ready.  I highly respect them for this because it means that we can fully trust new releases to be very stable with copious amounts of polish.

Stays out of the way. One of the things I respect most about Django is that it doesn’t get in your way.  While it provides a complete stack for everything from data model handling to view templates you are free to substitute components with any of your own.  Don’t like the built-in authentication mechanism?  No problem.  Plug in another one or write your own.  Django’s concept of “applications” (basically pluggable modules) makes this easy.  Want to use your own file storage mechanism instead of what Django provides?  You’re more than welcome.

Built-in caching. Django was designed from the ground up with caching in mind and not only provides its own caching mechanisms but also plugs into existing, tried-and-true caching mechanisms like memcached (used by Facebook).

Easy admin interfaces. Django comes with a fully customisable admin interface for your projects which will hook directly into your data models.  This allows you to concentrate on developing your end user views without having to first get data entry forms in place.  Of course there’s no reason why you can’t continue to use the built-in admin tools or allow portions of them to be used by your end users.

Sane templates. Django’s templating system is just plain sexy.  It’s got the lot: fully-featured tags, batteries-included filters, and very simple, Pythonic, easy-to-learn syntax.

I could go on.  Apart from all that I’ve just mentioned, Django is also backed by Google and has the stamp of approval from Python’s creator himself, Guido von Rossum.

Not convinced?  Just take a look at the vast array of sites being created every day on djangosites.org.

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.