test shoppingFood API

This commit is contained in:
smilerz 2021-12-15 07:24:12 -06:00
parent 1364f75f21
commit ee4ab41c1c
5 changed files with 123 additions and 22 deletions

View File

@ -104,18 +104,18 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None
x_ing = Ingredient.objects.filter(step__recipe=x, food__on_hand=False, space=space) x_ing = Ingredient.objects.filter(step__recipe=x, food__on_hand=False, space=space)
else: else:
x_ing = Ingredient.objects.filter(step__recipe=x, space=space) x_ing = Ingredient.objects.filter(step__recipe=x, space=space)
for i in [x for x in x_ing if not x.food.ignore_shopping]: for i in [x for x in x_ing if not x.food.ignore_shopping]:
ShoppingListEntry.objects.create( ShoppingListEntry.objects.create(
list_recipe=list_recipe, list_recipe=list_recipe,
food=i.food, food=i.food,
unit=i.unit, unit=i.unit,
ingredient=i, ingredient=i,
amount=i.amount * Decimal(servings_factor), amount=i.amount * Decimal(servings_factor),
created_by=created_by, created_by=created_by,
space=space, space=space,
) )
# dont' add food to the shopping list that are actually recipes that will be added as ingredients # dont' add food to the shopping list that are actually recipes that will be added as ingredients
ingredients = ingredients.exclude(food__recipe=x) ingredients = ingredients.exclude(food__recipe=x)
add_ingredients = list(ingredients.values_list('id', flat=True)) + related_step_ing add_ingredients = list(ingredients.values_list('id', flat=True)) + related_step_ing
if not append: if not append:

View File

@ -0,0 +1,92 @@
# test create
# test create units
# test amounts
# test create wrong space
# test sharing
# test delete
# test delete checked (nothing should happen)
# test delete not shared (nothing happens)
# test delete shared
import json
import pytest
from django.contrib import auth
from django.urls import reverse
from django_scopes import scope, scopes_disabled
from cookbook.models import Food, ShoppingListEntry
from cookbook.tests.factories import FoodFactory
SHOPPING_LIST_URL = 'api:shoppinglistentry-list'
SHOPPING_FOOD_URL = 'api:food-shopping'
@pytest.fixture()
def food(request, space_1, u1_s1):
return FoodFactory(space=space_1)
def test_shopping_forbidden_methods(food, u1_s1):
r = u1_s1.post(
reverse(SHOPPING_FOOD_URL, args={food.id}))
assert r.status_code == 405
r = u1_s1.delete(
reverse(SHOPPING_FOOD_URL, args={food.id}))
assert r.status_code == 405
r = u1_s1.get(
reverse(SHOPPING_FOOD_URL, args={food.id}))
assert r.status_code == 405
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 204],
['u1_s2', 404],
['a1_s1', 204],
])
def test_shopping_food_create(request, arg, food):
c = request.getfixturevalue(arg[0])
r = c.put(reverse(SHOPPING_FOOD_URL, args={food.id}))
assert r.status_code == arg[1]
if r.status_code == 204:
assert len(json.loads(c.get(reverse(SHOPPING_LIST_URL)).content)) == 1
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 204],
['u1_s2', 404],
['a1_s1', 204],
])
def test_shopping_food_delete(request, arg, food):
c = request.getfixturevalue(arg[0])
r = c.put(
reverse(SHOPPING_FOOD_URL, args={food.id}),
{'_delete': "true"},
content_type='application/json'
)
assert r.status_code == arg[1]
if r.status_code == 204:
assert len(json.loads(c.get(reverse(SHOPPING_LIST_URL)).content)) == 0
def test_shopping_food_share(u1_s1, u2_s1, food, space_1):
with scope(space=space_1):
food2 = FoodFactory(space=space_1)
r = u1_s1.put(reverse(SHOPPING_FOOD_URL, args={food.id}))
r = u2_s1.put(reverse(SHOPPING_FOOD_URL, args={food.id}))
assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 1
assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 1
with scopes_disabled():
user = auth.get_user(u1_s1)
user2 = auth.get_user(u2_s1)
user.userpreference.shopping_share.add(user2)
user.userpreference.save()
assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 1
assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 2

View File

