Как предотвратить саморекурсивный выбор полей FK / MTM в Django Admin - PullRequest
10 голосов
/ 15 мая 2009

Для модели с полями ForeignKeyField (FKF) или ManyToManyField (MTMF) с внешним ключом «self», как я могу предотвратить self (рекурсивный) выбор в Django Admin (admin).

Короче говоря, должна быть возможность предотвратить сам (рекурсивный) выбор экземпляра модели в админке. Это применяется при редактировании существующих экземпляров модели, а не при создании новых экземпляров.

Например, возьмите следующую модель для статьи в новостном приложении;

class Article(models.Model):           
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    related_articles = models.ManyToManyField('self')

Если имеется 3 Article экземпляра (название: a1-3), при редактировании существующего Article экземпляра через администратора поле related_articles по умолчанию представляется в виде html (множественного) поля выбора, которое обеспечивает список ВСЕХ статей (Article.objects.all()). Пользователь должен видеть и иметь возможность выбирать Article экземпляров, отличных от самого себя, например, При редактировании Article a1, related_articles доступно для выбора = a2, a3.

В настоящее время я вижу 3 возможных способа сделать это в порядке уменьшения предпочтения;

  1. Предоставляет способ задания набора запросов, предоставляя доступные варианты в поле формы администратора для related_articles (через фильтр исключающих запросов, например, Article.objects.filter(~Q(id__iexact=self.id)), чтобы исключить текущий редактируемый экземпляр из списка related_articles, который может видеть пользователь и выберите из. Создание / настройка используемого набора запросов может происходить в конструкторе (__init__) пользовательского Article ModelForm или с помощью некоторого динамического параметра limit_choices_to Model. Для этого потребуется способ получить экземпляр редактируется для использования для фильтрации.
  2. Переопределите функцию save_model класса Article Model или ModelAdmin, чтобы проверить и удалить себя из related_articles перед сохранением экземпляра. Это по-прежнему означает, что пользователи с правами администратора могут видеть и выбирать все статьи, включая редактируемый экземпляр (для существующих статей).
  3. Отфильтровывать собственные ссылки, когда это необходимо для использования вне администратора, например, шаблоны.

Идеальное решение (1) в настоящее время возможно сделать через пользовательские формы модели вне администратора, поскольку можно передать отфильтрованную переменную набора запросов для экземпляра, редактируемого в конструкторе формы модели. Вопрос в том, можете ли вы получить экземпляр Article, то есть «self», отредактированный администратором перед созданием формы, чтобы сделать то же самое.

Может быть, я поступаю неправильно, но если вам разрешено определять FKF / MTMF для той же модели, тогда должен быть способ заставить администратора - делать правильные вещи - и запретить пользователю выбирать себя, исключив его из списка доступных вариантов.

Примечание: Решение 2 и 3 можно сделать сейчас, и мы стараемся избегать их получения в качестве ответов, в идеале я хотел бы получить ответ на решение 1.

Ответы [ 3 ]

9 голосов
/ 09 декабря 2009

Карл прав, вот пример кода, который можно вставить в admin.py

Я считаю, что навигация по отношениям с Django может быть сложной, если у вас нет твердого понимания, и живой пример может стоить в 1000 раз больше, чем «прочитайте это» (не то, что вам не нужно понимать, что происходит).

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['myManyToManyField'].queryset = MyModel.objects.exclude(
            id__exact=self.instance.id)
2 голосов
/ 16 мая 2009

Вы можете использовать пользовательскую ModelForm в администраторе (установив атрибут «form» вашего подкласса ModelAdmin ). Таким образом, вы делаете это так же, как в любом другом месте администратора.

1 голос
/ 28 сентября 2016

Вы также можете переопределить метод get_form ModelAdmin следующим образом:

def get_form(self, request, obj=None, **kwargs):
    """
    Modify the fields in the form that are self-referential by
    removing self instance from queryset
    """
    form = super().get_form(request, obj=None, **kwargs)
    # obj won't exist yet for create page
    if obj:
        # Finds fieldnames of related fields whose model is self
        rmself_fields = [f.name for f in self.model._meta.get_fields() if (
            f.concrete and f.is_relation and f.related_model is self.model)]
        for fieldname in rmself_fields:
            form.base_fields[fieldname]._queryset =
                form.base_fields[fieldname]._queryset.exclude(id=obj.id)
    return form

Обратите внимание, что это решение «по размеру подходит всем», которое автоматически находит самоссылающиеся поля модели и удаляет self из всех: -)

...