如何在相关字段上使用日期过滤器在用户的 django 管理面板中获得统计信息?
How can I have statistics in django admin panel on User with date filter on related field?
相关型号
class AbstractTask(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
issued_at = models.DateTimeField(auto_now_add=True)
问题
我需要在管理面板中每天显示一些 User
统计信息。假设我只需要发布的任务数。而且我需要能够按发行日期过滤它(昨天、前天发行了多少,等等)。
我是怎么做到的
我使用 User
代理模型为不同的统计页面注册 ModelAdmin
。
我在 task__issued_at
字段上使用略微修改(更改的日期范围)DateFieldListFilter
:
list_filter = [
('task__issued_at', DateFieldListFilter),
'username',
]
日期字段的过滤器不起作用
过滤器不起作用,因为它们最终会生成类似于以下内容的查询:
queryset = (User.objects
.annotate(
# Different statistics.
num_tasks=Count('task'),
)
.filter(
# DateFieldListFilter.
task__issued_at__gte='2020-01-01',
task__issued_at__lt='2020-01-02',
)
.values('id', 'num_tasks')
)
SQL:
SELECT "auth_user"."id",
COUNT("task"."id") AS "num_tasks"
FROM "auth_user"
LEFT OUTER JOIN "task" ON ("auth_user"."id" = "task"."user_id")
INNER JOIN "task" T3 ON ("auth_user"."id" = T3."user_id")
WHERE (T3."issued_at" >= 2020-01-01 00:00:00+03:00
AND T3."issued_at" < 2020-01-02 00:00:00+03:00)
GROUP BY "auth_user"."id"
问题是当我只需要一个时,过滤器会在 table“任务”上添加第二个连接。
通过添加 .filter(task__isnull=False)
强制第一个内部联接没有帮助。它只是继续执行两个相同的内部连接。
django 2 和 3 中的行为相同。
可以在Django中完成吗?
最好尽可能简单:没有原始 sql,没有太多魔法,继续使用 DateFieldListFilter。
但任何解决方案都会有所帮助。
下面的替代 QuerySet 给出了相同的结果,没有任何额外的 joins:
(queryset = User.objects
.annotate(
# Different statistics.
num_tasks=Count(
'task',
filter=models.Q(
Q(task__issued_at__gte='2020-01-01') &
Q(task__issued_at__lt='2020-01-02')
)
),
)
.values('id', 'num_tasks')
)
SQL:
SELECT "auth_user"."id", COUNT("task"."id")
FILTER (WHERE ("task"."issued_at" >= 2020-01-01 00:00:00+03:00 AND "task"."issed_at" < 2020-01-02 00:00:00+03:00)) AS "num_tasks"
FROM "auth_user"
LEFT OUTER JOIN "task" ON ("auth_user"."id" = "task"."user_id")
GROUP BY "auth_user"."id"
但不确定与您的性能相比。
无论如何,要使其与 DateFieldListFilter
一起工作,您只需重写 queryset
方法:
class CustomDateFieldListFilter(DateFieldListFilter):
def queryset(self, request, queryset):
# Compare the requested value to decide how to filter the queryset.
q_objects = models.Q()
for key, value in self.used_parameters.items():
q_objects &= models.Q(**{key: value})
return queryset.annotate(num_tasks=Count('task', filter=models.Q(q_objects))).values('id', 'num_tasks')
并指定新的 class:
list_filter = [
('task__issued_at', CustomDateFieldListFilter),
...
]
就是这样。
相关型号
class AbstractTask(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
issued_at = models.DateTimeField(auto_now_add=True)
问题
我需要在管理面板中每天显示一些 User
统计信息。假设我只需要发布的任务数。而且我需要能够按发行日期过滤它(昨天、前天发行了多少,等等)。
我是怎么做到的
我使用 User
代理模型为不同的统计页面注册 ModelAdmin
。
我在 task__issued_at
字段上使用略微修改(更改的日期范围)DateFieldListFilter
:
list_filter = [
('task__issued_at', DateFieldListFilter),
'username',
]
日期字段的过滤器不起作用
过滤器不起作用,因为它们最终会生成类似于以下内容的查询:
queryset = (User.objects
.annotate(
# Different statistics.
num_tasks=Count('task'),
)
.filter(
# DateFieldListFilter.
task__issued_at__gte='2020-01-01',
task__issued_at__lt='2020-01-02',
)
.values('id', 'num_tasks')
)
SQL:
SELECT "auth_user"."id",
COUNT("task"."id") AS "num_tasks"
FROM "auth_user"
LEFT OUTER JOIN "task" ON ("auth_user"."id" = "task"."user_id")
INNER JOIN "task" T3 ON ("auth_user"."id" = T3."user_id")
WHERE (T3."issued_at" >= 2020-01-01 00:00:00+03:00
AND T3."issued_at" < 2020-01-02 00:00:00+03:00)
GROUP BY "auth_user"."id"
问题是当我只需要一个时,过滤器会在 table“任务”上添加第二个连接。
通过添加 .filter(task__isnull=False)
强制第一个内部联接没有帮助。它只是继续执行两个相同的内部连接。
django 2 和 3 中的行为相同。
可以在Django中完成吗? 最好尽可能简单:没有原始 sql,没有太多魔法,继续使用 DateFieldListFilter。 但任何解决方案都会有所帮助。
下面的替代 QuerySet 给出了相同的结果,没有任何额外的 joins:
(queryset = User.objects
.annotate(
# Different statistics.
num_tasks=Count(
'task',
filter=models.Q(
Q(task__issued_at__gte='2020-01-01') &
Q(task__issued_at__lt='2020-01-02')
)
),
)
.values('id', 'num_tasks')
)
SQL:
SELECT "auth_user"."id", COUNT("task"."id")
FILTER (WHERE ("task"."issued_at" >= 2020-01-01 00:00:00+03:00 AND "task"."issed_at" < 2020-01-02 00:00:00+03:00)) AS "num_tasks"
FROM "auth_user"
LEFT OUTER JOIN "task" ON ("auth_user"."id" = "task"."user_id")
GROUP BY "auth_user"."id"
但不确定与您的性能相比。
无论如何,要使其与 DateFieldListFilter
一起工作,您只需重写 queryset
方法:
class CustomDateFieldListFilter(DateFieldListFilter):
def queryset(self, request, queryset):
# Compare the requested value to decide how to filter the queryset.
q_objects = models.Q()
for key, value in self.used_parameters.items():
q_objects &= models.Q(**{key: value})
return queryset.annotate(num_tasks=Count('task', filter=models.Q(q_objects))).values('id', 'num_tasks')
并指定新的 class:
list_filter = [
('task__issued_at', CustomDateFieldListFilter),
...
]
就是这样。