“List_display” in un ModelAdmin di Django può visualizzare gli attributi dei campi ForeignKey?


296

Ho un Personmodello con una relazione di chiave esterna con Book, che ha un numero di campi, ma sono più preoccupato per author(un CharField standard).

Detto questo, nel mio PersonAdminmodello, vorrei visualizzare book.authorutilizzando list_display:

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

Ho provato tutti i metodi ovvi per farlo, ma nulla sembra funzionare.

Eventuali suggerimenti?

Risposte:


472

Come altra opzione, puoi cercare look come:

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'

Non dovrebbero essere entrambi get_author, dal momento che è quello a cui effettivamente fa riferimento la stringa (e la breve descrizione)? O modificare l'argomento formato stringa in obj.book.reviews?
Carl G,

1
@AnatoliyArkhipov, c'è un modo (basato sulla risposta di Terr ). Ho già aggiornato il codice in questa risposta.
Denilson Sá Maia,

perché non puoi semplicemente avere author = ForeignKey(Author)nel modello del libro, e poi list_display = ('author')?
alias51,

3
Ciò provoca una query per riga visualizzata
nell'amministratore

1
@marcelm è quello che select_relatedserve. la get_queryset()della UserAdmindovranno essere sovrascritti.
interDist,

142

Nonostante tutte le grandi risposte sopra e perché ero nuovo di Django, ero ancora bloccato. Ecco la mia spiegazione da una prospettiva molto nuova.

models.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 (Modo errato) - pensi che funzionerebbe usando 'model__field' come riferimento, ma non funziona

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

admin.site.register(Book, BookAdmin)

admin.py (Modo corretto) : ecco come si fa riferimento a un nome di chiave esterna nel modo 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)

Per ulteriori riferimenti, consultare il collegamento al modello Django qui


3
per il campo dell'ordine non dovrebbe essere = 'author__name'?
Yunti,

2
Funziona perfettamente, tuttavia non sono sicuro del perché. objè BookAdmin?
Steven Church,

Wow. Mi ci è voluta un'ora sul web per trovare questo. Questo dovrebbe essere reso molto più chiaro nella documentazione di Django
Sevenearths,

67

Come il resto, sono andato anche con i callable. Ma hanno un aspetto negativo: per impostazione predefinita, non puoi ordinarli. Fortunatamente, esiste una soluzione per questo:

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'

la firma del metodo dovrebbe esseredef author(self, obj):
fodera il

Quando ho fatto il commento non è stato il caso, ma sembra che dalla versione 1.8 il metodo ottiene l'oggetto passato ad esso. Ho aggiornato la mia risposta.
Arjen,

46

Si noti che l'aggiunta di get_author funzione rallenterebbe la visualizzazione_elenco nell'amministratore, poiché la visualizzazione di ogni persona comporterebbe una query SQL.

Per evitare ciò, è necessario modificare il get_querysetmetodo in PersonAdmin, ad esempio:

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

Prima: 73 query in 36.02ms (67 query duplicate in admin)

Dopo: 6 query in 10,81 ms


3
Questo è davvero importante e dovrebbe sempre essere implementato
xleon,

Questo è davvero importante. In alternativa, se uno dovesse scendere lungo il __str__percorso, basta aggiungere la chiave straniera a list_displayelist_select_related
Scratch'N'Purr

22

Secondo la documentazione, è possibile visualizzare solo la __unicode__rappresentazione di un ForeignKey:

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

Sembra strano che non supporti il 'book__author'formato di stile utilizzato in qualsiasi altra parte dell'API DB.

Si scopre che esiste un biglietto per questa funzione , che è contrassegnato come Non risolto.


11
@Mermoz davvero? Sembra che il ticket rimanga impostato come wontfix. Nemmeno sembra funzionare (Django 1.3)
Dave,

