style(*.py): use Black as python code formatter

use Black as python code formatter

Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
Ching 2022-01-22 18:02:32 +08:00
parent beb10e00a9
commit f683341f66
20 changed files with 529 additions and 614 deletions

11
.vscode/settings.json vendored
View File

@ -1,2 +1,13 @@
{ {
"python.formatting.provider": "black",
"python.formatting.blackArgs": [
"-S",
"-C",
"-l 120"
],
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"editor.rulers": [
120
]
} }

View File

@ -2,12 +2,11 @@
from rest_framework import pagination from rest_framework import pagination
from rest_framework.response import Response from rest_framework.response import Response
class PagePaginationWithPageCount(pagination.PageNumberPagination): class PagePaginationWithPageCount(pagination.PageNumberPagination):
page_size_query_param = 'page_size' page_size_query_param = 'page_size'
def get_paginated_response(self, data):
response = super().get_paginated_response(data)
response.data['page_count'] = self.page.paginator.num_pages
return response
def get_paginated_response(self, data):
response = super().get_paginated_response(data)
response.data['page_count'] = self.page.paginator.num_pages
return response

View File

@ -37,14 +37,11 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
# third party # third party
'corsheaders', 'corsheaders',
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',
'django_filters', 'django_filters',
# user apps # user apps
'timer', 'timer',
'recipe', 'recipe',
@ -74,9 +71,9 @@ TEMPLATES = [
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
], ]
}, },
}, }
] ]
WSGI_APPLICATION = 'dsite.wsgi.application' WSGI_APPLICATION = 'dsite.wsgi.application'
@ -85,30 +82,17 @@ WSGI_APPLICATION = 'dsite.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = { DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3')}}
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation # Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
}, {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{ {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
] ]
@ -140,11 +124,10 @@ REST_FRAMEWORK = {
# 'DEFAULT_PERMISSION_CLASSES': [ # 'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly' # 'rest_framework.permissions.IsAuthenticatedOrReadOnly'
# ], # ],
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'DEFAULT_PAGINATION_CLASS': 'core.pagination.PagePaginationWithPageCount', 'DEFAULT_PAGINATION_CLASS': 'core.pagination.PagePaginationWithPageCount',
'PAGE_SIZE': 10, 'PAGE_SIZE': 10,
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
} }
# CORS # CORS
@ -153,7 +136,7 @@ REST_FRAMEWORK = {
# 'http://localhost:8080', # 'http://localhost:8080',
# ) # )
CORS_ALLOWED_ORIGINS = [] CORS_ALLOWED_ORIGINS = []
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
# CORS_ALLOW_ALL_ORIGINS = False # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect # CORS_ALLOW_ALL_ORIGINS = False # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect

View File

@ -7,8 +7,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
@ -21,5 +20,5 @@ class Migration(migrations.Migration):
('rate', models.IntegerField(default=0)), ('rate', models.IntegerField(default=0)),
('difficulty', models.IntegerField(default=0)), ('difficulty', models.IntegerField(default=0)),
], ],
), )
] ]

View File

