Question

Je suis à l'aide d' django-filtre avec django-repos-cadre et j'essaie d'instancier un filtre qui accepte les listes de numéros pour le filtrage des requêtes définies

class MyFilter(django_filters.FilterSet):   
    ids = django_filters.NumberFilter(name='id',lookup_type='in')
    class Meta:
        model = MyModel
        fields = ('ids',)

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    filter_class = MyFilter

Si je passe dans une liste séparée par des virgules des nombres entiers, le filtre est totalement ignoré.

Si je passe dans un seul entier, il obtient grâce à django-filtre dans django validateur de formulaire et se plaint:

'Decimal' object is not iterable

Est-il un moyen de créer un django-objet de filtre qui permet de gérer une liste d'entiers et correctement le filtre vers le bas le queryset?

Était-ce utile?

La solution

Pour le meilleur ou pire, j'ai créé un filtre personnalisé pour cela:

class IntegerListFilter(django_filters.Filter):
    def filter(self,qs,value):
        if value not in (None,''):
            integers = [int(v) for v in value.split(',')]
            return qs.filter(**{'%s__%s'%(self.name, self.lookup_type):integers})
        return qs

qui est utilisé comme:

class MyFilter(django_filters.FilterSet):   
    ids = IntegerListFilter(name='id',lookup_type='in')
    class Meta:
        model = MyModel
        fields = ('ids',)

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    filter_class = MyFilter

Maintenant, mon interface accepte les listes d'entiers délimités par des virgules.

Autres conseils

Je sais que c'est un vieux post, mais il y a maintenant une meilleure solution.Le changement qui fait correcte est affichée ici.

Ils ont ajouté un BaseInFilter et un BaseRangeFilter.La documentation est ici.

Grande image, BaseFilter vérifie CSV, puis lorsqu'il est mélangé avec un autre filtre ne fait que ce que vous demandez.Votre code peut maintenant être écrite comme:

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

class MyModelViewSet(viewsets.ModelViewSet):
    ids = NumberInFilter(name='id', lookup_expr='in')

    class Meta:
        model = MyModel
        fields = ['ids']

Voici une solution complète:

from django_filters import Filter, FilterSet
from rest_framework.filters import DjangoFilterBackend
from rest_framework.viewsets import ModelViewSet
from .models import User
from .serializers import UserSerializer


class ListFilter(Filter):

    def filter(self, qs, value):
        if not value:
            return qs

        self.lookup_type = 'in'
        values = value.split(',')
        return super(ListFilter, self).filter(qs, values)


class UserFilter(FilterSet):
    ids = ListFilter(name='id')

    class Meta:
        model = User
        fields = ['ids']


class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    filter_backends = (DjangoFilterBackend,)
    filter_class = UserFilter

According to a post in the django-filter issues:

from django_filters import Filter
from django_filters.fields import Lookup

class ListFilter(Filter):
    def filter(self, qs, value):
        return super(ListFilter, self).filter(qs, Lookup(value.split(u","), "in"))

I have personally used this without any issue in my projects, and it works without having to create a per-type filter.

Based on @yndolok answer I have come to a general solution. I think filtering by a list of ids is a very common task and therefore should be included in the FilterBackend:

class ListFilter(django_filters.Filter):

    """Class to filter from list of integers."""

    def filter(self, qs, value):
        """Filter function."""
        if not value:
            return qs
        self.lookup_type = 'in'
        try:
            map(int, value.split(','))
            return super(ListFilter, self).filter(qs, value.split(','))
        except ValueError:
            return super(ListFilter, self).filter(qs, [None])


class FilterBackend(filters.DjangoFilterBackend):

    """A filter backend that includes ListFilter."""

    def get_filter_class(self, view, queryset=None):
        """Append ListFilter to AutoFilterSet."""
        filter_fields = getattr(view, 'filter_fields', None)

        if filter_fields:
            class AutoFilterSet(self.default_filter_set):
                ids = ListFilter(name='id')

                class Meta:
                    model = queryset.model
                    fields = list(filter_fields) + ["ids"]

            return AutoFilterSet

        else:
            return super(FilterBackend, self).get_filter_class(view, queryset)

Uptodate solution:

from django_filters import rest_framework as filters

name-->field_name

lookup_type-->lookup_expr

class IntegerListFilter(filters.Filter):
    def filter(self,qs,value):
        if value not in (None,''):
            integers = [int(v) for v in value.split(',')]
            return qs.filter(**{'%s__%s'%(self.field_name, self.lookup_expr):integers})
        return qs

class MyFilter(filters.FilterSet):   
    ids = IntegerListFilter(field_name='id',lookup_expr='in')
    class Meta:
        model = MyModel
        fields = ('ids',)

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    filter_class = MyFilter

As I have answered here DjangoFilterBackend with multiple ids, it is now pretty easy to make a filter that accepts list and validates the contents

For Example:


from django_filters import rest_framework as filters


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

class MyFilter(filters.FilterSet):
    id_in = NumberInFilter(field_name='id', lookup_expr='in')

    class Meta:
        model = MyModel
        fields = ['id_in', ]

This will accept a list of integers from a get parameter. For example /endpoint/?id_in=1,2,3

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top