Подтвердить что ты не робот

Интерфейс администратора Django: использование horizontal_filter с встроенным полем ManyToMany

У меня есть поле модели Django, которое я хотел бы включить. Поле является отношением "многие ко многим". Таким образом, есть "Проекты" и "Профили пользователей". Каждый профиль пользователя может выбирать любое количество проектов.

В настоящее время у меня есть "табличный" встроенный просмотр. Есть ли способ иметь "горизонтальный фильтр", чтобы я мог легко добавлять и удалять проекты из профиля пользователя?

Для примера см. прилагаемое изображение. enter image description here

Здесь код модели для профиля пользователя:

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)
4b9b3361

Ответ 1

Проблема заключается не в том, чтобы иметь встроенные линии; это с пути ModelForm работает, в общем. Они только создают поля формы для реальных полей в модели, а не связанные атрибуты менеджера. Однако вы можете добавить эту функциональность в форму:

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_horizontal, поэтому вместо этого мы просто попросим использовать тот же виджетов FilteredSelectMultiple, который он будет использовать, если он был указан в filter_horizontal.

Мы первоначально установили набор запросов как весь набор UserProfile, вы не можете его фильтровать здесь, но на этом этапе определения класса форма не была создана и, следовательно, не имеет ее instance установлен еще. В результате мы переопределяем __init__, чтобы мы могли установить отфильтрованный запрос в качестве начального значения поля.

Наконец, мы переопределим метод save, чтобы мы могли установить связанное содержимое менеджера так же, как и данные в формате POST, и все готово.

Ответ 2

Небольшое дополнение, когда вы имеете дело со многими и многими отношениями с самим собой. Можно хотеть исключить себя из вариантов:

if self.instance.pk:
        self.fields['field_being_added'].queryset = self.fields['field_being_added'].queryset.exclude(pk=self.instance.pk)
        self.fields['field_being_added'].initial = """Corresponding result queryset"""