Kann "list_display" in einem Django ModelAdmin Attribute von ForeignKey-Feldern anzeigen?


296

Ich habe ein PersonModell mit einer Fremdschlüsselbeziehung Book, das eine Reihe von Feldern enthält, aber ich bin am meisten besorgt über author(ein Standard-CharField).

Damit wird gesagt, in meinem PersonAdminModell, würde Ich mag zum Anzeigen book.authorverwenden list_display:

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

Ich habe alle offensichtlichen Methoden dafür ausprobiert, aber nichts scheint zu funktionieren.

Irgendwelche Vorschläge?

Antworten:


472

Als weitere Option können Sie nachschlagen wie:

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'

Sollte nicht beides sein get_author, da die Zeichenfolge, die Sie zurückgeben (und die Kurzbeschreibung), tatsächlich darauf verweist? Oder ändern Sie das Argument für das Zeichenfolgenformat in obj.book.reviews?
Carl G

1
@AnatoliyArkhipov, es gibt einen Weg (basierend auf Terr Antwort ). Ich habe den Code in dieser Antwort bereits aktualisiert.
Denilson Sá Maia

warum kannst du nicht einfach author = ForeignKey(Author)im Buchmodell haben und dann list_display = ('author')?
Alias ​​51

3
Dies führt zu einer Abfrage pro Zeile, die im Administrator angezeigt wird :(
marcelm

1
@marcelm das ist was dafür select_relatedist. das get_queryset()des UserAdminWillens muss überschrieben werden.
InterDist

142

Trotz all der großartigen Antworten oben und weil ich neu bei Django war, steckte ich immer noch fest. Hier ist meine Erklärung aus einer sehr neuen Perspektive.

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 (Falscher Weg) - Sie denken, es würde funktionieren, wenn Sie 'model__field' als Referenz verwenden, aber das tut es nicht

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

admin.site.register(Book, BookAdmin)

admin.py (korrekter Weg) - Auf diese Weise verweisen Sie auf einen Fremdschlüsselnamen auf Django-Weise

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)

Weitere Referenz finden Sie in den Modell Django Link hier


3
sollte das Bestellfeld nicht = 'author__name' sein?
Yunti

2
Das funktioniert perfekt, aber ich bin mir nicht sicher warum. objist BookAdmin?
Steven Church

Beeindruckend. Ich habe eine Stunde im Internet gebraucht, um das zu finden. Dies sollte in der Django-Dokumentation
Sevenearths

67

Wie die anderen ging ich auch mit Callables. Aber sie haben einen Nachteil: Standardmäßig können Sie nicht bei ihnen bestellen. Zum Glück gibt es dafür eine Lösung:

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'

Methodensignatur sollte seindef author(self, obj):
sheats

Als ich den Kommentar abgegeben habe, war dies nicht der Fall, aber es scheint, dass die Methode seit Version 1.8 das Objekt an sie übergeben bekommt. Ich habe meine Antwort aktualisiert.
Arjen

46

Bitte beachten Sie, dass das Hinzufügen der get_authorFunktion die list_display im Administrator verlangsamen würde, da das Anzeigen jeder Person eine SQL-Abfrage durchführen würde.

Um dies zu vermeiden, müssen Sie die get_querysetMethode in PersonAdmin ändern, zum Beispiel:

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

Vorher: 73 Abfragen in 36,02 ms (67 doppelte Abfragen in admin)

Nachher: ​​6 Abfragen in 10,81 ms


3
Dies ist wirklich wichtig und sollte immer implementiert werden
xleon

Das ist in der Tat wichtig. Alternativ, wenn man die __str__Route entlang gehen sollte , fügen Sie einfach den Fremdschlüssel zu list_displayundlist_select_related
Scratch'N'Purr

22

Gemäß der Dokumentation können Sie nur die __unicode__Darstellung eines ForeignKey anzeigen:

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

Scheint seltsam, dass es das 'book__author'Stilformat nicht unterstützt, das überall in der DB-API verwendet wird.

Es stellt sich heraus, dass es für diese Funktion ein Ticket gibt , das als "Nicht reparieren" gekennzeichnet ist.


11
@Mermoz wirklich? Es scheint, dass das Ticket als Wontfix gesetzt bleibt. Es scheint auch nicht zu funktionieren (Django 1.3)
Dave

