feat(recipe model;recipe api): 增加 ingredient 和 recipe-ingredient model; 增加相关 API

增加 ingredient 和 recipe-ingredient model; 增加相关 API

Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
Ching 2022-02-13 01:18:02 +08:00
parent 931fecd152
commit 54819c6861
8 changed files with 193 additions and 19 deletions

View File

@ -33,3 +33,4 @@ zipp==3.5.0
redis==4.1.0
black
instagram_private_api
drf-nested-routers

View File

@ -18,8 +18,8 @@ Including another URLconf
from django.urls import include, path
from django.conf.urls import url
from rest_framework import routers
from rest_framework.authtoken import views
from rest_framework_nested import routers
router = routers.DefaultRouter()
# Wire up our API using automatic URL routing.

View File

@ -0,0 +1,45 @@
# Generated by Django 3.2.6 on 2022-02-12 13:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [('recipe', '0003_recipe_status')]
operations = [
migrations.CreateModel(
name='Ingredient',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('unit', models.CharField(max_length=32)),
('status', models.CharField(default='active', max_length=32)),
],
),
migrations.CreateModel(
name='RecipeIngredient',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.FloatField()),
(
'ingredient',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='recipe_ingredients',
to='recipe.ingredient',
),
),
(
'recipe',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='recipe_ingredients',
to='recipe.recipe',
),
),
('status', models.CharField(default='active', max_length=32)),
],
),
]

View File

@ -6,6 +6,27 @@ from utils import const
import utils
class Ingredient(models.Model):
name = models.CharField(max_length=128)
unit = models.CharField(max_length=32)
status = models.CharField(max_length=32, default=const.INGREDIENT_STATUS_ACTIVE)
class RecipeIngredient(models.Model):
recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE, related_name='recipe_ingredients')
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, related_name='recipe_ingredients')
status = models.CharField(max_length=32, default=const.INGREDIENT_STATUS_ACTIVE)
quantity = models.FloatField()
def serialize(self):
return {
'id': self.id,
'recipe': {'id': self.recipe.id, 'name': self.recipe.name},
'ingredient': {'id': self.ingredient.id, 'name': self.ingredient.name, 'unit': self.ingredient.unit},
'quantity': self.quantity,
}
class Recipe(models.Model):
name = models.CharField(max_length=128)
recipe_type = models.CharField(max_length=32, default=const.RECIPE_TYPE_MEAT)
@ -17,10 +38,20 @@ class Recipe(models.Model):
def serialize(self, verbose=False):
data = {'id': self.id, 'name': self.name, 'recipe_type': self.recipe_type}
if verbose:
data.update({'difficulty': self.difficulty, 'rate': self.rate, 'note': self.note})
data.update(
{
'difficulty': self.difficulty,
'rate': self.rate,
'note': self.note,
'recipe_ingredients': self.get_ingredients(),
}
)
return data
def get_ingredients(self):
return [i.serialize() for i in self.recipeingredient_set.order_by('id')]
@classmethod
def create_from_str(cls, content):
content = content.strip()

View File

@ -1,16 +1,68 @@
from os import read
from django.contrib.auth.models import User, Group
from rest_framework import serializers
import recipe.models
from utils import const
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = recipe.models.Ingredient
fields = '__all__'
class RecipeIngredientSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
ingredient = IngredientSerializer()
def update(self, instance, validated_data):
if 'ingredient' in validated_data:
validated_data.pop('ingredient')
if 'recipe' in validated_data:
validated_data.pop('recipe')
return super().update(instance, validated_data)
class Meta:
model = recipe.models.RecipeIngredient
fields = '__all__'
class RecipeSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
recipe_ingredients = RecipeIngredientSerializer(many=True)
def update(self, instance, validated_data):
if 'recipe_ingredients' in validated_data:
recipe_ingredients = validated_data.pop('recipe_ingredients')
if recipe_ingredients:
for recipe_ingredient in recipe_ingredients:
recipe_ingredient['recipe'] = instance
RecipeIngredientSerializer.update(recipe_ingredient)
return super().update(instance, validated_data)
def create(self, validated_data):
if 'recipe_ingredients' in validated_data:
recipe_ingredients = validated_data.pop('recipe_ingredients')
instance = super().create(validated_data)
if recipe_ingredients:
for recipe_ingredient in recipe_ingredients:
recipe_ingredient['recipe'] = instance
RecipeIngredientSerializer.create(recipe_ingredient)
return instance
@property
def data(self):
# exclude deleted recipe_ingredients
data_ = super().data
data_['recipe_ingredients'] = [
ingredient
for ingredient in data_['recipe_ingredients']
if ingredient['status'] != const.INGREDIENT_STATUS_DELETED
]
return data_
class Meta:
model = recipe.models.Recipe
fields = '__all__'
fields = ('recipe_ingredients', 'id', 'name', 'recipe_type', 'status', 'note', 'rate', 'difficulty')
class WeekRecipeSerializer(serializers.ModelSerializer):

View File

