Django admin自定义字段按条件获取最新相关对象的结果
Django admin custom field fetching result of latest related object by condition
我有三个模型
class Customer(models.Model):
created = models.DateTimeField(auto_now_add=True)
# some other fields, which don't matter
class ActivityStatus(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=5)
class Activities(models.Model):
created = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(ActivityStatus)
planned_execution_date = models.DateField()
我在管理员中也有两个
class CustomerAdmin(admin.ModelAdmin):
fields = () # doesn't matter really
list_display() # doesn't matter really
class ActivityAdmin(admin.ModelAdmin):
fields = () # doesn't matter really
list_display() # doesn't matter really
我想添加要在 CustomerAdmin 的 changelist_view 上显示和排序的自定义字段 (last_activity_planned_date)。这是条件:
- 显示客户最新 activity 的计划执行日期,但前提是此 activity 有 status_id = 2。否则什么都不显示
示例 1
客户 1 有 2 个活动 - 最近的有 status_id = 2 和 planned_execution_date = 2017-09-17
changelist_view:
|id|last_activity_planned_date|
|1|2017-09-17|
示例 2
客户 1 有 2 个活动 - 最近的有 status_id = 3 和 planned_execution_date = 2017-09-27
|id|last_activity_planned_date|
|1|null|
您可以添加custom fields in list_display
:
class CustomerAdmin(admin.ModelAdmin):
fields = ()
list_display = ('last_activity_planned_date',)
def last_activity_planned_date(self, obj):
latest_activity = obj.activities.order_by('-planned_execution_date').first()
if latest_activity and latest_activity.status_id == 2:
return latest_activity.planned_execution_date
else:
return None
但是,要使此列可排序会非常复杂。即使在原始 SQL 中,它看起来也是一个复杂的查询。在 Django 2.1 中添加了对 admin_order_field
查询表达式的支持,但我仍然无法找到一个合适的 SQL 查询来实现这种排序。
一个解决方案是在字段 Customer.last_activity_planned_date
中复制此信息(使用 db_index=True
以提高排序性能)。
如果您负担得起仅定期更新此信息,您可以设置一个 cronjob 以每 15 分钟、1 小时或 1 天更新一次(根据您的需要和进行更新所需的资源)。
如果您需要此信息始终准确无误,则必须在每次创建、更新或删除 Activities
条目时更新它。根据您的需要,有多种方法可以做到这一点,包括 SQL triggers and Django signals。这工作得很好,但不是百分百确定,所以即使你使用这个解决方案,我还是建议设置一个每日 cronjob 以确保所有数据都是一致的。
使用 我设法找到了解决方案
from django.db.models.expressions import Subquery, OuterRef
ACTIVITY_STATUS_NEW = 2
def get_queryset(self, request):
queryset = super().get_queryset(request)
activities_per_customer = Activities.objects.filter(customer_id=OuterRef('pk'))
latest_activity_per_customer = (activities_per_customer.order_by('pk', '-created')).distinct('pk')
latest_active_activity_per_customer =(latest_activity_per_customer.filter(status_id=ACTIVITY_STATUS_NEW))
queryset = queryset.annotate(_last_activity_planned_date=Subquery(latest_active_activity_per_customer.values('planned_execution_date')[:1]),)
return queryset
然后
list_display = ('id', 'last_activity_planned_date')
def last_activity_planned_date(self, obj):
return obj._last_activity_planned_date
last_activity_planned_date.short_description = "Optional description"
我有三个模型
class Customer(models.Model):
created = models.DateTimeField(auto_now_add=True)
# some other fields, which don't matter
class ActivityStatus(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=5)
class Activities(models.Model):
created = models.DateTimeField(auto_now_add=True)
status = models.ForeignKey(ActivityStatus)
planned_execution_date = models.DateField()
我在管理员中也有两个
class CustomerAdmin(admin.ModelAdmin):
fields = () # doesn't matter really
list_display() # doesn't matter really
class ActivityAdmin(admin.ModelAdmin):
fields = () # doesn't matter really
list_display() # doesn't matter really
我想添加要在 CustomerAdmin 的 changelist_view 上显示和排序的自定义字段 (last_activity_planned_date)。这是条件: - 显示客户最新 activity 的计划执行日期,但前提是此 activity 有 status_id = 2。否则什么都不显示
示例 1
客户 1 有 2 个活动 - 最近的有 status_id = 2 和 planned_execution_date = 2017-09-17
changelist_view:
|id|last_activity_planned_date|
|1|2017-09-17|
示例 2
客户 1 有 2 个活动 - 最近的有 status_id = 3 和 planned_execution_date = 2017-09-27
|id|last_activity_planned_date|
|1|null|
您可以添加custom fields in list_display
:
class CustomerAdmin(admin.ModelAdmin):
fields = ()
list_display = ('last_activity_planned_date',)
def last_activity_planned_date(self, obj):
latest_activity = obj.activities.order_by('-planned_execution_date').first()
if latest_activity and latest_activity.status_id == 2:
return latest_activity.planned_execution_date
else:
return None
但是,要使此列可排序会非常复杂。即使在原始 SQL 中,它看起来也是一个复杂的查询。在 Django 2.1 中添加了对 admin_order_field
查询表达式的支持,但我仍然无法找到一个合适的 SQL 查询来实现这种排序。
一个解决方案是在字段 Customer.last_activity_planned_date
中复制此信息(使用 db_index=True
以提高排序性能)。
如果您负担得起仅定期更新此信息,您可以设置一个 cronjob 以每 15 分钟、1 小时或 1 天更新一次(根据您的需要和进行更新所需的资源)。
如果您需要此信息始终准确无误,则必须在每次创建、更新或删除 Activities
条目时更新它。根据您的需要,有多种方法可以做到这一点,包括 SQL triggers and Django signals。这工作得很好,但不是百分百确定,所以即使你使用这个解决方案,我还是建议设置一个每日 cronjob 以确保所有数据都是一致的。
使用
from django.db.models.expressions import Subquery, OuterRef
ACTIVITY_STATUS_NEW = 2
def get_queryset(self, request):
queryset = super().get_queryset(request)
activities_per_customer = Activities.objects.filter(customer_id=OuterRef('pk'))
latest_activity_per_customer = (activities_per_customer.order_by('pk', '-created')).distinct('pk')
latest_active_activity_per_customer =(latest_activity_per_customer.filter(status_id=ACTIVITY_STATUS_NEW))
queryset = queryset.annotate(_last_activity_planned_date=Subquery(latest_active_activity_per_customer.values('planned_execution_date')[:1]),)
return queryset
然后
list_display = ('id', 'last_activity_planned_date')
def last_activity_planned_date(self, obj):
return obj._last_activity_planned_date
last_activity_planned_date.short_description = "Optional description"