在 django admin 中获取 list_display 以显示多对一关系的“多”端

Jam*_*new 3 python django django-admin python-3.x

我想使用 list_display 显示所有宠物主人(客户),并为每个主人显示他们所有宠物(患者)的逗号分隔列表。

外键在Patient表中,这样一个主人可以有很多宠物,但一个宠物只能有一个主人。

我有以下工作,但想要一些关于这是否是可接受的方法的建议。

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)
Run Code Online (Sandbox Code Playgroud)

这是它的样子: 在此处输入图片说明

感谢您的任何指导。

更新:这是我目前所处的位置:

这是我迄今为止设法开始工作的内容

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
Run Code Online (Sandbox Code Playgroud)

这是遵循文档 re: following关系向后

当然,上面的示例将客户端对象的数量“固定”为一个 (pk=1),因此我不确定如何获得所有客户端的结果。

@pleasedontbelong - 我试过你的代码,非常感谢。我几乎肯定做错了什么,因为我收到了一个错误。但是你知道 FK 现在有

 related_name = 'patient_fk'
Run Code Online (Sandbox Code Playgroud)

这解释了为什么我不使用patient_set(因为FOO_set被覆盖)

所以这就是我所拥有的:

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()
Run Code Online (Sandbox Code Playgroud)

我得到的错误是“'ClientAdmin' 对象没有属性'patient_fk'”并且与上面代码的最后一行有关。

有任何想法吗?

谢谢!

编辑

我试过布赖恩的代码:

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

    def getpatients(self, obj):
        p = obj.patient_pk.all()
        return list(p)
Run Code Online (Sandbox Code Playgroud)

...并且出现错误 'Client' object has no attribute 'patient_fk'

如果我运行我的原始代码,它仍然可以正常工作:

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)
Run Code Online (Sandbox Code Playgroud)

作为参考,这是我的课程:

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)
    ....
Run Code Online (Sandbox Code Playgroud)

Jam*_*new 8

这现在有效:

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())
Run Code Online (Sandbox Code Playgroud)

此页面只需要 6 个查询即可显示...

在此处输入图片说明

...与我的原始代码(如下)相比,它运行单独的查询来检索每个客户端的患者(每页 100 个客户端)

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)
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

这是我对它的工作原理和原因的理解(请随时指出任何错误):

每种模式都有一个经理,其默认名称对象允许我们访问数据库的记录。为了从模型中提取所有记录,我们在幕后SomeModel.objects.all()- 只是get_queryset返回的QuerySetManager 类方法。

因此,如果我们需要调整从模型返回的内容 - 即 QuerySet - 那么我们需要覆盖获取它的方法,即get_queryset。我们的新方法与我们要覆盖的方法同名:

 def get_queryset(self, obj):
Run Code Online (Sandbox Code Playgroud)

现在,上述方法对如何访问模式数据一无所知。它不包含任何代码。为了访问数据,我们需要调用“真正的” get_queryset 方法(我们正在覆盖的方法),以便我们可以实际获取数据,调整它(添加一些额外的患者信息),然后返回它。

要访问“原始” get_queryset 方法并获取 QuerySet 对象(包含所有模型数据,没有患者),然后我们使用super().

super() 使我们可以访问父类上的方法。

例如:

在此处输入图片说明

在我们的例子中,它让我们获取 ClientAdmin 的get_queryset()方法。

def get_queryset(self, obj):
    qs = super(ClientAdmin, self).get_queryset(obj)
Run Code Online (Sandbox Code Playgroud)

qs 将模型中的所有数据保存在一个 QuerySet 对象中。

要“添加”位于一对多关系末尾的所有患者对象(一个客户端可以有多个患者),我们使用prefetch_related()

return qs.prefetch_related('patient_fk')'
Run Code Online (Sandbox Code Playgroud)

这会为每个 Client 执行查找并通过遵循“patient_fk”外键返回任何 Patient 对象。这是由 Python(而不是 SQL)在幕后执行的,因此最终结果是一个新的 QuerySet - 由单个数据库查找生成 - 包含我们不仅需要列出主模型中的所有对象而且还需要的所有数据包括来自其他模型的相关对象。

那么,如果我们覆盖Manager.get_queryset()方法会发生什么?好吧,然后我们只获取特定表(客户)中的数据,没有关于患者的信息(......以及 100 个额外的数据库命中):

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
Run Code Online (Sandbox Code Playgroud)

我希望这可以帮助那里的人。我的解释中的任何错误让我知道,我会更正。