Configuring Many-to-many field in django admin with related_name

7.6k views Asked by At

I have the following model and admin defined in djando 1.5. This is a many-to-many relationship between subnet and vlan. I use the related_name option in the ManyToMany field to be able to get the collection of vlan from the related subnet object. Adding subnet to vlans from the admin works well. However when I try to add an horizontal_filer to the subnet admin in order to add van to its vlan set I get an error saying that the vlans attribute doesn't exist. I'm using subnet object in some view and I can access the vlans attribute just right.

What am I doing wrong here ? I've seen similar post but I couldn't adapt any of the proposed solution with success.

Thanks for your help

model.py

from django.db import models

class Subnet(models.Model):
    networkAddress = models.CharField(max_length=15)
    size = models.IntegerField()

    def __unicode__(self):
        return "%s/%s" % (self.networkAddress, self.size)

class IpAddress(models.Model):
    ipAddress = models.CharField(max_length=15)
    subnet = models.ForeignKey(Subnet)

    def __unicode__(self):
        return "%s" % (self.ipAddress)

class Vlan(models.Model):
    number = models.IntegerField()
    description = models.CharField(max_length=150)
    subnets = models.ManyToManyField(Subnet, related_name='vlans', blank=True)

    def __unicode__(self):
        return "VLAN %s (%s)" % (self.number, self.description)

admin.py

from network.models import Subnet, IpAddress, Vlan
from django.contrib import admin

class SubnetAdmin(admin.ModelAdmin):
    filter_horizontal = ('vlans',)

admin.site.register(Subnet, SubnetAdmin)
admin.site.register(IpAddress)
admin.site.register(Vlan)

and the error I get

Request Method:     GET
Request URL:    http://127.0.0.1:8000/admin/
Django Version:     1.5.2
Exception Type:     ImproperlyConfigured
Exception Value:    

'SubnetAdmin.filter_horizontal' refers to field 'vlans' that is missing from model 'network.Subnet'.
2

There are 2 answers

0
dan-klasson On

Apparently this is an 8 year old feature request. There is django-admin-extend. Or you could just throw something like this in there:

from django.contrib import admin as admin_module

class SiteForm(ModelForm):
    user_profiles = forms.ModelMultipleChoiceField(
        label='Users granted access',
        queryset=UserProfile.objects.all(),
        required=False,
        help_text='Admin users (who can access everything) not listed separately',
        widget=admin_module.widgets.FilteredSelectMultiple('user profiles', False))

class SiteAdmin(admin_module.ModelAdmin):
    fields = ('user_profiles',)

    def save_model(self, request, obj, form, change):
        # save without m2m field (can't save them until obj has id)
        super(SiteAdmin, self).save_model(request, obj, form, change) 
        # if that worked, deal with m2m field
        obj.user_profiles.clear()
        for user_profile in form.cleaned_data['user_profiles']:
             obj.user_profiles.add(user_profile)

    def get_form(self, request, obj=None, **kwargs):
        if obj:
            self.form.base_fields['user_profiles'].initial = [ o.pk for o in obj.userprofile_set.all() ]
        else:
            self.form.base_fields['user_profiles'].initial = []
        return super(SiteAdmin, self).get_form(request, obj, **kwargs)

It should give you a filter_horizontal when you specify it in the fields tuple.

0
Wtower On

I have created a public gist that covers this specific issue.

https://gist.github.com/Wtower/0b181cc06f816e4feac14e7c0aa2e9d0

The general idea is to use that specific base form class in order to define a 'reverse' m2m field for the form that would otherwise not include it. Then easily override the form in the admin class.

Although the code is not quite complicated, the gist code is somehow long to include within the answer so apologies for that.