使用模型 属性 的 Django 管理列表过滤器

Django admin list filter using a model property

我有如下模型:

class Order(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    number = models.CharField(max_length=36, blank=True, null=True)
    external_number = models.CharField(default=None, blank=True, null=True, max_length=250)

    @property
    def is_external(self) -> bool:
        return self.external_number is not None

然后我像下面这样注册我的模型:

@admin.register(Order)
class OrderAdmin(ReadOnlyIDAdminClass):
    list_filter = ["number", "is_external"]
    list_display = ["id", "is_external"]

但由于 is_external 不是数据库字段,我得到以下错误:

(admin.E116) The value of 'list_filter[1]' refers to 'is_external', which does not refer to a Field

我尝试过创建自定义过滤器之类的方法:

class IsExternal(admin.FieldListFilter):

    # Human-readable title which will be displayed in the
    # right admin sidebar just above the filter options.
    title = 'is external'

    # Parameter for the filter that will be used in the URL query.
    parameter_name = 'is_external'

    def lookups(self, request, model_admin):
        return (
            ('True', True), 
            ('False', False)
        )

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(external_number__isnull=False)
        return queryset.filter(external_number__isnull=True)

然后更新我的管理员:

@admin.register(Order)
class OrderAdmin(ReadOnlyIDAdminClass):
    list_filter = ["number", ("is_external", IsExternal)]
    list_display = ["id", "is_external"]

但它引发了:

Order has no field named 'is_external' 我觉得有道理,但是有办法吗?我觉得我在搞砸什么。

我认为在尝试修复此问题数小时后,直到现在我才找到了可行的解决方案。 我们利用上面的自定义过滤器,但是我们直接使用它如:

@admin.register(Order)
class OrderAdmin(ReadOnlyIDAdminClass):
    list_filter = ["number",  IsExternal]
    list_display = ["id", "is_external"]

现在应该可以了;另请注意,在我们的过滤器查询集的 self.value() 中,它返回一个字符串,因此我们应该相应地转换/解析我们的值,在我上面的过滤器中,我将获取值 True or False 作为字符串。

编辑: 我已经更新了我的自定义过滤器,使其像 Django 一样运行,我们使用值 10 来表示 TrueFalse.

class IsExternal(SimpleListFilter):

    # Human-readable title which will be displayed in the
    # right admin sidebar just above the filter options.
    title = 'is external'

    # Parameter for the filter that will be used in the URL query.
    parameter_name = 'is_external'

    def lookups(self, request, model_admin):
        return (
            (1, 'yes'), 
            (0, 'no')
        )

    def queryset(self, request, queryset):
        if self.value() == "1":
            return queryset.filter(external_number__isnull=False)
        elif self.value() == "0":
            return queryset.filter(external_number__isnull=True)
        return queryset

请注意,这现在使用模型 属性 中定义的相同逻辑(但不是 属性 本身),但在数据库级别。如果有什么方法可以直接使用属性,希望能分享!