James Agnew James Agnew - 3 months ago 26
Python Question

Get list_display in django admin to display the 'many' end of a many-to-one relationship

I would like to display all pet owners (Clients) using list_display and for each owner a comma-separate list of all of their pets (Patients).

The foreign key is in the Patient table, such that an owner can have many pets, but a pet can only have one owner.

I've got the following to work but would like some advise as to whether this is an acceptable approach.

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)


This is what it looks like:
enter image description here

Thanks for any guidance.

UPDATE:
Here's where I'm at so far:

Here's what I've managed to get working so far

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


This is following the docs re: following relationships backwards.

Of course, the above example 'fixes' the number of client objects to just one (pk=1) so I'm not sure how I'd get the results for all of the Clients.

@pleasedontbelong - I've tried your code, thank you very much. I'm almost certainly doing something wrong as I'm getting an error.but So you know the FK now has

related_name = 'patient_fk'


which explains why I'm not using patient_set (since FOO_set is overriden)

So here's what I have:

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


The error I get is "'ClientAdmin' object has no attribute 'patient_fk'" and relates to the last line of the code above.

Any ideas?

Thanks!

EDIT

I've tried Brian's code:

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

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


...and am getting error
'Client' object has no attribute 'patient_fk'


If I run my original code, it still works ok:

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)


For reference, here are my classes:

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

Answer

if it works :+1: !!

few notes however: it will execute one query for each Client, so if you display 100 clients on the admin, django will execute 100 queries

You could maybe improve it by changing the main queryset (like this) on the admin and using prefetch_related('patients')

should be something like:

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

Hope this helps

Note:

you can get all the Patients related to a Client using the related object reference, something like:

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

the last line is similar to:

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

finally you can override the patient_set name and make it prettier, read https://docs.djangoproject.com/en/1.9/topics/db/queries/#following-relationships-backward

I haven't tested it, It would be nice to have a feedback to see if this will prevent the n+1 problem

Comments