@ -5,15 +5,11 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [('recipe', '0001_initial')]
('recipe', '0001_initial'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='recipe', model_name='recipe', name='recipe_type', field=models.CharField(default='meat', max_length=32)
name='recipe_type',
field=models.CharField(default='meat', max_length=32),
), ),
migrations.CreateModel( migrations.CreateModel(
name='DailyRecipe', name='DailyRecipe',

View File

@ -5,226 +5,178 @@ import datetime
from utils import const from utils import const
import utils import utils
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)
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): def serialize(self, verbose=False):
data = { data = {'id': self.id, 'name': self.name, 'recipe_type': self.recipe_type}
'id': self.id, if verbose:
'name': self.name, data.update({'difficulty': self.difficulty, 'rate': self.rate, 'note': self.note})
'recipe_type': self.recipe_type,
}
if verbose:
data.update({
'difficulty': self.difficulty,
'rate': self.rate,
'note': self.note,
})
return data return data
@classmethod @classmethod
def create_from_str(cls, content): def create_from_str(cls, content):
content = content.strip() content = content.strip()
if not content: if not content:
return return
name, *data = content.split(' ') name, *data = content.split(' ')
recipe_type = rate = difficulty = None recipe_type = rate = difficulty = None
keys = [recipe_type, rate, difficulty] keys = [recipe_type, rate, difficulty]
for x in range(len(data)): for x in range(len(data)):
keys[x] = data[x] keys[x] = data[x]
recipe_type, rate, difficulty = keys recipe_type, rate, difficulty = keys
if recipe_type == '': if recipe_type == '':
recipe_type = const.RECIPE_TYPE_MEAT recipe_type = const.RECIPE_TYPE_MEAT
elif recipe_type == '': elif recipe_type == '':
recipe_type = const.RECIPE_TYPE_VEGETABLE recipe_type = const.RECIPE_TYPE_VEGETABLE
elif recipe_type == '': elif recipe_type == '':
recipe_type = const.RECIPE_TYPE_SOUP recipe_type = const.RECIPE_TYPE_SOUP
else: else:
recipe_type = const.RECIPE_TYPE_MEAT recipe_type = const.RECIPE_TYPE_MEAT
if rate: if rate:
try: try:
rate = int(rate) rate = int(rate)
except: except:
rate = 0 rate = 0
else: else:
rate = 0 rate = 0
if difficulty: if difficulty:
try: try:
difficulty = int(difficulty) difficulty = int(difficulty)
except: except:
difficulty = 0 difficulty = 0
else: else:
difficulty = 0 difficulty = 0
recipe = cls.objects.create( recipe = cls.objects.create(name=name, recipe_type=recipe_type, rate=rate, difficulty=difficulty)
name=name, return recipe
recipe_type=recipe_type,
rate=rate,
difficulty=difficulty
)
return recipe
@property @property
def display_recipe_type(self): def display_recipe_type(self):
if self.recipe_type == const.RECIPE_TYPE_VEGETABLE: if self.recipe_type == const.RECIPE_TYPE_VEGETABLE:
return '' return ''
elif self.recipe_type == const.RECIPE_TYPE_MEAT: elif self.recipe_type == const.RECIPE_TYPE_MEAT:
return '' return ''
elif self.recipe_type == const.RECIPE_TYPE_SOUP: elif self.recipe_type == const.RECIPE_TYPE_SOUP:
return '' return ''
return '' return ''
@property @property
def display_rate(self): def display_rate(self):
return '🍚' * self.rate return '🍚' * self.rate
@property @property
def display_difficult(self): def display_difficult(self):
return '⭐️' * self.difficulty return '⭐️' * self.difficulty
def construct_lart_card(self): def construct_lart_card(self):
data = { data = {
"config": { "config": {"wide_screen_mode": True},
"wide_screen_mode": True "header": {"title": {"tag": "plain_text", "content": self.name}, "template": "blue"},
}, "elements": [
"header": { {
"title": { "tag": "markdown",
"tag": "plain_text", "content": "**类型**%s\n**评分**%s\n**难度**%s"
"content": self.name, % (self.display_recipe_type, self.display_rate, self.display_difficult),
}, },
"template": "blue" {
}, "tag": "action",
"elements": [ "actions": [
{ {
"tag": "markdown", "tag": "button",
"content": "**类型**%s\n**评分**%s\n**难度**%s" % ( "text": {"tag": "plain_text", "content": "查看"},
self.display_recipe_type, self.display_rate, self.display_difficult) "multi_url": {
}, "url": "https://recipe.tunpok.com/recipe/%s" % self.id,
{ "android_url": "https://recipe.tunpok.com/recipe-mobile/recipe/%s" % self.id,
"tag": "action", "ios_url": "https://recipe.tunpok.com/recipe-mobile/recipe/%s" % self.id,
"actions": [ "pc_url": "https://recipe.tunpok.com/recipe/%s" % self.id,
{ },
"tag": "button", "type": "primary",
"text": { },
"tag": "plain_text", {"tag": "button", "text": {"tag": "plain_text", "content": "删除"}, "type": "danger"},
"content": "查看" ],
}, },
"multi_url": { ],
"url": "https://recipe.tunpok.com/recipe/%s" % self.id,
"android_url": "https://recipe.tunpok.com/recipe-mobile/recipe/%s" % self.id,
"ios_url": "https://recipe.tunpok.com/recipe-mobile/recipe/%s" % self.id,
"pc_url": "https://recipe.tunpok.com/recipe/%s" % self.id
},
"type": "primary"
},
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "删除"
},
"type": "danger"
}
]
} }
] return data
}
return data
class DailyRecipe(models.Model): class DailyRecipe(models.Model):
recipes = models.ManyToManyField(Recipe) recipes = models.ManyToManyField(Recipe)
date = models.DateField() date = models.DateField()
meal_type = models.CharField( meal_type = models.CharField(max_length=32, default=const.DAILY_RECIPE_MEAL_TYPE_SUPPER)
max_length=32,
default=const.DAILY_RECIPE_MEAL_TYPE_SUPPER,)
def generate_recipe(self, prev_recipes=None, ignore_prev=False): def generate_recipe(self, prev_recipes=None, ignore_prev=False):
if not prev_recipes: if not prev_recipes:
prev_recipes = [] prev_recipes = []
if ignore_prev: if ignore_prev:
prev_recipes = [] prev_recipes = []
recipes = [] recipes = []
retry = 5 retry = 5
# meat # meat
for x in range(0,2): for x in range(0, 2):
while True: while True:
recipe = Recipe.objects.filter( recipe = Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_MEAT).order_by('?').first()
recipe_type=const.RECIPE_TYPE_MEAT, if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
).order_by('?').first() recipes.append(recipe.id)
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes: break
recipes.append(recipe.id) else:
break retry -= 1
else: if retry <= 0:
retry -= 1 retry = 5
if retry <= 0: break
retry = 5
break
# vegetable # vegetable
for x in range(0, 1): for x in range(0, 1):
while True: while True:
recipe = Recipe.objects.filter( recipe = Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_VEGETABLE).order_by('?').first()
recipe_type=const.RECIPE_TYPE_VEGETABLE, if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
).order_by('?').first() recipes.append(recipe.id)
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes: break
recipes.append(recipe.id) else:
break retry -= 1
else: if retry <= 0:
retry -= 1 retry = 5
if retry <= 0: break
retry = 5
break
# soup # soup
if random.randint(0,2): if random.randint(0, 2):
recipe = Recipe.objects.filter( recipe = Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_SOUP).order_by('?').first()
recipe_type=const.RECIPE_TYPE_SOUP, # if recipe not in recipes and recipe not in prev_recipes:
).order_by('?').first() if recipe:
# if recipe not in recipes and recipe not in prev_recipes: recipes.append(recipe.id)
if recipe:
recipes.append(recipe.id)
self.recipes.set(Recipe.objects.filter(id__in=recipes)) self.recipes.set(Recipe.objects.filter(id__in=recipes))
def serialize(self): def serialize(self):
data = { data = {const.RECIPE_TYPE_MEAT: [], const.RECIPE_TYPE_VEGETABLE: [], const.RECIPE_TYPE_SOUP: []}
const.RECIPE_TYPE_MEAT: [], for key_, value_ in data.items():
const.RECIPE_TYPE_VEGETABLE: [], for recipe in self.recipes.filter(recipe_type=key_).order_by('id'):
const.RECIPE_TYPE_SOUP: [], value_.append(recipe.serialize())
}
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 = now()
date = date.replace(year=self.date.year, month=self.date.month, date = date.replace(year=self.date.year, month=self.date.month, day=self.date.day, hour=0, minute=0)
day=self.date.day, hour=0, minute=0) data['date'] = utils.timestamp_of(utils.day_start(date))
data['date'] = utils.timestamp_of(utils.day_start(date)) data['id'] = self.id
data['id'] = self.id return data
return data
@classmethod @classmethod
def get_week_recipe_data(cls): def get_week_recipe_data(cls):
today = localtime() today = localtime()
week_start = (today - datetime.timedelta(days=today.weekday())).date() week_start = (today - datetime.timedelta(days=today.weekday())).date()
week_end = week_start + datetime.timedelta(days=6) week_end = week_start + datetime.timedelta(days=6)
daily_recipes = cls.objects.filter( daily_recipes = cls.objects.filter(date__gte=week_start, date__lte=week_end).order_by('date')
date__gte=week_start, data = [{}] * (7 - len(daily_recipes))
date__lte=week_end,
).order_by('date')
data = [{}] * (7 - len(daily_recipes))
for daily_recipe in daily_recipes: for daily_recipe in daily_recipes:
data.append(daily_recipe.serialize()) data.append(daily_recipe.serialize())
return data return data

