Search code examples
pythondjangodjango-filterdjango-filters

Django-filter BaseInFilter


I am currently trying to use BaseInFilter. Users can input the ids separated by commas like the one shown in the picture. I want to make a validator(or regulate user's input) so that the user can only input integers here.

enter image description here

If the user enters characters, the Django backend will throw out this error. error

I have also tried this:

class BaseIntegerFilter(filters.BaseInFilter, filters.NumberFilter):
    pass

class HardwareFilter(filters.FilterSet):
    queryset = Hardware
    serializer_class = HardwareSerializer
    id = filters.BaseIntegerFilter(field_name="id", label="Comma separated list of hardware IDs", help_text="Comma separated list of hardware IDs")

But this solution doesn't allow me to input commas in the textbox anymore.

Could anyone help? Any help will be really appreatiated!


Solution

  • Here is some guidance with regards to usage.

    Using filterset_class with BaseInFilter

    views.py:

    from django_filters.rest_framework import DjangoFilterBackend
    from rest_framework import viewsets
    
    from my_app.filters import MyFilter
    from my_app.models import MyModel
    from my_app.serializers import MySerializer
    
    
    class MyViewSet(viewsets.ModelViewSet):
        queryset = MyModel.objects.all()
        serializer_class = MySerializer
        filter_backends = [DjangoFilterBackend]
        filterset_class = MyFilter
        lookup_field = 'id'
    

    filters.py:

    from django_filters.rest_framework import BaseInFilter, FilterSet, NumberFilter
    
    from my_app.models import MyModel
    
    
    class _NumberInFilter(BaseInFilter, NumberFilter):
        pass
    
    
    class MyFilter(FilterSet):
        id__in = _NumberInFilter(field_name='id', lookup_expr='in')
    
        class Meta:
            model = MyModel
            fields = '__all__'
    

    HTTP request (numbers):

    $ curl http://127.0.0.1:8000/my_app/my_view_set/?id__in=2,5,6
    [{"id":5,"name":"Some name 5"},{"id":2,"name":"Some name 2"},{"id":6,"name":"Some name 6"}]
    
    • Note that the lookup in the query parameter was id__in and not just id.
    • curl was used here. For easier access, you can also enter it in a browser, Postman, etc.

    HTTP request (numbers and letters):

    $ curl http://127.0.0.1:8000/my_app/my_view_set/?id__in=2,5FD,6
    {"id__in":["Enter a number."]}
    

    Using filterset_fields

    views.py:

    from django_filters.rest_framework import DjangoFilterBackend
    from rest_framework import viewsets
    
    from my_app.models import MyModel
    from my_app.serializers import MySerializer
    
    
    class MyViewSet(viewsets.ModelViewSet):
        queryset = MyModel.objects.all()
        serializer_class = MySerializer
        filter_backends = [DjangoFilterBackend]
        filterset_fields = {'id': ['in']}
        lookup_field = 'id'
    

    HTTP request:

    • Same as with filterset_class

    UI rendering error

    The HTTP requests performed above are manual, without using the UI provided by djangorestframework. Upon trying it with the UI, I experienced the same problem as you stated:

    But this solution doesn't allow me to input commas in the textbox anymore.

    This is not related to the filtering mechanism provided by django_filters (which happens in the backend) as this is a pure frontend issue specifically with the form validation.

    A manual workaround I did was inspect the textbox (right-click + inspect) then update the HTML line from number to text. So from:

    <input type="number" name="id__in" step="any" id="id_id__in">
    

    To:

    <input type="text" name="id__in" step="any" id="id_id__in">
    

    enter image description here

    HTTP Request (numbers): enter image description here

    HTTP request (numbers and letters): enter image description here

    You may want to intercept the actual rendering of the HTML by the djangorestframework or django_filters to do this cleanly.