¿Puede "list_display" en un Django ModelAdmin mostrar atributos de campos ForeignKey?

296

Tengo un Personmodelo que tiene una relación de clave externa Bookcon varios campos, pero me preocupa más author(un CharField estándar).

Dicho esto, en mi PersonAdminmodelo, me gustaría mostrar book.authorusando list_display:

class PersonAdmin(admin.ModelAdmin):
    list_display = ['book.author',]

He probado todos los métodos obvios para hacerlo, pero nada parece funcionar.

¿Alguna sugerencia?

Huuuze
fuente

Respuestas:

472

Como otra opción, puede hacer búsquedas como:

class UserAdmin(admin.ModelAdmin):
    list_display = (..., 'get_author')

    def get_author(self, obj):
        return obj.book.author
    get_author.short_description = 'Author'
    get_author.admin_order_field = 'book__author'
imjoevasquez
fuente
¿No deberían ser ambos get_author, ya que eso es a lo que realmente hace referencia la cadena que está devolviendo (y la breve descripción)? ¿O cambiar el argumento de formato de cadena a obj.book.reviews?
Carl G
1
@AnatoliyArkhipov, hay una manera (basada en la respuesta de Terr ). Ya he actualizado el código en esta respuesta.
Denilson Sá Maia
¿por qué no puedes simplemente tener author = ForeignKey(Author)el modelo de libro y luego list_display = ('author')?
alias51
3
Esto provoca una consulta por fila que se muestra en el administrador :(
marcelm
1
@marcelm para eso select_relatedestá. la get_queryset()de la UserAdmintendrá que ser sobrescrita.
interDist
142

A pesar de todas las excelentes respuestas anteriores y debido a que soy nuevo en Django, todavía estaba atrapado. Aquí está mi explicación desde una perspectiva muy novata.

modelos.py

class Author(models.Model):
    name = models.CharField(max_length=255)

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=255)

admin.py (manera incorrecta) : cree que funcionaría usando 'model__field' como referencia, pero no funciona

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'author__name', ]

admin.site.register(Book, BookAdmin)

admin.py (forma correcta) : así es como se hace referencia a un nombre de clave externa de la manera Django

class BookAdmin(admin.ModelAdmin):
    model = Book
    list_display = ['title', 'get_name', ]

    def get_name(self, obj):
        return obj.author.name
    get_name.admin_order_field  = 'author'  #Allows column order sorting
    get_name.short_description = 'Author Name'  #Renames column head

    #Filtering on side - for some reason, this works
    #list_filter = ['title', 'author__name']

admin.site.register(Book, BookAdmin)

Para referencia adicional, vea el enlace del modelo Django aquí

Será
fuente
3
para el campo de orden, ¿no debería ser = 'author__name'?
Yunti
2
Esto funciona perfectamente, sin embargo, no estoy seguro de por qué. objes BookAdmin?
Steven Church
Guau. Me llevó una hora en la web encontrar esto. Esto debería hacerse mucho más claro en la documentación de Django
Sevenearths
67

Como el resto, también fui con callables. Pero tienen un inconveniente: de forma predeterminada, no puede ordenarlos. Afortunadamente, hay una solución para eso:

Django> = 1.8

def author(self, obj):
    return obj.book.author
author.admin_order_field  = 'book__author'

Django <1.8

def author(self):
    return self.book.author
author.admin_order_field  = 'book__author'
Arjen
fuente
la firma del método debe serdef author(self, obj):
sheats
Cuando hice el comentario, no era el caso, pero parece que desde la versión 1.8 el método le pasa el objeto. He actualizado mi respuesta.
Arjen
46

Tenga en cuenta que agregar la get_authorfunción ralentizaría list_display en el administrador, porque mostrar a cada persona haría una consulta SQL.

Para evitar esto, debe modificar el get_querysetmétodo en PersonAdmin, por ejemplo:

def get_queryset(self, request):
    return super(PersonAdmin,self).get_queryset(request).select_related('book')

Antes: 73 consultas en 36.02 ms (67 consultas duplicadas en administrador)

Después: 6 consultas en 10.81 ms

Hambre
fuente
3
Esto es realmente importante y siempre debe implementarse
xleon
Esto es importante de hecho. Alternativamente, si uno fuera por la __str__ruta, simplemente agregue la llave extranjera a list_displayylist_select_related
Scratch'N'Purr
22

De acuerdo con la documentación, solo puede mostrar la __unicode__representación de una ForeignKey:

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-display

Parece extraño que no sea compatible con el 'book__author'formato de estilo que se usa en todas partes en la API DB.

Resulta que hay un boleto para esta función , que está marcado como No soluciona.

