Django admin много-ко многим промежуточным моделям, использующим сквозные = и filter_horizontal

Вот как выглядят мои модели:

class QuestionTagM2M(models.Model):
    tag = models.ForeignKey('Tag')
    question = models.ForeignKey('Question')
    date_added = models.DateTimeField(auto_now_add=True)

class Tag(models.Model):
    description = models.CharField(max_length=100, unique=True)

class Question(models.Model):
    tags = models.ManyToManyField(Tag, through=QuestionTagM2M, related_name='questions')

Все, что я действительно хотел сделать, это добавить временную метку, когда было создано заданное отношение manytomany. Это имеет смысл, но это также добавляет немного сложности. Помимо удаления функциональности .add() [несмотря на то, что единственное поле, которое я действительно добавляю, автоматически создается, поэтому технически это не должно мешать этому]. Но я могу жить с этим, так как я не возражаю против добавления дополнительного QuestionTagM2M.objects.create(question=,tag=), если это означает получение дополнительной функциональности временной метки. Моя проблема заключается в том, что мне очень хотелось бы сохранить мой виджет javascript filter_horizontal в админе. Я знаю, что документы говорят, что я могу использовать встроенную строку вместо этого, но это слишком громоздко, потому что в любом случае нет дополнительных полей, которые действительно были бы во встроенном ключе, кроме внешнего ключа, в Tag. Кроме того, в более крупной схеме моей схемы базы данных мои объекты Question уже отображаются как встроенные на моей странице администратора, а поскольку Django не поддерживает вложенные строки в admin [пока], у меня нет способа выбрать теги для данного вопроса. Есть ли способ переопределить formfield_for_manytomany(self, db_field, request=None, **kwargs) или что-то подобное, чтобы разрешить использование виджета filter_horizontal и автоматического создания столбца date_added в базе данных? Это похоже на то, что django должен иметь возможность делать изначально, если вы укажете, что все столбцы промежуточного элемента автоматически создаются (кроме внешних), возможно, с помощью auto_created=True? или что-то похожее

Ответы

Ответ 1

Есть способы сделать это

  • Как предоставлено @obsoleter в комментарий ниже: установите QuestionTagM2M._meta.auto_created = True и обработайте проблемы w/syncdb.
  • Динамически добавьте поле date_added в модель M2M модели Question в models.py

    class Question(models.Model):
        # use auto-created M2M model
        tags = models.ManyToMany(Tag, related_name='questions')
    
    
    # add date_added field to the M2M model
    models.DateTimeField(auto_now_add=True).contribute_to_class(
             Question.tags.through, 'date_added')
    

    Тогда вы можете использовать его в admin как обычно ManyToManyField.
    В оболочке Python используйте Question.tags.through для ссылки на модель M2M.

    Примечание. Если вы не используете South, то syncdb достаточно; Если вы это сделаете, South не нравится таким образом и не будет замораживать поле date_added, вам нужно вручную записать миграцию, чтобы добавить/удалить соответствующий столбец.

  • Настроить ModelAdmin:

    • Не определяйте fields внутри настраиваемого ModelAdmin, задайте filter_horizontal. Это будет обходить проверку поля, упомянутую в ответе Ирфана.
    • Настроить formfield_for_dbfield() или formfield_for_manytomany(), чтобы администратор Django использовал widgets.FilteredSelectMultiple для поля tags.
    • Настроить метод save_related() внутри класса ModelAdmin, например

def save_related(self, request, form, *args, **kwargs):
    tags = form.cleaned_data.pop('tags', ())
    question = form.instance
    for tag in tags:
        QuestionTagM2M.objects.create(tag=tag, question=question)
    super(QuestionAdmin, self).save_related(request, form, *args, **kwargs)
  • Кроме того, вы можете исправить __set__() дескриптора поля ReverseManyRelatedObjectsDescriptor для ManyToManyField для date_added, чтобы сохранить экземпляр M2M без исключения.

Ответ 2

Документы могут быть изменены с момента публикации предыдущих ответов. Я взглянул на ссылку django docs, о которой упоминал @Irfan, и это кажется более прямым, чем раньше.

Добавьте встроенный класс в admin.py и установите модель в модель M2M

class QuestionTagM2MInline(admin.TabularInline):
    model = QuestionTagM2M
    extra = 1

установите inlines в свой класс администратора, чтобы содержать только что определенную строку Inline

class QuestionAdmin(admin.ModelAdmin):
    #...other stuff here
    inlines = (QuestionTagM2MInline,)

Не забудьте зарегистрировать этот класс администратора

admin.site.register(Question, QuestionAdmin)

После выполнения вышеописанного, когда я нажимаю на вопрос, у меня есть форма, чтобы делать все обычные изменения на нем и ниже, это список элементов в моей связи m2m, где я могу добавлять записи или редактировать существующие.

Ответ 3

Из https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-intermediary-models

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

Однако вы можете попытаться включить поле тегов явно, используя fields = ('tags',) в admin. Это приведет к этому исключению проверки

"QuestionAdmin.fields" не может включать теги поля ManyToManyField, потому что "теги" вручную задает "сквозную" модель.

Эта проверка выполняется в https://github.com/django/django/blob/master/django/contrib/admin/validation.py#L256

        if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
            raise ImproperlyConfigured("'%s.%s' "
                "can't include the ManyToManyField field '%s' because "
                "'%s' manually specifies a 'through' model." % (
                    cls.__name__, label, field, field))

Я не думаю, что вы можете обойти эту проверку, если вы не реализуете свое собственное поле, которое будет использоваться как ManyToManyField.