From 1dd97b9e5424641ee97e4caf1f06cacf7fcc6078 Mon Sep 17 00:00:00 2001 From: Ching Date: Sat, 2 Oct 2021 20:28:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(dailyrecipe=20and=20week=20recipe):=20[A]?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0dailyrecipe=20=E6=AF=8F=E6=97=A5=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=EF=BC=8C=E5=A2=9E=E5=8A=A0=20weekrecipe=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [A] 增加dailyrecipe 每日菜单,增加 weekrecipe 接口 Signed-off-by: Ching --- recipe/migrations/0002_auto_20211002_1926.py | 27 +++++ recipe/models.py | 96 ++++++++++++++- recipe/serializers.py | 7 ++ recipe/urls.py | 1 + recipe/views.py | 40 +++++-- utils/__init__.py | 118 +++++++++++++++++++ utils/const.py | 18 +++ 7 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 recipe/migrations/0002_auto_20211002_1926.py create mode 100644 utils/__init__.py create mode 100644 utils/const.py diff --git a/recipe/migrations/0002_auto_20211002_1926.py b/recipe/migrations/0002_auto_20211002_1926.py new file mode 100644 index 0000000..82d9966 --- /dev/null +++ b/recipe/migrations/0002_auto_20211002_1926.py @@ -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')), + ], + ), + ] diff --git a/recipe/models.py b/recipe/models.py index 70c06ed..40d65d9 100644 --- a/recipe/models.py +++ b/recipe/models.py @@ -1,9 +1,101 @@ +import random 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): 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) rate = 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 diff --git a/recipe/serializers.py b/recipe/serializers.py index ab7eb5a..2f7b379 100644 --- a/recipe/serializers.py +++ b/recipe/serializers.py @@ -8,3 +8,10 @@ class RecipeSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = recipe.models.Recipe fields = '__all__' + + +class WeekRecipeSerializer(serializers.HyperlinkedModelSerializer): + + class Meta: + model = recipe.models.DailyRecipe + fields = '__all__' diff --git a/recipe/urls.py b/recipe/urls.py index eb15e99..0f7aa50 100644 --- a/recipe/urls.py +++ b/recipe/urls.py @@ -10,4 +10,5 @@ from recipe import views urlpatterns = [ url(r'^recipe/(?P\d+)$', views.RecipeAPI.as_view(), name='recipe-detail'), url(r'^recipe/$', views.RecipeListAPI.as_view()), + url(r'^week-recipe/$', views.WeekRecipeListAPI.as_view()), ] diff --git a/recipe/views.py b/recipe/views.py index 20c49ea..a5ad0ff 100644 --- a/recipe/views.py +++ b/recipe/views.py @@ -1,11 +1,7 @@ +import datetime + from django.shortcuts import render - -# Create your views here. - -import django.views.generic -from django.http import JsonResponse -from django.urls import reverse - +from django.utils.timezone import now, localtime from rest_framework import authentication, permissions, status, viewsets import rest_framework.generics from rest_framework.response import Response @@ -34,3 +30,33 @@ class RecipeListAPI(rest_framework.generics.ListAPIView, queryset = recipe.models.Recipe.objects.all() 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) diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..047b34a --- /dev/null +++ b/utils/__init__.py @@ -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) + diff --git a/utils/const.py b/utils/const.py new file mode 100644 index 0000000..83dc137 --- /dev/null +++ b/utils/const.py @@ -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, +]