feat(dailyrecipe and week recipe): [A] 增加dailyrecipe 每日菜单,增加 weekrecipe 接口
[A] 增加dailyrecipe 每日菜单,增加 weekrecipe 接口 Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
parent
b4fa5d9519
commit
1dd97b9e54
27
recipe/migrations/0002_auto_20211002_1926.py
Normal file
27
recipe/migrations/0002_auto_20211002_1926.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 3.2.6 on 2021-10-02 11:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('recipe', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='recipe',
|
||||||
|
name='recipe_type',
|
||||||
|
field=models.CharField(default='meat', max_length=32),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DailyRecipe',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date', models.DateField()),
|
||||||
|
('meal_type', models.CharField(default='supper', max_length=32)),
|
||||||
|
('recipes', models.ManyToManyField(to='recipe.Recipe')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,9 +1,101 @@
|
|||||||
|
import random
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from utils import const
|
||||||
|
import utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
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)
|
recipe_type = models.CharField(max_length=32,
|
||||||
|
default=const.RECIPE_TYPE_MEAT)
|
||||||
note = models.TextField(null=True)
|
note = models.TextField(null=True)
|
||||||
rate = models.IntegerField(default=0)
|
rate = models.IntegerField(default=0)
|
||||||
difficulty = models.IntegerField(default=0)
|
difficulty = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class DailyRecipe(models.Model):
|
||||||
|
recipes = models.ManyToManyField(Recipe)
|
||||||
|
date = models.DateField()
|
||||||
|
meal_type = models.CharField(
|
||||||
|
max_length=32,
|
||||||
|
default=const.DAILY_RECIPE_MEAL_TYPE_SUPPER,)
|
||||||
|
|
||||||
|
def generate_recipe(self, prev_recipes=None, ignore_prev=False):
|
||||||
|
if not prev_recipes:
|
||||||
|
prev_recipes = []
|
||||||
|
if ignore_prev:
|
||||||
|
prev_recipes = []
|
||||||
|
|
||||||
|
recipes = []
|
||||||
|
retry = 5
|
||||||
|
|
||||||
|
# meat
|
||||||
|
for x in range(0,2):
|
||||||
|
while True:
|
||||||
|
recipe = Recipe.objects.filter(
|
||||||
|
recipe_type=const.RECIPE_TYPE_MEAT,
|
||||||
|
).order_by('?').first()
|
||||||
|
if recipe.id not in recipes and recipe.id not in prev_recipes:
|
||||||
|
recipes.append(recipe.id)
|
||||||
|
break
|
||||||
|
if retry <= 0:
|
||||||
|
retry = 5
|
||||||
|
break
|
||||||
|
|
||||||
|
# vegetable
|
||||||
|
for x in range(0, 1):
|
||||||
|
while True:
|
||||||
|
recipe = Recipe.objects.filter(
|
||||||
|
recipe_type=const.RECIPE_TYPE_VEGETABLE,
|
||||||
|
).order_by('?').first()
|
||||||
|
if recipe.id not in recipes and recipe.id not in prev_recipes:
|
||||||
|
recipes.append(recipe.id)
|
||||||
|
break
|
||||||
|
retry -= 1
|
||||||
|
if retry <= 0:
|
||||||
|
retry = 5
|
||||||
|
break
|
||||||
|
|
||||||
|
# soup
|
||||||
|
if random.randint(0,2):
|
||||||
|
recipe = Recipe.objects.filter(
|
||||||
|
recipe_type=const.RECIPE_TYPE_SOUP,
|
||||||
|
).order_by('?').first()
|
||||||
|
# if recipe not in recipes and recipe not in prev_recipes:
|
||||||
|
if recipe:
|
||||||
|
recipes.append(recipe.id)
|
||||||
|
|
||||||
|
self.recipes.set(Recipe.objects.filter(id__in=recipes))
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
data = {
|
||||||
|
const.RECIPE_TYPE_MEAT: [],
|
||||||
|
const.RECIPE_TYPE_VEGETABLE: [],
|
||||||
|
const.RECIPE_TYPE_SOUP: [],
|
||||||
|
}
|
||||||
|
for key_, value_ in data.items():
|
||||||
|
for recipe in self.recipes.filter(
|
||||||
|
recipe_type=key_).order_by('id'):
|
||||||
|
value_.append(recipe.serialize())
|
||||||
|
|
||||||
|
date = now()
|
||||||
|
date.replace(year=self.date.year, month=self.date.month, day=self.date.day,)
|
||||||
|
data['date'] = utils.timestamp_of(utils.day_start(date))
|
||||||
|
return data
|
||||||
|
|||||||
@ -8,3 +8,10 @@ class RecipeSerializer(serializers.HyperlinkedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = recipe.models.Recipe
|
model = recipe.models.Recipe
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class WeekRecipeSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = recipe.models.DailyRecipe
|
||||||
|
fields = '__all__'
|
||||||
|
|||||||
@ -10,4 +10,5 @@ from recipe import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^recipe/(?P<pk>\d+)$', views.RecipeAPI.as_view(), name='recipe-detail'),
|
url(r'^recipe/(?P<pk>\d+)$', views.RecipeAPI.as_view(), name='recipe-detail'),
|
||||||
url(r'^recipe/$', views.RecipeListAPI.as_view()),
|
url(r'^recipe/$', views.RecipeListAPI.as_view()),
|
||||||
|
url(r'^week-recipe/$', views.WeekRecipeListAPI.as_view()),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,11 +1,7 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.utils.timezone import now, localtime
|
||||||
# Create your views here.
|
|
||||||
|
|
||||||
import django.views.generic
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from rest_framework import authentication, permissions, status, viewsets
|
from rest_framework import authentication, permissions, status, viewsets
|
||||||
import rest_framework.generics
|
import rest_framework.generics
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -34,3 +30,33 @@ class RecipeListAPI(rest_framework.generics.ListAPIView,
|
|||||||
queryset = recipe.models.Recipe.objects.all()
|
queryset = recipe.models.Recipe.objects.all()
|
||||||
serializer_class = recipe.serializers.RecipeSerializer
|
serializer_class = recipe.serializers.RecipeSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class WeekRecipeListAPI(rest_framework.generics.ListAPIView,
|
||||||
|
rest_framework.generics.CreateAPIView):
|
||||||
|
|
||||||
|
queryset = recipe.models.DailyRecipe.objects.all()
|
||||||
|
serializer_class = recipe.serializers.WeekRecipeSerializer
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# Monday == 0 ... Sunday == 6
|
||||||
|
today = localtime()
|
||||||
|
recipes = []
|
||||||
|
for x in range(0, 7-today.weekday()):
|
||||||
|
daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create(
|
||||||
|
date=today + datetime.timedelta(days=1)
|
||||||
|
)
|
||||||
|
daily_recipe.generate_recipe()
|
||||||
|
recipes.append(daily_recipe.recipes.values_list('id', flat=True))
|
||||||
|
return Response({}, status=status.HTTP_201_CREATED, headers={})
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
today = localtime()
|
||||||
|
week_start = (today - datetime.timedelta(days=today.weekday())).date()
|
||||||
|
week_end = week_start + datetime.timedelta(days=6)
|
||||||
|
daily_recipes = recipe.models.DailyRecipe.objects.filter(
|
||||||
|
date__gte=week_start,
|
||||||
|
date__lte=week_end,
|
||||||
|
).order_by('date')
|
||||||
|
data = []
|
||||||
|
for daily_recipe in daily_recipes:
|
||||||
|
data.append(daily_recipe.serialize())
|
||||||
|
return Response(data)
|
||||||
|
|||||||
118
utils/__init__.py
Normal file
118
utils/__init__.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
import calendar
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
def timestamp_of(d):
|
||||||
|
if hasattr(d, 'isoformat'):
|
||||||
|
return calendar.timegm(d.utctimetuple())
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def now():
|
||||||
|
return timezone.localtime(timezone.now())
|
||||||
|
|
||||||
|
|
||||||
|
def midnight():
|
||||||
|
return now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
def is_today(tme):
|
||||||
|
if day_of(tme) != day_of(midnight()):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def current_time_n(n):
|
||||||
|
return now() - timedelta(days=n)
|
||||||
|
|
||||||
|
|
||||||
|
def day_n(n):
|
||||||
|
"""Midnight of N days ago."""
|
||||||
|
return midnight() - timedelta(days=n)
|
||||||
|
|
||||||
|
|
||||||
|
def day_of(d):
|
||||||
|
"""Returns date part."""
|
||||||
|
return str(d.date())
|
||||||
|
|
||||||
|
|
||||||
|
def midnight_of(d):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
d : datetime object
|
||||||
|
Return:
|
||||||
|
Midnight of datetime.
|
||||||
|
"""
|
||||||
|
d = timezone.localtime(d)
|
||||||
|
return d.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
def day_start(date):
|
||||||
|
""" 返回 date 这天的开始时间
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date: `Datetime` 对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
返回 date 这天的开始时间(0 点),`Datetime` 对象
|
||||||
|
"""
|
||||||
|
return timezone.localtime(date).replace(hour=0, minute=0, second=0,
|
||||||
|
microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
def week_start(date):
|
||||||
|
""" 返回 date 这天所在周的开始时间
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date: `Datetime` 对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
返回 date 这天所在周开始时间(周一),`Datetime` 对象
|
||||||
|
"""
|
||||||
|
return timezone.localtime(date) + dateutil.relativedelta.relativedelta(
|
||||||
|
weekday=dateutil.relativedelta.MO(-1), hour=0, minute=0, second=0,
|
||||||
|
microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
def month_start(d):
|
||||||
|
""" 返回 date 这天所在月的开始时间
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date: `Datetime` 对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
返回 date 这天所在月的开始时间(1号),`Datetime` 对象
|
||||||
|
"""
|
||||||
|
return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
|
||||||
|
day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
def month_end(d):
|
||||||
|
"""返回 date 这天所在月的结束时间,即最后一天的 23:59:59
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: `Datetime` 对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
返回 date 这天所在月的最后一天的 23:59:59 的时间
|
||||||
|
"""
|
||||||
|
start = month_start(d)
|
||||||
|
month_days = calendar.monthrange(start.year, start.month)[1]
|
||||||
|
return start + timedelta(days=month_days, seconds=-1)
|
||||||
|
|
||||||
|
|
||||||
|
def year_start(d):
|
||||||
|
""" 返回 date 这天所在年的开始时间
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date: `Datetime` 对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
返回 date 这天所在年的开始时间(1月 1号),`Datetime` 对象
|
||||||
|
"""
|
||||||
|
return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
|
||||||
|
month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
18
utils/const.py
Normal file
18
utils/const.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
DAILY_RECIPE_MEAL_TYPE_BREAKFAST = 'breakfast'
|
||||||
|
DAILY_RECIPE_MEAL_TYPE_LUNCH = 'lunch'
|
||||||
|
DAILY_RECIPE_MEAL_TYPE_SUPPER = 'supper'
|
||||||
|
DAILY_RECIPE_MEAL_TYPE_CHOICE = [
|
||||||
|
DAILY_RECIPE_MEAL_TYPE_BREAKFAST,
|
||||||
|
DAILY_RECIPE_MEAL_TYPE_LUNCH,
|
||||||
|
DAILY_RECIPE_MEAL_TYPE_SUPPER,
|
||||||
|
]
|
||||||
|
|
||||||
|
RECIPE_TYPE_MEAT = 'meat'
|
||||||
|
RECIPE_TYPE_VEGETABLE = 'vegetable'
|
||||||
|
RECIPE_TYPE_SOUP = 'soup'
|
||||||
|
|
||||||
|
RECIPE_TYPE_CHOICE = [
|
||||||
|
RECIPE_TYPE_MEAT,
|
||||||
|
RECIPE_TYPE_VEGETABLE,
|
||||||
|
RECIPE_TYPE_SOUP,
|
||||||
|
]
|
||||||
Loading…
x
Reference in New Issue
Block a user