paginators.py 4.06 KB
Newer Older
1 2 3 4 5
""" Paginatator methods for edX API implementations."""

from django.http import Http404
from django.core.paginator import Paginator, InvalidPage

6 7 8 9 10 11 12 13 14 15 16 17
from rest_framework.response import Response
from rest_framework import pagination


class DefaultPagination(pagination.PageNumberPagination):
    """
    Default paginator for APIs in edx-platform.

    This is configured in settings to be automatically used
    by any subclass of Django Rest Framework's generic API views.
    """
    page_size_query_param = "page_size"
18
    max_page_size = 100
19 20 21 22 23 24 25 26 27 28

    def get_paginated_response(self, data):
        """
        Annotate the response with pagination information.
        """
        return Response({
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
            'count': self.page.paginator.count,
            'num_pages': self.page.paginator.num_pages,
29 30
            'current_page': self.page.number,
            'start': (self.page.number - 1) * self.get_page_size(self.request),
31 32 33
            'results': data
        })

34

35 36 37 38 39 40 41 42 43 44
class NamespacedPageNumberPagination(pagination.PageNumberPagination):
    """
    Pagination scheme that returns results with pagination metadata
    embedded in a "pagination" attribute.  Can be used with data
    that comes as a list of items, or as a dict with a "results"
    attribute that contains a list of items.
    """

    page_size_query_param = "page_size"

45 46 47 48 49 50 51 52 53 54 55 56
    def get_result_count(self):
        """
        Returns total number of results
        """
        return self.page.paginator.count

    def get_num_pages(self):
        """
        Returns total number of pages the results are divided into
        """
        return self.page.paginator.num_pages

57 58 59 60 61 62 63
    def get_paginated_response(self, data):
        """
        Annotate the response with pagination information
        """
        metadata = {
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
64 65
            'count': self.get_result_count(),
            'num_pages': self.get_num_pages(),
66 67 68 69 70 71 72 73 74 75 76 77 78
        }
        if isinstance(data, dict):
            if 'results' not in data:
                raise TypeError(u'Malformed result dict')
            data['pagination'] = metadata
        else:
            data = {
                'results': data,
                'pagination': metadata,
            }
        return Response(data)


79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
def paginate_search_results(object_class, search_results, page_size, page):
    """
    Takes edx-search results and returns a Page object populated
    with db objects for that page.

    :param object_class: Model class to use when querying the db for objects.
    :param search_results: edX-search results.
    :param page_size: Number of results per page.
    :param page: Page number.
    :return: Paginator object with model objects
    """
    paginator = Paginator(search_results['results'], page_size)

    # This code is taken from within the GenericAPIView#paginate_queryset method.
    # It is common code, but
    try:
        page_number = paginator.validate_number(page)
    except InvalidPage:
        if page == 'last':
            page_number = paginator.num_pages
        else:
100
            raise Http404("Page is not 'last', nor can it be converted to an int.")
101 102 103

    try:
        paged_results = paginator.page(page_number)
104 105 106 107 108 109 110
    except InvalidPage as exception:
        raise Http404(
            "Invalid page {page_number}: {message}".format(
                page_number=page_number,
                message=str(exception)
            )
        )
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

    search_queryset_pks = [item['data']['pk'] for item in paged_results.object_list]
    queryset = object_class.objects.filter(pk__in=search_queryset_pks)

    def ordered_objects(primary_key):
        """ Returns database object matching the search result object"""
        for obj in queryset:
            if obj.pk == primary_key:
                return obj

    # map over the search results and get a list of database objects in the same order
    object_results = map(ordered_objects, search_queryset_pks)
    paged_results.object_list = object_results

    return paged_results