many-to-many in list display django

72,079

Solution 1

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

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

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.

Share:
72,079

Related videos on Youtube

Mdjon26
Author by

Mdjon26

Updated on July 08, 2022

Comments

  • Mdjon26
    Mdjon26 almost 2 years
    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?

  • Mdjon26
    Mdjon26 over 10 years
    This looks like a really good solution. Thank you. Although, I'm now getting an error saying "null value in column "product_id" violates not-null constraint " Any idea what this means?
  • karthikr
    karthikr over 10 years
    That could be a data issue. Check the values in the database
  • Cloud Artisans
    Cloud Artisans almost 7 years
    Since this brings down the database to its knees, how would you do it with select_related() or prefetch_related() to improve performance?
  • goetz
    goetz almost 7 years
    Just in case the optimisation question is still interesting, I just had the same problem and found out that you could simply implement an optimised get_queryset() method for your ModelAdmin, see stackoverflow.com/questions/12354099/…
  • Sebastián Vansteenkiste
    Sebastián Vansteenkiste over 4 years
    That's an interesting link @goetz but I'm not sure we can all follow on how that relates to improving performance
  • Sebastián Vansteenkiste
    Sebastián Vansteenkiste over 4 years
    Actually, What about setting the get_products as a property and then using @cached_property?
  • goetz
    goetz over 4 years
    @SebastiánVansteenkiste Good idea, maybe cached_property would help. But I think it would probably not. When you use an optimised get_queryset, you could for example annotate/pre-process the data there, like doing the concatenation of products in SQL instead of in Django, and store your custom data in your queryset. Then you would only need to execute that logic once in SQL and not for every row when the property is accessed.
  • Sebastián Vansteenkiste
    Sebastián Vansteenkiste over 4 years
    Good point. I should look into doing my own optimized get_queryset, I just haven't got around to reading the full docs (nor found a simple enough example of what I should be doing)