django-tables2中基于django-auditlog关联对象的自定义列排序问题
解决方案
要实现major_category列的排序,核心是将Python模型方法my_category()的逻辑转换为数据库可执行的ORM表达式,因为annotate只能操作数据库层面的计算,无法直接调用Python方法。以下是具体实现步骤和代码:
关键思路
- 转译Python方法为数据库表达式:分析
my_category()的业务逻辑,用Django ORM的Case/When、Subquery、Func等工具将其转为数据库能理解的计算规则。 - 处理泛型关联:利用
LogEntry的content_type和object_id字段,关联到对应的变更对象,通过子查询获取每个对象的category值。 - 对齐渲染与排序逻辑:确保排序用的计算字段和
render_major_category的输出格式一致(比如首字母大写),避免排序结果和展示内容不匹配。 - 处理对象不存在的情况:通过
Case的default参数设置默认值为"No data",和渲染逻辑的异常处理对齐。
完整代码示例
假设你的变更对象包含Product模型,其my_category()方法返回关联的category.name;另外还有Order模型,my_category()根据status返回不同值。以下是自定义列的完整实现:
from django.db.models import Case, When, Value, CharField, Subquery, OuterRef, Func from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django_tables2 import Column # 根据你的数据库选择对应的首字母大写函数 # PostgreSQL版本 class InitCap(Func): function = 'INITCAP' # MySQL版本(替换上面的InitCap) # class InitCap(Func): # function = 'CONCAT' # template = "%(function)s(UPPER(SUBSTRING(%(expressions)s, 1, 1)), LOWER(SUBSTRING(%(expressions)s, 2)))" class MajorCategoryColumn(Column): @staticmethod def render_major_category(record): try: changed_object = MajorCategoryColumn.get_changed_object(record) answer = changed_object.my_category().capitalize() except ObjectDoesNotExist: answer = "No data" return answer @staticmethod def get_changed_object(record): changed_object_type = record.content_type try: changed_object = changed_object_type.get_object_for_this_type(id=record.object_id) except ObjectDoesNotExist: raise ObjectDoesNotExist(str(changed_object_type)) return changed_object def order(self, queryset, is_descending): # 获取各模型的ContentType product_ct = ContentType.objects.get_for_model(Product) order_ct = ContentType.objects.get_for_model(Order) # 1. 处理Product模型的category逻辑 product_category_subquery = Product.objects.filter( id=OuterRef('object_id'), content_type=product_ct ).values('category__name')[:1] # 2. 处理Order模型的category逻辑(对应my_category()的判断) order_category_subquery = Order.objects.filter( id=OuterRef('object_id'), content_type=order_ct ).annotate( order_category=Case( When(status='completed', then=Value('order')), default=Value('pending'), output_field=CharField() ) ).values('order_category')[:1] # 3. 整合所有模型的计算结果,生成排序用字段 queryset = queryset.annotate( major_category_raw=Case( When(content_type=product_ct, then=Subquery(product_category_subquery)), When(content_type=order_ct, then=Subquery(order_category_subquery)), default=Value('No data'), output_field=CharField() ) ).annotate( # 应用首字母大写,和渲染逻辑保持一致 major_category_sort=InitCap(F('major_category_raw')) ).order_by(('-' if is_descending else '') + 'major_category_sort') return (queryset, True)
注意事项
- 如果
my_category()包含复杂的Python逻辑(比如调用外部API、处理非数据库字段),这种方案无法直接适用,此时可能需要预计算并存储category值到LogEntry的扩展字段,但你明确不想创建扩展类,所以优先保证my_category()逻辑能转译为数据库表达式。 - 新增变更对象模型时,只需在
order方法中添加对应的When条件和子查询即可。 - 测试时需验证对象已被删除的场景,确保排序时默认值"No data"能正确参与排序。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

