diff --git a/cookbook/models.py b/cookbook/models.py index e89e7745a..cd335f7ca 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -459,15 +459,11 @@ class MealType(models.Model, PermissionModelMixin): class MealPlan(models.Model, PermissionModelMixin): - recipe = models.ForeignKey( - Recipe, on_delete=models.CASCADE, blank=True, null=True - ) + recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True) servings = models.DecimalField(default=1, max_digits=8, decimal_places=4) title = models.CharField(max_length=64, blank=True, default='') created_by = models.ForeignKey(User, on_delete=models.CASCADE) - shared = models.ManyToManyField( - User, blank=True, related_name='plan_share' - ) + shared = models.ManyToManyField(User, blank=True, related_name='plan_share') meal_type = models.ForeignKey(MealType, on_delete=models.CASCADE) note = models.TextField(blank=True) date = models.DateField() diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 60a601f0c..f736538be 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -47,10 +47,15 @@ class SpacedModelSerializer(serializers.ModelSerializer): class MealTypeSerializer(SpacedModelSerializer): + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + return super().create(validated_data) + class Meta: model = MealType fields = ('id', 'name', 'order', 'created_by') - read_only_fields = ('space',) + read_only_fields = ('created_by',) class UserNameSerializer(WritableNestedModelSerializer): @@ -303,6 +308,10 @@ class MealPlanSerializer(SpacedModelSerializer): def get_note_markdown(self, obj): return markdown(obj.note) + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + return super().create(validated_data) + class Meta: model = MealPlan fields = ( @@ -310,6 +319,7 @@ class MealPlanSerializer(SpacedModelSerializer): 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name' ) + read_only_fields = ('created_by',) class ShoppingListRecipeSerializer(SpacedModelSerializer): diff --git a/cookbook/tests/api/test_api_meal_plan.py b/cookbook/tests/api/test_api_meal_plan.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cookbook/tests/api/test_api_meal_type.py b/cookbook/tests/api/test_api_meal_type.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cookbook/tests/pytest/api/test_api_meal_plan.py b/cookbook/tests/pytest/api/test_api_meal_plan.py new file mode 100644 index 000000000..a5227112d --- /dev/null +++ b/cookbook/tests/pytest/api/test_api_meal_plan.py @@ -0,0 +1,134 @@ +import json +from datetime import datetime, timedelta + +import pytest +from django.contrib import auth +from django.urls import reverse +from django_scopes import scopes_disabled + +from cookbook.models import Food, MealPlan, MealType + +LIST_URL = 'api:mealplan-list' +DETAIL_URL = 'api:mealplan-detail' + + +@pytest.fixture() +def meal_type(space_1, u1_s1): + return MealType.objects.get_or_create(name='test', space=space_1, created_by=auth.get_user(u1_s1))[0] + + +@pytest.fixture() +def obj_1(space_1, recipe_1_s1, meal_type, u1_s1): + return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, date=datetime.now(), created_by=auth.get_user(u1_s1)) + + +@pytest.fixture +def obj_2(space_1, recipe_1_s1, meal_type, u1_s1): + return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, date=datetime.now(), created_by=auth.get_user(u1_s1)) + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 200], + ['u1_s1', 200], + ['a1_s1', 200], +]) +def test_list_permission(arg, request): + c = request.getfixturevalue(arg[0]) + assert c.get(reverse(LIST_URL)).status_code == arg[1] + + +def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2): + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + obj_1.space = space_2 + obj_1.save() + + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + +def test_list_filter(obj_1, u1_s1): + r = u1_s1.get(reverse(LIST_URL)) + assert r.status_code == 200 + response = json.loads(r.content) + assert len(response) == 1 + + response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?from_date={(datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d")}').content) + assert len(response) == 0 + + response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?to_date={(datetime.now() - timedelta(days=2)).strftime("%Y-%m-%d")}').content) + assert len(response) == 0 + + response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?from_date={(datetime.now() - timedelta(days=2)).strftime("%Y-%m-%d")}&to_date={(datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d")}').content) + assert len(response) == 1 + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 404], + ['u1_s1', 200], + ['a1_s1', 404], + ['g1_s2', 404], + ['u1_s2', 404], + ['a1_s2', 404], +]) +def test_update(arg, request, obj_1): + c = request.getfixturevalue(arg[0]) + r = c.patch( + reverse( + DETAIL_URL, + args={obj_1.id} + ), + {'title': 'new'}, + content_type='application/json' + ) + response = json.loads(r.content) + assert r.status_code == arg[1] + if r.status_code == 200: + assert response['title'] == 'new' + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 201], + ['u1_s1', 201], + ['a1_s1', 201], +]) +def test_add(arg, request, u1_s2, recipe_1_s1, meal_type): + c = request.getfixturevalue(arg[0]) + r = c.post( + reverse(LIST_URL), + {'recipe': recipe_1_s1.id, 'meal_type': meal_type.id, 'date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test'}, + content_type='application/json' + ) + response = json.loads(r.content) + assert r.status_code == arg[1] + if r.status_code == 201: + assert response['title'] == 'test' + r = c.get(reverse(DETAIL_URL, args={response['id']})) + assert r.status_code == 200 + r = u1_s2.get(reverse(DETAIL_URL, args={response['id']})) + assert r.status_code == 404 + + +def test_delete(u1_s1, u1_s2, obj_1): + r = u1_s2.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + assert r.status_code == 404 + + r = u1_s1.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + + assert r.status_code == 204 + with scopes_disabled(): + assert MealPlan.objects.count() == 0 diff --git a/cookbook/tests/pytest/api/test_api_meal_type.py b/cookbook/tests/pytest/api/test_api_meal_type.py new file mode 100644 index 000000000..61fae730b --- /dev/null +++ b/cookbook/tests/pytest/api/test_api_meal_type.py @@ -0,0 +1,132 @@ +import json + +import pytest +from django.contrib import auth +from django.urls import reverse +from django_scopes import scopes_disabled + +from cookbook.models import Food, MealType + +LIST_URL = 'api:mealtype-list' +DETAIL_URL = 'api:mealtype-detail' + + +@pytest.fixture() +def obj_1(space_1, u1_s1): + return MealType.objects.get_or_create(name='test_1', created_by=auth.get_user(u1_s1), space=space_1)[0] + + +@pytest.fixture +def obj_2(space_1, u1_s1): + return MealType.objects.get_or_create(name='test_2', created_by=auth.get_user(u1_s1), space=space_1)[0] + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 200], + ['u1_s1', 200], + ['a1_s1', 200], +]) +def test_list_permission(arg, request): + c = request.getfixturevalue(arg[0]) + assert c.get(reverse(LIST_URL)).status_code == arg[1] + + +def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2): + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + obj_1.space = space_2 + obj_1.save() + + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 403], + ['u1_s1', 200], + ['a1_s1', 200], + ['g1_s2', 403], + ['u1_s2', 404], + ['a1_s2', 404], +]) +def test_update(arg, request, obj_1): + c = request.getfixturevalue(arg[0]) + r = c.patch( + reverse( + DETAIL_URL, + args={obj_1.id} + ), + {'name': 'new'}, + content_type='application/json' + ) + response = json.loads(r.content) + assert r.status_code == arg[1] + if r.status_code == 200: + assert response['name'] == 'new' + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 201], + ['u1_s1', 201], + ['a1_s1', 201], +]) +def test_add(arg, request, u1_s2): + c = request.getfixturevalue(arg[0]) + r = c.post( + reverse(LIST_URL), + {'name': 'test'}, + content_type='application/json' + ) + response = json.loads(r.content) + assert r.status_code == arg[1] + if r.status_code == 201: + assert response['name'] == 'test' + r = c.get(reverse(DETAIL_URL, args={response['id']})) + assert r.status_code == 200 + r = u1_s2.get(reverse(DETAIL_URL, args={response['id']})) + assert r.status_code == 404 + +# TODO make name in space unique +# def test_add_duplicate(u1_s1, u1_s2, obj_1): +# r = u1_s1.post( +# reverse(LIST_URL), +# {'name': obj_1.name}, +# content_type='application/json' +# ) +# response = json.loads(r.content) +# assert r.status_code == 201 +# assert response['id'] == obj_1.id +# +# r = u1_s2.post( +# reverse(LIST_URL), +# {'name': obj_1.name}, +# content_type='application/json' +# ) +# response = json.loads(r.content) +# assert r.status_code == 201 +# assert response['id'] != obj_1.id + + +def test_delete(u1_s1, u1_s2, obj_1): + r = u1_s2.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + assert r.status_code == 404 + + r = u1_s1.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + + assert r.status_code == 204 + with scopes_disabled(): + assert Food.objects.count() == 0