View File

@ -4,23 +4,25 @@ from rest_framework import serializers
import recipe.models import recipe.models
class RecipeSerializer(serializers.ModelSerializer): class RecipeSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True) id = serializers.IntegerField(read_only=True)
class Meta:
model = recipe.models.Recipe class Meta:
fields = '__all__' model = recipe.models.Recipe
fields = '__all__'
class WeekRecipeSerializer(serializers.ModelSerializer): class WeekRecipeSerializer(serializers.ModelSerializer):
class Meta:
class Meta: model = recipe.models.DailyRecipe
model = recipe.models.DailyRecipe fields = '__all__'
fields = '__all__'
class DailyRecipeSerializer(serializers.ModelSerializer): class DailyRecipeSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True) id = serializers.IntegerField(read_only=True)
recipes = RecipeSerializer(many=True) recipes = RecipeSerializer(many=True)
class Meta:
model = recipe.models.DailyRecipe class Meta:
fields = '__all__' model = recipe.models.DailyRecipe
fields = '__all__'

View File

@ -1,4 +1,5 @@
from django.conf.urls import include, url from django.conf.urls import include, url
# from django.core.urlresolvers import reverse # from django.core.urlresolvers import reverse
from django.urls import path from django.urls import path
from rest_framework import routers from rest_framework import routers
@ -11,6 +12,5 @@ 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()), url(r'^week-recipe/$', views.WeekRecipeListAPI.as_view()),
url(r'^daily-recipe/(?P<pk>\d+)$', views.DailyRecipeAPI.as_view(), url(r'^daily-recipe/(?P<pk>\d+)$', views.DailyRecipeAPI.as_view(), name='dailyrecipe-detail'),
name='dailyrecipe-detail'),
] ]

View File

