Django prepopulated_fields not working?

Note – Just skip to the end of this article if you’re eager to find out the reason.

Yesterday I found that Django even had a special field SlugField for post slugs. I’m just starting with Django so there’s a lot I don’t know yet. It’s cool that they also designed a “prepopulated_fields” property in the admin module.

class ArticleAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}

Its value is a dictionary, with key being the slug field name of your model, and value being a tuple (note the way Python defines a single element tuple!) or list of fields where the slug should get its value from.

Slugs are commonly used for user-friendly (and maybe SEO-friendly) URLs. WordPress has built-in functionality to automatically generate post slugs after you name your post title. So I guess SlugField and prepopulated_fields are designed just for this purpose.

But the problem came when I tried to see it in action in an edit page. I got everything correct but the slug field in the form just didn’t update no matter what I did to its source field. Of course it’s implemented using JavaScript, but looking at the source code, it only generated an useless stub.

I started to dig into the source code of Django’s admin module and had a hard time to find how the prepopulated_fields_js tag work, since I’m new to Django. At first I naively thought it was a bug in contrib/admin/templates/admin/prepopulated_fields_js.html:

{% for field in prepopulated_fields %}

I believed that’s the whole thing of template tag definition. The context doesn’t have “prepopulated_fields”, so the fix was changing this line to:

{% for field in adminform.prepopulated_fields %}

And it worked! Did I just fixed a bug in Django? Was it so easy? Couldn’t be. I went to #django channel and described my situation. An experienced user pointed me to contrib/admin/templatetags/admin_modify.py, where the prepopulated_fields_js tag was actually defined:

@register.inclusion_tag('admin/prepopulated_fields_js.html', takes_context=True)
def prepopulated_fields_js(context):
    """
    Creates a list of prepopulated_fields that should render Javascript for
    the prepopulated fields for both the admin form and inlines.
    """
    prepopulated_fields = []
    if context['add'] and 'adminform' in context:
        prepopulated_fields.extend(context['adminform'].prepopulated_fields)
    if 'inline_admin_formsets' in context:
        for inline_admin_formset in context['inline_admin_formsets']:
            for inline_admin_form in inline_admin_formset:
                if inline_admin_form.original is None:
                    prepopulated_fields.extend(inline_admin_form.prepopulated_fields)
    context.update({'prepopulated_fields': prepopulated_fields})
    return context

So here comes

The fact

That tag does add prepopulated_fields to the context, but only when current action is ‘add’. I was always testing my code in an editing page, so nothing happened. I used svn log/diff to see the history of changes to this tag, but it was designed this way in the beginning.

Yes it’s a feature not a bug :) Think about the URL. For some sites, the URL doesn’t contain any numerical or hash IDs, and the post slug may be the only identifier. If this slug is changed after editing a title (fixing a typo, for example), people who bookmarked this page won’t be able to visit it again, and search engine crawlers can’t follow the old links again. WordPress also designed post slug to work this way.

But Django team should update the documentation to reflect this design so that new users won’t be confused.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your intelligence before hitting * Time limit is exhausted. Please reload CAPTCHA.