Skip to main content
 首页 » 编程设计

python之在django admin中获取list_display显示多对一关系的 'many'结尾

2025年05月04日55yyy_WW

我想使用 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) 

这是它的样子:

感谢您的指导。

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

到目前为止,这是我设法开始工作的内容

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 

这是遵循文档:following relationships backwards .

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

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

 related_name = 'patient_fk' 

这解释了为什么我不使用 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() 

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

有什么想法吗?

谢谢!

编辑

我试过 Brian 的代码:

class ClientAdmin(admin.ModelAdmin): 
    list_display = ('first_name', 'last_name', 'mobile', 'getpatients') 
 
    def getpatients(self, obj): 
        p = obj.patient_pk.all() 
        return list(p) 

...并且出现错误 '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) 

作为引用,这是我的类(class):

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

请您参考如下方法:

这现在有效:

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

这个页面只需要 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) 

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

每个模型都有一个Manager,其默认名称是objects,允许我们访问数据库记录。要从模型中提取所有记录,我们使用 SomeModel.objects.all()其中 - 在幕后 - 只是由 Manager 类的 get_queryset 方法返回的 QuerySet。

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

 def get_queryset(self, obj): 

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

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

super()使我们能够访问父类的方法。

例如:

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

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

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

为了“添加”位于一对多关系末尾的所有 Patients 对象(一个 Client 可以有很多 Patients),我们使用 prefetch_related() :

return qs.prefetch_related('patient_fk')' 

这对每个客户端执行查找,并通过遵循“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 

我希望这对那里的人有所帮助。如果我的解释中有任何错误,请告诉我,我会更正。