Django 管理界面:使用带有内联 ManyToMany 字段的 Horizo​​ntal_filter

2022-01-25 00:00:00 python django django-admin

问题描述

我有一个想要内联的 Django 模型字段.字段是多对多的关系.所以有项目"和用户配置文件".每个用户配置文件都可以选择任意数量的项目.

I have a Django model field that I'd like to inline. The field is a many-to-many relationship. So there are "Projects" and "User profiles". Each user profile can select any number of projects.

目前,我已经让表格"内联视图正常工作.有没有办法拥有一个水平过滤器",以便我可以轻松地从用户配置文件中添加和删除项目?

Currently, I've got the "tabular" inline view working. Is there a way to have a "horizontal filter" so that I can easily add and remove projects from a user profile?

示例请看附图.

这是用户个人资料的型号代码:

Here's the model code for the User Profile:

class UserProfile(models.Model):
    user = models.OneToOneField(User, unique=True)
    projects = models.ManyToManyField(Project, blank=True, help_text="Select the projects that this user is currently working on.")

以及项目的型号代码:

class Project(models.Model):
    name = models.CharField(max_length=100, unique=True)
    application_identifier = models.CharField(max_length=100)
    type = models.IntegerField(choices=ProjectType)
    account = models.ForeignKey(Account)
    principle_investigator = models.ForeignKey(User)
    active = models.BooleanField()

以及视图的管理代码:

class UserProfileInline(admin.TabularInline):
    model = UserProfile.projects.through
    extra = 0
    verbose_name = 'user'
    verbose_name_plural = 'users'

class ProjectAdmin(admin.ModelAdmin):
    list_display = ('name', 'application_identifier', 'type', 'account', 'active')
    search_fields = ('name', 'application_identifier', 'account__name')
    list_filter = ('type', 'active')
    inlines = [UserProfileInline,]
admin.site.register(Project, ProjectAdmin)


解决方案

问题不在于内联;一般来说,它来自 ModelForm 的工作方式.他们只为模型上的实际字段构建表单字段,而不是相关的经理属性.但是,您可以将此功能添加到表单中:

The problem isn't from having inlines; it's from the way ModelForms work, in general. They only build form fields for actual fields on the model, not related manager attributes. However, you can add this functionality to the form:

from django.contrib.admin.widgets import FilteredSelectMultiple

class ProjectAdminForm(forms.ModelForm):
    class Meta:
        model = Project

    userprofiles = forms.ModelMultipleChoiceField(
        queryset=UserProfile.objects.all(),
        required=False,
        widget=FilteredSelectMultiple(
            verbose_name='User Profiles',
            is_stacked=False
        )
    )

    def __init__(self, *args, **kwargs):
        super(ProjectAdminForm, self).__init__(*args, **kwargs)
            if self.instance.pk:
                self.fields['userprofiles'].initial = self.instance.userprofile_set.all()

    def save(self, commit=True):
        project = super(ProjectAdminForm, self).save(commit=False)  
        if commit:
            project.save()

        if project.pk:
            project.userprofile_set = self.cleaned_data['userprofiles']
            self.save_m2m()

        return project

class ProjectAdmin(admin.ModelAdmin):
    form = ProjectAdminForm
    ...

可能需要进行一些演练.首先,我们定义一个 userprofiles 表单域.它将使用 ModelMultipleChoiceField,默认情况下会产生一个多选框.由于这不是模型上的实际字段,我们不能只将它添加到 filter_horizo​​ntal,所以我们改为告诉它简单地使用相同的小部件 FilteredSelectMultiple,如果它被列在 filter_horizo​​ntal 中,它将使用它.

A little walkthrough is probably in order. First, we define a userprofiles form field. It will use a ModelMultipleChoiceField, which by default will result in a multiple select box. Since this isn't an actual field on the model, we can't just add it to filter_horizontal, so we instead tell it to simply use the same widget, FilteredSelectMultiple, that it would use if it were listed in filter_horizontal.

我们最初将查询集设置为整个 UserProfile 集,你不能在这里过滤它,但是,因为在类定义的这个阶段,表单还没有被实例化,因此没有'还没有设置它的 instance.因此,我们重写了 __init__ 以便我们可以将过滤后的查询集设置为字段的初始值.

We initially set the queryset as the entire UserProfile set, you can't filter it here, yet, because at this stage of the class definition, the form hasn't been instantiated and thus doesn't have it's instance set yet. As a result, we override __init__ so that we can set the filtered queryset as the field's initial value.

最后,我们重写 save 方法,这样我们就可以将相关管理器的内容设置为与表单的 POST 数据中的内容相同,您就完成了.

Finally, we override the save method, so that we can set the related manager's contents to the same as what was in the form's POST data, and you're done.

相关文章