Django 管理界面:使用带中间表的 ManyToMany 字段的 Horizo​​ntal_filter

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

问题描述

我正在尝试增强 django 管理界面,类似于

I am trying to enhance the django admin interface similar to what has been done in the accepted answer of this SO post. I have a many-to-many relationship between a User table and a Project table. In the django admin, I would like to be able to assign users to a project as in the image below:

使用简单的 ManyToManyField 可以正常工作,但问题是我的模型使用 ManyToManyFieldthrough 参数来使用中间表.我不能使用 save_m2m()set() 函数,我对如何调整下面的代码以使其工作一无所知.

It works fine with a simple ManyToManyField but the problem is that my model uses the through parameter of the ManyToManyField to use an intermediary table. I cannot use the save_m2m() and set() function and I am clueless on how to adapt the code below to make it work.

型号:

class UserProfile(models.Model):
    user = models.OneToOneField(User, unique=True)
    projects = models.ManyToManyField(Project, through='Membership')

class Project(models.Model):
    name = models.CharField(max_length=100, unique=True)
    application_identifier = models.CharField(max_length=100)
    type = models.IntegerField(choices=ProjectType)
    ...

class Membership(models.Model):
    project = models.ForeignKey(Project,on_delete=models.CASCADE)
    user = models.ForeignKey(UserProfile,on_delete=models.CASCADE)

    # extra fields
    rating = models.IntegerField(choices=ProjectType)
    ...

admin.py中用于小部件的代码:

from django.contrib.admin.widgets import FilteredSelectMultiple

class ProjectAdminForm(forms.ModelForm):
    class Meta:
        model = Project
        fields = "__all__" # not in original SO post

    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
    ...

注意:中间模型中的所有额外字段都不需要在项目管理视图中更改(它们是自动计算的)并且它们都有默认值.

Note: all the extra fields from the intermediary model do not need to be changed in the Project Admin view (they are automatically computed) and they all have a default value.

感谢您的帮助!


解决方案

我可以找到解决这个问题的方法.这个想法是:

I could find a way of solving this issue. The idea is:

  1. 当且仅当它们是新条目时才在 Membership 表中创建新条目(否则它会删除 Membership 表中其他字段的现有数据)
  2. 删除从成员资格表中取消选择的条目

为此,我替换了:

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

作者:

if project.pk:
    # Get the existing relationships
    current_project_selections = Membership.objects.filter(project=project)
    current_selections = [o.userprofile for o in current_project_selections]

    # Get the submitted relationships
    submitted_selections = self.cleaned_data['userprofiles']

    # Create new relation in Membership table if they do not exist
    for userprofile in submitted_selections :
        if userprofile not in current_selections:
            Membership(project=project,userprofile=userprofile).save()

    # Remove the relations that were deselected from the Membership table
    for project_userprofile in current_project_selections:
        if project_userprofile.userprofile not in submitted_selections :
            project_userprofile.delete()

相关文章