3

I have made a filter API like this:

localhost/api/allpackages?price_min=700&price_max=900&destination=Spain&new_activity=Swimming&tour_type=Group%20Tour&featured=true&fix_departure=true

But according to new changes, I should be able to filter like this:

localhost/api/allpackages?destination=Spain&destination=Japan&destination=Thailand....featured=true...

There can be multiple values for a single parameter, because user can now click the checkboxes on the frontend. How can I achieve this?

My models:

class Package(models.Model):
    operator = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
    destination = models.ForeignKey(Destination, on_delete=models.CASCADE)
    package_name = models.CharField(max_length=255)
    featured = models.BooleanField(default=False)
    price = models.IntegerField()
    duration = models.IntegerField(default=5)
    discount = models.CharField(max_length=255, default="15% OFF")
    discounted_price = models.IntegerField(default=230)
    savings = models.IntegerField(default=230)
    tour_type = models.CharField(max_length=100, choices=TOUR_TYPE, default='Group Tour')
    new_activity = models.ManyToManyField(NewActivity)
    accommodation = models.CharField(max_length=255,default='Guest House & Hotel')
    transport = models.CharField(max_length=150, default='Flight')
    age_range = models.CharField(max_length=100, default='6 to 79 years old')
    fix_departure = models.BooleanField(default=False)
....
...

My views:

class AllPackageAPIView(ListAPIView):
    queryset = Package.objects.all()
    serializer_class = PackageSerializer
    filterset_class = PackageFilter
    
 
    def get_queryset(self):
        new_activity = self.request.query_params.get('new_activity', None)
        destination = self.request.query_params.get('destination', None)
        if new_activity is not None:
            if destination is not None:
                return Package.objects.filter(destination__name=destination, new_activity__title=new_activity)
            else:
                return Package.objects.filter(new_activity__title=new_activity)
        elif destination is not None:
            if new_activity is not None:
                return Package.objects.filter(destination__name=destination, new_activity__title=new_activity)
            else:
                return Package.objects.filter(destination__name=destination)
        else:
            return Package.objects.all()

My filter:

class PackageFilter(filters.FilterSet):
   price = filters.RangeFilter()

   class Meta:
      model = Package
      fields = ['price','featured', 'fix_departure',
                'tour_type',]

My serializers:

class PackageSerializer(serializers.ModelSerializer):
  
    class Meta:
        model = Package
        fields = ['id', 'operator','destination', 'package_name', 'duration', 'featured', 'price', 'discount', 'discounted_price',
                       'tour_type','new_activity', 'accommodation', 'transport', 'age_range',
                  'savings', 'fix_departure', 'rating', 'image', 'date_created', ]
        # fields = '__all__'
        depth = 1

I have done this but now no data are shown. The get list is empty. I used get_queryset(self) as a function and now self.request.GET.get for querying.

My updated view:

class AllPackageAPIView(ListAPIView):
    queryset = Package.objects.all()
    serializer_class = PackageSerializer
    filterset_class = PackageFilter

def get_queryset(self): 
    new_activity = self.request.GET.get('new_activity', None)
    destination = self.request.GET.get("destination", "")
    destination_values = destination.split(",")
    if new_activity is not None:
        if destination is not None:
            return Package.objects.filter(destination__name=destination_values, new_activity__title=new_activity)
        else:
            return Package.objects.filter(new_activity__title=new_activity)
    elif destination is not None:
        if new_activity is not None:
            return Package.objects.filter(destination__name=destination_values, new_activity__title=new_activity)
        else:
            return Package.objects.filter(destination__name=destination_values)
    else:
        return Package.objects.all()

My solution:

def get_queryset(self):
# def get(self, request, format=None, *args, **kwargs):

    new_activity = self.request.GET.get('new_activity',None)
    destination = self.request.GET.get("destination",None)
    tour_type = self.request.GET.get("tour_type",None)
    if new_activity is not None:
        new_activity = self.request.GET.get('new_activity', "")
        new_activity_values = new_activity.split(",")
        if destination is not None:
            destination = self.request.GET.get("destination", "")
            destination_values = destination.split(",")
            if tour_type is not None:
                tour_type = self.request.GET.get("tour_type", "")
                tour_type_values = tour_type.split(",")
                return Package.objects.filter(destination__name__in=destination_values,new_activity__title__in=new_activity_values,
                                          tour_type__in=tour_type_values)
            else:
                return Package.objects.filter(destination__name__in=destination_values,
                                              new_activity__title__in=new_activity_values)
        else:
            return Package.objects.filter(new_activity__title__in=new_activity_values)
    elif destination is not None:
        destination = self.request.GET.get("destination", "")
        destination_values = destination.split(",")
        if new_activity is not None:
            new_activity = self.request.GET.get('new_activity', "")
            new_activity_values = new_activity.split(",")
            if tour_type is not None:
                tour_type = self.request.GET.get("tour_type", "")
                tour_type_values = tour_type.split(",")
                return Package.objects.filter(destination__name__in=destination_values,
                                              new_activity__title__in=new_activity_values,
                                              tour_type__in=tour_type_values)
            else:
                return Package.objects.filter(destination__name__in=destination_values,
                                              new_activity__title__in=new_activity_values
                                              )
        else:
            return Package.objects.filter(destination__name__in=destination_values)
    elif tour_type is not None:
        tour_type = self.request.GET.get("tour_type", "")
        tour_type_values = tour_type.split(",")
        if destination is not None:
            destination = self.request.GET.get("destination", "")
            destination_values = destination.split(",")
            if new_activity is not None:
                new_activity = self.request.GET.get('new_activity', "")
                new_activity_values = new_activity.split(",")
                return Package.objects.filter(destination__name__in=destination_values,
                                              new_activity__title__in=new_activity_values,
                                              tour_type__in=tour_type_values)
            else:
                return Package.objects.filter(destination__name__in=destination_values,
                                                       tour_type__in=tour_type_values)
        else:
            return Package.objects.filter(tour_type__in=tour_type_values)
    else:
        return Package.objects.all()

