Ошибка кэширования фреймворка Django REST

TL; DR

Я ищу способ очистите кэш после запроса или полностью отключите его при выполнении тестов. Django Rest Framework, похоже, кэширует результаты, и мне нужен способ обойти это.

длинная версия и код

Ну, это оказалось очень странным, поскольку я продолжал его тестировать. В конце концов, я получил его, чтобы работать, но мне действительно не нравится мой обходной путь и во имя знаний, я должен выяснить, почему это происходит и как правильно решить эту проблему.

Итак, у меня есть APITestCase класс объявил так:

class UserTests(APITestCase):

внутри этого класса у меня есть тестовая функция для моего user-list view, так как у меня есть пользовательский набор запросов в зависимости от разрешений. Чтобы прояснить ситуацию:

  • суперпользователь может получить весь список пользователей (возвращено 4 экземпляра),
  • сотрудники не могут видеть суперпользователей (возвращено 3 экземпляра),
  • нормальный пользователи могут получить только 1 результат, их собственный пользователь (возвращается 1 экземпляр)

версия тестовой функции, которая работает:

def test_user_querysets(self):

    url = reverse('user-list')

    # Creating a user
    user = User(username='user', password=self.password)
    user.set_password(self.password)
    user.save()

    # Creating a second user
    user2 = User(username='user2', password=self.password)
    user2.set_password(self.password)
    user2.save()

    # Creating a staff user
    staff_user = User(username='staff_user', password=self.password, is_staff=True)
    staff_user.set_password(self.password)
    staff_user.save()

    # Creating a superuser
    superuser = User(username='superuser', password=self.password, is_staff=True, is_superuser=True)
    superuser.set_password(self.password)
    superuser.save()



    # SUPERUSER

    self.client.logout()
    self.client.login(username=superuser.username, password=self.password)

    response = self.client.get(url)

    # HTTP_200_OK
    self.assertEqual(response.status_code, status.HTTP_200_OK)

    # All users contained in list
    self.assertEqual(response.data['extras']['total_results'], 4)



    # STAFF USER

    self.client.logout()
    self.client.login(username=staff_user.username, password=self.password)

    response = self.client.get(url)

    # HTTP_200_OK
    self.assertEqual(response.status_code, status.HTTP_200_OK)

    # Superuser cannot be contained in list
    self.assertEqual(response.data['extras']['total_results'], 3)



    # REGULAR USER

    self.client.logout()
    self.client.login(username=user2.username, password=self.password)

    response = self.client.get(url)

    # HTTP_200_OK
    self.assertEqual(response.status_code, status.HTTP_200_OK)

    # Only 1 user can be returned
    self.assertEqual(response.data['extras']['total_results'], 1)

    # User returned is current user
    self.assertEqual(response.data['users'][0]['username'], user2.username)

как вы видите, я тестирую разрешения пользователя в этом порядке: суперпользователь, персонал, обычный пользователь. И это работает, так что...

смешное:

если я изменяю порядок тестов и начинаю с обычного пользователя, персонала, суперпользователя, тесты терпят неудачу. Ответ от первого запроса кэшируется, а затем я получаю то же самое ответ при входе в систему как штатный пользователь, поэтому количество результатов снова равно 1.

версия, которая не работает:

это точно так же, как и раньше, только тесты сделаны в обратном порядке

def test_user_querysets(self):

    url = reverse('user-list')

    # Creating a user
    user = User(username='user', password=self.password)
    user.set_password(self.password)
    user.save()

    # Creating a second user
    user2 = User(username='user2', password=self.password)
    user2.set_password(self.password)
    user2.save()

    # Creating a staff user
    staff_user = User(username='staff_user', password=self.password, is_staff=True)
    staff_user.set_password(self.password)
    staff_user.save()

    # Creating a superuser
    superuser = User(username='superuser', password=self.password, is_staff=True, is_superuser=True)
    superuser.set_password(self.password)
    superuser.save()



    # REGULAR USER

    self.client.logout()
    self.client.login(username=user2.username, password=self.password)

    response = self.client.get(url)

    # HTTP_200_OK
    self.assertEqual(response.status_code, status.HTTP_200_OK)

    # Only 1 user can be returned
    self.assertEqual(response.data['extras']['total_results'], 1)

    # User returned is current user
    self.assertEqual(response.data['users'][0]['username'], user2.username)



    # STAFF USER

    self.client.logout()
    self.client.login(username=staff_user.username, password=self.password)

    response = self.client.get(url)

    # HTTP_200_OK
    self.assertEqual(response.status_code, status.HTTP_200_OK)

    # Superuser cannot be contained in list
    self.assertEqual(response.data['extras']['total_results'], 3)



    # SUPERUSER

    self.client.logout()
    self.client.login(username=superuser.username, password=self.password)

    response = self.client.get(url)

    # HTTP_200_OK
    self.assertEqual(response.status_code, status.HTTP_200_OK)

    # All users contained in list
    self.assertEqual(response.data['extras']['total_results'], 4)

Я работаю в python 2.7 со следующими версиями пакетов:

Django==1.8.6
djangorestframework==3.3.1
Markdown==2.6.4
MySQL-python==1.2.5
wheel==0.24.0

обновление

я использую кэш django по умолчанию, что означает, что я ничего не поставил о кэше в настройки django.

как было предложено, я попытался отключить кэш Django по умолчанию:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
    )
}

проблема остается.

хотя я не думаю, что проблема находится здесь, это мой UserViewSet:

api.py (важная часть)

class UserViewSet(  
    mixins.RetrieveModelMixin, 
    mixins.UpdateModelMixin,
    mixins.ListModelMixin,
    viewsets.GenericViewSet
):
    queryset = User.objects.all()
    serializer_class = UserExpenseSerializer
    permission_classes = (IsAuthenticated, )
    allowed_methods = ('GET', 'PATCH', 'OPTIONS', 'HEAD')

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return UserExpenseSerializer
        return UserSerializer

    def get_queryset(self):
        if(self.action == 'list'):
            return User.objects.all()
        if self.request.user.is_superuser:
            return User.objects.all()
        if self.request.user.is_staff:
            return User.objects.exclude(is_superuser=True)
        return User.objects.filter(pk = self.request.user.id)

    def list(self, request):
        filter_obj = UsersFilter(self.request)
        users = filter_obj.do_query()
        extras = filter_obj.get_extras()
        serializer = UserSerializer(users, context={'request' : request}, many=True)
        return Response({'users' : serializer.data, 'extras' : extras}, views.status.HTTP_200_OK)

filters.py

class UsersFilter:
    offset = 0
    limit = 50
    count = 0
    total_pages = 0
    filter_params = {}

    def __init__(self, request):

        if not request.user.is_superuser:
            self.filter_params['is_superuser'] = False

        if (not request.user.is_superuser and not request.user.is_staff):
            self.filter_params['pk'] = request.user.id

        # Read query params
        rpp = request.query_params.get('rpp') or 50
        page = request.query_params.get('page') or 1
        search_string = request.query_params.get('search')

        # Validate

        self.rpp = int(rpp) or 50
        self.page = int(page) or 1

        # Set filter
        set_if_not_none(self.filter_params, 'username__contains', search_string)

        # Count total results

        self.count = User.objects.filter(**self.filter_params).count()
        self.total_pages = int(self.count / self.rpp) + 1

        # Set limits
        self.offset = (self.page - 1) * self.rpp
        self.limit = self.page * self.rpp

    def get_filter_params(self):
        return self.filter_params

    def get_offset(self):
        return self.offset

    def get_limit(self):
        return self.limit

    def do_query(self):
        users = User.objects.filter(**self.filter_params)[self.offset:self.limit]
        return users

    def get_query_info(self):
        query_info = {
            'total_results' : self.count,
            'results_per_page' : self.rpp,
            'current_page' : self.page,
            'total_pages' : self.total_pages
        }
        return query_info

обновление 2

As Линовия указала, что проблема не в кэше или любой другой проблеме DRF, а в фильтре. Вот фиксированный класс фильтра:

class UsersFilter:

    def __init__(self, request):

        self.filter_params = {}
        self.offset = 0
        self.limit = 50
        self.count = 0
        self.total_pages = 0
        self.extras = {}

        if not request.user.is_superuser:
        # and so long...

2 ответов


фактически вы создаете нового пользователя, который должен сделать 2 пользователя, и вы утверждаете длину против 3. Не будет работать даже без кэширования.

изменить: Так что вы на самом деле у вас проблема, потому что использование mutables объектами на уровне классов.

вот злой код:

class UsersFilter:
    filter_params = {}

    def __init__(self, request):
        if not request.user.is_superuser:
            self.filter_params['is_superuser'] = False

который на самом деле должен быть:

class UsersFilter:
    def __init__(self, request):
        filter_params = {}
        if not request.user.is_superuser:
            self.filter_params['is_superuser'] = False

В Противном Случае UsersFilter.filter_params будет храниться от одного запроса к другому и никогда не сбрасывается. Видеть http://www.toptal.com/python/python-class-attributes-an-overly-thorough-guide для получения более подробной информации об этом.


вы можете отключить кэширование в режиме отладки, добавив это в свой settings.py

if DEBUG:
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
        }
    }

https://docs.djangoproject.com/en/dev/topics/cache/?from=olddocs/#dummy-caching-for-development

затем вы можете отключить кэширование при помощи кнопок DEBUG на settings.py или с помощью отдельной разработки /тестирования и / развертывания settings.py файлы.


если вы хотите избежать отдельных файлов или переключения, то вы можете установить DEBUG true для некоторых тестов с the override_settings оформитель:

from django.test.utils import override_settings
from django.test import TestCase
from django.conf import settings

class MyTest(TestCase):
    @override_settings(DEBUG=True)
    def test_debug(self):
        self.assertTrue(settings.DEBUG)