@ -11,73 +11,66 @@ import recipe.models
import recipe.serializers import recipe.serializers
from utils import const from utils import const
class RecipeAPI(rest_framework.generics.RetrieveUpdateAPIView): class RecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
# authentication_classes = (authentication.TokenAuthentication, # authentication_classes = (authentication.TokenAuthentication,
# authentication.SessionAuthentication, # authentication.SessionAuthentication,
# authentication.BasicAuthentication) # authentication.BasicAuthentication)
# permission_classes = (permissions.IsAuthenticated,) # permission_classes = (permissions.IsAuthenticated,)
queryset = recipe.models.Recipe.objects.all() queryset = recipe.models.Recipe.objects.all()
serializer_class = recipe.serializers.RecipeSerializer serializer_class = recipe.serializers.RecipeSerializer
class RecipeListAPI(rest_framework.generics.ListAPIView, class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
rest_framework.generics.CreateAPIView):
# authentication_classes = (authentication.TokenAuthentication, # authentication_classes = (authentication.TokenAuthentication,
# authentication.SessionAuthentication, # authentication.SessionAuthentication,
# authentication.BasicAuthentication) # authentication.BasicAuthentication)
# permission_classes = (permissions.IsAuthenticated,) # permission_classes = (permissions.IsAuthenticated,)
queryset = recipe.models.Recipe.objects.all() queryset = recipe.models.Recipe.objects.all()
serializer_class = recipe.serializers.RecipeSerializer 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,
'rate': const.FILTER_GTE_LTE, 'rate': const.FILTER_GTE_LTE,
} }
class WeekRecipeListAPI(rest_framework.generics.ListAPIView, class WeekRecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
rest_framework.generics.CreateAPIView):
queryset = recipe.models.DailyRecipe.objects.all() queryset = recipe.models.DailyRecipe.objects.all()
serializer_class = recipe.serializers.WeekRecipeSerializer serializer_class = recipe.serializers.WeekRecipeSerializer
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# Monday == 0 ... Sunday == 6 # Monday == 0 ... Sunday == 6
today = localtime() today = localtime()
recipes = [] recipes = []
for x in range(0, 7-today.weekday()): for x in range(0, 7 - today.weekday()):
daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create( daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create(date=today + datetime.timedelta(days=x))
date=today + datetime.timedelta(days=x) daily_recipe.generate_recipe(recipes)
) recipes.extend(daily_recipe.recipes.values_list('id', flat=True))
daily_recipe.generate_recipe(recipes) return Response(recipe.models.DailyRecipe.get_week_recipe_data(), status=status.HTTP_201_CREATED, headers={})
recipes.extend(daily_recipe.recipes.values_list('id', flat=True))
return Response(recipe.models.DailyRecipe.get_week_recipe_data(),
status=status.HTTP_201_CREATED, headers={})
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
data = recipe.models.DailyRecipe.get_week_recipe_data() data = recipe.models.DailyRecipe.get_week_recipe_data()
return Response(data) return Response(data)
class DailyRecipeAPI(rest_framework.generics.RetrieveUpdateAPIView): class DailyRecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
queryset = recipe.models.DailyRecipe.objects.all() queryset = recipe.models.DailyRecipe.objects.all()
serializer_class = recipe.serializers.DailyRecipeSerializer serializer_class = recipe.serializers.DailyRecipeSerializer
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk']) daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk'])
daily_recipe.generate_recipe() daily_recipe.generate_recipe()
return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED, return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED, headers={})
headers={})
def put(self, request, *args, **kwargs): def put(self, request, *args, **kwargs):
daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk']) daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk'])
recipes = request.data.get('meat',[]) recipes = request.data.get('meat', [])
recipes.extend(request.data.get('vegetable', [])) recipes.extend(request.data.get('vegetable', []))
recipes.extend(request.data.get('soup', [])) recipes.extend(request.data.get('soup', []))
daily_recipe.recipes.set(recipe.models.Recipe.objects.filter( daily_recipe.recipes.set(recipe.models.Recipe.objects.filter(id__in=recipes))
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={})

View File

@ -2,10 +2,12 @@
# --coding:utf-8-- # --coding:utf-8--
import os import os
import sys import sys
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dsite.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dsite.settings")
sys.path.insert(0, '../') sys.path.insert(0, '../')
sys.path.insert(0, './') sys.path.insert(0, './')
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
get_wsgi_application() get_wsgi_application()
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
@ -36,48 +38,46 @@ KEDAI_ID = '107263380636355825'
logging.basicConfig(filename='/root/develop/log/dodo.log', level=logging.INFO) logging.basicConfig(filename='/root/develop/log/dodo.log', level=logging.INFO)
logger = logging.getLogger('/root/develop/log/dodo.log') logger = logging.getLogger('/root/develop/log/dodo.log')
mastodon_cli = Mastodon( mastodon_cli = Mastodon(access_token='Ug_bUMWCk3RLamOnqYIytmeB0nO6aNfjdmf06mAj2bE', api_base_url='https://nofan.xyz')
access_token = 'Ug_bUMWCk3RLamOnqYIytmeB0nO6aNfjdmf06mAj2bE',
api_base_url = 'https://nofan.xyz'
)
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True) pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
redis_cli = redis.Redis(host='localhost', port=6379, decode_responses=True) redis_cli = redis.Redis(host='localhost', port=6379, decode_responses=True)
class AESCipher(object):
class AESCipher(object):
def __init__(self, key): def __init__(self, key):
self.bs = AES.block_size self.bs = AES.block_size
self.key=hashlib.sha256(AESCipher.str_to_bytes(key)).digest() self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest()
@staticmethod @staticmethod
def str_to_bytes(data): def str_to_bytes(data):
u_type = type(b"".decode('utf8')) u_type = type(b"".decode('utf8'))
if isinstance(data, u_type): if isinstance(data, u_type):
return data.encode('utf8') return data.encode('utf8')
return data return data
@staticmethod @staticmethod
def _unpad(s): def _unpad(s):
return s[:-ord(s[len(s) - 1:])] return s[: -ord(s[len(s) - 1 :])]
def decrypt(self, enc): def decrypt(self, enc):
iv = enc[:AES.block_size] iv = enc[: AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv) cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])) return self._unpad(cipher.decrypt(enc[AES.block_size :]))
def decrypt_string(self, enc): def decrypt_string(self, enc):
enc = base64.b64decode(enc) enc = base64.b64decode(enc)
return self.decrypt(enc).decode('utf8') return self.decrypt(enc).decode('utf8')
def get_tenant_access_token(): # 获取token def get_tenant_access_token(): # 获取token
token = redis_cli.get('tenant_access_token_%s' % APP_ID) token = redis_cli.get('tenant_access_token_%s' % APP_ID)
if token: if token:
return token return token
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/" url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
headers = { headers = {"Content-Type": "application/json"}
"Content-Type": "application/json" req_body = {"app_id": APP_ID, "app_secret": APP_SECRET}
}
req_body = {
"app_id": APP_ID,
"app_secret": APP_SECRET
}
data = bytes(json.dumps(req_body), encoding='utf8') data = bytes(json.dumps(req_body), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST') req = request.Request(url=url, data=data, headers=headers, method='POST')
@ -94,33 +94,27 @@ def get_tenant_access_token(): # 获取token
logger.error("get tenant_access_token error, code =%s", code) logger.error("get tenant_access_token error, code =%s", code)
return "" return ""
token = rsp_dict.get("tenant_access_token", "") token = rsp_dict.get("tenant_access_token", "")
redis_cli.set('tenant_access_token_%s' % APP_ID, redis_cli.set('tenant_access_token_%s' % APP_ID, rsp_dict.get("tenant_access_token", ""), ex=60 * 30)
rsp_dict.get("tenant_access_token", ""),
ex=60*30)
return token return token
def get_group_name(chat_id): def get_group_name(chat_id):
group_name = redis_cli.get('group_name_%s' % chat_id) group_name = redis_cli.get('group_name_%s' % chat_id)
if not group_name: if not group_name:
url = "https://open.feishu.cn/open-apis/im/v1/chats/" url = "https://open.feishu.cn/open-apis/im/v1/chats/"
headers = { headers = {"Content-Type": "application/json", "Authorization": "Bearer " + get_tenant_access_token()}
"Content-Type": "application/json", try:
"Authorization": "Bearer " + get_tenant_access_token() resp = requests.get(url + chat_id, headers=headers)
} resp_data = resp.json()
try: code = resp_data.get("code", -1)
resp = requests.get(url+chat_id, headers=headers) if code == 0:
resp_data = resp.json() group_name = resp_data.get('data', {}).get('name')
code = resp_data.get("code", -1) redis_cli.set('group_name_%s' % chat_id, group_name, ex=60 * 60 * 24)
if code == 0: except:
group_name = resp_data.get('data', {}).get('name') # todo: log
redis_cli.set('group_name_%s' % chat_id, return
group_name, return group_name
ex=60*60*24)
except:
# todo: log
return
return group_name
class RequestHandler(BaseHTTPRequestHandler): class RequestHandler(BaseHTTPRequestHandler):
@ -156,12 +150,12 @@ class RequestHandler(BaseHTTPRequestHandler):
event_id = obj.get('header', {}).get('event_id', '') event_id = obj.get('header', {}).get('event_id', '')
# 重复收到的事件不处理 # 重复收到的事件不处理
if event_id and redis_cli.get(event_id): if event_id and redis_cli.get(event_id):
self.response("") self.response("")
return return
event = obj.get("event") event = obj.get("event")
if event.get("message"): if event.get("message"):
self.handle_message(event, event_id) self.handle_message(event, event_id)
return return
return return
def handle_request_url_verify(self, post_obj): def handle_request_url_verify(self, post_obj):
@ -192,71 +186,77 @@ class RequestHandler(BaseHTTPRequestHandler):
text = text.lstrip() text = text.lstrip()
orig_text = text orig_text = text
if ADD_GROUP_NAME: if ADD_GROUP_NAME:
group_name = get_group_name(msg.get("chat_id")) group_name = get_group_name(msg.get("chat_id"))
text = '%s #%s' % (text, group_name) text = '%s #%s' % (text, group_name)
else: else:
open_id = {"open_id": event.get("sender", {}).get( open_id = {"open_id": event.get("sender", {}).get('sender_id', {}).get('open_id')}
'sender_id', {}).get('open_id')}
self.response("") self.response("")
if orig_text.startswith('/'): if orig_text.startswith('/'):
redis_cli.set(event_id, int(time.time()), ex=60*60*7) redis_cli.set(event_id, int(time.time()), ex=60 * 60 * 7)
if orig_text not in ['/last', '/del']: if orig_text not in ['/last', '/del']:
flag = False flag = False
for action_ in ['/deploy ', '/菜谱 ']: for action_ in ['/deploy ', '/菜谱 ']:
if orig_text.startswith(action_): if orig_text.startswith(action_):
flag = True flag = True
break break
if not flag: if not flag:
self.msg_compoment(access_token, open_id, '指令错误') self.msg_compoment(access_token, open_id, '指令错误')
return return
if orig_text == '/last': if orig_text == '/last':
try: try:
statuses = mastodon_cli.account_statuses(KEDAI_ID, limit=1) statuses = mastodon_cli.account_statuses(KEDAI_ID, limit=1)
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser') s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
self.msg_compoment(access_token, open_id, self.msg_compoment(access_token, open_id, s_text.get_text(''))
s_text.get_text('')) except Exception as exc:
except Exception as exc: logger.error('operation error: %s', str(exc))
logger.error('operation error: %s', str(exc)) elif orig_text == '/del':
elif orig_text == '/del': try:
try: statuses = mastodon_cli.account_statuses(KEDAI_ID, limit=1)
statuses = mastodon_cli.account_statuses(KEDAI_ID, limit=1) mastodon_cli.status_delete(statuses[0]['id'])
mastodon_cli.status_delete(statuses[0]['id']) s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser') self.msg_compoment(access_token, open_id, '已删除: ' + s_text.get_text(''))
self.msg_compoment(access_token, open_id, except Exception as exc:
'已删除: ' + s_text.get_text('')) logger.error('operation error: %s', str(exc))
except Exception as exc: elif orig_text.startswith('/deploy '):
logger.error('operation error: %s', str(exc)) site_ = orig_text.split('/deploy ')[1]
elif orig_text.startswith('/deploy '): if site_ == 'dsite':
site_ = orig_text.split('/deploy ')[1] self.msg_compoment(access_token, open_id, '🚧 %s 开始部署 🚧' % site_)
if site_ == 'dsite': subprocess.call("/root/deploy/dsite_prepare.sh")
self.msg_compoment(access_token, open_id, '🚧 %s 开始部署 🚧' % site_) subprocess.run(["supervisorctl", "restart", "dsite"])
subprocess.call("/root/deploy/dsite_prepare.sh") self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_)
subprocess.run(["supervisorctl", "restart", "dsite"]) else:
self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_) self.msg_compoment(access_token, open_id, '⚠️ %s 不存在 ⚠️' % site_)
else: elif orig_text.startswith('/菜谱 '):
self.msg_compoment(access_token, open_id, '⚠️ %s 不存在 ⚠️' % site_) content = orig_text.split('/菜谱 ')[1]
elif orig_text.startswith('/菜谱 '): recipe_ = recipe.models.Recipe.create_from_str(content)
content = orig_text.split('/菜谱 ')[1] if recipe_:
recipe_ = recipe.models.Recipe.create_from_str(content) self.msg_compoment(
if recipe_: access_token,
self.msg_compoment(access_token, open_id, None, open_id,
const.LARK_WEBHOOK_MSG_TYPE_INTERACTIVE, None,
recipe_.construct_lart_card()) const.LARK_WEBHOOK_MSG_TYPE_INTERACTIVE,
else: recipe_.construct_lart_card(),
self.msg_compoment(access_token, open_id, '⚠️ 创建失败 ⚠️') )
return else:
self.msg_compoment(access_token, open_id, '⚠️ 创建失败 ⚠️')
return
try: try:
toot_resp = mastodon_cli.status_post(text) toot_resp = mastodon_cli.status_post(text)
if toot_resp.get('id'): if toot_resp.get('id'):
self.msg_compoment(access_token, open_id, '📟 dodo 📟') self.msg_compoment(access_token, open_id, '📟 dodo 📟')
redis_cli.set(event_id, int(time.time()), ex=60*60*7) redis_cli.set(event_id, int(time.time()), ex=60 * 60 * 7)
else: else:
self.msg_compoment(access_token, open_id, """⚠️ didi ⚠️ self.msg_compoment(
access_token,
open_id,
"""⚠️ didi ⚠️
%s %s
""" % json.loads(toot_resp)) """
% json.loads(toot_resp),
)
except Exception as exc: except Exception as exc:
logger.error('send toot error: %s', str(exc)) logger.error('send toot error: %s', str(exc))
return return
elif msg_type == "image": elif msg_type == "image":
@ -271,15 +271,10 @@ class RequestHandler(BaseHTTPRequestHandler):
def send_message(self, token, open_id, text, msg_type=None, content=None): def send_message(self, token, open_id, text, msg_type=None, content=None):
url = "https://open.feishu.cn/open-apis/message/v4/send/" url = "https://open.feishu.cn/open-apis/message/v4/send/"
headers = { headers = {"Content-Type": "application/json", "Authorization": "Bearer " + token}
"Content-Type": "application/json",
"Authorization": "Bearer " + token
}
if not msg_type: if not msg_type:
msg_type = const.LARK_WEBHOOK_MSG_TYPE_TEXT msg_type = const.LARK_WEBHOOK_MSG_TYPE_TEXT
req_body = { req_body = {"msg_type": msg_type}
"msg_type": msg_type
}
if msg_type == const.LARK_WEBHOOK_MSG_TYPE_TEXT: if msg_type == const.LARK_WEBHOOK_MSG_TYPE_TEXT:
req_body['content'] = {'text': text} req_body['content'] = {'text': text}
elif msg_type == const.LARK_WEBHOOK_MSG_TYPE_INTERACTIVE: elif msg_type == const.LARK_WEBHOOK_MSG_TYPE_INTERACTIVE:
@ -299,13 +294,12 @@ class RequestHandler(BaseHTTPRequestHandler):
rsp_dict = json.loads(rsp_body) rsp_dict = json.loads(rsp_body)
code = rsp_dict.get("code", -1) code = rsp_dict.get("code", -1)
if code != 0: if code != 0:
logger.error("send message error, code = %s, msg =%s", logger.error("send message error, code = %s, msg =%s", code, rsp_dict.get("msg", ""))
code,
rsp_dict.get("msg", ""))
def msg_compoment(self, token, open_id, text, msg_type=None, content=None): def msg_compoment(self, token, open_id, text, msg_type=None, content=None):
self.send_message(token, open_id, text, msg_type, content) self.send_message(token, open_id, text, msg_type, content)
def run(): def run():
port = 5000 port = 5000
server_address = ('', port) server_address = ('', port)

