You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

django-tables2中基于django-auditlog关联对象的自定义列排序问题

解决方案

要实现major_category列的排序,核心是将Python模型方法my_category()的逻辑转换为数据库可执行的ORM表达式,因为annotate只能操作数据库层面的计算,无法直接调用Python方法。以下是具体实现步骤和代码:

关键思路

  1. 转译Python方法为数据库表达式:分析my_category()的业务逻辑,用Django ORM的Case/When、Subquery、Func等工具将其转为数据库能理解的计算规则。
  2. 处理泛型关联:利用LogEntry的content_type和object_id字段,关联到对应的变更对象,通过子查询获取每个对象的category值。
  3. 对齐渲染与排序逻辑:确保排序用的计算字段和render_major_category的输出格式一致(比如首字母大写),避免排序结果和展示内容不匹配。
  4. 处理对象不存在的情况:通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 02:58:36