Reputation: 2255
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
?
Upvotes: 145
Views: 98783
Reputation: 1
For example, there are Category
and Product
models which have many-to-many relationship as shown below. *I use 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)
Then, there are Category
and Product
admins as shown below:
# "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',)
Then, Category
admin has 5 objects as shown below:
And, Product
admin has 6 objects as shown below:
Now, define get_products
and get_categories()
with @admin.display(), then set them to list_display in Category
and Product
admins respectively as shown below. *description
argument in @admin.display()
can rename the columns in Django Admin from GET PRODUCTS
and GET CATEGORIES
to PRODUCTS
and CATEGORIES
respectively:
# "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()]
Then, PRODUCTS
column is displayed in Category
admin as shown below:
Then, CATEGORIES
column is displayed in Product
admin as shown below:
Also, you can display PRODUCTS
and CATEGORIES
columns by defining get_products()
and get_categories()
with @admin.display()
in Category
and Product
models respectively as shown below:
# "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()]
Then, set them to list_display
in Category
and Product
admins respectively as shown below:
# "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',)
Upvotes: 6
Reputation: 2538
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.
Upvotes: 25
Reputation: 99670
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')
Upvotes: 252
Reputation: 331
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')
Upvotes: 32