Compare commits
8 Commits
931fecd152
...
e5c79047c9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c79047c9 | ||
|
|
206b35baec | ||
|
|
e0f3d31b0b | ||
|
|
69a55778ec | ||
|
|
1c424275f1 | ||
|
|
54819c6861 | ||
|
|
3b492ee812 | ||
|
|
66a6777139 |
@ -33,3 +33,4 @@ zipp==3.5.0
|
||||
redis==4.1.0
|
||||
black
|
||||
instagram_private_api
|
||||
drf-nested-routers
|
||||
|
||||
@ -27,7 +27,6 @@ DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -138,7 +138,7 @@ export default {
|
||||
});
|
||||
} else {
|
||||
axios
|
||||
.put(config.publicPath + '/recipe/recipe/' + recipe_id, data)
|
||||
.put(config.publicPath + '/recipe/recipe/' + recipe_id + '/', data)
|
||||
.then(function () {
|
||||
ElMessage({
|
||||
message: '修改成功.',
|
||||
@ -152,7 +152,7 @@ export default {
|
||||
},
|
||||
onSubmitDelete(recipe_id) {
|
||||
axios
|
||||
.delete(config.publicPath + '/recipe/recipe/' + recipe_id)
|
||||
.delete(config.publicPath + '/recipe/recipe/' + recipe_id + '/')
|
||||
.then(function () {
|
||||
ElMessage.error('删除成功.');
|
||||
location.reload();
|
||||
|
||||
@ -42,6 +42,94 @@
|
||||
type="textarea"
|
||||
/>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group
|
||||
class="recipe-create"
|
||||
inset
|
||||
border
|
||||
v-if="form.recipe_ingredients && form.recipe_ingredients.length > 0"
|
||||
title="数量为 0 的食材将被删除"
|
||||
>
|
||||
<van-cell
|
||||
v-bind:title="recipe_ingredient.ingredient.name"
|
||||
v-for="recipe_ingredient in form.recipe_ingredients"
|
||||
:key="recipe_ingredient.id"
|
||||
>
|
||||
<template #value>
|
||||
<van-stepper
|
||||
v-model.number="recipe_ingredient.quantity"
|
||||
default-value="recipe_ingredient.quantity"
|
||||
:decimal-length="1"
|
||||
allow-empty="true"
|
||||
min="0"
|
||||
/>
|
||||
{{ recipe_ingredient.ingredient.unit }}
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
|
||||
<div class="recipe-create">
|
||||
<van-row gutter="20">
|
||||
<van-col span="8">
|
||||
<van-button
|
||||
icon="plus"
|
||||
type="warning"
|
||||
plain
|
||||
round
|
||||
hairline
|
||||
class="submit-button"
|
||||
@click="showIngredientDialog = true"
|
||||
>创建食材</van-button
|
||||
>
|
||||
</van-col>
|
||||
<van-col span="16">
|
||||
<van-button
|
||||
icon="plus"
|
||||
type="primary"
|
||||
plain
|
||||
round
|
||||
hairline
|
||||
class="submit-button"
|
||||
@click="showIngredientPicker = true"
|
||||
>添加食材</van-button
|
||||
>
|
||||
</van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
|
||||
<van-popup v-model:show="showIngredientPicker" round position="bottom">
|
||||
<van-picker
|
||||
:columns="ingredient_columns"
|
||||
:columns-field-names="customFieldName"
|
||||
@cancel="showIngredientPicker = false"
|
||||
@confirm="onConfirmIngredient"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<van-dialog
|
||||
v-model:show="showIngredientDialog"
|
||||
title="创建食材"
|
||||
show-cancel-button
|
||||
confirm-button-text="创建"
|
||||
@confirm="onCreateIngredient"
|
||||
>
|
||||
<van-cell-group inset border>
|
||||
<van-field
|
||||
v-model="ingredient_name"
|
||||
label="名称"
|
||||
name="ingredient_name"
|
||||
placeholder="葱、蒜、姜..."
|
||||
:rules="[{ required: true, message: '请填写食材名称' }]"
|
||||
/>
|
||||
<van-field
|
||||
v-model="ingredient_unit"
|
||||
label="单位"
|
||||
name="ingredient_unit"
|
||||
placeholder="个、斤、千克..."
|
||||
:rules="[{ required: true, message: '请填写单位' }]"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</van-dialog>
|
||||
<div class="recipe-create">
|
||||
<van-row gutter="20">
|
||||
<van-col span="8" v-if="recipe_id">
|
||||
@ -49,7 +137,6 @@
|
||||
class="submit-button"
|
||||
round
|
||||
type="danger"
|
||||
plain
|
||||
hairline
|
||||
:disabled="disable_submit"
|
||||
@click="onSubmitDelete(recipe_id)"
|
||||
@ -62,7 +149,6 @@
|
||||
class="submit-button"
|
||||
round
|
||||
type="primary"
|
||||
plain
|
||||
hairline
|
||||
:disabled="disable_submit"
|
||||
@click="onSubmit(recipe_id)"
|
||||
@ -75,7 +161,6 @@
|
||||
class="submit-button"
|
||||
round
|
||||
type="primary"
|
||||
plain
|
||||
hairline
|
||||
:disabled="disable_submit"
|
||||
@click="onSubmit(recipe_id)"
|
||||
@ -90,21 +175,27 @@
|
||||
|
||||
<script>
|
||||
import {
|
||||
Form,
|
||||
Field,
|
||||
Button,
|
||||
Cell,
|
||||
CellGroup,
|
||||
Col,
|
||||
Dialog,
|
||||
Field,
|
||||
Form,
|
||||
Picker,
|
||||
Popup,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Rate,
|
||||
Button,
|
||||
Toast,
|
||||
Col,
|
||||
Row,
|
||||
Stepper,
|
||||
Toast,
|
||||
} from 'vant';
|
||||
import axios from 'axios';
|
||||
import config from '@/config/index';
|
||||
import router from '@/router/index';
|
||||
import constants from '@/utils/constants.js';
|
||||
import router from '@/router/index';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: ['recipe_'],
|
||||
@ -115,17 +206,29 @@ export default {
|
||||
},
|
||||
},
|
||||
components: {
|
||||
[Form.name]: Form,
|
||||
[Field.name]: Field,
|
||||
[Button.name]: Button,
|
||||
[Cell.name]: Cell,
|
||||
[CellGroup.name]: CellGroup,
|
||||
[Col.name]: Col,
|
||||
[Dialog.Component.name]: Dialog.Component,
|
||||
[Field.name]: Field,
|
||||
[Form.name]: Form,
|
||||
[Picker.name]: Picker,
|
||||
[Popup.name]: Popup,
|
||||
[Radio.name]: Radio,
|
||||
[RadioGroup.name]: RadioGroup,
|
||||
[Rate.name]: Rate,
|
||||
[Button.name]: Button,
|
||||
[Col.name]: Col,
|
||||
[Row.name]: Row,
|
||||
[Stepper.name]: Stepper,
|
||||
},
|
||||
data() {
|
||||
const customFieldName = {
|
||||
text: 'name',
|
||||
};
|
||||
const showIngredientPicker = ref(false);
|
||||
const showIngredientDialog = ref(false);
|
||||
const disable_submit = ref(false);
|
||||
|
||||
return {
|
||||
form: {
|
||||
name: null,
|
||||
@ -133,12 +236,22 @@ export default {
|
||||
rate: 0,
|
||||
difficulty: 0,
|
||||
note: null,
|
||||
recipe_ingredients: null,
|
||||
},
|
||||
loading: false,
|
||||
disable_submit,
|
||||
recipe_id: null,
|
||||
ingredient_columns: null,
|
||||
customFieldName,
|
||||
showIngredientPicker,
|
||||
showIngredientDialog,
|
||||
ingredient_name: null,
|
||||
ingredient_unit: null,
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
mounted() {
|
||||
this.getIngredients();
|
||||
},
|
||||
methods: {
|
||||
onSubmit(recipe_id) {
|
||||
if (!this.form.name) {
|
||||
@ -153,6 +266,9 @@ export default {
|
||||
difficulty: this.form.difficulty,
|
||||
rate: this.form.rate,
|
||||
note: this.form.note ? this.form.note : null,
|
||||
recipe_ingredients: this.form.recipe_ingredients
|
||||
? this.form.recipe_ingredients
|
||||
: [],
|
||||
};
|
||||
if (!recipe_id) {
|
||||
axios
|
||||
@ -161,14 +277,19 @@ export default {
|
||||
(response) => (response, router.push({ name: 'RecipeMobileHome' }))
|
||||
);
|
||||
} else {
|
||||
axios.put(config.publicPath + '/recipe/recipe/' + recipe_id, data).then(
|
||||
(Toast.success({
|
||||
message: '修改成功',
|
||||
forbidClick: true,
|
||||
duration: 500,
|
||||
}),
|
||||
(this.loading = false))
|
||||
);
|
||||
axios
|
||||
.put(config.publicPath + '/recipe/recipe/' + recipe_id + '/', data)
|
||||
.then(
|
||||
(response) => (
|
||||
response,
|
||||
Toast.success({
|
||||
message: '修改成功',
|
||||
forbidClick: true,
|
||||
duration: 500,
|
||||
}),
|
||||
(this.loading = false)
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
onSubmitDelete(recipe_id) {
|
||||
@ -176,14 +297,99 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
axios.delete(config.publicPath + '/recipe/recipe/' + recipe_id).then(
|
||||
(Toast.success({
|
||||
message: '删除成功',
|
||||
axios
|
||||
.delete(config.publicPath + '/recipe/recipe/' + recipe_id + '/')
|
||||
.then(
|
||||
(Toast.success({
|
||||
message: '删除成功',
|
||||
forbidClick: true,
|
||||
duration: 500,
|
||||
}),
|
||||
(this.loading = false))
|
||||
);
|
||||
},
|
||||
getIngredients() {
|
||||
axios.get(config.publicPath + '/recipe/ingredient/').then((response) => {
|
||||
this.ingredient_columns = response.data['results'];
|
||||
});
|
||||
},
|
||||
onConfirmIngredient(value) {
|
||||
var exists = false;
|
||||
this.showIngredientPicker = false;
|
||||
if (this.form.recipe_ingredients === null) {
|
||||
this.form.recipe_ingredients = [];
|
||||
}
|
||||
for (let i = 0; i < this.form.recipe_ingredients.length; i++) {
|
||||
if (this.form.recipe_ingredients[i].ingredient.id == value.id) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exists) {
|
||||
Toast.fail({
|
||||
message: '该食材已存在',
|
||||
forbidClick: true,
|
||||
duration: 500,
|
||||
}),
|
||||
(this.loading = false))
|
||||
duration: 800,
|
||||
});
|
||||
} else {
|
||||
this.form.recipe_ingredients.push({
|
||||
id: 0,
|
||||
ingredient: {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
unit: value.unit,
|
||||
}, // 添加的食材
|
||||
});
|
||||
}
|
||||
},
|
||||
onCreateIngredient() {
|
||||
console.log(
|
||||
'onCreateIngredient',
|
||||
this.ingredient_name,
|
||||
this.ingredient_unit
|
||||
);
|
||||
if (!this.ingredient_name || !this.ingredient_unit) {
|
||||
Toast.fail({
|
||||
message: '请输入食材名称和单位',
|
||||
forbidClick: true,
|
||||
duration: 800,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.showIngredientDialog = false;
|
||||
for (let i = 0; i < this.ingredient_columns.length; i++) {
|
||||
if (this.ingredient_columns[i].name == this.ingredient_name) {
|
||||
Toast.fail({
|
||||
message: '该食材已存在',
|
||||
forbidClick: true,
|
||||
duration: 800,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
axios
|
||||
.post(config.publicPath + '/recipe/ingredient/', {
|
||||
name: this.ingredient_name,
|
||||
unit: this.ingredient_unit,
|
||||
})
|
||||
.then(
|
||||
(response) => (
|
||||
response,
|
||||
this.ingredient_columns.unshift({
|
||||
id: response.data.id,
|
||||
name: this.ingredient_name,
|
||||
unit: this.ingredient_unit,
|
||||
}),
|
||||
Toast.success({
|
||||
message: '创建成功',
|
||||
forbidClick: true,
|
||||
duration: 800,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// this.ingredient_name = value.name;
|
||||
// this.ingredient_unit = value.unit;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -17,9 +17,12 @@
|
||||
}"
|
||||
>
|
||||
<template #title>
|
||||
<van-tag class="recipe-type-tag" plain type="primary">{{
|
||||
constants.formatRecipeType(recipe.recipe_type)
|
||||
}}</van-tag>
|
||||
<van-tag
|
||||
class="recipe-type-tag"
|
||||
plain
|
||||
v-bind:type="getTagType(recipe.recipe_type)"
|
||||
>{{ constants.formatRecipeType(recipe.recipe_type) }}</van-tag
|
||||
>
|
||||
<span class="recipe-name"> {{ recipe.name }}</span>
|
||||
</template>
|
||||
<template #value>
|
||||
@ -46,7 +49,7 @@ export default {
|
||||
[CollapseItem.name]: CollapseItem,
|
||||
[Icon.name]: Icon,
|
||||
},
|
||||
data: function() {
|
||||
data: function () {
|
||||
return {
|
||||
loading: false,
|
||||
finished: false,
|
||||
@ -57,6 +60,13 @@ export default {
|
||||
constants: constants,
|
||||
is_link: true,
|
||||
activeNames: ['1'],
|
||||
getTagType: function (recipe_type) {
|
||||
return {
|
||||
[constants.RECIPE_TYPE_MEAT]: 'primary',
|
||||
[constants.RECIPE_TYPE_VEGETABLE]: 'success',
|
||||
[constants.RECIPE_TYPE_SOUP]: 'warning',
|
||||
}[recipe_type];
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
|
||||
@ -21,7 +21,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
axios
|
||||
.get(config.publicPath + '/recipe/recipe/' + this.$route.params.id)
|
||||
.get(config.publicPath + '/recipe/recipe/' + this.$route.params.id + '/')
|
||||
.then((response) => (this.recipe = response.data));
|
||||
},
|
||||
};
|
||||
|
||||
@ -2,12 +2,8 @@
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-menu mode="horizontal" default-active="#" router>
|
||||
<el-menu-item index="/">
|
||||
首页
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/week-recipe/">
|
||||
每周菜谱
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/"> 首页 </el-menu-item>
|
||||
<el-menu-item index="/week-recipe/"> 每周菜谱 </el-menu-item>
|
||||
<el-menu-item index="#">
|
||||
{{ recipe.name }}
|
||||
</el-menu-item>
|
||||
@ -33,7 +29,7 @@ import config from '@/config/index';
|
||||
export default {
|
||||
name: 'RecipeDetail',
|
||||
components: { input_recipe },
|
||||
data: function() {
|
||||
data: function () {
|
||||
return {
|
||||
recipe: {},
|
||||
constants: constants,
|
||||
@ -41,7 +37,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
axios
|
||||
.get(config.publicPath + '/recipe/recipe/' + this.$route.params.id)
|
||||
.get(config.publicPath + '/recipe/recipe/' + this.$route.params.id + '/')
|
||||
.then((response) => (this.recipe = response.data));
|
||||
},
|
||||
methods: {
|
||||
|
||||
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,61 @@
|
||||
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):
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
|
||||
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, read_only=True)
|
||||
|
||||
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,38 @@ class RecipeAPI(rest_framework.generics.RetrieveUpdateDestroyAPIView):
|
||||
instance.status = const.RECIPE_STATUS_DELETED
|
||||
instance.save(update_fields=['status'])
|
||||
|
||||
def perform_update(self, serializer):
|
||||
instance = self.get_object()
|
||||
if 'recipe_ingredients' in self.request.data:
|
||||
recipe_ingredients = self.request.data.pop('recipe_ingredients')
|
||||
if recipe_ingredients:
|
||||
for recipe_ingredient in recipe_ingredients:
|
||||
# 没有 id 则新增食材
|
||||
if not recipe_ingredient['id'] and recipe_ingredient['quantity']:
|
||||
ingredient = recipe.models.Ingredient.objects.get(id=recipe_ingredient['ingredient']['id'])
|
||||
del recipe_ingredient['ingredient']
|
||||
del recipe_ingredient['id']
|
||||
recipe.models.RecipeIngredient.objects.create(
|
||||
recipe=instance, ingredient=ingredient, **recipe_ingredient
|
||||
)
|
||||
# 有 id 没数量则删除食材
|
||||
elif recipe_ingredient['id'] and not recipe_ingredient['quantity']:
|
||||
recipe.models.RecipeIngredient.objects.filter(id=recipe_ingredient['id']).update(
|
||||
status=const.INGREDIENT_STATUS_DELETED
|
||||
)
|
||||
# 有 id 有数量则更新食材
|
||||
elif recipe_ingredient['id'] and recipe_ingredient['quantity']:
|
||||
del recipe_ingredient['ingredient']
|
||||
recipe.models.RecipeIngredient.objects.filter(id=recipe_ingredient['id']).update(
|
||||
**recipe_ingredient
|
||||
)
|
||||
|
||||
class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
|
||||
return super().perform_update(serializer)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
self.queryset = recipe.models.Recipe.objects.exclude(status=const.RECIPE_STATUS_DELETED)
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
# 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 +64,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 +101,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).order_by('-id')
|
||||
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']
|
||||
|
||||
@ -10,7 +10,6 @@ reload(sys)
|
||||
|
||||
import utils.lark
|
||||
import git
|
||||
import ipdb
|
||||
|
||||
if __name__ == '__main__':
|
||||
# def notify():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user