19

这是我的模型的外观:

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_horizontal在管理员中保留我的 javascript 小部件。我知道文档说我可以使用内联,但这太笨拙了,因为除了外键之外,实际上没有其他字段可以在内联中Tag

此外,在我的数据库架构的更大方案中,我的Question对象已经在我的管理页面上显示为内联,并且由于 Django 不支持管理中的嵌套内联 [尚未],我无法为给定选择标签问题。

有没有什么方法可以覆盖formfield_for_manytomany(self, db_field, request=None, **kwargs)或类似的东西来允许我使用漂亮的filter_horizontal小部件并自动创建date_added数据库的列?

这似乎是 django 应该能够在本机上做的事情,只要您指定中间的所有列都是自动创建的(除了外键),也许是auto_created=True? 或类似的东西

4

3 回答 3

10

办法做到这一点

  • 正如@obsoleter 在下面的评论中所提供的:设置QuestionTagM2M._meta.auto_created = True和处理同步数据库很重要。
  • 在models.pydate_added中为model的M2M模型动态添加字段Question

    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 shell 中,Question.tags.through用来指代 M2M 模型。

    注意,如果你不使用South,那就syncdb足够了;如果这样做,South不喜欢这种方式并且不会冻结date_added字段,则需要手动编写迁移以添加/删除相应的列。

  • 自定义 ModelAdmin:

    1. 不要fields在自定义的 ModelAdmin 内部定义,只定义filter_horizontal. 这将绕过 Irfan 的回答中提到的字段验证。
    2. 自定义formfield_for_dbfield()formfield_for_manytomany()使 Django 管理员widgets.FilteredSelectMultiple用于该tags字段。
    3. 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)
  • 此外,您可以修补ManyToManyField 的字段描述符__set__()以保存 M2M 实例而不会引发异常。ReverseManyRelatedObjectsDescriptordate_added
于 2012-04-18T05:15:05.573 回答
3

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

当您使用 ManyToManyField 的 through 参数指定中间模型时,管理员默认不会显示小部件。这是因为该中间模型的每个实例都需要比单个小部件中显示的更多信息,并且多个小部件所需的布局将根据中间模型而有所不同。

但是,您可以尝试使用fields = ('tags',)in admin 显式包含 tags 字段。这将导致此验证异常

“QuestionAdmin.fields”不能包含 ManyToManyField 字段“tags”,因为“tags”手动指定了“通过”模型。

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

于 2012-04-15T18:01:05.477 回答
2

自发布先前的答案以来,文档可能已更改。我查看了@Irfan 提到的django 文档链接,它似乎比以前更直接。

为您添加一个内联类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 关系中的元素列表,我可以在其中添加条目或编辑现有条目。

于 2014-04-22T17:25:35.270 回答