如何在Django管理后台中使用“list_display”显示多对多字段?

137
class Product(models.Model):
    products = models.CharField(max_length=256)
    
    def __unicode__(self):
        return self.products

class PurchaseOrder(models.Model):
    product = models.ManyToManyField('Product')
    vendor = models.ForeignKey('VendorProfile')
    dollar_amount = models.FloatField(verbose_name='Price')

我有那段代码。不幸的是,在 admin.py 中出现了错误,涉及到 ManyToManyField

class PurchaseOrderAdmin(admin.ModelAdmin):
    fields = ['product', 'dollar_amount']
    list_display = ('product', 'vendor')

错误提示如下:
“PurchaseOrderAdmin.list_display[0]”,“product”是不支持的ManyToManyField。
但是,当我从“list_display”中删除“product”时,它可以编译。那么,如何在不出现错误的情况下显示“product”在“list_display”中?
编辑:也许更好的问题是如何在“list_display”中显示ManyToManyField?
4个回答

239

你可能不能直接地去做。 list_display的文档中

由于这将需要为表中的每一行执行单独的SQL语句,因此不支持ManyToManyField字段。如果您仍想这样做,请为您的模型提供自定义方法,并将该方法的名称添加到list_display中。(有关list_display中自定义方法的更多信息,请参见下文。)

你可以像这样做:

class PurchaseOrderAdmin(admin.ModelAdmin):
    fields = ['product', 'dollar_amount']
    list_display = ('get_products', 'vendor')

    def get_products(self, obj):
        return "\n".join([p.products for p in obj.product.all()])

或者定义一个模型方法,并使用它

class PurchaseOrder(models.Model):
    product = models.ManyToManyField('Product')
    vendor = models.ForeignKey('VendorProfile')
    dollar_amount = models.FloatField(verbose_name='Price')

    def get_products(self):
        return "\n".join([p.products for p in self.product.all()])

并且在管理员 list_display

list_display = ('get_products', 'vendor')

这看起来是一个非常好的解决方案。谢谢你。不过,我现在遇到了一个错误,它说“在列“product_id”中的空值违反了非空约束”。你知道这是什么意思吗? - Mdjon26
5
由于这会使数据库举步维艰,您如何使用select_related()或prefetch_related()来提高性能? - Cloud Artisans
6
如果优化问题仍然有趣,我刚刚遇到了同样的问题,并发现你可以为你的ModelAdmin实现一个经过优化的get_queryset()方法,详见https://dev59.com/Fmct5IYBdhLWcg3wF5tF#12354293。 - goetz
1
@SebastiánVansteenkiste 好主意,也许 cached_property 会有所帮助。但我认为可能不会。当您使用优化的 get_queryset 时,您可以在那里进行注释/预处理数据,例如在 SQL 中执行产品连接而不是在 Django 中,并将自定义数据存储在查询集中。然后,当访问该属性时,您只需要在 SQL 中执行一次逻辑,而不是对每行都执行一次。 - goetz
1
好的观点。我应该研究一下如何编写自己的优化get_queryset,只是我还没有找到一个足够简单的示例来说明我应该做什么,也没有时间去阅读完整的文档。 - Sebastián Vansteenkiste
显示剩余3条评论

29

你可以用这种方式来做,友好地检查以下代码片段:

class Categories(models.Model):
    """ Base category model class """

    title       = models.CharField(max_length=100)
    description = models.TextField()
    parent      = models.ManyToManyField('self', default=None, blank=True)
    when        = models.DateTimeField('date created', auto_now_add=True)

    def get_parents(self):
        return ",".join([str(p) for p in self.parent.all()])

    def __unicode__(self):
        return "{0}".format(self.title)

在您的admin.py模块中调用以下方法:

class categories(admin.ModelAdmin):
    list_display    = ('title', 'get_parents', 'when')

17

如果您想节省额外的查询,可以在get_queryset方法中使用prefetch_related,如下所示:

class PurchaseOrderAdmin(admin.ModelAdmin):
    fields = ['product', 'dollar_amount']
    list_display = ('get_products', 'vendor')

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        return qs.prefetch_related('product')

    def get_products(self, obj):
        return ",".join([p.products for p in obj.product.all()])
根据 文档,这种方式只需要额外查询一次即可获取所有PurchaseOrder实例的相关Product项目,而不是需要每个PurchaseOrder实例一个查询。

2
例如,下面展示了具有多对多关系的CategoryProduct模型。*我使用的是Django 4.2.1版本。
# "models.py"

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

class Product(models.Model):
    categories = models.ManyToManyField(Category)
    name = models.CharField(max_length=50)
    price = models.DecimalField(decimal_places=2, max_digits=5)

然后,下面展示了CategoryProduct的管理员:
# "admin.py"

from django.contrib import admin
from .models import Category, Product

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'name')
    ordering = ('id',)

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'price')
    ordering = ('id',)

然后,Category 管理员有以下 5 个对象:

enter image description here

而且,“产品”管理员有6个对象,如下所示:

enter image description here

现在,使用@admin.display()定义get_productsget_categories(),然后将它们分别设置为CategoryProduct管理员中的list_display,如下所示。在@admin.display()中的description参数可以将Django Admin中的列名从GET PRODUCTSGET CATEGORIES重命名为PRODUCTSCATEGORIES
# "admin.py"

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'get_products')
    ordering = ('id',)            # Here

    # Here
    @admin.display(description='products')
    def get_products(self, obj):
        return [product.name for product in obj.product_set.all()]

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):      # Here  
    list_display = ('id', 'name', 'price', 'get_categories')
    ordering = ('id',)
    
    # Here
    @admin.display(description='categories')
    def get_categories(self, obj):
        return [category.name for category in obj.categories.all()]

然后,在“类别”管理中,如下所示显示了“产品”列:

enter image description here

然后,CATEGORIES列显示在Product管理页面中,如下所示:

enter image description here

此外,您可以通过在Category和Product模型中分别定义get_products()和get_categories()函数,并使用@admin.display()来显示PRODUCTS和CATEGORIES列,如下所示:

同样地,您可以通过在Category和Product模型中分别定义get_products()和get_categories()函数,并使用@admin.display()来显示PRODUCTS和CATEGORIES列,如下所示:

# "models.py"

from django.db import models
from django.contrib import admin

class Category(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return self.name

    # Here
    @admin.display(description='products')
    def get_products(self):
        return [product.name for product in self.product_set.all()]

class Product(models.Model):
    categories = models.ManyToManyField(Category)
    name = models.CharField(max_length=50)
    price = models.DecimalField(decimal_places=2, max_digits=5)

    # Here
    @admin.display(description='categories')
    def get_categories(self):
        return [category.name for category in self.categories.all()]

然后,将它们分别设置为CategoryProduct管理员中的list_display,如下所示:
# "admin.py"

...

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'get_products')
    ordering = ('id',)            # Here

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):      # Here
    list_display = ('id', 'name', 'price', 'get_categories')
    ordering = ('id',)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接