Skip to content

Commit 2cb89bd

Browse files
author
Bobur Yusupov
committed
Finished the project
1 parent 2f1e9e3 commit 2cb89bd

6 files changed

+202
-5
lines changed

media/test

Whitespace-only changes.
Binary file not shown.

recipe/test/test_ingredients_api.py

+45-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
from django.contrib.auth import get_user_model
55
from django.urls import reverse
66
from django.test import TestCase
7+
from decimal import Decimal
78

89
from rest_framework import status
910
from rest_framework.test import APIClient
1011

1112
from core.models import (
1213
Ingredient,
14+
Recipe,
1315
)
1416
from recipe.serializers import (
1517
IngredientSerializer
@@ -93,4 +95,46 @@ def test_delete_ingredient_url(self):
9395

9496
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
9597
ingredients = Ingredient.objects.filter(user=self.user)
96-
self.assertFalse(ingredients.exists())
98+
self.assertFalse(ingredients.exists())
99+
100+
def test_filter_ingredients_assigned_to_recipes(self):
101+
"""Test listing ingridients by those assigned to recipes."""
102+
ing1 = Ingredient.objects.create(user=self.user, name="Apple")
103+
ing2 = Ingredient.objects.create(user=self.user, name="Banana")
104+
recipe = Recipe.objects.create(
105+
title="Apple cake",
106+
time_minutes=5,
107+
price=Decimal("4.5"),
108+
user=self.user,
109+
)
110+
recipe.ingredients.add(ing1)
111+
112+
response = self.client.get(INGREDIENT_URL, {'assigned_only': 1})
113+
114+
s1 = IngredientSerializer(ing1)
115+
s2 = IngredientSerializer(ing2)
116+
self.assertIn(s1.data, response.data)
117+
self.assertNotIn(s2.data, response.data)
118+
119+
def test_filtered_ingredients_unique(self):
120+
"""Test filtered ingredients returns a unique list."""
121+
ing = Ingredient.objects.create(user=self.user, name="Egg")
122+
Ingredient.objects.create(user=self.user, name="Lentils")
123+
recipe1 = Recipe.objects.create(
124+
title="Egg Benedict",
125+
time_minutes=20,
126+
price=Decimal("20"),
127+
user=self.user,
128+
)
129+
recipe2 = Recipe.objects.create(
130+
title="Herb Eggs",
131+
time_minutes=60,
132+
price=Decimal("4.0"),
133+
user=self.user,
134+
)
135+
recipe1.ingredients.add(ing)
136+
recipe2.ingredients.add(ing)
137+
138+
response = self.client.get(INGREDIENT_URL, {'assigned_only': 1})
139+
140+
self.assertEqual(len(response.data), 1)

recipe/test/test_recipe_api.py

+47
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,53 @@ def test_clear_recipe_ingredients(self):
421421
self.assertEqual(response.status_code, status.HTTP_200_OK)
422422
self.assertEqual(recipe.ingredients.count(), 0)
423423

424+
def test_filter_by_tags(self):
425+
"""Test filtering recipes by tags."""
426+
r1 = create_recipe(user=self.user, title="Uzbek Palov")
427+
r2 = create_recipe(user=self.user, title="Tajik Palov")
428+
tag1 = Tag.objects.create(user=self.user, name="Palov")
429+
tag2 = Tag.objects.create(user=self.user, name="National")
430+
r1.tags.add(tag1)
431+
r2.tags.add(tag2)
432+
r3 = create_recipe(user=self.user, title="Kurutob")
433+
434+
params = {
435+
'tags': f'{tag1.id},{tag2.id}'
436+
}
437+
response = self.client.get(RECIPE_URL, params)
438+
439+
s1 = RecipeSerializer(r1)
440+
s2 = RecipeSerializer(r2)
441+
s3 = RecipeSerializer(r3)
442+
443+
self.assertIn(s1.data, response.data)
444+
self.assertIn(s2.data, response.data)
445+
self.assertNotIn(s3.data, response.data)
446+
447+
def test_filter_by_ingredients(self):
448+
"""Test filtering recipes by tags."""
449+
r1 = create_recipe(user=self.user, title="Shashlik")
450+
r2 = create_recipe(user=self.user, title="Kebab")
451+
ing1 = Ingredient.objects.create(user=self.user, name="Meat")
452+
ing2 = Ingredient.objects.create(user=self.user, name="Onion")
453+
r1.ingredients.add(ing1)
454+
r2.ingredients.add(ing2)
455+
r3 = create_recipe(user=self.user, title="Kurutob")
456+
457+
params = {
458+
'ingredients': f'{ing1.id},{ing2.id}'
459+
}
460+
response = self.client.get(RECIPE_URL, params)
461+
462+
s1 = RecipeSerializer(r1)
463+
s2 = RecipeSerializer(r2)
464+
s3 = RecipeSerializer(r3)
465+
466+
self.assertIn(s1.data, response.data)
467+
self.assertIn(s2.data, response.data)
468+
self.assertNotIn(s3.data, response.data)
469+
470+
424471
class ImageUploadTests(TestCase):
425472
"""Tests for image upload APIs."""
426473
def setUp(self):

recipe/test/test_tags_api.py

+48-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
"""
22
Test for the tag API
33
"""
4+
from decimal import Decimal
45
from django.contrib.auth import get_user_model
56
from django.urls import reverse
67
from django.test import TestCase
78

89
from rest_framework import status
910
from rest_framework.test import APIClient
1011

11-
from core.models import Tag
12+
from core.models import (
13+
Tag,
14+
Recipe,
15+
)
1216

1317
from recipe.serializers import TagSerializer
1418

@@ -91,4 +95,46 @@ def test_delete_tag(self):
9195

9296
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
9397
tags = Tag.objects.filter(user=self.user)
94-
self.assertFalse(tags.exists())
98+
self.assertFalse(tags.exists())
99+
100+
def test_filter_tags_assigned_to_recipes(self):
101+
"""Test listing ingridients by those assigned to recipes."""
102+
tag1 = Tag.objects.create(user=self.user, name="Apple")
103+
tag2 = Tag.objects.create(user=self.user, name="Banana")
104+
recipe = Recipe.objects.create(
105+
title="Apple cake",
106+
time_minutes=5,
107+
price=Decimal("4.5"),
108+
user=self.user,
109+
)
110+
recipe.tags.add(tag1)
111+
112+
response = self.client.get(TAGS_URL, {'assigned_only': 1})
113+
114+
s1 = TagSerializer(tag1)
115+
s2 = TagSerializer(tag2)
116+
self.assertIn(s1.data, response.data)
117+
self.assertNotIn(s2.data, response.data)
118+
119+
def test_filtered_tags_unique(self):
120+
"""Test filtered ingredients returns a unique list."""
121+
tag = Tag.objects.create(user=self.user, name="Egg")
122+
Tag.objects.create(user=self.user, name="Lentils")
123+
recipe1 = Recipe.objects.create(
124+
title="Egg Benedict",
125+
time_minutes=20,
126+
price=Decimal("20"),
127+
user=self.user,
128+
)
129+
recipe2 = Recipe.objects.create(
130+
title="Herb Eggs",
131+
time_minutes=60,
132+
price=Decimal("4.0"),
133+
user=self.user,
134+
)
135+
recipe1.tags.add(tag)
136+
recipe2.tags.add(tag)
137+
138+
response = self.client.get(TAGS_URL, {'assigned_only': 1})
139+
140+
self.assertEqual(len(response.data), 1)

recipe/views.py

+62-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
"""
22
Views for the recipe APIs.
33
"""
4+
from drf_spectacular.utils import (
5+
extend_schema,
6+
extend_schema_view,
7+
OpenApiParameter,
8+
OpenApiTypes,
9+
)
410
from rest_framework.viewsets import (
511
ModelViewSet,
612
GenericViewSet,
@@ -29,6 +35,22 @@
2935
RecipeImageSerializer,
3036
)
3137

38+
@extend_schema_view(
39+
list=extend_schema(
40+
parameters=[
41+
OpenApiParameter(
42+
'tags',
43+
OpenApiTypes.STR,
44+
description="Comma separatedlist of IDs to filter",
45+
),
46+
OpenApiParameter(
47+
'ingredients',
48+
OpenApiTypes.STR,
49+
description="Comma separated list of ingredient IDs to filter",
50+
)
51+
]
52+
)
53+
)
3254

3355
class RecipeViewSet(ModelViewSet):
3456
"""View for manage recipe APIs."""
@@ -37,9 +59,26 @@ class RecipeViewSet(ModelViewSet):
3759
authentication_classes = [TokenAuthentication]
3860
permission_classes = [IsAuthenticated]
3961

62+
def _params_to_ints(self, qs):
63+
"""Convert a list of strings into integers."""
64+
return [int(str_id) for str_id in qs.split(',')]
65+
4066
def get_queryset(self):
4167
"""Retrieve recipes for authenticated user."""
42-
return self.queryset.filter(user=self.request.user).order_by("-id")
68+
# return self.queryset.filter(user=self.request.user).order_by("-id")
69+
tags = self.request.query_params.get('tags')
70+
ingredients = self.request.query_params.get('ingredients')
71+
queryset = self.queryset
72+
if tags:
73+
tag_ids = self._params_to_ints(tags)
74+
queryset = queryset.filter(tags__id__in=tag_ids)
75+
if ingredients:
76+
ingredient_ids = self._params_to_ints(ingredients)
77+
queryset = queryset.filter(ingredients__id__in=ingredient_ids)
78+
79+
return queryset.filter(
80+
user=self.request.user
81+
).order_by("-id").distinct()
4382

4483
def get_serializer_class(self):
4584
"""Return the serializer class for request."""
@@ -65,6 +104,18 @@ def upload_image(self, request, pk=None):
65104
return Response(serializer.data, status=status.HTTP_200_OK)
66105
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
67106

107+
@extend_schema_view(
108+
list=extend_schema(
109+
parameters=[
110+
OpenApiParameter(
111+
'assigned_only',
112+
OpenApiTypes.INT, enum=[0, 1],
113+
description="Filter by items assigned to recipes."
114+
)
115+
]
116+
)
117+
)
118+
68119
class BaseRecipeAttrViewSet(ListModelMixin,
69120
GenericViewSet,
70121
UpdateModelMixin,
@@ -75,7 +126,16 @@ class BaseRecipeAttrViewSet(ListModelMixin,
75126

76127
def get_queryset(self):
77128
"""Filter queryset for authenticated user."""
78-
return self.queryset.filter(user=self.request.user).order_by('-name')
129+
assigned_only = bool(
130+
int(self.request.query_params.get('assigned_only', 0))
131+
)
132+
queryset = self.queryset
133+
if assigned_only:
134+
queryset = queryset.filter(recipe__isnull=False)
135+
136+
return queryset.filter(
137+
user=self.request.user
138+
).order_by('-name').distinct()
79139

80140
class TagViewSet(BaseRecipeAttrViewSet):
81141
"""Manage tags in database."""

0 commit comments

Comments
 (0)