View File

@ -4,27 +4,23 @@ import timer.models
class OfficeHoursForm(forms.ModelForm): class OfficeHoursForm(forms.ModelForm):
class Meta: class Meta:
model = timer.models.OfficeHours model = timer.models.OfficeHours
fields = ['user', 'begins_at', 'ends_at'] fields = ['user', 'begins_at', 'ends_at']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') self.user = kwargs.pop('user')
return super().__init__(*args, **kwargs) return super().__init__(*args, **kwargs)
def full_clean(self):
if not self.user.is_authenticated:
raise forms.ValidationError('Invalid User.')
return
def save(self):
begins_at = timer.models.OfficeHours.parse_time_str(self.data.get('begins_at'))
user = self.user
obj = timer.models.OfficeHours.objects.create(
user=user,
begins_at=begins_at,
ends_at=timer.models.OfficeHours.get_ends_at(begins_at))
return obj
def full_clean(self):
if not self.user.is_authenticated:
raise forms.ValidationError('Invalid User.')
return
def save(self):
begins_at = timer.models.OfficeHours.parse_time_str(self.data.get('begins_at'))
user = self.user
obj = timer.models.OfficeHours.objects.create(
user=user, begins_at=begins_at, ends_at=timer.models.OfficeHours.get_ends_at(begins_at)
)
return obj

View File

@ -9,9 +9,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
@ -22,5 +20,5 @@ class Migration(migrations.Migration):
('ends_at', models.DateTimeField()), ('ends_at', models.DateTimeField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
], ],
), )
] ]

View File

@ -6,22 +6,22 @@ import datetime
class OfficeHours(models.Model): class OfficeHours(models.Model):
begins_at = models.DateTimeField() begins_at = models.DateTimeField()
ends_at = models.DateTimeField() ends_at = models.DateTimeField()
user = models.ForeignKey(User, on_delete=django.db.models.deletion.CASCADE) user = models.ForeignKey(User, on_delete=django.db.models.deletion.CASCADE)
@classmethod @classmethod
def parse_time_str(cls, time_srt): def parse_time_str(cls, time_srt):
return datetime.datetime.strptime(time_srt, '%Y-%m-%d %H:%M') return datetime.datetime.strptime(time_srt, '%Y-%m-%d %H:%M')
@classmethod @classmethod
def get_ends_at(cls, begins_at): def get_ends_at(cls, begins_at):
return begins_at + datetime.timedelta(hours=9.5) return begins_at + datetime.timedelta(hours=9.5)
@property @property
def get_begins_at_str(self): def get_begins_at_str(self):
return localtime(self.begins_at).strftime('%Y-%m-%d %H:%M') return localtime(self.begins_at).strftime('%Y-%m-%d %H:%M')
@property @property
def get_ends_at_str(self): def get_ends_at_str(self):
return localtime(self.ends_at).strftime('%H:%M') return localtime(self.ends_at).strftime('%H:%M')

View File

@ -5,17 +5,17 @@ import timer.models
class OfficeHoursSerializer(serializers.HyperlinkedModelSerializer): class OfficeHoursSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = timer.models.OfficeHours model = timer.models.OfficeHours
class UserSerializer(serializers.HyperlinkedModelSerializer): class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ('url', 'username', 'email', 'groups') fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer): class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = Group model = Group
fields = ('url', 'name') fields = ('url', 'name')

View File

@ -1,4 +1,5 @@
from django.conf.urls import include, url from django.conf.urls import include, url
# from django.core.urlresolvers import reverse # from django.core.urlresolvers import reverse
from django.urls import path from django.urls import path
from rest_framework import routers from rest_framework import routers

View File