@ -1,16 +1,22 @@
from django.conf.urls import include, url
# from django.core.urlresolvers import reverse
from django.urls import path
from rest_framework import routers
from recipe import views
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
from rest_framework_nested import routers
router = routers.DefaultRouter()
router.register(r'recipe', views.RecipeAPI)
router.register(r'ingredient', views.IngredientAPI)
recipe_nested_router = routers.NestedSimpleRouter(router, r'recipe', lookup='recipe')
recipe_nested_router.register(r'recipe-ingredient', views.RecipeIngredientAPI)
urlpatterns = [
url(r'^recipe/(?P<pk>\d+)$', views.RecipeAPI.as_view(), name='recipe-detail'),
url(r'^recipe/$', views.RecipeListAPI.as_view()),
url(r'^week-recipe/$', views.WeekRecipeListAPI.as_view()),
url(r'^daily-recipe/(?P<pk>\d+)$', views.DailyRecipeAPI.as_view(), name='dailyrecipe-detail'),
path(r'', include(router.urls)),
path(r'', include(recipe_nested_router.urls)),
]

View File

@ -12,7 +12,7 @@ import recipe.serializers
from utils import const
class RecipeAPI(rest_framework.generics.RetrieveUpdateDestroyAPIView):
class RecipeAPI(viewsets.ModelViewSet):
# authentication_classes = (authentication.TokenAuthentication,
# authentication.SessionAuthentication,
@ -25,15 +25,10 @@ class RecipeAPI(rest_framework.generics.RetrieveUpdateDestroyAPIView):
instance.status = const.RECIPE_STATUS_DELETED
instance.save(update_fields=['status'])
def list(self, request, *args, **kwargs):
self.queryset = recipe.models.Recipe.objects.exclude(status=const.RECIPE_STATUS_DELETED)
return super().list(request, *args, **kwargs)
class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
# authentication_classes = (authentication.TokenAuthentication,
# authentication.SessionAuthentication,
# authentication.BasicAuthentication)
# permission_classes = (permissions.IsAuthenticated,)
queryset = recipe.models.Recipe.objects.exclude(status=const.RECIPE_STATUS_DELETED)
serializer_class = recipe.serializers.RecipeSerializer
filterset_fields = {
'recipe_type': const.FILTER_EXACT,
'difficulty': const.FILTER_GTE_LTE,
@ -41,7 +36,7 @@ class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics
}
class WeekRecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
class WeekRecipeListAPI(rest_framework.generics.ListCreateAPIView):
queryset = recipe.models.DailyRecipe.objects.all()
serializer_class = recipe.serializers.WeekRecipeSerializer
@ -78,3 +73,44 @@ class DailyRecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
recipes.extend(request.data.get('soup', []))
daily_recipe.recipes.set(recipe.models.Recipe.objects.filter(id__in=recipes))
return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED, headers={})
class IngredientAPI(viewsets.ModelViewSet):
# authentication_classes = (authentication.TokenAuthentication,
# authentication.SessionAuthentication,
# authentication.BasicAuthentication)
# permission_classes = (permissions.IsAuthenticated,)
queryset = recipe.models.Ingredient.objects.all()
serializer_class = recipe.serializers.IngredientSerializer
def list(self, request, *args, **kwargs):
self.queryset = recipe.models.Ingredient.objects.exclude(status=const.INGREDIENT_STATUS_DELETED)
return super().list(request, *args, **kwargs)
def perform_destroy(self, instance):
instance.status = const.INGREDIENT_STATUS_DELETED
instance.save(update_fields=['status'])
class RecipeIngredientAPI(viewsets.ModelViewSet):
# authentication_classes = (authentication.TokenAuthentication,
# authentication.SessionAuthentication,
# authentication.BasicAuthentication)
# permission_classes = (permissions.IsAuthenticated,)
queryset = recipe.models.RecipeIngredient.objects.exclude(status=const.INGREDIENT_STATUS_DELETED)
serializer_class = recipe.serializers.RecipeIngredientSerializer
def get_queryset(self):
return self.queryset.filter(recipe=self.kwargs['recipe_pk'])
def perform_destroy(self, instance):
instance.status = const.INGREDIENT_STATUS_DELETED
instance.save(update_fields=['status'])
def create(self, request, *args, **kwargs):
recipe_ingredient = recipe.models.RecipeIngredient.objects.create(
recipe_id=kwargs['recipe_pk'], ingredient_id=request.data['ingredient'], quantity=request.data['quantity']
)
return Response(recipe_ingredient.serialize(), status=status.HTTP_201_CREATED, headers={})

View File

@ -14,6 +14,9 @@ RECIPE_TYPE_SOUP = 'soup'
RECIPE_STATUS_ACTIVE = 'active'
RECIPE_STATUS_DELETED = 'deleted'
INGREDIENT_STATUS_ACTIVE = 'active'
INGREDIENT_STATUS_DELETED = 'deleted'
RECIPE_TYPE_CHOICE = [RECIPE_TYPE_MEAT, RECIPE_TYPE_VEGETABLE, RECIPE_TYPE_SOUP]
FILTER_EXACT = ['exact']