1.11 existiert noch nicht. Ich mache seit einem Dutzend Jahren Django und ich erinnere mich nie an diesen :(
Aaron McMillin

12

Ich habe gerade ein Snippet gepostet, mit dem admin.ModelAdmin die Syntax '__' unterstützt:

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

So können Sie tun:

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

Dies geschieht im Grunde genommen genauso wie in den anderen Antworten beschrieben, es wird jedoch automatisch (1) das Setzen von admin_order_field (2) das Setzen von short_description und (3) das Ändern des Abfragesatzes durchgeführt, um einen Datenbanktreffer für jede Zeile zu vermeiden.


Ich mag diese Idee sehr, aber sie scheint mit den neuesten Django-Versionen nicht mehr zu funktionieren:AttributeError: type object 'BaseModel' has no attribute '__metaclass__'
Vincent van Leeuwen

10

Sie können in der Listenanzeige alles anzeigen, was Sie möchten, indem Sie einen Callable verwenden. Es würde so aussehen:

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

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

Dieser ist gut für Situationen geeignet, in denen viele verschiedene Modelle häufig dasselbe Attribut aufrufen. wird es in 1.3+ unterstützt?
Kagali-San

3
Das Problem dabei ist die Anzahl der am Ende durchgeführten SQL-Abfragen. Für jedes Objekt in der Liste wird eine Abfrage durchgeführt. Aus diesem Grund wäre 'field__attribute' sehr praktisch, da Django dies sicherlich nur auf eine SQL-Abfrage übertragen würde. Seltsam, dass es dies bereits nicht unterstützt.
Emyller

7

Dies ist bereits akzeptiert, aber wenn es andere Dummies gibt (wie ich), die es nicht sofort aus der derzeit akzeptierten Antwort erhalten haben , hier ein bisschen mehr Details.

Die Modellklasse, auf die verwiesen wird ForeignKey, muss eine __unicode__Methode enthalten, wie hier:

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

    def __unicode__(self):
        return self.name

Das machte den Unterschied für mich und sollte für das obige Szenario gelten. Dies funktioniert unter Django 1.0.2.


4
Auf Python 3 wäre dies def __str__(self):.
Martlark

5

Wenn Sie viele Beziehungsattributfelder verwenden müssen list_displayund nicht für jedes eine Funktion (und deren Attribute) erstellen möchten, würde eine schmutzige, aber einfache Lösung die ModelAdminInstace- __getattr__Methode überschreiben und die Callables im laufenden Betrieb erstellen:

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

Als Kern hier

Aufrufbare spezielle Attribute wie booleanund short_descriptionmüssen als ModelAdminAttribute definiert werden, z . B. book__author_verbose_name = 'Author name'und category__is_new_boolean = True.

Das aufrufbare admin_order_fieldAttribut wird automatisch definiert.

Vergessen Sie nicht, das Attribut list_select_related in Ihrem ModelAdminzu verwenden, damit Django zusätzliche Abfragen vermeidet.


1
Ich habe es gerade mit einer Django 2.2-Installation versucht und es hat bei mir großartig funktioniert, während andere Ansätze dies aus irgendeinem Grund nicht getan haben. Beachten Sie, dass Sie heutzutage Reduzieren von functools oder anderswo importieren müssen ...
Paul Brackin

5

In PyPI ist ein sehr einfach zu verwendendes Paket verfügbar, das genau das erledigt : django-related-admin . Sie können den Code auch in GitHub sehen .

Auf diese Weise möchten Sie Folgendes erreichen:

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

Beide Links enthalten vollständige Details zur Installation und Verwendung, sodass ich sie hier nicht einfügen werde, falls sie sich ändern.

Nur als Randnotiz: Wenn Sie bereits etwas anderes als verwenden model.Admin(z. B. habe ich SimpleHistoryAdminstattdessen verwendet), können Sie Folgendes tun : class MyAdmin(SimpleHistoryAdmin, RelatedFieldAdmin).


getter_for_related_field funktioniert in Version 1.9 nicht, daher scheint es nicht die beste Wahl für diejenigen zu sein, die gerne anpassen.
GriMel

4

Wenn Sie es in Inline versuchen, werden Sie nur erfolgreich sein, wenn:

in Ihrer Inline:

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

in Ihrem Modell (MyModel):

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

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

-1

Die Antwort von AlexRobbins hat bei mir funktioniert, außer dass die ersten beiden Zeilen im Modell enthalten sein müssen (vielleicht wurde dies angenommen?) Und sich auf sich selbst beziehen sollten:

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

Dann funktioniert der Admin-Teil gut.


-5

Ich bevorzuge das:

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

    @staticmethod
    def submodel__field(obj):
        return obj.submodel.field
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.