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:
parent
931fecd152
commit
54819c6861
@ -33,3 +33,4 @@ zipp==3.5.0
|
||||
redis==4.1.0
|
||||
black
|
||||
instagram_private_api
|
||||
drf-nested-routers
|
||||
|
||||
@ -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.
|
||||
|
||||
45
recipe/migrations/0004_ingredient_recipeingredient.py
Normal file
45
recipe/migrations/0004_ingredient_recipeingredient.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -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()
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)),
|
||||
]
|
||||
|
||||
@ -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={})
|
||||
|
||||
@ -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']
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user