Django admin使用through=和filter\u horizon的多对多中介模型

2024-05-17 19:43:02 发布

您现在位置:Python中文网/ 问答频道 /正文

我的模特就是这样的:

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

我真正想做的就是在创建一个给定的多人关系时添加一个时间戳。这是有道理的,但也增加了一点复杂性。除了删除.add()功能之外[尽管我真正添加的唯一字段是自动创建的,所以从技术上讲,它不应该再干扰这个功能]。但我可以接受,因为我不介意做额外的QuestionTagM2M.objects.create(question=,tag=),如果这意味着获得额外的时间戳功能。我的问题是我真的很想在管理中保留我的filter_horizontaljavascript小部件。我知道文档中说我可以使用内联,但这太难了,因为除了Tag的外键之外,内联中没有其他字段。另外,在我的数据库模式的更大方案中,我的Question对象已经在我的管理页面上显示为内联,而且由于Django不支持管理中的嵌套内联,因此我无法为给定的问题选择标记。是否有任何方法可以重写formfield_for_manytomany(self, db_field, request=None, **kwargs)或类似的内容,以允许我使用漂亮的filter_horizontal小部件并自动创建数据库的date_added列?这似乎是django本机应该能够做的事情,只要您指定中间层中的所有列都是自动创建的(除了外键),可能是使用auto_created=True?或者类似的东西


Tags: 功能addtrueaddedautodatemodelmodels
3条回答

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

When you specify an intermediary model using the through argument to a ManyToManyField, the admin will not display a widget by default. This is because each instance of that intermediary model requires more information than could be displayed in a single widget, and the layout required for multiple widgets will vary depending on the intermediate model.

但是,您可以尝试通过在管理中使用fields = ('tags',)显式地包含标记字段。这将导致此验证异常

'QuestionAdmin.fields' can't include the ManyToManyField field 'tags' because 'tags' manually specifies a 'through' model.

此验证在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使用。

有很多方法可以做到这一点

  • 由@obsoleter在the comment below中提供:设置QuestionTagM2M._meta.auto_created = True,并处理w/syncdb事务。
  • 在models.py中将date_added字段动态添加到Question模型的M2M模型

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

    然后你可以在管理中正常使用它ManyToManyField
    在Python shell中,使用Question.tags.through来引用M2M模型。

    注意,如果不使用South,那么syncdb就足够了;如果使用,South就不喜欢 这样就不会冻结date_added字段,您需要手动编写迁移来添加/删除相应的列。

  • 自定义模型管理:

    1. 不要在自定义的ModelAdmin中定义fields,只定义filter_horizontal。这将绕过Irfan的答案中提到的字段验证。
    2. 自定义formfield_for_dbfield()formfield_for_manytomany(),使Django admin对tags字段使用widgets.FilteredSelectMultiple
    3. 在ModelAdmin类中自定义save_related()方法,如

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)
  • 此外,您还可以修补ManyToManyField的ReverseManyRelatedObjectsDescriptor字段描述符的__set__(),以便date_added在不引发异常的情况下保存M2M实例。

自上一个答案发布后,文档可能已更改。我看了一下@Irfan提到的django docs链接,它看起来比以前更加直截了当。

将内联类添加到admin.py,并将模型设置为M2M模型

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

在管理类中将inlines设置为包含刚才定义的内联

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

别忘了注册这个管理类

admin.site.register(Question, QuestionAdmin)

完成上述操作后,当我单击一个问题时,我有一个表单来对其进行所有常规编辑,下面是m2m关系中的元素列表,我可以在其中添加条目或编辑现有条目。

相关问题 更多 >