Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
452 views
in Technique[技术] by (71.8m points)

python - Django admin interface: using horizontal_filter with ManyToMany field with intermediate table

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: Widget to use in Project admin interface

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.

The model:

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

The code used for the widget in 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.

Thanks for your help!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

  1. Create new entries in the Membership table if and only if they are new (otherwise it would erase the existing data for the other fields in the Membership table)
  2. Remove entries that were deselected from the Membership table

To do this, I replaced:

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

By:

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()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...