¿Cómo mostrar un campo de muchos a muchos con “list_display” en Django Admin?

6 minutos de lectura

Avatar de usuario de Mdjon26
mdjon26

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

yo tengo ese codigo Desafortunadamente, el error viene en admin.py con el ManyToManyField

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

El error dice:

‘OrderAdmin.list_display[0]’, ‘producto’ es un ManyToManyField que no es compatible.

Sin embargo, se compila cuando tomo 'product' fuera de list_display. Entonces, ¿cómo puedo mostrar 'product' en list_display sin dar errores?

editar: Tal vez una mejor pregunta sería ¿cómo se muestra un ManyToManyField en list_display?

avatar de usuario de karthikr
Karthikr

Es posible que no pueda hacerlo directamente. De la documentación de list_display

Los campos ManyToManyField no son compatibles, porque eso implicaría ejecutar una instrucción SQL separada para cada fila de la tabla. No obstante, si desea hacer esto, asigne a su modelo un método personalizado y agregue el nombre de ese método a list_display. (Consulte a continuación para obtener más información sobre los métodos personalizados en list_display).

Puedes hacer algo como esto:

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

O definir un método de modelo y utilizarlo

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

y en el administrador list_display

list_display = ('get_products', 'vendor')

  • Esto parece una muy buena solución. Gracias. Aunque ahora recibo un error que dice “valor nulo en la columna” product_id “viola la restricción no nula” ¿Alguna idea de lo que esto significa?

    – Mdjon26

    07/08/2013 a las 16:34


  • Dado que esto pone de rodillas a la base de datos, ¿cómo lo haría con select_related() o prefetch_related() para mejorar el rendimiento?

    – Artesanos de la nube

    14 mayo 2017 a las 23:39

  • En caso de que la pregunta de optimización siga siendo interesante, acabo de tener el mismo problema y descubrí que simplemente podría implementar un optimizado get_queryset() método para su ModelAdminconsulte stackoverflow.com/questions/12354099/…

    – goetz

    11 de julio de 2017 a las 13:45


  • @SebastiánVansteenkiste Buena idea, quizás cached_property ayudaría. Pero creo que probablemente no lo sería. Cuando utiliza un optimizado get_queryset, podría, por ejemplo, anotar/procesar previamente los datos allí, como hacer la concatenación de productos en SQL en lugar de en Django, y almacenar sus datos personalizados en su conjunto de consultas. Entonces solo necesitaría ejecutar esa lógica una vez en SQL y no para cada fila cuando se accede a la propiedad.

    – goetz

    8 de agosto de 2019 a las 16:55

  • Buen punto. Debería considerar hacer mi propio optimizado get_querysetsimplemente no pude leer los documentos completos (ni encontré un ejemplo lo suficientemente simple de lo que debería estar haciendo)

    – Sebastián Vansteenkiste

    8 de agosto de 2019 a las 17:07

De esta manera puede hacerlo, por favor revise el siguiente fragmento:

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)

Y en su método de llamada de módulo admin.py de la siguiente manera:

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

Avatar de usuario de Lucas B
lucas b

Si desea ahorrar consultas adicionales, puede utilizar prefetch_related en el get_queryset método como a continuación:

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

De acuerdo con la DocumentosDe esta manera, solo se necesitaría una consulta adicional para obtener Product artículos de todos PurchaseOrder instancias en lugar de necesitar una consulta por cada PurchaseOrder instancia.

Super Kai - Avatar de usuario de Kazuya Ito
Súper Kai – Kazuya Ito

por ejemplo, hay Category y Product modelos que tienen relación de muchos a muchos Como se muestra abajo. *Yo suelo 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)

Entonces, hay Category y Product administradores como se muestra a continuación:

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

Entonces, Category admin tiene 5 objetos como se muestra a continuación:

ingrese la descripción de la imagen aquí

Y, Product admin tiene 6 objetos como se muestra a continuación:

ingrese la descripción de la imagen aquí

Ahora, define get_products y get_categories() con @admin.display()luego ajústelos a mostrar_lista en Category y Product administradores respectivamente como se muestra a continuación. *description argumento en @admin.display() puede cambiar el nombre de las columnas en Django Admin desde GET PRODUCTS y GET CATEGORIES a PRODUCTS y CATEGORIES respectivamente:

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

Entonces, PRODUCTS la columna se muestra en Category administrador como se muestra a continuación:

ingrese la descripción de la imagen aquí

Entonces, CATEGORIES la columna se muestra en Product administrador como se muestra a continuación:

ingrese la descripción de la imagen aquí

Además, puede mostrar PRODUCTS y CATEGORIES columnas definiendo get_products() y get_categories() con @admin.display() en Category y Product modelos respectivamente como se muestra a continuación:

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

Luego, ajústelos a list_display en Category y Product administradores respectivamente como se muestra a continuación:

# "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',)

¿Ha sido útil esta solución?