diff --git a/cookbook/tests/api/test_api_keyword.py b/cookbook/tests/api/test_api_keyword.py index 6865f214..90fb68c3 100644 --- a/cookbook/tests/api/test_api_keyword.py +++ b/cookbook/tests/api/test_api_keyword.py @@ -44,14 +44,12 @@ class TestApiKeyword(TestViews): self.assertEqual(len(response), 1) def test_keyword_update(self): - # can update storage as admin - r = self.admin_client_1.patch(reverse('api:keyword-detail', args={self.keyword_1.id}), {'name': 'new'}, content_type='application/json') + r = self.user_client_1.patch(reverse('api:keyword-detail', args={self.keyword_1.id}), {'name': 'new'}, content_type='application/json') response = json.loads(r.content) self.assertEqual(r.status_code, 200) self.assertEqual(response['name'], 'new') def test_keyword_delete(self): - # can delete storage as admin - r = self.admin_client_1.delete(reverse('api:keyword-detail', args={self.keyword_1.id})) + r = self.user_client_1.delete(reverse('api:keyword-detail', args={self.keyword_1.id})) self.assertEqual(r.status_code, 204) self.assertEqual(Keyword.objects.count(), 1) diff --git a/cookbook/tests/api/test_api_unit.py b/cookbook/tests/api/test_api_unit.py new file mode 100644 index 00000000..0eec36c9 --- /dev/null +++ b/cookbook/tests/api/test_api_unit.py @@ -0,0 +1,53 @@ +import json + +from django.urls import reverse + +from cookbook.models import Unit +from cookbook.tests.views.test_views import TestViews + + +class TestApiUnit(TestViews): + + def setUp(self): + super(TestApiUnit, self).setUp() + self.unit_1 = Unit.objects.create( + name='Beef' + ) + self.unit_2 = Unit.objects.create( + name='Chicken' + ) + + def test_keyword_list(self): + # verify view permissions are applied accordingly + self.batch_requests([(self.anonymous_client, 403), (self.guest_client_1, 403), (self.user_client_1, 200), (self.admin_client_1, 200), (self.superuser_client, 200)], + reverse('api:unit-list')) + + # verify storage is returned + r = self.user_client_1.get(reverse('api:unit-list')) + self.assertEqual(r.status_code, 200) + response = json.loads(r.content) + self.assertEqual(len(response), 2) + self.assertEqual(response[0]['name'], self.unit_1.name) + + r = self.user_client_1.get(f'{reverse("api:unit-list")}?limit=1') + response = json.loads(r.content) + self.assertEqual(len(response), 1) + + r = self.user_client_1.get(f'{reverse("api:unit-list")}?query=Pork') + response = json.loads(r.content) + self.assertEqual(len(response), 0) + + r = self.user_client_1.get(f'{reverse("api:unit-list")}?query=Beef') + response = json.loads(r.content) + self.assertEqual(len(response), 1) + + def test_keyword_update(self): + r = self.user_client_1.patch(reverse('api:unit-detail', args={self.unit_1.id}), {'name': 'new'}, content_type='application/json') + response = json.loads(r.content) + self.assertEqual(r.status_code, 200) + self.assertEqual(response['name'], 'new') + + def test_keyword_delete(self): + r = self.user_client_1.delete(reverse('api:unit-detail', args={self.unit_1.id})) + self.assertEqual(r.status_code, 204) + self.assertEqual(Unit.objects.count(), 1) diff --git a/cookbook/urls.py b/cookbook/urls.py index e267057e..687b3596 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -15,8 +15,8 @@ router.register(r'storage', api.StorageViewSet) router.register(r'sync', api.SyncViewSet) router.register(r'sync-log', api.SyncLogViewSet) router.register(r'keyword', api.KeywordViewSet) - router.register(r'unit', api.UnitViewSet) + router.register(r'food', api.FoodViewSet) router.register(r'step', api.StepViewSet) router.register(r'recipe', api.RecipeViewSet) diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 13a9adda..9ef92232 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -14,12 +14,14 @@ from django.db.models import Q from django.http import HttpResponse, FileResponse, JsonResponse from django.shortcuts import redirect from django.utils.translation import gettext as _ +from django.views.generic.base import View from icalendar import Calendar, Event from rest_framework import viewsets, permissions, decorators from rest_framework.exceptions import APIException from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, ListModelMixin from rest_framework.parsers import JSONParser, FileUploadParser, MultiPartParser from rest_framework.response import Response +from rest_framework.viewsets import ViewSetMixin from cookbook.helper.permission_helper import group_required, CustomIsOwner, CustomIsAdmin, CustomIsUser, CustomIsGuest from cookbook.helper.recipe_url_import import get_from_html @@ -89,7 +91,21 @@ class SyncLogViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [CustomIsAdmin, ] -class KeywordViewSet(viewsets.ModelViewSet): +class StandardFilterMixin(ViewSetMixin): + + def get_queryset(self): + queryset = self.queryset + query = self.request.query_params.get('query', None) + if query is not None: + queryset = queryset.filter(name__icontains=query) + + limit = self.request.query_params.get('limit', None) + if limit is not None: + queryset = queryset[:int(limit)] + return queryset + + +class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin): """ list: optional parameters @@ -101,16 +117,11 @@ class KeywordViewSet(viewsets.ModelViewSet): serializer_class = KeywordSerializer permission_classes = [CustomIsUser] - def get_queryset(self): # TODO create standard filter/limit mixin - queryset = Keyword.objects.all() - query = self.request.query_params.get('query', None) - if query is not None: - queryset = queryset.filter(name__icontains=query) - limit = self.request.query_params.get('limit', None) - if limit is not None: - queryset = queryset[:int(limit)] - return queryset +class UnitViewSet(viewsets.ModelViewSet, StandardFilterMixin): + queryset = Unit.objects.all() + serializer_class = FoodSerializer + permission_classes = [CustomIsUser] class RecipeBookViewSet(RetrieveModelMixin, UpdateModelMixin, ListModelMixin, viewsets.GenericViewSet): @@ -159,39 +170,11 @@ class MealTypeViewSet(viewsets.ModelViewSet): return queryset -class UnitViewSet(viewsets.ModelViewSet): - queryset = Unit.objects.all() - serializer_class = FoodSerializer - permission_classes = [CustomIsUser] - - def get_queryset(self): # TODO create standard filter/limit mixin - queryset = self.queryset - query = self.request.query_params.get('query', None) - if query is not None: - queryset = queryset.filter(name__icontains=query) - - limit = self.request.query_params.get('limit', None) - if limit is not None: - queryset = queryset[:int(limit)] - return queryset - - -class FoodViewSet(viewsets.ModelViewSet): +class FoodViewSet(viewsets.ModelViewSet, StandardFilterMixin): queryset = Food.objects.all() serializer_class = FoodSerializer permission_classes = [CustomIsUser] - def get_queryset(self): # TODO create standard filter/limit mixin - queryset = self.queryset - query = self.request.query_params.get('query', None) - if query is not None: - queryset = queryset.filter(name__icontains=query) - - limit = self.request.query_params.get('limit', None) - if limit is not None: - queryset = queryset[:int(limit)] - return queryset - class IngredientViewSet(viewsets.ModelViewSet): queryset = Ingredient.objects.all() @@ -205,7 +188,7 @@ class StepViewSet(viewsets.ModelViewSet): permission_classes = [CustomIsUser] -class RecipeViewSet(viewsets.ModelViewSet): +class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin): """ list: optional parameters @@ -217,18 +200,6 @@ class RecipeViewSet(viewsets.ModelViewSet): serializer_class = RecipeSerializer permission_classes = [permissions.IsAuthenticated] # TODO split read and write permission for meal plan guest - def get_queryset(self): # TODO create standard filter/limit mixin - queryset = self.queryset - - query = self.request.query_params.get('query', None) - if query is not None: - queryset = queryset.filter(name__icontains=query) - - limit = self.request.query_params.get('limit', None) - if limit is not None: - queryset = queryset[:int(limit)] - return queryset - @decorators.action( detail=True, methods=['PUT'],