django-simplefilters

website·Indexed 30 days ago

https://lukaszb.github.io/django-simplefilters/

A drop-in replacement for the built-in Django Rest Framework filters package that provides an easy way to retrieve parameters from the URL and apply them to a queryset. It supports both Django Rest Framework and plain Django views, featuring FilterSet and BaseFilter for custom and built-in filtering logic.

Tokens
4.1K
Snippets
33
Records
33
Agent score
73%

What's inside django-simplefilters

  1. Use FilterSet outside of the request-response cycle

    The perform_filtering_for_query_params method can be called with any dictionary of parameters, allowing django-simplefilters to be used in standalone functions, scripts, or background tasks without a formal HTTP request object.
    from . import filtersets
    from . import models
    
    def get_todos(params):
        queryset = models.Todo.objects.all()
        fs = filtersets.Todo()
        # Pass a manual dictionary instead of request.GET
        return fs.perform_filtering_for_query_params(queryset, {'status': ['active', 'archived']})
  2. Define a FilterSet for DRF using django-simplefilters

    Create a filter set by inheriting from simplefilters.FilterSet. Use filter decorators like @filters.CharFilter and @filters.DateTimeFilter to define methods that modify the queryset based on request values. For example, many=True in CharFilter allows handling multiple values for an __in lookup.
    # filtersets.py
    import simplefilters as filters
    
    class Todo(filters.FilterSet):
        @filters.CharFilter(many=True)
        def filter_status(self, qs, values):
            return qs.filter(status__in=values)
    
        @filters.DateTimeFilter()
        def filter_modified_after(self, qs, value):
            return qs.filter(modified_at__gte=value)
  3. Define a FilterSet for data filtering

    Create a filtering class by inheriting from simplefilters.FilterSet. Use filter decorators like @filters.CharFilter or @filters.DateTimeFilter to define methods that modify the queryset based on provided values.
    import simplefilters as filters
    
    class Todo(filters.FilterSet):
        @filters.CharFilter(many=True)
        def filter_status(self, qs, values):
            return qs.filter(status__in=values)
    
        @filters.DateTimeFilter()
        def filter_modified_after(self, qs, value):
            return qs.filter(modified_at__gte=value)
  4. Define a FilterSet for data filtering

    Create a filtering class by inheriting from simplefilters.FilterSet. Use filter decorators like @filters.CharFilter or @filters.DateTimeFilter to define methods that modify the queryset based on provided values.
    import simplefilters as filters
    
    class Todo(filters.FilterSet):
        @filters.CharFilter(many=True)
        def filter_status(self, qs, values):
            return qs.filter(status__in=values)
    
        @filters.DateTimeFilter()
        def filter_modified_after(self, qs, value):
            return qs.filter(modified_at__gte=value)
  5. Create a custom filter by subclassing BaseFilter

    To implement a custom filter in django-simplefilters, subclass BaseFilter. The BaseFilter class provides the raw string value directly from the query string. To modify or validate this value before it is used, override the get_single_value(self, value) method. This is useful for converting string inputs into specific Python types or raising validation errors for invalid input.
    from rest_framework.exceptions import ValidationError
    import simplefilters as filters
    
    class FlagFilter(filters.BaseFilter):
        """
        Example filter that converts 'yes'/'no' strings to Boolean True/False
        and raises a 400 ValidationError for any other input.
        """
        def get_single_value(self, value):
            if value == 'yes':
                return True
            elif value == 'no':
                return False
            else:
                msg = f'unrecognized querystring value: {value!r}'
                raise ValidationError({'detail': msg})
  6. Define a FilterSet for DRF using django-simplefilters

    Create a filter set by inheriting from simplefilters.FilterSet. Use decorators like @filters.CharFilter and @filters.DateTimeFilter on methods to define how specific query parameters should filter the queryset. The method receives the current queryset (qs) and the filter value(s) (values or value).
    # filtersets.py
    import simplefilters as filters
    
    class Todo(filters.FilterSet):
    
        @filters.CharFilter(many=True)
        def filter_status(self, qs, values):
            return qs.filter(status__in=values)
    
        @filters.DateTimeFilter()
        def filter_modified_after(self, qs, value):
            return qs.filter(modified_at__gte=value)
  7. Install django-simplefilters

    Install the django-simplefilters package using pip to provide an easy way to retrieve URL parameters and narrow queryset results in Django Rest Framework.
    pip install django-simplefilters
  8. Implement parametrized tests for filtering logic

    When testing filtering, it is recommended to use parametrized tests (such as those provided by pytest) to verify multiple behaviors in a single test function. Key scenarios to validate include:

    • Ensuring a specific query parameter is respected and handled correctly.
    • Verifying that changing the parameter value yields different results.
    • Confirming that invalid/wrong values are ignored.
    • Ensuring that the absence of a parameter returns the full data set.
    @pytest.mark.parametrize('url, expected_todos', [
        ('?status=open', {'todo1'}),
        ('?status=done', {'todo2', 'todo3'}),
        ('?status=done&status=archived', {'todo2', 'todo3', 'todo4'}),
        ('?status=wrong', set()),
        ('?foo=bar', {'todo1', 'todo2', 'todo3', 'todo4'}),
    ])
    def test_list__filter_by_status(arf, url, expected_todos):
        TodoFactory('todo1', status='open')
        TodoFactory('todo2', status='done')
        TodoFactory('todo3', status='done')
        TodoFactory('todo4', status='archived')
    
        request = arf.get(url)
        response = views.todo_list(request)
        assert response.status_code == 200, response.data
        assert {todo['title'] for todo in response.data} == expected_todos
  9. Configure query parameters using BaseFilter options

    All built-in filters inherit from BaseFilter. You can customize how query parameters are mapped to filter functions using the following parameters:

    • param (optional): The name of the query parameter to match. If omitted, the parameter name is derived from the function name (e.g., filter_status matches ?status=...).
    • many (boolean): If True, the filter function receives a list of values instead of a single value. This is used when a query string contains multiple instances of the same parameter (e.g., ?status=new&status=in_progress).
    # Example: Custom param name 's' instead of 'status'
    @filters.CharField("s")
    def filter_status(self, qs, value):
        return qs.filter(status=value)
    
    # Example: Handling multiple values with many=True
    # Query string: '?status=new&status=in_progress'
    @filters.CharField(many=True)
    def filter_status(self, qs, values):
        # values: ['new', 'in_progress']
        return qs.filter(status__in=values)
  10. Configure query parameters using BaseFilter options

    All built-in filters inherit from BaseFilter. You can customize how query parameters are mapped to filter functions using the following options:

    • param: (Optional) The name of the query parameter to look for. If omitted, the library automatically derives the parameter name from the function name (e.g., filter_status becomes status).
    • many: If set to True, the filter function receives a list of values instead of a single value. This is used when a query string contains multiple instances of the same parameter (e.g., ?status=new&status=in_progress).
    # Custom param: matches '?s=new' instead of '?status=new'
    @filters.CharField("s")
    def filter_status(self, qs, value):
        return qs.filter(status=value)
    
    # many=True: handles multiple values like '?status=new&status=in_progress'
    @filters.CharField(many=True)
    def filter_status(self, qs, values):
        # values: ['new', 'in_progress']
        return qs.filter(status__in=values)
  11. Filter by string values with CharField

    Use the @filters.CharField() decorator to create a filter that handles string values from the query string.
    @filters.CharField()
    def filter_status(self, qs, value):
        return qs.filter(status=value)