Many-to-many field, save() method and the Django admin

Tags: django

I’ve got a model UserGroup with many-to-many fields for managers and members:

class UserGroup(models.Model):
    managers = models.ManyToManyField(User)
    members = models.ManyToManyField(User)
    # Note: some stuff stripped out for brevity

I wanted every manager to be a member, too, automatically. So I added a custom save() method:

class UserGroup(models.Model):
    managers = models.ManyToManyField(User)
    members = models.ManyToManyField(User)
    # Note: some stuff stripped out for brevity

    def save(self, *args, **kwargs):
        if self.id:
            members = self.members.all()
            for manager in self.managers.all():
                if manager not in members:
                    self.members.add(manager)
        super(UserGroup, self).save(*args, **kwargs)

Which worked fine in my unittest, but not in the actual admin interface.

I found the reason on stackoverflow: the Django admin clears the many-to-many fields after saving the model and sets them anew with the data it knows about. So my save() method worked fine, but saw its work zapped by Django’s admin…

Django’s development docs say that 1.4 will have a save_related() model admin method. Which sounds like it could help work around this issue.

The solution I ended up with was to add a custom model admin form and to use the clean() method to just modify the form data. I got the idea also from stackoverflow. Here’s the relevant part of my admin.py:

class UserGroupAdminForm(ModelForm):
    class Meta:
        model = UserGroup

    def clean(self):
        """Make sure all managers are also members."""
        for manager in self.cleaned_data['managers']:
            if manager not in self.cleaned_data['members']:
                self.cleaned_data['members'].append(manager)
        return self.cleaned_data


class UserGroupAdmin(admin.ModelAdmin):
    model = UserGroup
    form = UserGroupAdminForm

It works fine now.

Addition 2011-1202. It didnt’ work so fine after all. There’s one problem, which you can also find on stackoverflow. Django converts the raw form data to python objects. In the case of these user object you get a queryset in the latest Django version instead of a list of user objects. .append() doesn’t work on a queryset. So I took one of the suggestions on stackoverflow and converted the queryset to a list, first:

def clean(self):
    """Make sure all managers are also members."""
    members = list(self.cleaned_data['members'])
    for manager in self.cleaned_data['managers']:
        if manager not in members:
            members.append(manager)
    self.cleaned_data['members'] = members
    return self.cleaned_data
Dutch station of Swalmen in the winter, late 1980s.
 
vanrees.org logo

Reinout van Rees

My name is Reinout van Rees and I program in Python, I live in the Netherlands, I cycle recumbent bikes and I have a model railway.

Weblog feeds

Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):