Фильтр списка администраторов Django с использованием свойства модели

У меня есть модель, как показано ниже:

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() нашего фильтра queryset возвращает строку, поэтому мы должны привести/разобрать наши значения соответствующим образом, в моем фильтре выше, я получу значения True or False как строку.

Редактирование: Я обновил свой пользовательский фильтр, чтобы он работал как фильтр Django, где мы используем значения 1 и 0 для True и False.

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

Обратите внимание, что сейчас используется та же логика, определенная в свойстве модели (но не само свойство), но на уровне базы данных. Если есть способ использовать свойства напрямую, я буду рад, если вы поделитесь!

Вернуться на верх