1.11 non esiste ancora. Ho fatto il django per una dozzina di anni e non me lo ricordo mai :(
Aaron McMillin il

12

Ho appena pubblicato uno snippet che rende admin.ModelAdmin che supporta la sintassi '__':

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

Quindi puoi fare:

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

Fondamentalmente, sta facendo la stessa cosa descritta nelle altre risposte, ma si occupa automaticamente di (1) impostazione admin_order_field (2) impostazione short_description e (3) modifica del queryset per evitare un hit del database per ogni riga.


Questa idea mi piace molto, ma non sembra funzionare più con le recenti versioni di django:AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
Vincent van Leeuwen,

10

È possibile mostrare ciò che si desidera nella visualizzazione elenco utilizzando un callable. Sarebbe così:

def book_author (oggetto):
  return object.book.author

class PersonAdmin (admin.ModelAdmin):
  list_display = [book_author,]

Questo è utile per situazioni in cui molti modelli diversi spesso chiamano lo stesso attributo; è supportato in 1.3+?
Kagali-san,

3
Il problema è la quantità di query SQL eseguite alla fine. Per ogni oggetto nell'elenco, verrà eseguita una query. Questo è il motivo per cui 'field__attribute' sarebbe molto utile, perché sicuramente Django lo estenderebbe a una sola query SQL. Strano che non ci sia già supporto.
emyller,

7

Questo è già accettato, ma se ci sono altri manichini là fuori (come me) che non l'hanno capito immediatamente dalla risposta attualmente accettata , ecco un po 'più di dettaglio.

La classe del modello a cui fa riferimento ForeignKeydeve disporre di un __unicode__metodo al suo interno, come qui:

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

    def __unicode__(self):
        return self.name

Ciò ha fatto la differenza per me e dovrebbe applicarsi allo scenario di cui sopra. Funziona su Django 1.0.2.


4
Su Python 3 questo sarebbe def __str__(self):.
Martlark,

5

Se hai molti campi di attributi di relazione da usare list_displaye non vuoi creare una funzione (ed i suoi attributi) per ognuno, una soluzione sporca ma semplice avrebbe la ModelAdminprecedenza sul __getattr__metodo Instace , creando al volo i callable:

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

Come qui

Attributi speciali richiamabili come booleane short_descriptiondevono essere definiti come ModelAdminattributi, ad es. book__author_verbose_name = 'Author name'Ecategory__is_new_boolean = True .

L' admin_order_fieldattributo richiamabile viene definito automaticamente.

Non dimenticare di utilizzare l' attributo list_select_related nel tuo ModelAdminper evitare che Django richieda query adizionali.


1
Ho appena provato questo con un'installazione di Django 2.2 e ha funzionato benissimo per me, mentre altri approcci no, per qualsiasi motivo. Si noti che al giorno d'oggi è necessario importare riduci da functools o altrove ...
Paul Brackin

5

Esiste un pacchetto molto facile da usare disponibile in PyPI che gestisce esattamente questo: django-related-admin . Puoi anche vedere il codice in GitHub .

Usando questo, quello che vuoi ottenere è semplice come:

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

Entrambi i collegamenti contengono i dettagli completi dell'installazione e dell'utilizzo, quindi non li incollerò qui nel caso in cui cambino.

Proprio come una nota a margine, se si sta già utilizzando qualcosa di diverso model.Admin(per esempio, stavo usando SimpleHistoryAdmininvece), si può fare questo: class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).


getter_for_related_field non funziona in 1.9, quindi sembra non essere la scelta migliore per coloro a cui piace personalizzare.
GriMel,

4

se lo provi in ​​linea, non riuscirai a meno che:

nel tuo inline:

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

nel tuo modello (MyModel):

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

    def localname(self):
        return self.localization.name

-1

La risposta di AlexRobbins ha funzionato per me, tranne per il fatto che le prime due righe devono essere nel modello (forse questo è stato assunto?), E dovrebbe fare riferimento a sé:

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

Quindi la parte admin funziona bene.


-5

Preferisco questo:

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

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.