Add pagination to v3 of profile list (#469)

Fix #466 

Adds pagination to profile listing for `v3` and above.

To Test:
- Test out v3 of the API for `/profiles/?name=` and make sure that the results are paginated and the order of the profiles returned are in descending order of `id`.
- Test out v2 of the API for `/profiles/?name=` and make sure that the results aren't paginated but the order of the profiles returned are in descending order of `id`.
This commit is contained in:
Gideon Thomas 2019-04-08 13:19:57 -04:00 коммит произвёл GitHub
Родитель d6b2d7fffd
Коммит 0734b1951c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 92 добавлений и 14 удалений

Просмотреть файл

@ -531,7 +531,7 @@ Depending on the API version specified, the `related_creator` object schema will
### `GET /api/pulse/profiles/?...` with filter arguments, and optional `format=json`
Returns a list of user profile objects each with the following schema:
Returns a list (paginated for `v3` and above) of user profile objects each with the following schema:
```
{
id: <integer: id of the profile>,
@ -552,6 +552,16 @@ Returns a list of user profile objects each with the following schema:
}
```
The schema for the paginated payload returned for `v3` and above is:
```
{
"count": <integer: total number of profiles found for this query>,
"next": <string: url to the next page of profiles> or null,
"previous": <string: url to the previous page of profiles> or null,
"results": <array: list of profile objects (see above)>
}
```
__NOTE__: Versions below `v3` will not use the above schema and will follow the [general profile object schema](#profile-object-schema) instead.
This route supports filtering based on properties of profiles.
@ -572,6 +582,8 @@ __NOTE__: At least one filter or search query from below must be specified, othe
- `?ordering=...` - You can sort these results using the `ordering` query param, passing it either `id`, `custom_name`, or `program_year` (reversed by prefixing a `-`, e.g. `-custom_name` for descending alphabetical order based on the custom profile name).
- `?basic=<true or false>` - This provides a way to only get basic information about profiles. Each profile object in the list will only contain the `id` of the profile and the `name` of the profile. This query can be useful for providing autocomplete options for profiles. __NOTE__ - This query is not compatible with version 1 of the API.
- `?page_size=<number>` - Specify how many profiles will be in each page of the API call (only for `v3` and above)
- `?page=<number>` - Page number of the API call (`v3` and above)
### `GET /api/pulse/myprofile/` with optional `?format=json`

Просмотреть файл

@ -1,7 +1,10 @@
import json
from math import ceil
from urllib.parse import urlencode
from django.core.urlresolvers import reverse
from django.conf import settings
from django.http.request import HttpRequest
from rest_framework.request import Request
from .models import UserProfile, ProfileType, ProgramType, ProgramYear
@ -12,6 +15,7 @@ from pulseapi.entries.serializers import (
EntryWithCreatorsBaseSerializer,
EntryWithV1CreatorsBaseSerializer,
)
from pulseapi.profiles.views.profiles import ProfilesPagination
from pulseapi.entries.factory import BasicEntryFactory
from pulseapi.users.factory import BasicEmailUserFactory
from pulseapi.creators.models import EntryCreator
@ -337,21 +341,58 @@ class TestProfileView(PulseMemberTestCase):
profile.thumbnail = None
profile.save()
# Expected queryset
ordering = query_dict.get('ordering', '-id').split(',')
profile_list = UserProfile.objects.filter(
profile_type=profile_type, **additional_filter_options
).order_by(*ordering)
url = reverse('profile_list', args=[api_version + '/'])
response = self.client.get('{url}?profile_type={type}&{qs}'.format(
url=url,
type=profile_type.value,
qs=urlencode(query_dict)
))
response_profiles = json.loads(str(response.content, 'utf-8'))
profile_list = profile_serializer_class(
UserProfile.objects.filter(profile_type=profile_type, **additional_filter_options),
context={'user': self.user},
many=True,
).data
if api_version == settings.API_VERSIONS['version_1'] or api_version == settings.API_VERSIONS['version_2']:
# v1 & v2 don't have pagination so we test only once
response_profiles = json.loads(str(
self.client.get(
'{url}?profile_type={type}&{qs}'.format(
url=url,
type=profile_type.value,
qs=urlencode(query_dict)
)
).content,
'utf-8'
))
serialized_profile_list = profile_serializer_class(
profile_list,
context={'user': self.user},
many=True,
).data
self.assertListEqual(response_profiles, serialized_profile_list)
return None
self.assertListEqual(response_profiles, profile_list)
page_size = ProfilesPagination().get_page_size(
request=Request(request=HttpRequest())
)
for page_number in range(ceil(len(profile_list) / page_size)):
start_index = page_number * page_size
end_index = start_index + page_size
response_profiles = json.loads(str(
self.client.get(
'{url}?profile_type={type}&{qs}&page={page_number}'.format(
url=url,
type=profile_type.value,
qs=urlencode(query_dict),
page_number=page_number + 1
)
).content,
'utf-8'
))['results']
serialized_profile_list = profile_serializer_class(
profile_list[start_index:end_index],
context={'user': self.user},
many=True,
).data
self.assertListEqual(response_profiles, serialized_profile_list)
def test_profile_list_v1(self):
self.run_test_profile_list(

Просмотреть файл

@ -6,6 +6,7 @@ from django.core.files.base import ContentFile
from django.conf import settings
from django.db.models import Q
from rest_framework import permissions, filters
from rest_framework.pagination import PageNumberPagination
from rest_framework.generics import (
RetrieveUpdateAPIView,
RetrieveAPIView,
@ -192,6 +193,12 @@ class ProfileCustomFilter(filters.FilterSet):
]
class ProfilesPagination(PageNumberPagination):
page_size = 30
page_size_query_param = 'page_size'
max_page_size = 50
class UserProfileListAPIView(ListAPIView):
"""
Query Params:
@ -204,14 +211,17 @@ class UserProfileListAPIView(ListAPIView):
basic=
One of the queries above must be specified to get a result set
You can also control pagination using the following query params:
page_size=(number)
page=(number)
"""
filter_backends = (
filters.OrderingFilter,
filters.DjangoFilterBackend,
filters.SearchFilter,
)
ordering_fields = ('id', 'custom_name', 'program_year',)
ordering = ('-id',)
search_fields = (
'custom_name',
'related_user__name',
@ -220,6 +230,7 @@ class UserProfileListAPIView(ListAPIView):
'user_bio_long',
'location',
)
pagination_class = ProfilesPagination
filter_class = ProfileCustomFilter
@ -240,6 +251,20 @@ class UserProfileListAPIView(ListAPIView):
'bookmarks_from',
)
def paginate_queryset(self, queryset):
request = self.request
if not request:
return super().paginate_queryset(queryset)
version = request.version
if version == settings.API_VERSIONS['version_1'] or version == settings.API_VERSIONS['version_2']:
# Don't paginate version 1 and 2 of the API
return None
return super().paginate_queryset(queryset)
def get_serializer_class(self):
request = self.request