Django Admin - 显示M2M模型的中介字段

4 投票
1 回答
2144 浏览
提问于 2025-04-16 00:22

我们有一个Django应用,里面有一系列的报纸文章。每篇文章都有两个多对多的关系,一个是“发言人”,另一个是“公司”(文章中提到的公司)。

现在,添加文章的页面差不多是我们想要的样子——就是标准的Django后台,我们使用了filter_horizontal来设置这两个多对多的关系。

接下来的步骤是为每个多对多关系添加一个“评分”字段,作为中间字段。

下面是我们models.py的一个例子:

class Article(models.Model):
    title = models.CharField(max_length=100)
    publication_date = models.DateField()
    entry_date = models.DateField(auto_now_add=True)
    abstract = models.TextField() # Can we restrict this to 450 characters?
    category = models.ForeignKey(Category)
    subject = models.ForeignKey(Subject)
    weekly_summary = models.BooleanField(help_text = 'Should this article be included in the weekly summary?')
    source_publication = models.ForeignKey(Publication)
    page_number = models.CharField(max_length=30)
    article_softcopy = models.FileField(upload_to='article_scans', null=True, blank=True, help_text='Optionally upload a soft-copy (scan) of the article.')
    url = models.URLField(null=True, blank=True, help_text = 'Enter a URL for the article. Include the protocl (e.g. http)')
    firm = models.ManyToManyField(Firm, null=True, blank=True, through='FirmRating')
    spokesperson = models.ManyToManyField(Spokeperson, null=True, blank=True, through='SpokespersonRating')

    def __unicode__(self):
        return self.title

class Firm(models.Model):
    name = models.CharField(max_length=50, unique=True)
    homepage = models.URLField(verify_exists=False, help_text='Enter the homepage of the firm. Include the protocol (e.g. http)')

    def __unicode__(self):
        return self.name

    class Meta:
        ordering = ['name']

class Spokeperson(models.Model):
    title = models.CharField(max_length=100)
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.first_name + ' ' + self.last_name

    class Meta:
        ordering = ['last_name', 'first_name']

class FirmRating(models.Model):
    firm = models.ForeignKey(Firm)
    article = models.ForeignKey(Article)
    rating = models.IntegerField()

class SpokespersonRating(models.Model):
    firm = models.ForeignKey(Spokesperson)
    article = models.ForeignKey(Article)
    rating = models.IntegerField()

问题是,一旦我们把“公司”和“发言人”字段改成“通过”并使用中间模型后,添加文章的页面就不再有filter_horizontal控件来添加公司/发言人和文章的关系了——它们完全消失了。你根本看不见它们。我也不知道为什么会这样。

我希望能有办法继续使用这个很酷的filter_horizontal控件来设置关系,然后在下面嵌入另一个字段来设置评分。不过,我不太确定怎么在仍然利用Django后台的情况下做到这一点。

我看到这里有一篇关于在Django后台重写单个控件的文章:

http://www.fictitiousnonsense.com/archives/22

但是,我不确定这个方法现在是否还有效,也不确定如何将其应用到这里,涉及到一个外键到中间模型(这基本上算是一个内联吗?)。

难道没有简单的方法可以做到这一切吗?

谢谢,
Victor

1 个回答

8

问题在于,管理员的一个方法 formfield_for_manytomanydjango.contrib.admin.options 中,对于有中介模型的多对多字段并不会返回一个表单字段! http://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L157

你需要在你的 ModelAdmin 中重写这个方法:

def formfield_for_manytomany(self, db_field, request=None, **kwargs):
    """
    Get a form Field for a ManyToManyField.
    """
    # If it uses an intermediary model that isn't auto created, don't show
    # a field in admin.
    if not db_field.rel.through._meta.auto_created:
        return None    # return something suitable for your needs here!
    db = kwargs.get('using')

    if db_field.name in self.raw_id_fields:
        kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
        kwargs['help_text'] = ''
    elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
        kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))

撰写回答