简体   繁体   中英

Django admin list filter using a model property

I have a model such as below:

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

And I register my model like below:

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

But since is_external is not a db field, I get the following error:

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

I have tried something like creating a custom filter:

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)

and then update my Admin:

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

But it raises:

Order has no field named 'is_external' which I think makes sense, but is there anyway to do this? I feel like I am messing on something.

I think after hours of trying to fix this, only now I found a working solution. We make use of the custom filter above, but we use it directly such as:

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

This should work now; also please notice that in self.value() of our filter queryset, it is returning a string, so we should cast/ parse our values accordingly, in my filter above, I will be getting the values True or False as string.

Edit: I have updated my custom filter to function like Django's, where we make use of values 1 and 0 for True and 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

Please notice that this is now using same logic defined in the model property (but not the property itself) but on the database level. If there is any way to make use directly of properties, I would love if you could share!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM