diff --git a/.travis.yml b/.travis.yml index 80b36fe5b7..338e30c185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,9 @@ notifications: - irc.smoothirc.net#zds-dev skip_join: true -services: mysql +services: + - mysql + - memcached sudo: false diff --git a/zds/api/DJRF3xPaginationKeyBit.py b/zds/api/DJRF3xPaginationKeyBit.py new file mode 100644 index 0000000000..0e556f5a5e --- /dev/null +++ b/zds/api/DJRF3xPaginationKeyBit.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +from rest_framework_extensions.key_constructor.bits import QueryParamsKeyBit + + +class DJRF3xPaginationKeyBit(QueryParamsKeyBit): + """ + A custom PaginationKeyBit for DJRF3 + + This class solve an upstream issue: + - Asking for page 1 and page 3 with a memcached instance will always return page 1. + This issue have been discussed here: https://botbot.me/freenode/restframework/2015-05-23/?tz=Europe%2FLondon&page=1 + and in this Pull Request: https://github.com/zestedesavoir/zds-site/pull/2761 + + This class should be deleted when an upstream solution will be proposed. + This class should be replaced by using bits.PaginationKeyBit() instead. + """ + def get_data(self, **kwargs): + kwargs['params'] = [] + + if hasattr(kwargs['view_instance'], 'paginator'): + pqp = kwargs['view_instance'].paginator.page_query_param + rqp = kwargs['view_instance'].request.query_params + # add the query param + kwargs['params'].append(pqp) + # get its value + rqp_pv = rqp.get(pqp, 1) + kwargs['params'].append(rqp_pv) + + return super(DJRF3xPaginationKeyBit, self).get_data(**kwargs) diff --git a/zds/member/api/tests.py b/zds/member/api/tests.py index fc077ffe3d..580f66ed6d 100644 --- a/zds/member/api/tests.py +++ b/zds/member/api/tests.py @@ -11,11 +11,14 @@ from zds.member.factories import ProfileFactory, StaffProfileFactory from zds.member.models import TokenRegister +from rest_framework_extensions.settings import extensions_api_settings +from django.core.cache import get_cache class MemberListAPITest(APITestCase): def setUp(self): self.client = APIClient() + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() def test_list_of_users_empty(self): """ @@ -52,6 +55,14 @@ def test_list_of_users_with_several_pages(self): self.assertEqual(response.data.get('count'), settings.REST_FRAMEWORK['PAGINATE_BY'] + 1) self.assertIsNotNone(response.data.get('next')) self.assertIsNone(response.data.get('previous')) + self.assertEqual(len(response.data.get('results')), settings.REST_FRAMEWORK['PAGINATE_BY']) + + response = self.client.get(reverse('api-member-list') + '?page=2') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data.get('count'), settings.REST_FRAMEWORK['PAGINATE_BY'] + 1) + self.assertIsNone(response.data.get('next')) + self.assertIsNotNone(response.data.get('previous')) + self.assertEqual(len(response.data.get('results')), 1) def test_list_of_users_for_a_page_given(self): """ @@ -315,6 +326,9 @@ def create_multiple_users(self, number_of_users=settings.REST_FRAMEWORK['PAGINAT class MemberMyDetailAPITest(APITestCase): + def setUp(self): + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_detail_of_the_member(self): """ Gets all information about the user. @@ -359,6 +373,8 @@ def setUp(self): self.client_authenticated = APIClient() authenticate_client(self.client_authenticated, client_oauth2, self.profile.user.username, 'hostel77') + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_detail_of_a_member(self): """ Gets all information about a user. @@ -626,6 +642,8 @@ def setUp(self): self.client_authenticated = APIClient() authenticate_client(self.client_authenticated, client_oauth2, self.staff.user.username, 'hostel77') + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_apply_read_only_at_a_member(self): """ Applies a read only sanction at a member given by a staff user. @@ -773,6 +791,8 @@ def setUp(self): self.client_authenticated = APIClient() authenticate_client(self.client_authenticated, client_oauth2, self.staff.user.username, 'hostel77') + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_apply_ban_at_a_member(self): """ Applies a ban sanction at a member given by a staff user. diff --git a/zds/member/api/views.py b/zds/member/api/views.py index b8804a6e7b..bd84fc1b52 100644 --- a/zds/member/api/views.py +++ b/zds/member/api/views.py @@ -9,6 +9,7 @@ from rest_framework_extensions.etag.decorators import etag from rest_framework_extensions.key_constructor import bits from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor +from zds.api.DJRF3xPaginationKeyBit import DJRF3xPaginationKeyBit from zds.member.api.serializers import ProfileListSerializer, ProfileCreateSerializer, \ ProfileDetailSerializer, ProfileValidatorSerializer @@ -21,7 +22,7 @@ class PagingSearchListKeyConstructor(DefaultKeyConstructor): - pagination = bits.PaginationKeyBit() + pagination = DJRF3xPaginationKeyBit() search = bits.QueryParamsKeyBit(['search']) list_sql_query = bits.ListSqlQueryKeyBit() unique_view_id = bits.UniqueViewIdKeyBit() diff --git a/zds/mp/api/tests.py b/zds/mp/api/tests.py index 75e58bc7f5..3176004cbc 100644 --- a/zds/mp/api/tests.py +++ b/zds/mp/api/tests.py @@ -2,11 +2,13 @@ from collections import OrderedDict from unittest import skip from django.contrib.auth.models import Group +from django.core.cache import get_cache from django.core.urlresolvers import reverse from django.test.utils import override_settings from rest_framework import status from rest_framework.test import APITestCase, APIClient +from rest_framework_extensions.settings import extensions_api_settings from zds import settings from zds.member.api.tests import create_oauth2_client, authenticate_client @@ -33,6 +35,8 @@ def setUp(self): self.bot_group.name = ZDS_APP["member"]["bot_group"] self.bot_group.save() + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_list_mp_with_client_unauthenticated(self): """ Gets list of private topics with an unauthenticated client. @@ -76,6 +80,14 @@ def test_list_of_private_topics_with_several_pages(self): self.assertEqual(response.data.get('count'), settings.REST_FRAMEWORK['PAGINATE_BY'] + 1) self.assertIsNotNone(response.data.get('next')) self.assertIsNone(response.data.get('previous')) + self.assertEqual(len(response.data.get('results')), settings.REST_FRAMEWORK['PAGINATE_BY']) + + response = self.client.get(reverse('api-mp-list') + '?page=2') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data.get('count'), settings.REST_FRAMEWORK['PAGINATE_BY'] + 1) + self.assertIsNone(response.data.get('next')) + self.assertIsNotNone(response.data.get('previous')) + self.assertEqual(len(response.data.get('results')), 1) def test_list_of_private_topics_for_a_page_given(self): """ @@ -371,6 +383,8 @@ def setUp(self): self.bot_group.name = ZDS_APP["member"]["bot_group"] self.bot_group.save() + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_detail_mp_with_client_unauthenticated(self): """ Gets details about a private topic with an unauthenticated client. @@ -610,6 +624,8 @@ def setUp(self): self.private_topic = PrivateTopicFactory(author=self.profile.user) + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_list_mp_with_client_unauthenticated(self): """ Gets list of private posts of a private topic given with an unauthenticated client. @@ -798,6 +814,8 @@ def setUp(self): client_oauth2 = create_oauth2_client(self.profile.user) authenticate_client(self.client, client_oauth2, self.profile.user.username, 'hostel77') + get_cache(extensions_api_settings.DEFAULT_USE_CACHE).clear() + def test_detail_private_post_with_client_unauthenticated(self): """ Gets details about a private post with an unauthenticated client. diff --git a/zds/mp/api/views.py b/zds/mp/api/views.py index 761ca8a151..a831cfed68 100644 --- a/zds/mp/api/views.py +++ b/zds/mp/api/views.py @@ -9,6 +9,7 @@ from rest_framework_extensions.etag.decorators import etag from rest_framework_extensions.key_constructor import bits from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor +from zds.api.DJRF3xPaginationKeyBit import DJRF3xPaginationKeyBit from zds.mp.api.permissions import IsParticipant, IsParticipantFromPrivatePost, IsLastPrivatePostOfCurrentUser, \ IsAloneInPrivatePost, IsAuthor @@ -19,7 +20,7 @@ class PagingPrivateTopicListKeyConstructor(DefaultKeyConstructor): - pagination = bits.PaginationKeyBit() + pagination = DJRF3xPaginationKeyBit() search = bits.QueryParamsKeyBit(['search', 'ordering']) list_sql_query = bits.ListSqlQueryKeyBit() unique_view_id = bits.UniqueViewIdKeyBit() @@ -33,7 +34,7 @@ class DetailKeyConstructor(DefaultKeyConstructor): class PagingPrivatePostListKeyConstructor(DefaultKeyConstructor): - pagination = bits.PaginationKeyBit() + pagination = DJRF3xPaginationKeyBit() list_sql_query = bits.ListSqlQueryKeyBit() unique_view_id = bits.UniqueViewIdKeyBit()