Django admin自定义字段按条件获取最新相关对象的结果

Posted

技术标签:

【中文标题】Django admin自定义字段按条件获取最新相关对象的结果【英文标题】:Django admin custom field fetching result of latest related object by condition 【发布时间】:2019-03-09 06:41:09 【问题描述】:

我有三个模型

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

我想添加自定义字段 (last_activity_planned_date) 以在 CustomerAdmin 的 changelist_view 上显示和排序。这是条件: - 显示客户最新活动的计划执行日期,但前提是此活动的 status_id = 2。否则不显示

示例 1

客户 1 有 2 个活动 - 最新的 status_id = 2 和planned_execution_date = 2017-09-17

更改列表视图:

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

【问题讨论】:

【参考方案1】:

使用this anwer我设法找到了解决方案

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"

【讨论】:

【参考方案2】:

你可以加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 以每 1500 万、1 小时或 1 天更新一次(根据您的需要和进行更新所需的资源)。

如果您希望此信息始终准确无误,您必须在每次创建、更新或删除Activities 条目时更新它。根据您的需要,有多种方法可以做到这一点,包括SQL triggers 和Django signals。这工作得很好,但不是百分百确定,所以即使你使用这个解决方案,我建议你设置一个每日 cronjob 以确保所有数据都是一致的。

【讨论】:

当然,我知道自定义字段,这样并不复杂。我的主要目标是让它可订购,这似乎是不可能的( 我认为你必须先弄清楚ORDER BY子句的SQL查询,然后尝试将其翻译成Django Query Expression。但是,即使您设法找到 SQL 查询,它也可能会在性能方面付出非常昂贵的代价。我通常不喜欢冗余。但在这种情况下,我认为将这些信息复制到新字段Customer.last_activity_planned_date (可能与db_index=True 一起用于表演)是相关的。您可以在创建、修改或删除 Activities 条目时更新此字段,也可以使用 cron 作业定期更新它。甚至两者兼而有之。

以上是关于Django admin自定义字段按条件获取最新相关对象的结果的主要内容,如果未能解决你的问题,请参考以下文章

基于列表显示中的自定义可调用在 Django Admin 中排序

根据值在 Admin 中自定义 Django 表单字段

Django Admin:仅对一个模型字段使用自定义小部件

Django Admin:向模板页面添加自定义字段

如何更改 Django Admin 自定义列表字段标签

django admin changelist_view 中的自定义 html 字段