Как предотвратить самостоятельный (рекурсивный) выбор для полей FK/MTM в Django Admin

Учитывая модель с полями ForeignKeyField (FKF) или ManyToManyField (MTMF) с ключом для "я", как я могу предотвратить самостоятельный (рекурсивный) выбор в 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 через admin поле related_articles представляется по умолчанию блоком выбора html (multiple), который предоставляет список ВСЕХ статей (Article.objects.all()). Пользователь должен видеть и иметь возможность выбирать Article экземпляры, отличные от самого себя, например. При редактировании Article a1, related_articles доступно для выбора = a2, a3.

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

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

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

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

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

Ответ 1

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

Ответ 2

Карл верен, здесь образец кода вырезания и вставки, который будет идти в 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)

Ответ 3

Вы также можете переопределить метод 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

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