Jonny Buchanan
fuente
11
@Mermoz realmente? Parece que el ticket permanece configurado como wontfix. Tampoco parece funcionar (Django 1.3)
Dave
1.11 todavía no existe. He estado haciendo django durante una docena de años y nunca recuerdo este :(
Aaron McMillin
12

Acabo de publicar un fragmento que hace que admin.ModelAdmin sea compatible con la sintaxis '__':

http://djangosnippets.org/snippets/2887/

Entonces puedes hacer:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

Básicamente, esto es lo mismo que se describe en las otras respuestas, pero automáticamente se encarga de (1) establecer admin_order_field (2) establecer short_description y (3) modificar el conjunto de consultas para evitar un acierto en la base de datos para cada fila.

Jack Cushman
fuente
Esta idea me gusta mucho, pero parece que ya no funciona con las recientes versiones de django:AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
Vincent van Leeuwen
10

Puede mostrar lo que quiera en la visualización de la lista utilizando un invocable. Se vería así:

def book_author (objeto):
  volver object.book.author

clase PersonAdmin (admin.ModelAdmin):
  list_display = [libro_autor,]

fuente
Este es bueno para situaciones, donde muchos modelos diferentes a menudo llaman al mismo atributo; ¿Es compatible con 1.3+?
kagali-san
3
El problema sobre esto es la cantidad de consultas SQL realizadas al final. Para cada objeto en la lista, hará una consulta. Esta es la razón por la cual 'field__attribute' sería muy útil, porque ciertamente Django abarcaría eso solo a una consulta SQL. Es extraño que ya no haya soporte para esto.
Emyller
7

Este ya ha sido aceptado, pero si hay otras tonterías (como yo) que no lo obtuvieron inmediatamente de la respuesta actualmente aceptada , aquí hay un poco más de detalle.

La clase de modelo referenciada por las ForeignKeynecesidades debe tener un __unicode__método dentro de ella, como aquí:

class Category(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

Eso marcó la diferencia para mí y debería aplicarse al escenario anterior. Esto funciona en Django 1.0.2.

Comunidad
fuente
44
En python 3 esto sería def __str__(self):.
Martlark
5

Si tiene muchos campos de atributos de relación para usar list_displayy no desea crear una función (y sus atributos) para cada uno, una solución simple pero simple sería anular el método de ModelAdmininstancia __getattr__, creando los invocables sobre la marcha:

class DynamicLookupMixin(object):
    '''
    a mixin to add dynamic callable attributes like 'book__author' which
    return a function that return the instance.book.author value
    '''

    def __getattr__(self, attr):
        if ('__' in attr
            and not attr.startswith('_')
            and not attr.endswith('_boolean')
            and not attr.endswith('_short_description')):

            def dyn_lookup(instance):
                # traverse all __ lookups
                return reduce(lambda parent, child: getattr(parent, child),
                              attr.split('__'),
                              instance)

            # get admin_order_field, boolean and short_description
            dyn_lookup.admin_order_field = attr
            dyn_lookup.boolean = getattr(self, '{}_boolean'.format(attr), False)
            dyn_lookup.short_description = getattr(
                self, '{}_short_description'.format(attr),
                attr.replace('_', ' ').capitalize())

            return dyn_lookup

        # not dynamic lookup, default behaviour
        return self.__getattribute__(attr)


# use examples    

@admin.register(models.Person)
class PersonAdmin(admin.ModelAdmin, DynamicLookupMixin):
    list_display = ['book__author', 'book__publisher__name',
                    'book__publisher__country']

    # custom short description
    book__publisher__country_short_description = 'Publisher Country'


@admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin, DynamicLookupMixin):
    list_display = ('name', 'category__is_new')

    # to show as boolean field
    category__is_new_boolean = True

Como lo esencial aquí

Los atributos especiales invocables como booleany short_descriptiondeben definirse como ModelAdminatributos, por ejemplo, book__author_verbose_name = 'Author name'y category__is_new_boolean = True.

El admin_order_fieldatributo invocable se define automáticamente.

No olvides usar el atributo list_select_related en tu ModelAdminpara hacer que Django evite consultas adicionales.

Cauê Thenório
fuente
1
Solo intenté esto con una instalación de Django 2.2 y funcionó muy bien para mí, mientras que otros enfoques no lo hicieron, por cualquier razón. Tenga en cuenta que hoy en día necesita importar reducciones desde functools o en otro lugar ...
Paul Brackin
5

Hay un paquete muy fácil de usar disponible en PyPI que maneja exactamente eso: django-related-admin . También puedes ver el código en GitHub .

Con esto, lo que quieres lograr es tan simple como:

class PersonAdmin(RelatedFieldAdmin):
    list_display = ['book__author',]

Ambos enlaces contienen detalles completos de instalación y uso, por lo que no los pegaré aquí en caso de que cambien.

Así como una nota al margen, si usted ya está usando algo distinto model.Admin(por ejemplo, yo estaba usando SimpleHistoryAdminen su lugar), se puede hacer esto: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).

Vlad Schnakovszki
fuente
getter_for_related_field no funciona en 1.9, por lo que parece que no es la mejor opción para quienes les gusta personalizar.
GriMel
4

Si lo prueba en línea, no tendrá éxito a menos que:

en su línea:

class AddInline(admin.TabularInline):
    readonly_fields = ['localname',]
    model = MyModel
    fields = ('localname',)

en su modelo (MyModel):

class MyModel(models.Model):
    localization = models.ForeignKey(Localizations)

    def localname(self):
        return self.localization.name
Eyal Ch
fuente
-1

La respuesta de AlexRobbins funcionó para mí, excepto que las dos primeras líneas deben estar en el modelo (¿tal vez se asumió esto?), Y deberían hacer referencia a sí mismo:

def book_author(self):
  return self.book.author

Entonces la parte de administrador funciona bien.


fuente
-5

Prefiero esto:

class CoolAdmin(admin.ModelAdmin):
    list_display = ('pk', 'submodel__field')

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field
wieczorek1990
fuente