如何在Django管理界面中访问ManyToManyField的双向关系?

12

Django的管理员filter_horizontal设置提供了一个漂亮的小部件来编辑多对多关系。但是它是一个特殊的设置,需要一个字段列表,因此只能在(定义了ManyToManyField的)模型的(管理员)上使用;如果我想要在(另一个模型的管理员中)反向读取关系,如何获得相同的小部件?

我的模型看起来像这样(可以忽略User/UserProfile的复杂性;虽然这是真实用例):

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

使用以下方法可以在UserProfile的管理表单中获取一个漂亮的小部件:

filter_horizontal = ('sites',)

但我不知道如何在Site管理界面上获得相同的功能。

我也可以通过添加一个内联到SiteAdmin来实现一部分,定义如下:

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

这种方法非常繁琐且不太方便;该小部件根本不直观,无法简单地管理多对多关系。

最后,有一个技巧在这里描述,涉及在Site上定义另一个ManyToManyField并确保它指向相同的数据库表(因为 Django 并不是真正设计用于在不同模型上具有描述相同数据的不同字段)。我希望有人可以给我展示更为简洁的方法。

1个回答

7

这里有一个(或多或少)整洁的解决方案,感谢http://blog.abiss.gr/mgogoulos/entry/many_to_many_relationships_and提供的帮助,并通过修复来自http://code.djangoproject.com/ticket/5247的Django bug来进行改进。

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)

这个表单使用与filter_horizontal设置相同的小部件,但是被硬编码到表单中。


有人知道这个想法的更新版本吗?已经更新到Django 1.4了吗?首先,这个bug已经在过去两年中被修复了,而且我不确定为什么SiteAdmin范围应该知道user_profiles是什么意思。实际上,我目前正在收到错误异常类型:ImproperlyConfigured 异常值: “HospitalAdmin.fields”指的是表单中缺少的字段“user_profiles”。当然,模型名称是不同的,但结构是相同的。Hospital=Site。 - Alex Whittemore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接