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
|
redis==4.1.0
|
||||||
black
|
black
|
||||||
instagram_private_api
|
instagram_private_api
|
||||||
|
drf-nested-routers
|
||||||
|
|||||||
@ -18,8 +18,8 @@ Including another URLconf
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from rest_framework import routers
|
|
||||||
from rest_framework.authtoken import views
|
from rest_framework.authtoken import views
|
||||||
|
from rest_framework_nested import routers
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
# Wire up our API using automatic URL routing.
|
# 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
|
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):
|
class Recipe(models.Model):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=128)
|
||||||
recipe_type = models.CharField(max_length=32, default=const.RECIPE_TYPE_MEAT)
|
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):
|
def serialize(self, verbose=False):
|
||||||
data = {'id': self.id, 'name': self.name, 'recipe_type': self.recipe_type}
|
data = {'id': self.id, 'name': self.name, 'recipe_type': self.recipe_type}
|
||||||
if verbose:
|
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
|
return data
|
||||||
|
|
||||||
|
def get_ingredients(self):
|
||||||
|
return [i.serialize() for i in self.recipeingredient_set.order_by('id')]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_str(cls, content):
|
def create_from_str(cls, content):
|
||||||
content = content.strip()
|
content = content.strip()
|
||||||
|
|||||||
@ -1,16 +1,68 @@
|
|||||||
from os import read
|
|
||||||
from django.contrib.auth.models import User, Group
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
import recipe.models
|
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):
|
class RecipeSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(read_only=True)
|
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:
|
class Meta:
|
||||||
model = recipe.models.Recipe
|
model = recipe.models.Recipe
|
||||||
fields = '__all__'
|
fields = ('recipe_ingredients', 'id', 'name', 'recipe_type', 'status', 'note', 'rate', 'difficulty')
|
||||||
|
|
||||||
|
|
||||||
class WeekRecipeSerializer(serializers.ModelSerializer):
|
class WeekRecipeSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@ -1,16 +1,22 @@
|
|||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
# from django.core.urlresolvers import reverse
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework import routers
|
|
||||||
from recipe import views
|
from recipe import views
|
||||||
|
|
||||||
|
|
||||||
# Wire up our API using automatic URL routing.
|
# Wire up our API using automatic URL routing.
|
||||||
# Additionally, we include login URLs for the browsable API.
|
# 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 = [
|
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'^week-recipe/$', views.WeekRecipeListAPI.as_view()),
|
||||||
url(r'^daily-recipe/(?P<pk>\d+)$', views.DailyRecipeAPI.as_view(), name='dailyrecipe-detail'),
|
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
|
from utils import const
|
||||||
|
|
||||||
|
|
||||||
class RecipeAPI(rest_framework.generics.RetrieveUpdateDestroyAPIView):
|
class RecipeAPI(viewsets.ModelViewSet):
|
||||||
|
|
||||||
# authentication_classes = (authentication.TokenAuthentication,
|
# authentication_classes = (authentication.TokenAuthentication,
|
||||||
# authentication.SessionAuthentication,
|
# authentication.SessionAuthentication,
|
||||||
@ -25,15 +25,10 @@ class RecipeAPI(rest_framework.generics.RetrieveUpdateDestroyAPIView):
|
|||||||
instance.status = const.RECIPE_STATUS_DELETED
|
instance.status = const.RECIPE_STATUS_DELETED
|
||||||
instance.save(update_fields=['status'])
|
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 = {
|
filterset_fields = {
|
||||||
'recipe_type': const.FILTER_EXACT,
|
'recipe_type': const.FILTER_EXACT,
|
||||||
'difficulty': const.FILTER_GTE_LTE,
|
'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()
|
queryset = recipe.models.DailyRecipe.objects.all()
|
||||||
serializer_class = recipe.serializers.WeekRecipeSerializer
|
serializer_class = recipe.serializers.WeekRecipeSerializer
|
||||||
@ -78,3 +73,44 @@ class DailyRecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
|
|||||||
recipes.extend(request.data.get('soup', []))
|
recipes.extend(request.data.get('soup', []))
|
||||||
daily_recipe.recipes.set(recipe.models.Recipe.objects.filter(id__in=recipes))
|
daily_recipe.recipes.set(recipe.models.Recipe.objects.filter(id__in=recipes))
|
||||||
return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED, headers={})
|
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_ACTIVE = 'active'
|
||||||
RECIPE_STATUS_DELETED = 'deleted'
|
RECIPE_STATUS_DELETED = 'deleted'
|
||||||
|
|
||||||
|
INGREDIENT_STATUS_ACTIVE = 'active'
|
||||||
|
INGREDIENT_STATUS_DELETED = 'deleted'
|
||||||
|
|
||||||
RECIPE_TYPE_CHOICE = [RECIPE_TYPE_MEAT, RECIPE_TYPE_VEGETABLE, RECIPE_TYPE_SOUP]
|
RECIPE_TYPE_CHOICE = [RECIPE_TYPE_MEAT, RECIPE_TYPE_VEGETABLE, RECIPE_TYPE_SOUP]
|
||||||
|
|
||||||
FILTER_EXACT = ['exact']
|
FILTER_EXACT = ['exact']
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user