2016-08-08 46 views
1

Ich möchte alle Tierbesitzer (Kunden) mit list_display und für jeden Besitzer eine Komma-separate Liste aller ihrer Haustiere anzeigen (Patienten).Holen Sie sich list_display in django admin, um das "viele" Ende einer Viele-zu-eins-Beziehung anzuzeigen

Der Fremdschlüssel befindet sich in der Patiententabelle, sodass ein Besitzer viele Haustiere haben kann, aber ein Haustier nur einen Besitzer haben kann.

Ich habe die folgenden zu arbeiten, würde aber gerne beraten, ob dies ein akzeptabler Ansatz ist.

from .models import Client, Patient 

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'patients') 

    def patients(self,obj): 
     p = Patient.objects.filter(client_id=obj.pk) 
     return list(p) 

Dies ist, wie es aussieht: enter image description here

Vielen Dank für jede Beratung.

UPDATE: Hier, wo ich so weit bin:

Hier ist, was ich habe es geschafft, so weit

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'getpatients') 
    def getpatients(self, request): 
     c = Client.objects.get(pk=1) 
     p = c.patient_fk.all() 
     return p 

zum Laufen zu bringen Dies folgt die Dokumentation re: following relationships backwards.

Natürlich "repariert" das obige Beispiel die Anzahl der Client-Objekte auf nur einen (pk = 1), also bin ich mir nicht sicher, wie ich die Ergebnisse für alle Clients bekommen würde.

@pleasedontbelong - Ich habe Ihren Code versucht, vielen Dank. Ich mache mit ziemlicher Sicherheit etwas falsch, wie ich bin ein error.but bekommen also Sie die FK wissen hat jetzt

related_name = 'patient_fk' 

was erklärt, warum ich verwende patient_set nicht (da FOO_set außer Kraft gesetzt wird)

Also hier ist, was ich habe:

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'getpatients') 

    def get_queryset(self, request): 
     qs = super(ClientAdmin, self).get_queryset(request) 
     return qs.prefetch_related('patient_fk') 

    def getpatients(self, obj): 
     return self.patient_fk.all() 

der Fehler ich erhalte, ist „‚Clientadmin‘Objekt hat kein Attribut‚patient_fk‘“ und bezieht sich auf die letzte Zeile des Codes oben.

Irgendwelche Ideen?

Danke!

EDIT

I-Code Brian versucht haben:

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'getpatients') 

    def getpatients(self, obj): 
     p = obj.patient_pk.all() 
     return list(p) 

... und Störung erhalte 'Client' object has no attribute 'patient_fk'

Wenn ich meinen ursprünglichen Code ausführen, ist es noch ok funktioniert:

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'getpatients') 

    def getpatients(self, obj): 
     p = Patient.objects.filter(client_id=obj.pk) 
     return list(p) 

Als Referenz sind hier meine Klassen:

class Client(TimeStampedModel): 
    first_name = models.CharField(max_length=30) 
    last_name = models.CharField(max_length=30) 
    .... 

class Patient(TimeStampedModel): 
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='patient_fk') 
    name = models.CharField(max_length=30) 
    .... 

Antwort

1

Dies funktioniert jetzt:

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'get_patients') 

    def get_queryset(self, obj): 
     qs = super(ClientAdmin, self).get_queryset(obj) 
     return qs.prefetch_related('patient_fk') 

    def get_patients(self, obj): 
     return list(obj.patient_fk.all()) 

Diese Seite benötigt nur 6 Abfragen anzuzeigen. ..

enter image description here

... im Vergleich zu meinem ursprünglichen Code (unten), die eine separate Abfrage ausgeführt wurde, die Patienten für jeden Client (100 Clients pro Seite) abrufen

from .models import Client, Patient 

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'patients') 

    def patients(self,obj): 
     p = Patient.objects.filter(client_id=obj.pk) 
     return list(p) 

enter image description here

Hier ist mein Verständnis davon, wie und warum das funktioniert (zögern Sie nicht, irgendwelche Fehler anzuzeigen):

Jedes Modell hat a Manager, dessen Standardname Objekte ist, die uns den Zugriff auf die Datenbankdatensätze ermöglichen. Um alle Datensätze von einem Modell zu ziehen, wir uns SomeModel.objects.all() die - unter der Haube - ist nur die QuerySet von der get_queryset Methode der Manager-Klasse zurückgegeben.

Also, wenn wir zwicken müssen, was von einem Modell - d. H. Das QuerySet - zurückgegeben wird, dann müssen wir die Methode überschreiben, die es ergreift, nämlich get_queryset.Unsere neue Methode hat denselben Namen wie die Methode, die wir außer Kraft setzen mögen:

def get_queryset(self, obj): 

nun das obige Verfahren weiß nichts darüber, wie der Zugang zu den Modi Daten zu erhalten. Es enthält keinen Code. Um auf die Daten zugreifen zu können, müssen wir die "echte" Methode get_queryset (die wir überschreiben) aufrufen, so dass wir tatsächlich Daten zurückholen, sie optimieren (zusätzliche Patienteninformationen hinzufügen) und sie dann zurückgeben können.

Um auf die 'original' get_queryset-Methode zuzugreifen und ein QuerySet-Objekt (mit allen Model-Daten, keine Patienten) zu erhalten, verwenden wir super().

