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:
Родитель
d6b2d7fffd
Коммит
0734b1951c
14
README.md
14
README.md
|
@ -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
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче