Related ManyToManyField in Django admin site - continued

Published byGoogle on

A few weeks ago, I posted an article about displaying related ManyToManyFields in the Django admin site. The trick presented in that article works and was tested in Django 1.5.8 and 1.6.5 (the latest releases at the time). In the mean time, as you probably know, Django 1.7 was released as well as a security update. Using any one of those releases (1.5.9, 1.5.10, 1.6.6, 1.6.7, 1.7), the trick presented in that article doesn't work as-is anymore. It requires a bit more code.

In this article, I'll give out that code and explain why it is now needed.

One of the features introduced in the pre-mentioned releases is a security check on admin urls to avoid manipulation by users. One of the checks is made on one of the parameters sent to popup windows triggered by the plus sign on related fields. The check is performed in the new to_field_allowed method of the ModelAdmin.
This method basically says whether it is ok to have a popup for this Model using the field passed as a parameter to the url. And in our case Django now replies: "No, you have no reason to have a popup for this field".

In short, the way Django currently determines whether you are allowed to have a popup for a particular model is to see whether that model is on the receiving end of a ForeignKey or a ManyToMany field. In our case we are on the emitting end of a ManyToMany field, that's why Django doesn't allow us to have a popup for our Model.

A regression bug has been accepted but in the mean time here is what we have to do is to create a ModelAdmin for Book and override its to_field_allowed method.

#related_m2m/admin.py
# Add this import at the top of the file
from django.db.models.fields import FieldDoesNotExist
.
.
.
#Add this class
class BookAdmin(admin.ModelAdmin):

  def to_field_allowed(self, request, to_field):
    rv = super(BookAdmin, self).to_field_allowed(request, to_field)
    if not rv:
      opts = self.model._meta
      try:
        return opts.get_field(to_field)==opts.pk and len(opts.many_to_many)
      except FieldDoesNotExist:
        return False
    return rv


admin.site.register(Author, AuthorAdmin)
#update this line
admin.site.register(Book, BookAdmin)

What we do here is that, if the original method returns False, we check whether or Model is the origin of any ManyToMany relationships. If it is, for security reasons (to keep the same spirit as the security release of Django), we check that the field passed as a parameter to the popup url is the Model's primary key (which is always the field used for ManyToMany relationships).

That's it for today, happy coding everyone!

As always, you can browse the code for this tutorial on http://vc.lasolution.be/projects/snippets/repository.
The code is available for checkout on http://code.lasolution.be/snippets.
The branch associated with this tutorial is related_m2m_1.7

PS: Thanks to JJ for pointing this out to me.

Tutorial Snippet Django

Comments

Post a comment