This works as a filter for checkbox searches in ecommerce website, but it has a problem. When calling API, it repeats some of the objects i.e. same package object in my case. If anyone can solve it, let me know.

0

3 Answers 3

3

I have solved this question before, I've decided to get multiple values in URL by using split , character.

Example: URL: localhost/api/allpackages?destination=Spain,Japan,Thailand....featured=true...

destination = self.request.GET.get("destination", "")

destination_values = destination.split(",")

Sample code about filtering first_name, last_name, and multiple values of username in User model.

model.py

class User(AbstractUser):
    @property
    def full_name(self):
        """Custom full name method as a property"""
        return str(self.first_name) + ' ' + str(self.last_name)

    def __str__(self):
        return self.email

view.py

class UserFilter(filters.FilterSet):
    class Meta:
        model = User
        fields = ['first_name', 'last_name']


class ListCreateUser(ListCreateAPIView):
    """
    List and Create User Generic contains create and list user APIs.
    """
    serializer_class = UserSerializer
    queryset = User.objects.all()
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_class = UserFilter

    def get_queryset(self):
        username = self.request.GET.get('username', '')

        if username:
            username_values = username.split(',')
            return User.objects.filter(username__in=username_values)

        return User.objects.all()

Results:

  • List all users
  • Filter by first_name, last_name, and username
  • Filter by usernameenter image description here
Sign up to request clarification or add additional context in comments.

24 Comments

return Package.objects.filter(destination__name=destination) so this should be replaced with return Package.objects.filter(destination__name=destination_values)?? after writing above code??
Just correct your code is return Package.objects.filter(destination__name__in= destination_values). This is a way when using __in. Or Q object to implement OR condition (docs.djangoproject.com/en/2.0/topics/db/queries/…)
I am getting this error 'list' object has no attribute 'split'. How to solve this? @vu Phan
|The main url ie localhost/api/allpackages. I havent put any parameters, yet it shows this error. How to solve it??
Got the error, because this destination = self.request.GET.get("destination", []). Updated my answer: destination = self.request.GET.get("destination", "")
|
2

I found that Django supports multi-value parameters with its QueryDict since at least 3.0 . So when you have the situation like:

https://www.example.com/foo?a=1&a=2

you can get all values of a with:

def my_function(request):
    a_list = request.query_params.getlist('a')
    # [1, 2]

This is not intuitive, since request.query_params.get('a') only returns the last element in the list (see documentation).

3 Comments

can you please tell me what should be the URL for https://www.example.com/foo?a=1&a=2 in urls.py
@AzharUddinSheikh: What about urlpatterns = [path('foo', views.my_function, name='my_func')]? This should hand over the request to the function my_function with the request as parameter. The views.my_function of course only applies, if my_function is defined in views.py.
This is the right answer, self.request.query_params.getlist() is available in django rest framework.
1

django-rest-framework does not provide multi-value filter support, you have to write it yourself if you want OR you can use djangorestframework-jsonapi it provides the multi-value filter and many other pluggable features

Membership in a list of values: ?filter[name.in]=abc,123,zzz (name in ['abc','123','zzz'])

You can configure the filter backends either by setting the REST_FRAMEWORK['DEFAULT_FILTER_BACKENDS'] or individually add them as .filter_backends

'DEFAULT_FILTER_BACKENDS': (
        'rest_framework_json_api.filters.QueryParameterValidationFilter',
        'rest_framework_json_api.filters.OrderingFilter',
        'rest_framework_json_api.django_filters.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
    ),

See this example https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#configuring-filter-backends

Your code with changes you don't need to write PackageFilter and get_queryset

from rest_framework_json_api import django_filters

class AllPackageAPIView(ListAPIView):
    queryset = Package.objects.all()
    serializer_class = PackageSerializer
    filter_backends = (django_filters.DjangoFilterBackend)
    filterset_fields = {'destination': ('exact', 'in'), ...}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.