HackSoftware / Django-Styleguide

Django styleguide used in HackSoft projects
MIT License
5.12k stars 520 forks source link

How would you handle searching? #128

Closed Farhaduneci closed 1 year ago

Farhaduneci commented 1 year ago

Hey there, thanks for your great effort. I wonder how would you handle a search query parameter which acts like SearchFilter inside a normal generic view?

RadoRado commented 1 year ago

@Farhaduneci hello :wave:

I'm not sure I'm picking the right context of the question, so if the provided answer is not what you are looking for, just provide more details + an example :pray:

Otherwise, when dealing with query params, we usually pass them thru a serializer (we call this FilterSerializer) and then pass the validated_data to the respective service or selector.

After this, how you handle filtration, is up to you. Usually https://django-filter.readthedocs.io/en/stable/ is more than enough.

Here is an example of a simple list API with filters from GET params - https://github.com/HackSoftware/Django-Styleguide-Example/blob/master/styleguide_example/users/apis.py#L13

Cheers!

Farhaduneci commented 1 year ago

I appreciate your near-immidate response. The repository feels so alive and under active maintenance 🔥

What you've stated isn't what I meant, unfortunately. I've seen your implementation of filters in the example project (which is a reversed-engineered version of the way that DRF handles this as you've stated in the docs).

My question is about the search functionality insted. Have a look at this View for example:

...

from rest_framework.filters import OrderingFilter, SearchFilter

class GiftAdminList(ListAPIView):
    serializer_class = GiftAdminListSerializer
    permission_classes = (FullDjangoModelPermissions,)

    filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]

    filterset_fields = ["type"]
    ordering_fields = ["created_at"]

    search_fields = ["invoice__id",      "user__email", "user__mobile"]

    swagger_tags = ["Gift"]

    def get_queryset(self):
        return Gift.objects.all()

Your example shows how we can, and you've handled the functionality of DjangoFilterBackend and OrderingFilter in this code.

What about the SearchFilter?

What is the best way to reverse-engineer this part?

Farhaduneci commented 1 year ago

@RadoRado I mention you here in case that you've missed this one!

RadoRado commented 1 year ago

@Farhaduneci Okay, if I understand you correctly, the question is - "How do we replicate this behavior in a selector?"

How I usually approach this is by examining what's the actual behavior of rest_framework.filters.SearchFilter:

The main thing it does is something called `filter_queryset:

from rest_framework.filters import SearchFilter

filter = SearchFilter()

qs = ...

qs = filter.filter_queryset(request, qs, view)

It gets the values from the params from request, and view is required because of the search_fields attribute.

This means we can simply create mock objects to pass to the search filter & construct a selector like that:

from types import SimpleNamespace as Object
from rest_framework.filters import SearchFilter

def some_selector(*, x, y, z, query_params):
    qs = SomeModel.objects.all()
    request = Object(query_params=query_params)
    view = Object(search_fields=your_search_fields)

    search_filter = SearchFilter()
    qs = search_filter.filter_queryset(request, qs, view)

    ....

And you are good to go :+1:

Of course, you can simply pass the request & view to the selector, if you feel like it.

RadoRado commented 1 year ago

Here's the implementation of SearchFilter for reference - https://github.com/encode/django-rest-framework/blob/master/rest_framework/filters.py#L39

Farhaduneci commented 1 year ago

Yes, that's the question I meant to ask. Your example is exactly what I was looking for. Thank you so much!