Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access both directions of ManyToManyField in Django Admin?

The Django admin filter_horizontal setting gives a nice widget for editing a many-to-many relation. But it's a special setting that wants a list of fields, so it's only available on the (admin for the) model which defines the ManyToManyField; how can I get the same widget on the (admin for the) other model, reading the relationship backwards?

My models look like this (feel free to ignore the User/UserProfile complication; it's the real use case though):

class Site(models.Model):
    pass
class UserProfile(models.Model):
    user = models.OneToOneField(to=User,unique=True)
    sites = models.ManyToManyField(Site,blank=True)

I can get a nice widget on the admin form for UserProfile with

filter_horizontal = ('sites',)

but can't see how to get the equivalent on the Site admin.

I can also get part-way by adding an inline to SiteAdmin, defined as:

class SiteAccessInline(admin_module.TabularInline):
    model = UserProfile.sites.through

It's roundabout and unhandy though; the widget is not at all intuitive for simply managing the many-to-many relationship.

Finally, there's a trick described here which involves defining another ManyToManyField on Site and making sure it points to the same database table (and jumping through some hoops because Django isn't really designed to have different fields on different models describing the same data). I'm hoping someone can show me something cleaner.

like image 589
Tikitu Avatar asked Nov 30 '10 17:11

Tikitu


People also ask

What is admin ModelAdmin in Django?

One of the most powerful parts of Django is the automatic admin interface. It reads metadata from your models to provide a quick, model-centric interface where trusted users can manage content on your site. The admin's recommended use is limited to an organization's internal management tool.

What is Prepopulated_fields in Django admin?

The attribute prepopulated_fields tells the admin application to automatically fill the field slug - in this case with the text entered into the name field.

How do I restrict access to admin pages in Django?

Django admin allows access to users marked as is_staff=True . To disable a user from being able to access the admin, you should set is_staff=False . This holds true even if the user is a superuser. is_superuser=True .


1 Answers

Here's a (more or less) tidy solution, thanks to http://blog.abiss.gr/mgogoulos/entry/many_to_many_relationships_and and with a fix for a Django bug taken from http://code.djangoproject.com/ticket/5247

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)

This uses the same widget as the filter_horizontal setting, but hard-coded into the form.

like image 148
Tikitu Avatar answered Sep 18 '22 13:09

Tikitu