django - 从 Django Admin 中的相关模型获取最新付款
问题描述
我想在 Django admin 中使用“前提”模型输出一个表。此外,我想在此表的附加列中包含输出,例如“最后一次公用事业付款”。它实际上是相关表中的一列。数据库中可能没有付款,因此管理员应该能够看到一个空单元格或付款日期。
我能够编写一个数据库查询来显示我需要的信息。其重要和工作部分如下:
SELECT jp.id,
jp.number apartment,
jp.building_number building,
jp.rent,
jp.arrears,
jpm.last_payment
FROM jasmin_premises jp
LEFT JOIN (
SELECT pm.premises_id,
max(pm.paid) last_payment
FROM jasmin_payment pm
GROUP BY pm.premises_id
) jpm ON jp.id = jpm.premises_id;
并且输出类似于以下内容:
id | apartment | building | rent | arrears | last_payment
--------------------------------------------------------------
170 | 1X | 6-A | 297.43 | 2.57, | NULL
72 | 2 | 4 | 289.66 | -678.38 | 2021-01-31
173 | 3Z | 7 | 432.86 | 515.72 | 2021-02-04
73 | 4 | 8-B | 292.25 | 515.44 | 2021-02-04
74 | 5 | 8-B | 112.42 | 3249.34 | NULL
75 | 6A | 122 | 328.48 | 386.23 | 2021-02-04
76 | 7 | 42 | 482.06 | 964.12 | 2021-01-31
77 | 8 | 1 | 433.71 | 867.42 | 2021-01-31
78 | 9C | 12 | 322.79 | 322.79 | 2021-02-04
79 | 10 | 122 | 324.22 | 0 | 2021-02-04
80 | 12 | 12 | 322.79 | 1232.46 | NULL
81 | 14 | 5-Z | 440.82 | 978.44 | 2021-02-04
我正在使用以下模型(仅是重要部分):
class Premises(models.Model):
number = models.CharField(
blank=False,
null=False,
max_length=10)
building_number = models.CharField(
blank=False,
null=False,
max_length=3)
rent = models.DecimalField(
blank=False,
null=False,
max_digits=12,
decimal_places=2,
default=0.0)
area = models.DecimalField(
blank=False,
null=False,
max_digits=5,
decimal_places=2)
class Payment(models.Model):
paid = models.DateField(
blank=False,
null=False)
premises = models.ForeignKey(
Premises,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name='payments',
db_index=True)
有没有办法覆盖admin.ModelAdmin.get_queryset
(例如使用注释)来获得一个额外的列,就像我上面的例子一样?有没有其他方法可以使用 Django ORM 对复合数据库查询进行 LEFT JOIN?
解决方案
解决方案是使用Subquery表达式向QuerySet添加显式子查询。我们还需要使用OuterRef,因为子查询中的查询集需要引用外部查询中的字段。
所以让我们创建一个子查询:
from django.db.models import OuterRef
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
下一步是将子查询传递给payments
查询集:
from django.db.models import Subquery
# 'payments' here is from example above
premises = Premises.objects.annotate(
last_payment=Subquery(payments.values('paid')[:1])
)
最后,让我们看看使用 SQL 来查询数据库中的对象行:
print(premises.query)
(输出已格式化,仅显示重要部分)
SELECT "jasmin_premises"."id",
"jasmin_premises"."number",
"jasmin_premises"."building_number",
"jasmin_premises"."arrears",
"jasmin_premises"."rent",
(SELECT U0."paid"
FROM "jasmin_payment" U0
WHERE U0."premises_id" = "jasmin_premises"."id"
ORDER BY U0."paid" DESC
LIMIT 1) AS "last_payment"
FROM "jasmin_premises";
现在,在执行测试之后,我们可以在 ModelAdmin 中使用它:
from django.contrib import admin
from django.db.models import OuterRef, Subquery
from .models import Payment, Premises
@admin.register(Premises)
class PremisesAdmin(admin.ModelAdmin):
list_display = (
'number',
'building_number',
'rent',
'arrears',
'last_payment',
)
def get_queryset(self, request):
qs = super().get_queryset(request)
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
qs = qs.annotate(
last_payment=Subquery(payments.values('paid')[:1]),
)
return qs
def last_payment(self, obj):
return obj.last_payment
last_payment.short_description = 'Last payment'
last_payment.admin_order_field = 'last_payment'
好吧,这不使用 JOIN,但这种方法将强制 Django 执行子查询。
可能在某些情况下,可以编写一个等效的查询集来更清晰或更有效地执行相同的任务,但是,这是我迄今为止取得的最好成绩。
推荐阅读
- javascript - 选中复选框时如何打开div?
- r - 描述错误(iris$Sepal.Length):找不到函数“描述”
- r - 从 csv 中读取选定的列以使用 data.table 编辑和写回相同的 csv?
- c++ - C++:array<> 初始化器太多
- python - 编写此代码的另一种方法(接受非空字符串并大写)
- javascript - 为键添加 JSON 中的所有元素
- scala - 在 Spark 中基于窗口和条件创建新列
- php - 通过 .htaccess 将所有 php 请求重定向到 index.php
- python - 如此接近,但我无法弄清楚出了什么问题
- c# - 在将它们全部显示在 GridView 中之前操作几个 SqlDataSource 查询结果之一