@ -19,59 +19,57 @@ from django.urls import reverse
class OfficeHoursAPI(CreateAPIView): class OfficeHoursAPI(CreateAPIView):
authentication_classes = (authentication.TokenAuthentication, authentication_classes = (
authentication.SessionAuthentication, authentication.TokenAuthentication,
authentication.BasicAuthentication) authentication.SessionAuthentication,
permission_classes = (permissions.IsAuthenticated,) authentication.BasicAuthentication,
)
permission_classes = (permissions.IsAuthenticated,)
queryset = timer.models.OfficeHours.objects.all() queryset = timer.models.OfficeHours.objects.all()
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
begins_at = request.data.get('begins_at') begins_at = request.data.get('begins_at')
if not begins_at: if not begins_at:
raise raise
try: try:
begins_at = timer.models.OfficeHours.parse_time_str(begins_at) begins_at = timer.models.OfficeHours.parse_time_str(begins_at)
ends_at = timer.models.OfficeHours.get_ends_at(begins_at) ends_at = timer.models.OfficeHours.get_ends_at(begins_at)
oh, __ = timer.models.OfficeHours.objects.get_or_create( oh, __ = timer.models.OfficeHours.objects.get_or_create(
begins_at=begins_at, begins_at=begins_at, ends_at=ends_at, user=request.user
ends_at=ends_at, )
user=request.user) except ValueError:
except ValueError: raise
raise oh.refresh_from_db()
oh.refresh_from_db() resp_data = {'begins_at': oh.get_begins_at_str, 'ends_at': oh.get_ends_at_str}
resp_data = {
'begins_at': oh.get_begins_at_str,
'ends_at': oh.get_ends_at_str,
}
return Response(resp_data, status=status.HTTP_201_CREATED) return Response(resp_data, status=status.HTTP_201_CREATED)
class OfficeHoursFormView(django.views.generic.FormView): class OfficeHoursFormView(django.views.generic.FormView):
template_name = 'index.html' template_name = 'index.html'
form_class = timer.forms.OfficeHoursForm form_class = timer.forms.OfficeHoursForm
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user kwargs['user'] = self.request.user
return kwargs return kwargs
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.form_class(**self.get_form_kwargs()) form = self.form_class(**self.get_form_kwargs())
try: try:
form.is_valid() form.is_valid()
form.save() form.save()
except django.forms.ValidationError as e: except django.forms.ValidationError as e:
return JsonResponse({'error': e.message}, status=status.HTTP_400_BAD_REQUEST) return JsonResponse({'error': e.message}, status=status.HTTP_400_BAD_REQUEST)
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
def get_success_url(self): def get_success_url(self):
return reverse('office-hours-page') return reverse('office-hours-page')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
today_oh = timer.models.OfficeHours.objects.filter(begins_at__date=localtime().date()).last() today_oh = timer.models.OfficeHours.objects.filter(begins_at__date=localtime().date()).last()
context['today_oh'] = today_oh context['today_oh'] = today_oh
return context return context

View File

