Implementación de filtros por tag y recetas
This commit is contained in:
parent
d20158552c
commit
e22bb8f062
1052
01_user_api.md
Normal file
1052
01_user_api.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -539,8 +539,10 @@ URL `localhost:8000/api/docs/`
|
||||
|
||||
----
|
||||
|
||||
- 1ra parte -> [API Recetas](./README.md)
|
||||
- 3ra parte -> [Tags](./README3.md)
|
||||
- 4ta parte -> [Ingredientes](./README4.md)
|
||||
- 5ta parte -> [Imagenes](./README5.md)
|
||||
- 6ta parte -> [filtrado](./README6.md)
|
||||
- [Inicio](./README.md)
|
||||
- [User API](./01_user_api.md)
|
||||
- [**Recipe API**](./02_recipe_api.md)
|
||||
- [Tag API](./03_tag_api.md)
|
||||
- [Ingredient API](./04_ingredient_api.md)
|
||||
- [Image API](./05_image_api.md)
|
||||
- [Filters](./06_filters.md)
|
@ -517,8 +517,10 @@ URL `localhost:8000/api/docs`
|
||||
|
||||
----
|
||||
|
||||
- 1ra parte -> [API Recetas](./README.md)
|
||||
- 2da parte -> [Recetas](./README2.md)
|
||||
- 4ta parte -> [Ingredientes](./README4.md)
|
||||
- 5ta parte -> [Imagenes](./README5.md)
|
||||
- 6ta parte -> [Filtrado](./README6.md)
|
||||
- [Inicio](./README.md)
|
||||
- [User API](./01_user_api.md)
|
||||
- [Recipe API](./02_recipe_api.md)
|
||||
- [**Tag API**](./03_tag_api.md)
|
||||
- [Ingredient API](./04_ingredient_api.md)
|
||||
- [Image API](./05_image_api.md)
|
||||
- [Filters](./06_filters.md)
|
@ -509,8 +509,10 @@ herencia
|
||||
|
||||
----
|
||||
|
||||
- 1ra parte -> [API Recetas](./README.md)
|
||||
- 2da parte -> [Recetas](./README2.md)
|
||||
- 3ra parte -> [Tags](./README3.md)
|
||||
- 5ta parte -> [Imagenes](./README5.md)
|
||||
- 6ta parte -> [Filtrado](./README6.md)
|
||||
- [Inicio](./README.md)
|
||||
- [User API](./01_user_api.md)
|
||||
- [Recipe API](./02_recipe_api.md)
|
||||
- [Tag API](./03_tag_api.md)
|
||||
- [**Ingredient API**](./04_ingredient_api.md)
|
||||
- [Image API](./05_image_api.md)
|
||||
- [Filters](./06_filters.md)
|
@ -440,8 +440,10 @@ Levantar aplicación `docker compose up` y visitar `locahost:8000/api/docs`
|
||||
|
||||
----
|
||||
|
||||
- 1ra parte -> [API Recetas](./README.md)
|
||||
- 2da parte -> [Recetas](./README2.md)
|
||||
- 3ra parte -> [Tags](./README3.md)
|
||||
- 4ta parte -> [Ingredientes](./README4.md)
|
||||
- 6ta parte -> [Filtrado](./README6.md)
|
||||
- [Inicio](./README.md)
|
||||
- [User API](./01_user_api.md)
|
||||
- [Recipe API](./02_recipe_api.md)
|
||||
- [Tag API](./03_tag_api.md)
|
||||
- [Ingredient API](./04_ingredient_api.md)
|
||||
- [**Image API**](./05_image_api.md)
|
||||
- [Filters](./06_filters.md)
|
302
06_filters.md
Normal file
302
06_filters.md
Normal file
@ -0,0 +1,302 @@
|
||||
# Filters
|
||||
|
||||
## Diseño
|
||||
|
||||
- Filtro de recetas por ingredientes/tags
|
||||
- Filtro de ingredientes/tags asignados a recetas, faciltando una lista para
|
||||
elegir
|
||||
- Definición de parametros **OpenAPI**, actualizar documentación
|
||||
|
||||
### Requests de ejemplo
|
||||
|
||||
- Filtrar recetas por **tags(s)**
|
||||
- `GET` `/api/recipe/recipes/?tags=1,2,3`
|
||||
- Filtrar recetas por **ingrediente(s)**
|
||||
- `GET` `/api/recipe/recipes/?ingredients=1,2,3`
|
||||
- Filtrar tags por **receta asignada**
|
||||
- `GET` `/api/recipe/tags/?assigned_only=1`
|
||||
- Filtrar ingredientes por **receta asignada**
|
||||
- `GET` `/api/recipe/ingredients/?assigned_only=1`
|
||||
|
||||
### OpenAPI Schema
|
||||
|
||||
- *Schema* auto generada
|
||||
- Configuracion manual de cierta documentación (custom query parmas filtering)
|
||||
- Uso del decorador `extend_schema_view` de **DRF_Spectacular**. Ej.
|
||||
|
||||
```py
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'tags',
|
||||
OpenApiTypes.STR,
|
||||
description='Coma separated list of tags IDs to filter',
|
||||
),
|
||||
OpenApiParameter(
|
||||
'ingredients',
|
||||
OpenApiTypes.STR,
|
||||
description='Coma separated list of ingredients IDs to filter',
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
class RecipeViewSet(viewsets.ModelViewSet):
|
||||
"""View for manage recipe APIs."""
|
||||
...
|
||||
```
|
||||
|
||||
## Test filtros
|
||||
|
||||
[`test_recipe_api.py`](./app/recipe/tests/test_recipe_api.py)
|
||||
|
||||
```py
|
||||
class PrivateRecipeApiTests(TestCase):
|
||||
"""Test authenticated API requests."""
|
||||
|
||||
...
|
||||
|
||||
def test_fitler_by_tags(self):
|
||||
"""Test filtering recipes by tags."""
|
||||
r1 = create_recipe(user=self.user, title='Sopa de Verduras')
|
||||
r2 = create_recipe(user=self.user, title='Arroz con Huevo')
|
||||
tag1 = Tag.objects.create(user=self.user, name='Vergan')
|
||||
tag2 = Tag.objects.create(user=self.user, name='Vegetariana')
|
||||
r1.tags.add(tag1)
|
||||
r2.tags.add(tag2)
|
||||
r3 = create_recipe(user=self.user, title='Pure con Prietas')
|
||||
|
||||
params = {'tags': f'{tag1.id}, {tag2.id}'}
|
||||
res = self.client.get(RECIPES_URL, params)
|
||||
|
||||
s1 = RecipeSerializer(r1)
|
||||
s2 = RecipeSerializer(r2)
|
||||
s3 = RecipeSerializer(r3)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertIn(s2.data, res.data)
|
||||
self.assertNotIn(s3.data, res.data)
|
||||
|
||||
def test_filter_by_ingredients(self):
|
||||
"""Test filtering recipes by ingredients."""
|
||||
r1 = create_recipe(user=self.user, title='Porotos con rienda')
|
||||
r2 = create_recipe(user=self.user, title='Pollo al jugo')
|
||||
in1 = Ingredient.objects.create(user=self.user, name='Porotos')
|
||||
in2 = Ingredient.objects.create(user=self.user, name='Pollo')
|
||||
r1.ingredients.add(in1)
|
||||
r2.ingredients.add(in2)
|
||||
r3 = create_recipe(user=self.user, title='Lentejas con arroz')
|
||||
|
||||
params = {'ingredients': f'{in1.id}, {in2.id}'}
|
||||
res = self.client.get(RECIPES_URL, params)
|
||||
|
||||
s1 = RecipeSerializer(r1)
|
||||
s2 = RecipeSerializer(r2)
|
||||
s3 = RecipeSerializer(r3)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertIn(s2.data, res.data)
|
||||
self.assertNotIn(s3.data, res.data)
|
||||
```
|
||||
|
||||
## Implementación filtros
|
||||
|
||||
[`recipe/views.py`](./app/recipe/views.py)
|
||||
|
||||
```py
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import (
|
||||
extend_schema_view,
|
||||
extend_schema,
|
||||
OpenApiParameter,
|
||||
)
|
||||
...
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'tags',
|
||||
OpenApiTypes.STR,
|
||||
description='Lista separada por coma de tags IDs a filtrar'
|
||||
),
|
||||
OpenApiParameter(
|
||||
'ingredients',
|
||||
OpenApiTypes.STR,
|
||||
description='Lista separada por coma de ingredientes IDs a \
|
||||
filtrar'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
class RecipeViewSet(viewsets.ModelViewSet):
|
||||
...
|
||||
|
||||
def _params_to_ints(self, qs):
|
||||
"""Convert a list of strings to integers."""
|
||||
return [int(str_id) for str_id in qs.split(',')]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Retrieve recipes for authenticated user."""
|
||||
tags = self.request.query_params.get('tags')
|
||||
ingredients = self.request.query_params.get('ingredients')
|
||||
queryset = self.queryset
|
||||
if tags:
|
||||
tag_ids = self._params_to_ints(tags)
|
||||
queryset = queryset.filter(tags__id__in=tag_ids)
|
||||
if ingredients:
|
||||
ingredients_ids = self._params_to_ints(ingredients)
|
||||
queryset = queryset.filter(ingredients__id__in=ingredients_ids)
|
||||
|
||||
return queryset.filter(
|
||||
user=self.request.user
|
||||
).order_by('-id').distinct()
|
||||
```
|
||||
|
||||
## Test para filtrar por tags e ingredientes
|
||||
|
||||
[`test_ingredients_api.py`](./app/recipe/tests/test_ingredients_api.py)
|
||||
|
||||
```py
|
||||
from decimal import Decimal
|
||||
from core.model import Recipe
|
||||
...
|
||||
|
||||
def test_filter_ingredients_assigned_to_recipes(self):
|
||||
"""Test listing ingredients by those assigned to recipes."""
|
||||
in1 = Ingredient.objects.create(user=self.user, name='Manzana')
|
||||
in2 = Ingredient.objects.create(user=self.user, name='Pavo')
|
||||
recipe = Recipe.objects.create(
|
||||
title='Pure de Manzana',
|
||||
time_minutes=5,
|
||||
price=Decimal('4.5'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe.ingredients.add(in1)
|
||||
|
||||
res = self.client.get(INGREDIENTS_URL, {'assigned_only': 1})
|
||||
|
||||
s1 = IngredientSerializer(in1)
|
||||
s2 = IngredientSerializer(in2)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertNotIn(s2.data, res.data)
|
||||
|
||||
def test_filtered_ingredients_unique(self):
|
||||
"""Test filtered ingredients returns a unique list."""
|
||||
ing = Ingredient.objects.create(user=self.user, name='Huevo')
|
||||
Ingredient.objects.create(user=self.user, name='Lentejas')
|
||||
recipe1 = Recipe.objects.create(
|
||||
title='Huevos a la copa',
|
||||
time_minutes=4,
|
||||
price=Decimal('1.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe2 = Recipe.objects.create(
|
||||
title='Huevos a cocidos',
|
||||
time_minutes=5,
|
||||
price=Decimal('1.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe1.ingredients.add(ing)
|
||||
recipe2.ingredients.add(ing)
|
||||
|
||||
res = self.client.get(INGREDIENTS_URL, {'assigned_only': 1})
|
||||
|
||||
self.assertEqual(len(res.data), 1)
|
||||
```
|
||||
|
||||
[`test_tags_api.py`](./app/recipe/tests/test_tags_api.py)
|
||||
|
||||
```py
|
||||
from decimal import Decimal
|
||||
from core.model import Recipe
|
||||
...
|
||||
|
||||
def test_filter_tags_assigned_to_recipes(self):
|
||||
"""Test listing tags to those assigned to recipes."""
|
||||
tag1 = Tag.objects.create(user=self.user, name='Desayuno')
|
||||
tag2 = Tag.objects.create(user=self.user, name='Almuerzo')
|
||||
recipe = Recipe.objects.create(
|
||||
title='Huevos Fritos',
|
||||
time_minutes='5',
|
||||
price=Decimal('2.5'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe.tags.add(tag1)
|
||||
|
||||
res = self.client.get(TAGS_URL, {'assigned_only': 1})
|
||||
|
||||
s1 = TagSerializer(tag1)
|
||||
s2 = TagSerializer(tag2)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertNotIn(s2.data, res.data)
|
||||
|
||||
def test_filter_tags_unique(self):
|
||||
"""Test filtered tags retunrs a unique list."""
|
||||
tag = Tag.objects.create(user=self.user, name='Desayuno')
|
||||
Tag.objects.create(user=self.user, name='Almuerzo')
|
||||
recipe1 = Recipe.objects.create(
|
||||
title='Panqueques',
|
||||
time_minutes='25',
|
||||
price=Decimal('5.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe2 = Recipe.objects.create(
|
||||
title='Avena con fruta',
|
||||
time_minutes='15',
|
||||
price=Decimal('7.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe1.tags.add(tag)
|
||||
recipe2.tags.add(tag)
|
||||
|
||||
res = self.client.get(TAGS_URL, {'assigned_only': 1})
|
||||
|
||||
self.assertEqual(len(res.data), 1)
|
||||
```
|
||||
|
||||
## Implementación filtrado por tags e ingredientes
|
||||
|
||||
[`recipe/views.py`](./app/recipe/views.py)
|
||||
|
||||
```py
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'assigned_only',
|
||||
OpenApiTypes.INT, enum=[0, 1],
|
||||
description='Filtro por items asignados a recetas.'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
class BaseRecipeAtrrViewSet(mixins.DestroyModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
"""Base viewset for recipe attributes."""
|
||||
authentication_classes = [TokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset to authenticated user."""
|
||||
assigned_only = bool(
|
||||
int(self.request.query_params.get('assigned_only', 0))
|
||||
)
|
||||
queryset = self.queryset
|
||||
if assigned_only:
|
||||
queryset = queryset.filter(recipe__isnull=False)
|
||||
|
||||
return queryset.filter(
|
||||
user=self.request.user
|
||||
).order_by('-name').distinct()
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
- [**Inicio**](./README.md)
|
||||
- [User API](./01_user_api.md)
|
||||
- [Recipe API](./02_recipe_api.md)
|
||||
- [Tag API](./03_tag_api.md)
|
||||
- [Ingredient API](./04_ingredient_api.md)
|
||||
- [Image API](./05_image_api.md)
|
||||
- [Filters](./06_filters.md)
|
@ -1,6 +1,8 @@
|
||||
"""
|
||||
Tests for the ingredients API.
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.test import TestCase
|
||||
@ -8,7 +10,10 @@ from django.test import TestCase
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core.models import Ingredient
|
||||
from core.models import (
|
||||
Ingredient,
|
||||
Recipe,
|
||||
)
|
||||
|
||||
from recipe.serializers import IngredientSerializer
|
||||
|
||||
@ -97,3 +102,45 @@ class PrivateIngredientsApiTests(TestCase):
|
||||
self.assertEqual(res.status_code, status.HTTP_204_NO_CONTENT)
|
||||
ingredients = Ingredient.objects.filter(user=self.user)
|
||||
self.assertFalse(ingredients.exists())
|
||||
|
||||
def test_filter_ingredients_assigned_to_recipes(self):
|
||||
"""Test listing ingredients by those assigned to recipes."""
|
||||
in1 = Ingredient.objects.create(user=self.user, name='Manzana')
|
||||
in2 = Ingredient.objects.create(user=self.user, name='Pavo')
|
||||
recipe = Recipe.objects.create(
|
||||
title='Pure de Manzana',
|
||||
time_minutes=5,
|
||||
price=Decimal('4.5'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe.ingredients.add(in1)
|
||||
|
||||
res = self.client.get(INGREDIENTS_URL, {'assigned_only': 1})
|
||||
|
||||
s1 = IngredientSerializer(in1)
|
||||
s2 = IngredientSerializer(in2)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertNotIn(s2.data, res.data)
|
||||
|
||||
def test_filtered_ingredients_unique(self):
|
||||
"""Test filtered ingredients returns a unique list."""
|
||||
ing = Ingredient.objects.create(user=self.user, name='Huevo')
|
||||
Ingredient.objects.create(user=self.user, name='Lentejas')
|
||||
recipe1 = Recipe.objects.create(
|
||||
title='Huevos a la copa',
|
||||
time_minutes=4,
|
||||
price=Decimal('1.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe2 = Recipe.objects.create(
|
||||
title='Huevos a cocidos',
|
||||
time_minutes=5,
|
||||
price=Decimal('1.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe1.ingredients.add(ing)
|
||||
recipe2.ingredients.add(ing)
|
||||
|
||||
res = self.client.get(INGREDIENTS_URL, {'assigned_only': 1})
|
||||
|
||||
self.assertEqual(len(res.data), 1)
|
||||
|
@ -394,6 +394,46 @@ class PrivateRecipeApiTests(TestCase):
|
||||
self.assertEqual(res.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(recipe.ingredients.count(), 0)
|
||||
|
||||
def test_filter_by_tags(self):
|
||||
"""Test filtering recipes by tags."""
|
||||
r1 = create_recipe(user=self.user, title='Sopa de Verduras')
|
||||
r2 = create_recipe(user=self.user, title='Arroz con Huevo')
|
||||
tag1 = Tag.objects.create(user=self.user, name='Vergan')
|
||||
tag2 = Tag.objects.create(user=self.user, name='Vegetariana')
|
||||
r1.tags.add(tag1)
|
||||
r2.tags.add(tag2)
|
||||
r3 = create_recipe(user=self.user, title='Pure con Prietas')
|
||||
|
||||
params = {'tags': f'{tag1.id}, {tag2.id}'}
|
||||
res = self.client.get(RECIPES_URL, params)
|
||||
|
||||
s1 = RecipeSerializer(r1)
|
||||
s2 = RecipeSerializer(r2)
|
||||
s3 = RecipeSerializer(r3)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertIn(s2.data, res.data)
|
||||
self.assertNotIn(s3.data, res.data)
|
||||
|
||||
def test_filter_by_ingredients(self):
|
||||
"""Test filtering recipes by ingredients."""
|
||||
r1 = create_recipe(user=self.user, title='Porotos con rienda')
|
||||
r2 = create_recipe(user=self.user, title='Pollo al jugo')
|
||||
in1 = Ingredient.objects.create(user=self.user, name='Porotos')
|
||||
in2 = Ingredient.objects.create(user=self.user, name='Pollo')
|
||||
r1.ingredients.add(in1)
|
||||
r2.ingredients.add(in2)
|
||||
r3 = create_recipe(user=self.user, title='Lentejas con arroz')
|
||||
|
||||
params = {'ingredients': f'{in1.id}, {in2.id}'}
|
||||
res = self.client.get(RECIPES_URL, params)
|
||||
|
||||
s1 = RecipeSerializer(r1)
|
||||
s2 = RecipeSerializer(r2)
|
||||
s3 = RecipeSerializer(r3)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertIn(s2.data, res.data)
|
||||
self.assertNotIn(s3.data, res.data)
|
||||
|
||||
|
||||
class ImageUploadTest(TestCase):
|
||||
"""Tests for the image upload API."""
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""
|
||||
Tests for tags API
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.test import TestCase
|
||||
@ -8,7 +10,10 @@ from django.test import TestCase
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core.models import Tag
|
||||
from core.models import (
|
||||
Tag,
|
||||
Recipe,
|
||||
)
|
||||
|
||||
from recipe.serializers import TagSerializer
|
||||
|
||||
@ -94,3 +99,45 @@ class PrivateTagsApiTests(TestCase):
|
||||
self.assertEqual(res.status_code, status.HTTP_204_NO_CONTENT)
|
||||
tags = Tag.objects.filter(user=self.user)
|
||||
self.assertFalse(tags.exists())
|
||||
|
||||
def test_filter_tags_assigned_to_recipes(self):
|
||||
"""Test listing tags to those assigned to recipes."""
|
||||
tag1 = Tag.objects.create(user=self.user, name='Desayuno')
|
||||
tag2 = Tag.objects.create(user=self.user, name='Almuerzo')
|
||||
recipe = Recipe.objects.create(
|
||||
title='Huevos Fritos',
|
||||
time_minutes='5',
|
||||
price=Decimal('2.5'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe.tags.add(tag1)
|
||||
|
||||
res = self.client.get(TAGS_URL, {'assigned_only': 1})
|
||||
|
||||
s1 = TagSerializer(tag1)
|
||||
s2 = TagSerializer(tag2)
|
||||
self.assertIn(s1.data, res.data)
|
||||
self.assertNotIn(s2.data, res.data)
|
||||
|
||||
def test_filter_tags_unique(self):
|
||||
"""Test filtered tags retunrs a unique list."""
|
||||
tag = Tag.objects.create(user=self.user, name='Desayuno')
|
||||
Tag.objects.create(user=self.user, name='Almuerzo')
|
||||
recipe1 = Recipe.objects.create(
|
||||
title='Panqueques',
|
||||
time_minutes='25',
|
||||
price=Decimal('5.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe2 = Recipe.objects.create(
|
||||
title='Avena con fruta',
|
||||
time_minutes='15',
|
||||
price=Decimal('7.0'),
|
||||
user=self.user,
|
||||
)
|
||||
recipe1.tags.add(tag)
|
||||
recipe2.tags.add(tag)
|
||||
|
||||
res = self.client.get(TAGS_URL, {'assigned_only': 1})
|
||||
|
||||
self.assertEqual(len(res.data), 1)
|
||||
|
@ -1,6 +1,12 @@
|
||||
"""
|
||||
Views for the recipe APIs.
|
||||
"""
|
||||
from drf_spectacular.utils import (
|
||||
extend_schema_view,
|
||||
extend_schema,
|
||||
OpenApiParameter,
|
||||
)
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from rest_framework import (
|
||||
viewsets,
|
||||
mixins,
|
||||
@ -19,6 +25,23 @@ from core.models import (
|
||||
from recipe import serializers
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'tags',
|
||||
OpenApiTypes.STR,
|
||||
description='Lista separada por coma de tags IDs a filtrar'
|
||||
),
|
||||
OpenApiParameter(
|
||||
'ingredients',
|
||||
OpenApiTypes.STR,
|
||||
description='Lista separada por coma de ingredientes IDs a \
|
||||
filtrar'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
class RecipeViewSet(viewsets.ModelViewSet):
|
||||
"""View for manage recipe APIs."""
|
||||
serializer_class = serializers.RecipeDetailSerializer
|
||||
@ -26,9 +49,25 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
||||
authentication_classes = [TokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def _params_to_ints(self, qs):
|
||||
"""Convert a list of strings to integers."""
|
||||
return [int(str_id) for str_id in qs.split(',')]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Retrieve recipes for authenticated user."""
|
||||
return self.queryset.filter(user=self.request.user).order_by('-id')
|
||||
tags = self.request.query_params.get('tags')
|
||||
ingredients = self.request.query_params.get('ingredients')
|
||||
queryset = self.queryset
|
||||
if tags:
|
||||
tag_ids = self._params_to_ints(tags)
|
||||
queryset = queryset.filter(tags__id__in=tag_ids)
|
||||
if ingredients:
|
||||
ingredients_ids = self._params_to_ints(ingredients)
|
||||
queryset = queryset.filter(ingredients__id__in=ingredients_ids)
|
||||
|
||||
return queryset.filter(
|
||||
user=self.request.user
|
||||
).order_by('-id').distinct()
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""Return the serializer class for request."""
|
||||
@ -56,6 +95,17 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
'assigned_only',
|
||||
OpenApiTypes.INT, enum=[0, 1],
|
||||
description='Filtro por items asignados a recetas.'
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
class BaseRecipeAtrrViewSet(mixins.DestroyModelMixin,
|
||||
mixins.UpdateModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
@ -66,7 +116,16 @@ class BaseRecipeAtrrViewSet(mixins.DestroyModelMixin,
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset to authenticated user."""
|
||||
return self.queryset.filter(user=self.request.user).order_by('-name')
|
||||
assigned_only = bool(
|
||||
int(self.request.query_params.get('assigned_only', 0))
|
||||
)
|
||||
queryset = self.queryset
|
||||
if assigned_only:
|
||||
queryset = queryset.filter(recipe__isnull=False)
|
||||
|
||||
return queryset.filter(
|
||||
user=self.request.user
|
||||
).order_by('-name').distinct()
|
||||
|
||||
|
||||
class TagViewSet(BaseRecipeAtrrViewSet):
|
||||
|
@ -1,5 +1,5 @@
|
||||
Django==4.2.5
|
||||
djangorestframework==3.14.0
|
||||
psycopg2>=2.9.9
|
||||
drf-spectacular>=0.16
|
||||
Pillow>=10
|
||||
psycopg2==2.9.9
|
||||
drf-spectacular==0.26.5
|
||||
Pillow==10.0.1
|
||||
|
Loading…
Reference in New Issue
Block a user