From: Stephen Finucane Date: Tue, 14 Apr 2020 09:46:05 +0000 (+0100) Subject: REST: Include 'first', 'last' refs in 'Link' header X-Git-Tag: v3.1.0~18 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=838ebe34d5bc239e4576fccdb476dfb5f0b549f5;p=thirdparty%2Fpatchwork.git REST: Include 'first', 'last' refs in 'Link' header I've no idea why this wasn't done from day one, but it's a huge usability win for anyone attempting to do pagination with this header. Note that this change is not versioned as I haven't figured out how to do that at this layer. Signed-off-by: Stephen Finucane --- diff --git a/docs/api/schemas/latest/patchwork.yaml b/docs/api/schemas/latest/patchwork.yaml index 06be99d3..3a1fdd3a 100644 --- a/docs/api/schemas/latest/patchwork.yaml +++ b/docs/api/schemas/latest/patchwork.yaml @@ -1351,7 +1351,10 @@ components: Links to related resources, in the format defined by [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5). This will include a link with relation type `next` to the - next page, if there is a next page. + next page and `prev` to the previous page, if there is a next + or previous page. It will also include links with the + relation type `first` and `last` pointing to the first and + last page, respectively. schema: type: string requestBodies: diff --git a/docs/api/schemas/patchwork.j2 b/docs/api/schemas/patchwork.j2 index f8c6d214..b9786654 100644 --- a/docs/api/schemas/patchwork.j2 +++ b/docs/api/schemas/patchwork.j2 @@ -1384,7 +1384,10 @@ components: Links to related resources, in the format defined by [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5). This will include a link with relation type `next` to the - next page, if there is a next page. + next page and `prev` to the previous page, if there is a next + or previous page. It will also include links with the + relation type `first` and `last` pointing to the first and + last page, respectively. schema: type: string requestBodies: diff --git a/docs/api/schemas/v1.0/patchwork.yaml b/docs/api/schemas/v1.0/patchwork.yaml index 53571b60..817b2f2a 100644 --- a/docs/api/schemas/v1.0/patchwork.yaml +++ b/docs/api/schemas/v1.0/patchwork.yaml @@ -1082,7 +1082,10 @@ components: Links to related resources, in the format defined by [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5). This will include a link with relation type `next` to the - next page, if there is a next page. + next page and `prev` to the previous page, if there is a next + or previous page. It will also include links with the + relation type `first` and `last` pointing to the first and + last page, respectively. schema: type: string requestBodies: diff --git a/docs/api/schemas/v1.1/patchwork.yaml b/docs/api/schemas/v1.1/patchwork.yaml index f83b5e01..574a8ad8 100644 --- a/docs/api/schemas/v1.1/patchwork.yaml +++ b/docs/api/schemas/v1.1/patchwork.yaml @@ -1082,7 +1082,10 @@ components: Links to related resources, in the format defined by [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5). This will include a link with relation type `next` to the - next page, if there is a next page. + next page and `prev` to the previous page, if there is a next + or previous page. It will also include links with the + relation type `first` and `last` pointing to the first and + last page, respectively. schema: type: string requestBodies: diff --git a/docs/api/schemas/v1.2/patchwork.yaml b/docs/api/schemas/v1.2/patchwork.yaml index e8cee0e5..7a4e8e8e 100644 --- a/docs/api/schemas/v1.2/patchwork.yaml +++ b/docs/api/schemas/v1.2/patchwork.yaml @@ -1217,7 +1217,10 @@ components: Links to related resources, in the format defined by [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5). This will include a link with relation type `next` to the - next page, if there is a next page. + next page and `prev` to the previous page, if there is a next + or previous page. It will also include links with the + relation type `first` and `last` pointing to the first and + last page, respectively. schema: type: string requestBodies: diff --git a/docs/api/schemas/v1.3/patchwork.yaml b/docs/api/schemas/v1.3/patchwork.yaml index 8b3b1154..6bd0419d 100644 --- a/docs/api/schemas/v1.3/patchwork.yaml +++ b/docs/api/schemas/v1.3/patchwork.yaml @@ -1351,7 +1351,10 @@ components: Links to related resources, in the format defined by [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5). This will include a link with relation type `next` to the - next page, if there is a next page. + next page and `prev` to the previous page, if there is a next + or previous page. It will also include links with the + relation type `first` and `last` pointing to the first and + last page, respectively. schema: type: string requestBodies: diff --git a/patchwork/api/base.py b/patchwork/api/base.py index d870a511..7baac275 100644 --- a/patchwork/api/base.py +++ b/patchwork/api/base.py @@ -12,6 +12,7 @@ from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response from rest_framework.serializers import HyperlinkedIdentityField from rest_framework.serializers import HyperlinkedModelSerializer +from rest_framework.utils.urls import replace_query_param from patchwork.api import utils @@ -59,19 +60,33 @@ class LinkHeaderPagination(PageNumberPagination): max_page_size = settings.MAX_REST_RESULTS_PER_PAGE page_size_query_param = 'per_page' + def get_first_link(self): + url = self.request.build_absolute_uri() + return replace_query_param(url, self.page_query_param, 1) + + def get_last_link(self): + url = self.request.build_absolute_uri() + page_number = self.page.paginator.num_pages + return replace_query_param(url, self.page_query_param, page_number) + def get_paginated_response(self, data): next_url = self.get_next_link() previous_url = self.get_previous_link() + first_url = self.get_first_link() + last_url = self.get_last_link() + + links = [] + + if next_url is not None: + links.append(f'<{next_url}>; rel="next"') + + if previous_url is not None: + links.append(f'<{previous_url}>; rel="prev"') + + links.append(f'<{first_url}>; rel="first"') + links.append(f'<{last_url}>; rel="last"') - link = '' - if next_url is not None and previous_url is not None: - link = '<{next_url}>; rel="next", <{previous_url}>; rel="prev"' - elif next_url is not None: - link = '<{next_url}>; rel="next"' - elif previous_url is not None: - link = '<{previous_url}>; rel="prev"' - link = link.format(next_url=next_url, previous_url=previous_url) - headers = {'Link': link} if link else {} + headers = {'Link': ', '.join(links)} if links else {} return Response(data, headers=headers) diff --git a/releasenotes/notes/improved-rest-pagination-headers-4140a70044bbd6cf.yaml b/releasenotes/notes/improved-rest-pagination-headers-4140a70044bbd6cf.yaml new file mode 100644 index 00000000..215fa11a --- /dev/null +++ b/releasenotes/notes/improved-rest-pagination-headers-4140a70044bbd6cf.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + The ``Link`` header included in REST API responses now includes ``first`` + and ``last`` relations, as described in `RFC 5988`__. As their name would + suggest, these can be used to navigate to the beginning and end of the + resource. + + .. __: https://datatracker.ietf.org/doc/html/rfc5988