many-to-many in list display django

PythonDjangoDjango AdminAdminDjango Queryset

Python Problem Overview


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?

Python Solutions


Solution 1 - Python

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

Solution 2 - Python

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

Solution 3 - Python

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMdjon26View Question on Stackoverflow
Solution 1 - PythonkarthikrView Answer on Stackoverflow
Solution 2 - PythonJKVView Answer on Stackoverflow
Solution 3 - PythonLucas BView Answer on Stackoverflow