many-to-many in list display django

Question:

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

I have that code. Unfortunately, the error comes in admin.py with the ManyToManyField

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

The error says:

‘PurchaseOrderAdmin.list_display[0]’, ‘product’ is a ManyToManyField
which is not supported.

However, it compiles when I take 'product' out of list_display. So how can I display 'product' in list_display without giving it errors?

edit: Maybe a better question would be how do you display a ManyToManyField in list_display?

Asked By: Mdjon26

||

Answers:

You may not be able to do it directly. From the documentation of list_display

ManyToManyField fields aren’t supported, because that would entail
executing a separate SQL statement for each row in the table. If you
want to do this nonetheless, give your model a custom method, and add
that method’s name to list_display. (See below for more on custom
methods in list_display.)

You can do something like this:

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()])

OR define a model method, and use that

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()])

and in the admin list_display

list_display = ('get_products', 'vendor')
Answered By: karthikr

This way you can do it, kindly checkout the following snippet:

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)

And in your admin.py module call method as follows:

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

If you want to save extra queries, you can use prefetch_related in the get_queryset method like below:

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()])

According to the Docs, In this way, there would be just one extra query needed to fetch related Product items of all PurchaseOrder instances instead of needing one query per each PurchaseOrder instance.

Answered By: Lucas B