pagination.py 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. import logging
  2. import urllib.parse as urlparse
  3. from urllib.parse import parse_qs
  4. from rest_framework.exceptions import ValidationError
  5. from rest_framework.pagination import CursorPagination
  6. from rest_framework.response import Response
  7. logger = logging.getLogger(__name__)
  8. class LinkHeaderPagination(CursorPagination):
  9. """Inform the user of pagination links via response headers, similar to
  10. what's described in
  11. https://developer.github.com/guides/traversing-with-pagination/.
  12. """
  13. page_size_query_param = "limit"
  14. max_hits = 1000
  15. def paginate_queryset(self, queryset, request, view=None):
  16. self.count = self.get_count(queryset)
  17. try:
  18. return super().paginate_queryset(queryset, request, view)
  19. except ValueError as err:
  20. # https://gitlab.com/glitchtip/glitchtip-backend/-/issues/136
  21. logging.warning("Pagination received invalid cursor", exc_info=True)
  22. raise ValidationError("Invalid page cursor") from err
  23. def get_count(self, queryset):
  24. """Count with max limit, to prevent slowdown"""
  25. return queryset[: self.max_hits].count()
  26. def get_paginated_response(self, data):
  27. next_url = self.get_next_link()
  28. previous_url = self.get_previous_link()
  29. links = []
  30. for url, label in (
  31. (previous_url, "previous"),
  32. (next_url, "next"),
  33. ):
  34. if url is not None:
  35. parsed = urlparse.urlparse(url)
  36. cursor = parse_qs(parsed.query).get(self.cursor_query_param, [""])[0]
  37. links.append(
  38. '<{}>; rel="{}"; results="true"; cursor="{}"'.format(
  39. url, label, cursor
  40. )
  41. )
  42. else:
  43. links.append(
  44. '<{}>; rel="{}"; results="false"'.format(self.base_url, label)
  45. )
  46. headers = {"Link": ", ".join(links)} if links else {}
  47. headers["X-Max-Hits"] = self.max_hits
  48. headers["X-Hits"] = self.count
  49. return Response(data, headers=headers)