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

时间:2018-10-03 23:50:57

标签: django django-admin django-queryset django-aggregation django-annotate

我有三种型号

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

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 |

2 个答案:

答案 0 :(得分: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"

答案 1 :(得分:0)

您可以添加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 triggersDjango signals。效果很好,但不能百分百确定,因此,即使您使用此解决方案,我也建议您设置每日cronjob以确保所有数据都是一致的。