super() gibt uns Zugriff auf eine Methode für eine Elternklasse.

Zum Beispiel:

enter image description here

In unserem Fall können wir die Clientadmin get_queryset() Methode greifen.

def get_queryset(self, obj): 
    qs = super(ClientAdmin, self).get_queryset(obj) 

qs halten alle Daten in das Modell in einem QuerySet Objekt.

To ‚hinzufügen in‘ alle Patienten Objekte, die am Ende der Beziehung One-to-many liegen (ein Client kann viele Patienten haben) verwenden wir prefetch_related():

return qs.prefetch_related('patient_fk')' 

Dies führt einen Nachschlag für jeden Client und gibt alle Patientenobjekte zurück, indem Sie dem Fremdschlüssel "patient_fk" folgen. Dies wird unter der Haube von Python (nicht SQL) durchgeführt, so dass das Endergebnis ein neuer QuerySet ist - generiert durch eine einzige Datenbanksuche - der alle Daten enthält, die wir nicht nur alle Objekte in unserem Hauptmodell auflisten müssen Enthalten Sie verwandte Objekte von anderen Modellen.

Also, was passiert, wenn wir nicht override Manager.get_queryset() Methode tun? Nun, dann bekommen wir nur die Daten, die in dem spezifischen Tabelle (Clients) ist, keine Daten zu Patienten (... und 100 zusätzliche Datenbank treffen):

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'get_patients') 
    #do not override Manager.get_queryset() 
    #def get_queryset(self, obj): 
    # qs = super(ClientAdmin, self).get_queryset(obj) 
    # return qs.prefetch_related('patient_fk') 

def get_patients(self, obj): 
    return list(obj.patient_fk.all()) 
    #forces extra per-client query by following patient_fk 

Ich hoffe, das hilft jemand da draußen. Irgendwelche Fehler in meiner Erklärung lassen mich wissen und ich werde korrigieren.

3

Wenn es funktioniert: +1: !!

paar Noten jedoch: es eine Abfrage für jeden Client ausgeführt werden, so dass, wenn Sie auf dem Server-Betreiber 100 Kunden angezeigt werden, django wird 100 ausführen fragt

Man könnte es vielleicht verbessern, indem die Haupt queryset Veränderung (like this) auf der Admin und prefetch_related('patients')

verwenden, sollten so etwas wie:

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'patients') 

    def get_queryset(self, request): 
     qs = super(ClientAdmin, self).get_queryset(request) 
     return qs.prefetch_related('patients') # do read the doc, maybe 'patients' is not the correct lookup for you 

    def patients(self,obj): 
     return self.patients_set.all() # since you have prefetched the patients I think it wont hit the database, to be tested 

hoffe, das hilft

Hinweis:

Sie alle Patienten an einen Client, um den related object reference Zusammenhang bekommen verwenden, so etwas wie:

# get one client 
client = Client.objects.last() 
# get all the client's patient 
patients = client.patient_set.all() 

der letzten Zeile ähnelt:

patients = Patient.objects.get(client=client) 

schließlich können Sie außer Kraft setzen die patient_set name und machen es schöner, lesen https://docs.djangoproject.com/en/1.9/topics/db/queries/#following-relationships-backward

Ich habe es nicht getestet, es wäre nett t o haben ein Feedback zu sehen, ob das n+1 problem verhindern

+0

Danke, ich bin immer noch ein wenig verwirrt, also ertragen Sie mit mir :) Können Sie beschreiben, wie ich das Queryset und prefetch_related() verwenden würde? Mein Verständnis ist, dass ich vom Client-Objekt eine "umgekehrte" Suche nach einer Eins-zu-Viele-Beziehung durchführen kann, d.h. alle zugehörigen Patientenfelder für jedes der gegebenen Client-Objekte. So wie es aussieht, habe ich Dutzende von Kombinationen ausprobiert und verstehe es immer noch nicht ganz, also würde ein Anstupsen in die richtige Richtung sehr geschätzt werden. –

0
def patients(self,obj): 
     p = obj.patients.all() 
     return list(p) 

dies, dass Sie related_name='patients'

EDIT gesetzt in Ihrem ForeignKey geht davon aus: fixed Fehler EDIT2: geändert reverse_name related_name und fügte hinzu,‘.Alle (‚)

+0

Danke. Ich denke, du meinst 'related_name', was ich tatsächlich gesetzt habe. Ich habe immer noch Schwierigkeiten damit zu verstehen, wie die Client-Klasse in der Lage ist, die verschiedenen Patienten 'name'-Werte zu" sehen ", ohne ein Patientenobjekt instanziieren zu müssen. Ich denke, deshalb muss ich sowohl 'related_name' als auch' prefetch_related' haben, aber für das Leben von mir bekomme ich nicht das Reverse-Lookup. –

+0

ja, ich meinte related_name, reparierte meine Antwort, auch ich habe vergessen, den Aufruf der all() Methode hinzuzufügen, die ich jetzt auch hinzugefügt habe –

+0

Vielen Dank Brian. Ich habe meinen Beitrag mit meinem Ergebnis bearbeitet, wobei immer noch das Attribut 'patient_pk' auf dem Client-Objekt fehlt ... irgendwelche Ideen? –