@ -6,113 +6,113 @@ from django.utils import timezone
def timestamp_of(d): def timestamp_of(d):
if hasattr(d, 'isoformat'): if hasattr(d, 'isoformat'):
return calendar.timegm(d.utctimetuple()) return calendar.timegm(d.utctimetuple())
return None return None
def now(): def now():
return timezone.localtime(timezone.now()) return timezone.localtime(timezone.now())
def midnight(): def midnight():
return now().replace(hour=0, minute=0, second=0, microsecond=0) return now().replace(hour=0, minute=0, second=0, microsecond=0)
def is_today(tme): def is_today(tme):
if day_of(tme) != day_of(midnight()): if day_of(tme) != day_of(midnight()):
return False return False
return True return True
def current_time_n(n): def current_time_n(n):
return now() - timedelta(days=n) return now() - timedelta(days=n)
def day_n(n): def day_n(n):
"""Midnight of N days ago.""" """Midnight of N days ago."""
return midnight() - timedelta(days=n) return midnight() - timedelta(days=n)
def day_of(d): def day_of(d):
"""Returns date part.""" """Returns date part."""
return str(d.date()) return str(d.date())
def midnight_of(d): def midnight_of(d):
""" """
Args: Args:
d : datetime object d : datetime object
Return: Return:
Midnight of datetime. Midnight of datetime.
""" """
d = timezone.localtime(d) d = timezone.localtime(d)
return d.replace(hour=0, minute=0, second=0, microsecond=0) return d.replace(hour=0, minute=0, second=0, microsecond=0)
def day_start(date): def day_start(date):
""" 返回 date 这天的开始时间 """返回 date 这天的开始时间
Args: Args:
date: `Datetime` 对象 date: `Datetime` 对象
Returns: Returns:
返回 date 这天的开始时间0 `Datetime` 对象 返回 date 这天的开始时间0 `Datetime` 对象
""" """
return timezone.localtime(date).replace(hour=0, minute=0, second=0, return timezone.localtime(date).replace(hour=0, minute=0, second=0, microsecond=0)
microsecond=0)
def week_start(date): def week_start(date):
""" 返回 date 这天所在周的开始时间 """返回 date 这天所在周的开始时间
Args: Args:
date: `Datetime` 对象 date: `Datetime` 对象
Returns: Returns:
返回 date 这天所在周开始时间周一`Datetime` 对象 返回 date 这天所在周开始时间周一`Datetime` 对象
""" """
return timezone.localtime(date) + dateutil.relativedelta.relativedelta( return timezone.localtime(date) + dateutil.relativedelta.relativedelta(
weekday=dateutil.relativedelta.MO(-1), hour=0, minute=0, second=0, weekday=dateutil.relativedelta.MO(-1), hour=0, minute=0, second=0, microsecond=0
microsecond=0) )
def month_start(d): def month_start(d):
""" 返回 date 这天所在月的开始时间 """返回 date 这天所在月的开始时间
Args: Args:
date: `Datetime` 对象 date: `Datetime` 对象
Returns: Returns:
返回 date 这天所在月的开始时间1`Datetime` 对象 返回 date 这天所在月的开始时间1`Datetime` 对象
""" """
return timezone.localtime(d) + dateutil.relativedelta.relativedelta( return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
day=1, hour=0, minute=0, second=0, microsecond=0) day=1, hour=0, minute=0, second=0, microsecond=0
)
def month_end(d): def month_end(d):
"""返回 date 这天所在月的结束时间,即最后一天的 23:59:59 """返回 date 这天所在月的结束时间,即最后一天的 23:59:59
Args: Args:
data: `Datetime` 对象 data: `Datetime` 对象
Returns: Returns:
返回 date 这天所在月的最后一天的 23:59:59 的时间 返回 date 这天所在月的最后一天的 23:59:59 的时间
""" """
start = month_start(d) start = month_start(d)
month_days = calendar.monthrange(start.year, start.month)[1] month_days = calendar.monthrange(start.year, start.month)[1]
return start + timedelta(days=month_days, seconds=-1) return start + timedelta(days=month_days, seconds=-1)
def year_start(d): def year_start(d):
""" 返回 date 这天所在年的开始时间 """返回 date 这天所在年的开始时间
Args: Args:
date: `Datetime` 对象 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)
Returns:
返回 date 这天所在年的开始时间1 1`Datetime` 对象
"""
return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
month=1, day=1, hour=0, minute=0, second=0, microsecond=0
)

View File

@ -2,20 +2,16 @@ DAILY_RECIPE_MEAL_TYPE_BREAKFAST = 'breakfast'
DAILY_RECIPE_MEAL_TYPE_LUNCH = 'lunch' DAILY_RECIPE_MEAL_TYPE_LUNCH = 'lunch'
DAILY_RECIPE_MEAL_TYPE_SUPPER = 'supper' DAILY_RECIPE_MEAL_TYPE_SUPPER = 'supper'
DAILY_RECIPE_MEAL_TYPE_CHOICE = [ DAILY_RECIPE_MEAL_TYPE_CHOICE = [
DAILY_RECIPE_MEAL_TYPE_BREAKFAST, DAILY_RECIPE_MEAL_TYPE_BREAKFAST,
DAILY_RECIPE_MEAL_TYPE_LUNCH, DAILY_RECIPE_MEAL_TYPE_LUNCH,
DAILY_RECIPE_MEAL_TYPE_SUPPER, DAILY_RECIPE_MEAL_TYPE_SUPPER,
] ]
RECIPE_TYPE_MEAT = 'meat' RECIPE_TYPE_MEAT = 'meat'
RECIPE_TYPE_VEGETABLE = 'vegetable' RECIPE_TYPE_VEGETABLE = 'vegetable'
RECIPE_TYPE_SOUP = 'soup' RECIPE_TYPE_SOUP = 'soup'
RECIPE_TYPE_CHOICE = [ RECIPE_TYPE_CHOICE = [RECIPE_TYPE_MEAT, RECIPE_TYPE_VEGETABLE, RECIPE_TYPE_SOUP]
RECIPE_TYPE_MEAT,
RECIPE_TYPE_VEGETABLE,
RECIPE_TYPE_SOUP,
]
FILTER_EXACT = ['exact'] FILTER_EXACT = ['exact']
FILTER_GTE_LTE = ['exact', 'gte', 'gt', 'lte', 'lt'] FILTER_GTE_LTE = ['exact', 'gte', 'gt', 'lte', 'lt']

View File

@ -2,6 +2,7 @@
import sys import sys
import os import os
from imp import reload from imp import reload
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
sys.path.append('/Users/ching/develop/dsite') sys.path.append('/Users/ching/develop/dsite')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "../dsite.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "../dsite.settings")
@ -12,14 +13,9 @@ import git
import ipdb import ipdb
if __name__ == '__main__': if __name__ == '__main__':
# def notify(): # def notify():
repo = git.Repo(search_parent_directories=True) repo = git.Repo(search_parent_directories=True)
commit = repo.head.commit commit = repo.head.commit
rev, branch = commit.name_rev.split(' ') rev, branch = commit.name_rev.split(' ')
msg = 'rev: %s\n\nauther: %s\n\nbranch: %s\n\nmessage: %s' % ( msg = 'rev: %s\n\nauther: %s\n\nbranch: %s\n\nmessage: %s' % (rev, commit.author.name, branch, commit.summary)
rev,
commit.author.name,
branch,
commit.summary
)
print(utils.lark.request({'text': msg})) print(utils.lark.request({'text': msg}))

View File

@ -13,25 +13,26 @@ from django.utils.timezone import now
def gen_sign(timestamp, secret): def gen_sign(timestamp, secret):
# 拼接timestamp和secret # 拼接timestamp和secret
string_to_sign = '{}\n{}'.format(timestamp, secret) string_to_sign = '{}\n{}'.format(timestamp, secret)
hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest() hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest()
# 对结果进行base64处理 # 对结果进行base64处理
sign = base64.b64encode(hmac_code).decode('utf-8') sign = base64.b64encode(hmac_code).decode('utf-8')
return sign
return sign
def request(content, msg_type=const.LARK_WEBHOOK_MSG_TYPE_TEXT): def request(content, msg_type=const.LARK_WEBHOOK_MSG_TYPE_TEXT):
""" content: {'text': 'xxxxx} """content: {'text': 'xxxxx}"""
""" timestamp = utils.timestamp_of(now())
timestamp = utils.timestamp_of(now()) data = {
data = { "timestamp": timestamp,
"timestamp": timestamp, "sign": gen_sign(timestamp, settings.LARK_WEBHOOK_SECRET),
"sign": gen_sign(timestamp, settings.LARK_WEBHOOK_SECRET), "msg_type": msg_type,
"msg_type": msg_type, "content": content,
"content": content} }
resp = requests.post(settings.LARK_WEBHOOK_URL, data=json.dumps(data)) resp = requests.post(settings.LARK_WEBHOOK_URL, data=json.dumps(data))
if resp.status_code == 200 and resp.json().get('StatusCode') == 0: if resp.status_code == 200 and resp.json().get('StatusCode') == 0:
return True return True
return False return False