获取django admin中的list_display以显示多对一关系的“多个”结尾

时间:2016-08-08 11:05:09

标签: python django python-3.x django-admin

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

外键位于患者表中,因此所有者可以拥有许多宠物,但宠物只能拥有一个宠物。

我已经有了以下工作,但是想知道这是否是一种可接受的方法。

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

感谢任何指导。

更新: 这是我到目前为止的地方:

这是我到目前为止工作的目标

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

3 个答案:

答案 0 :(得分:3)

如果有效:+1:!!

然而,几个注意事项:它将为每个客户端执行一个查询,因此如果您在管理员上显示100个客户端,则django将执行100个查询

您可以通过更改管理员的主查询集(like this)并使用prefetch_related('patients')

来改进它

应该是这样的:

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

希望这有帮助

注意:

您可以使用related object reference获取与客户相关的所有患者,例如:

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

最后一行类似于:

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

最后,您可以覆盖patient_set名称并使其更漂亮,请阅读https://docs.djangoproject.com/en/1.9/topics/db/queries/#following-relationships-backward

我还没有对它进行过测试,我很高兴收到反馈,看看是否会阻止n+1 problem

答案 1 :(得分:1)

这现在有效:

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个查询...

enter image description here

...与原始代码(下方)相比较,该代码运行单独的查询以检索每个客户的患者(每页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)

enter image description here

以下是我对其工作原理和原因的理解(随意指出任何错误):

每个模型都有一个管理器,其默认名称为对象,允许我们访问数据库记录。要从模型中提取所有记录,我们SomeModel.objects.all() - 在引擎盖下 - 只是Manager类的 get_queryset 方法返回的 QuerySet 。 / p>

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

 def get_queryset(self, obj):

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

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

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

例如:

enter image description here

在我们的案例中,它可以让我们抓住ClientAdmin的get_queryset()方法。

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

qs在QuerySet对象中保存Model中的所有数据。

要“加入”位于一对多关系结束时的所有患者对象(客户可以有很多患者),我们使用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

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

答案 2 :(得分:0)

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

这假设您在ForeignKey中设置了related_name='patients'

编辑:修正了错误 EDIT2:将reverse_name更改为related_name并添加了'.all()'