Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

Fixing Django 1.2.4’s SuspiciousOperation on filtering

Jerry Stratton, December 24, 2010

As of Django 1.2.4, you can no longer create filtered links to other models in the admin. For example, here’s something I use to show all siblings of a page:

[toggle code]

  • class PageAdmin(models.ModelAdmin):
    • list_display = (…, 'siblings')
    • def siblings(self, page):
      • if page.parent:
        • link = '/admin/pages/page/?parent__id__exact=' + unicode(page.parent.id)
        • text = '<a href="' + link + '">siblings</a>'
        • return text
      • return None
    • siblings.short_description='Siblings'
    • siblings.allow_tags = True

Because this can result in information leakage, it’s been disabled as of 1.2.4. You’ll end up with an error message that looks like:

SuspiciousOperation at /admin/pages/page/
Filtering by parent__id__exact not allowed

As far as I can tell, the only filters that are allowed are the ones that appear in the sidebar. But for something like this, where there are thousands of possibilities, putting it in the sidebar would be disastrous.

The announcement was unhelpful, but from the source, the ModelAdmin class checks for valid filters using a lookup_allowed method. That method returns True if the lookup should be allowed, and False if it should not. We can override that method to add a new keyword to ModelAdmin, valid_lookups, that accepts a list of lookups that should be allowed.

[toggle code]

  • class SmarterModelAdmin(admin.ModelAdmin):
    • valid_lookups = ()
    • def lookup_allowed(self, lookup, *args, **kwargs):
      • if lookup.startswith(self.valid_lookups):
        • return True
      • return super(SmarterModelAdmin, self).lookup_allowed(lookup, *args, **kwargs)

This sets up an empty tuple called valid_lookups; it can be assigned to just like any other option in ModelAdmin instances. It uses startswith so that if you want to enable a field for filtering you don’t have to enable every permutation of it. Just the field name will do; or you can enable the specific filter (such as parent__id__exact).

Because the method uses startswith, valid_lookups needs to be a tuple.

[toggle code]

  • class PageAdmin(SmarterModelAdmin):
    • valid_lookups = ('parent',)

This will also enable fields with similar names. For example, if you enable all filters for “key” and you also have a “keyword” lookup, enabling “key” will enable both of them. Add an underscore or two if you need to avoid that. For example, “key__”.

Judging from my reading of the Django 1.3 release notes, there will be a mechanism for doing something similar to this in 1.3, but I’m not sure.

Update: fixed bad tuple from ('parent') to ('parent',) noticed by Jonathan Hartley. It still worked before because a bad tuple turns into a string, and startswith works with both strings and tuples.

February 8, 2011: lookup_allowed gets new parameter for value

I’ve updated the lookup_allowed method in SmarterModelAdmin because it looks like (a) there won’t be an official solution in Django 1.3, and lookup_allowed is going to get a new parameter.

[toggle code]

  • class SmarterModelAdmin(admin.ModelAdmin):
    • valid_lookups = ()
    • def lookup_allowed(self, lookup, *args, **kwargs):
      • if lookup.startswith(self.valid_lookups):
        • return True
      • return super(SmarterModelAdmin, self).lookup_allowed(lookup, *args, **kwargs)

Overall this seems like a good change to me. If done right it allows filtering based on value as well as on field.

Hopefully, a later Django will alleviate the need to use an undocumented override. The main reason I didn’t put much work in this solution is that I thought I saw something about there already being a solution in 1.3; sounds like that isn’t the case.

Um. Semi-undocumented.

This was a little annoying:

It’s unfortunate that people are externally documenting the “fix” for the security problem to be “remove the security”, but there’s not much we can do beyond documenting the change.

That is of course untrue. You could provide a sanctioned method for allowing filters on lookups that don’t appear in a list_filter. Saying that adding a valid_lookups property is “removing the security” is saying that their fix isn’t a fix at all, since it removes the security from list_filter fields. In both cases the code is just looking at a list of fields and relations for which admin filtering should be allowed.

Being able to drill into a database is useful, as the existence of list_filter shows. The “hack” that allows building custom admin queries to drill into field values in the admin display is well-known and well-promulgated and if it isn’t in the documentation, it is from the time when the documentation was “read the source”.

  1. <- Apple Mail and GeekTool
  2. Apple software can’t connect ->