@ -10,7 +10,7 @@ from django.utils import timezone
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pytest_factoryboy import LazyFixture, register from pytest_factoryboy import LazyFixture, register
from cookbook.models import Food, Ingredient, ShoppingListEntry from cookbook.models import Food, Ingredient, ShoppingListEntry, Step
from cookbook.tests.factories import MealPlanFactory, RecipeFactory, StepFactory, UserFactory from cookbook.tests.factories import MealPlanFactory, RecipeFactory, StepFactory, UserFactory
SHOPPING_LIST_URL = 'api:shoppinglistentry-list' SHOPPING_LIST_URL = 'api:shoppinglistentry-list'
@ -176,8 +176,9 @@ def test_shopping_recipe_edit(request, recipe, sle_count, use_mealplan, u1_s1, u
@pytest.mark.parametrize("user2, sle_count", [ @pytest.mark.parametrize("user2, sle_count", [
({'mealplan_autoadd_shopping': False}, (0, 17)), ({'mealplan_autoadd_shopping': False}, (0, 17)),
({'mealplan_autoinclude_related': False}, (7, 7)), ({'mealplan_autoinclude_related': False}, (8, 8)),
({'mealplan_autoexclude_onhand': False}, (19, 19)), ({'mealplan_autoexclude_onhand': False}, (19, 19)),
({'mealplan_autoexclude_onhand': False, 'mealplan_autoinclude_related': False}, (9, 9)),
], indirect=['user2']) ], indirect=['user2'])
@pytest.mark.parametrize("use_mealplan", [(False), (True), ]) @pytest.mark.parametrize("use_mealplan", [(False), (True), ])
@pytest.mark.parametrize("recipe", [({'steps__recipe_count': 1})], indirect=['recipe']) @pytest.mark.parametrize("recipe", [({'steps__recipe_count': 1})], indirect=['recipe'])
@ -186,13 +187,14 @@ def test_shopping_recipe_userpreference(recipe, sle_count, use_mealplan, user2):
user = auth.get_user(user2) user = auth.get_user(user2)
# setup recipe with 10 ingredients, 1 step recipe with 10 ingredients, 2 food onhand(from recipe and step_recipe), 1 food ignore shopping # setup recipe with 10 ingredients, 1 step recipe with 10 ingredients, 2 food onhand(from recipe and step_recipe), 1 food ignore shopping
ingredients = Ingredient.objects.filter(step__recipe=recipe) ingredients = Ingredient.objects.filter(step__recipe=recipe)
food = Food.objects.get(id=ingredients[0].food.id)
food.on_hand = True
food.save()
food = Step.objects.filter(type=Step.RECIPE).first().ingredients.first()
food.on_hand = True
food.save()
food = Food.objects.get(id=ingredients[2].food.id) food = Food.objects.get(id=ingredients[2].food.id)
food.on_hand = True
food.save()
food = recipe.steps.filter(type=Step.RECIPE).first().step_recipe.steps.first().ingredients.first().food
food = Food.objects.get(id=food.id)
food.on_hand = True
food.save()
food = Food.objects.get(id=ingredients[4].food.id)
food.ignore_shopping = True food.ignore_shopping = True
food.save() food.save()

View File

@ -413,7 +413,10 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
pagination_class = DefaultPagination pagination_class = DefaultPagination
@decorators.action(detail=True, methods=['PUT'], serializer_class=FoodShoppingUpdateSerializer,) @decorators.action(detail=True, methods=['PUT'], serializer_class=FoodShoppingUpdateSerializer,)
# TODO DRF only allows one action in a decorator action without overriding get_operation_id_base() this should be PUT and DELETE probably
def shopping(self, request, pk): def shopping(self, request, pk):
if self.request.space.demo:
raise PermissionDenied(detail='Not available in demo', code=None)
obj = self.get_object() obj = self.get_object()
shared_users = list(self.request.user.get_shopping_share()) shared_users = list(self.request.user.get_shopping_share())
shared_users.append(request.user) shared_users.append(request.user)
@ -652,12 +655,16 @@ class RecipeViewSet(viewsets.ModelViewSet):
return Response(serializer.data) return Response(serializer.data)
return Response(serializer.errors, 400) return Response(serializer.errors, 400)
# TODO: refactor API to use post/put/delete or leave as put and change VUE to use list_recipe after creating
# DRF only allows one action in a decorator action without overriding get_operation_id_base()
@decorators.action( @decorators.action(
detail=True, detail=True,
methods=['PUT'], methods=['PUT'],
serializer_class=RecipeShoppingUpdateSerializer, serializer_class=RecipeShoppingUpdateSerializer,
) )
def shopping(self, request, pk): def shopping(self, request, pk):
if self.request.space.demo:
raise PermissionDenied(detail='Not available in demo', code=None)
obj = self.get_object() obj = self.get_object()
ingredients = request.data.get('ingredients', None) ingredients = request.data.get('ingredients', None)
servings = request.data.get('servings', None) servings = request.data.get('servings', None)

View File

@ -254,13 +254,13 @@ def latest_shopping_list(request):
@group_required('user') @group_required('user')
def shopping_list(request, pk=None): def shopping_list(request, pk=None): # TODO deprecate
html_list = request.GET.getlist('r') html_list = request.GET.getlist('r')
recipes = [] recipes = []
for r in html_list: for r in html_list:
r = r.replace('[', '').replace(']', '') r = r.replace('[', '').replace(']', '')
if re.match(r'^([0-9])+,([0-9])+[.]*([0-9])*$', r): if re.match(r'^([0-9])+,([0-9])+[.]*([0-9])*$', r): # vulnerable to DoS
rid, multiplier = r.split(',') rid, multiplier = r.split(',')
if recipe := Recipe.objects.filter(pk=int(rid), space=request.space).first(): if recipe := Recipe.objects.filter(pk=int(rid), space=request.space).first():
recipes.append({'recipe': recipe.id, 'multiplier': multiplier}) recipes.append({'recipe': recipe.id, 'multiplier': multiplier})