djangodockernginxdjango-rest-frameworkgunicorn

How to change the host in next key in a paginated URL in django rest framework?


I have a ModelSerializer in Django Rest Framework with paginated responses. So I have deployed it with gunicorn in a docker container.

gunicorn -c gunicorn_config.py app.wsgi --bind 0.0.0.0:5000

Now the problem is in the paginated responses. The next key is something like.

next: "http://0.0.0.0:5000/admin/users/?page=2&per_page=10"

In my client-side where I am consuming these APIs, I just check the next key and fetch the next response. But since the next key has the host as 0.0.0.0:5000 hence it will cause API call failure. And the purpose is not served for the next key.

So at the moment, my API server is running in a separate docker container. Which is set up via the reverse proxy in nginx.


Solution

  • So I made a custom pagination class extending PageNumberPagination

    from rest_framework.pagination import PageNumberPagination
    def replace_query_param(url, key, val):
        """
        Given a URL and a key/val pair, set or replace an item in the query
        parameters of the URL, and return the new URL.
        """
        (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url))
        scheme = "https"
        netloc = "api.example.com"
        query_dict = parse.parse_qs(query, keep_blank_values=True)
        query_dict[force_str(key)] = [force_str(val)]
        query = parse.urlencode(sorted(list(query_dict.items())), doseq=True)
        return parse.urlunsplit((scheme, netloc, path, query, fragment))
    
    
    def remove_query_param(url, key):
        """
        Given a URL and a key/val pair, remove an item in the query
        parameters of the URL, and return the new URL.
        """
        (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url))
        scheme = "https"
        netloc = "api.example.com"
        query_dict = parse.parse_qs(query, keep_blank_values=True)
        query_dict.pop(key, None)
        query = parse.urlencode(sorted(list(query_dict.items())), doseq=True)
        return parse.urlunsplit((scheme, netloc, path, query, fragment))
    
    class LargeResultsSetPagination(PageNumberPagination):
        page_size = 1000
        page_size_query_param = 'per_page'
        max_page_size = 1000
    
        def get_next_link(self):
            if not self.page.has_next():
                return None
            url = self.request.build_absolute_uri()
            page_number = self.page.next_page_number()
            return replace_query_param(url, self.page_query_param, page_number)
    
        def get_previous_link(self):
            if not self.page.has_previous():
                return None
            url = self.request.build_absolute_uri()
            page_number = self.page.previous_page_number()
            if page_number == 1:
                return remove_query_param(url, self.page_query_param)
            return replace_query_param(url, self.page_query_param, page_number)
    

    Now I am using this pagination class in all my ViewSets

    class TestViewSet(viewsets.ModelViewSet):
        permission_classes = [permissions.IsAuthenticated]
    
        queryset = Test.objects.all().order_by("pk")
        serializer_class = test_serializers.TestSerializer
        pagination_class = LargeResultsSetPagination
        search_fields = ['name', 'description', 'follow_up', 'follow_up_type']
        filter_backends = (filters.SearchFilter,)
    

    And it does the job, the original inspiration https://stackoverflow.com/a/62422235/5884045