Search code examples
pythondjangodjango-rest-frameworkdjango-filter

Create alias for filtering ChoiceField in django-filters


I have following filterset for some model:

class MyModel(models.Model):
    STATUS_ACTIVE = 0
    STATUS_DONE = 1
    STATUSES = (
        (STATUS_ACTIVE, 'Active'),
        (STATUS_DONE, 'Done'),
    )

    status = models.IntegerField(choices=STATUSES, default=STATUS_ACTIVE)


class ModelFilter(FilterSet):
    status = ChoiceFilter(choices=MyModel.STATUSES)

    class Meta:
        model = MyModel
        fields = (
            'status',
        )

When I make request to some API, i should use status as number - /app/model?status=0.

How to make alias for it, so that i can use /app/model?status=active instead of number, without changing model?


Solution

  • At th end I found source code inside the library and created this solution:

    class AliasesChoiceField(ChoiceField):
        def valid_value(self, value):
            for _, v in self.choices:
                if value == v or str(value) == str(v):
                    return True
            return False
    
    class AliasesChoiceFilter(ChoiceFilter):
        field_class = AliasesChoiceField
        def filter(self, qs, value):
            for val, alias in self.extra['choices']:
                if value == alias:
                    return super(AliasesChoiceFilter, self).filter(qs, val)
            return super(AliasesChoiceFilter, self).filter(qs, value)
    

    At AliasesChoiceField.valid_value() i just copied the base method with simple changes to validate value correctly.

    AliasesChoiceFilter use field_class for validation, so i just replace it with ChoiceFilter, and change filter() method to right mapping of my choice parameters.

    Finally my model looks like this

    class ModelFilter(FilterSet):
        STATUSES = (
            (MyModel.STATUS_ACTIVE, 'active'),
            (MyModel.STATUS_DONE, 'done')
        )
        status = AliasesChoiceFilter(choices=STATUSES)
    

    And API route /app/model?status=active works correctly, setting right status on filtering.

    But! This solution wouldn't work for Django Admin, or through web api interface, because it will offer real values, and will try to pass 0 instead of active