Merge branch 'develop' of github.com:looching/dsite into develop

This commit is contained in:
Ching 2022-02-11 19:54:28 +08:00
commit cc0c22c68b
12 changed files with 286 additions and 36 deletions

View File

@ -9,5 +9,7 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.rulers": [ "editor.rulers": [
120 120
] ],
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active"
} }

View File

@ -31,3 +31,4 @@ user-agents==2.2.0
wcwidth==0.2.5 wcwidth==0.2.5
zipp==3.5.0 zipp==3.5.0
redis==4.1.0 redis==4.1.0
black

View File

@ -1,5 +1,5 @@
<template> <template>
<el-row justify="left"> <el-row justify="left" gutter="10">
<el-col> <el-col>
<el-form :rules="rules" ref="form" :model="form" label-position="left"> <el-form :rules="rules" ref="form" :model="form" label-position="left">
<el-form-item label="名字" prop="name"> <el-form-item label="名字" prop="name">
@ -54,6 +54,7 @@
<el-input type="textarea" v-model="form.note"></el-input> <el-input type="textarea" v-model="form.note"></el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-col :span="12" v-if="recipe_id">
<el-button <el-button
type="primary" type="primary"
plain plain
@ -61,6 +62,25 @@
@click="onSubmit(recipe_id)" @click="onSubmit(recipe_id)"
>提交</el-button >提交</el-button
> >
</el-col>
<el-col :span="24" v-else>
<el-button
type="primary"
plain
class="summit-recipe"
@click="onSubmit(recipe_id)"
>提交</el-button
>
</el-col>
<el-col :span="12" v-if="recipe_id">
<el-button
type="danger"
plain
class="summit-recipe"
@click="onSubmitDelete(recipe_id)"
>删除</el-button
>
</el-col>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-col> </el-col>
@ -69,6 +89,8 @@
<script> <script>
import axios from 'axios'; import axios from 'axios';
import config from '@/config/index'; import config from '@/config/index';
import { ElMessage } from 'element-plus';
export default { export default {
props: ['recipe_'], props: ['recipe_'],
watch: { watch: {
@ -105,6 +127,10 @@ export default {
axios axios
.post(config.publicPath + '/recipe/recipe/', data) .post(config.publicPath + '/recipe/recipe/', data)
.then(function () { .then(function () {
ElMessage({
message: '创建成功.',
type: 'success',
});
location.reload(); location.reload();
}) })
.catch(function (error) { .catch(function (error) {
@ -114,13 +140,27 @@ export default {
axios axios
.put(config.publicPath + '/recipe/recipe/' + recipe_id, data) .put(config.publicPath + '/recipe/recipe/' + recipe_id, data)
.then(function () { .then(function () {
location.reload(); ElMessage({
message: '修改成功.',
type: 'success',
});
}) })
.catch(function (error) { .catch(function (error) {
console.log(error); console.log(error);
}); });
} }
}, },
onSubmitDelete(recipe_id) {
axios
.delete(config.publicPath + '/recipe/recipe/' + recipe_id)
.then(function () {
ElMessage.error('删除成功.');
location.reload();
})
.catch(function (error) {
console.log(error);
});
},
}, },
}; };
</script> </script>
@ -128,5 +168,6 @@ export default {
<style scoped> <style scoped>
.summit-recipe { .summit-recipe {
width: 100%; width: 100%;
margin-bottom: 10px;
} }
</style> </style>

View File

@ -43,10 +43,12 @@
/> />
</van-cell-group> </van-cell-group>
<div class="recipe-create"> <div class="recipe-create">
<van-row gutter="20">
<van-col span="12" v-if="recipe_id">
<van-button <van-button
class="submit-button"
round round
type="primary" type="primary"
block
plain plain
hairline hairline
:disabled="disable_submit" :disabled="disable_submit"
@ -54,12 +56,51 @@
:loading="loading" :loading="loading"
>提交</van-button >提交</van-button
> >
</van-col>
<van-col span="24" v-else>
<van-button
class="submit-button"
round
type="primary"
plain
hairline
:disabled="disable_submit"
@click="onSubmit(recipe_id)"
:loading="loading"
>提交</van-button
>
</van-col>
<van-col span="12" v-if="recipe_id">
<van-button
class="submit-button"
round
type="danger"
plain
hairline
:disabled="disable_submit"
@click="onSubmitDelete(recipe_id)"
:loading="loading"
>删除</van-button
>
</van-col>
</van-row>
</div> </div>
</van-form> </van-form>
</template> </template>
<script> <script>
import { Form, Field, CellGroup, Radio, RadioGroup, Rate, Button } from 'vant'; import {
Form,
Field,
CellGroup,
Radio,
RadioGroup,
Rate,
Button,
Toast,
Col,
Row,
} from 'vant';
import axios from 'axios'; import axios from 'axios';
import config from '@/config/index'; import config from '@/config/index';
import router from '@/router/index'; import router from '@/router/index';
@ -81,6 +122,8 @@ export default {
[RadioGroup.name]: RadioGroup, [RadioGroup.name]: RadioGroup,
[Rate.name]: Rate, [Rate.name]: Rate,
[Button.name]: Button, [Button.name]: Button,
[Col.name]: Col,
[Row.name]: Row,
}, },
data() { data() {
return { return {
@ -118,11 +161,30 @@ export default {
(response) => (response, router.push({ name: 'RecipeMobileHome' })) (response) => (response, router.push({ name: 'RecipeMobileHome' }))
); );
} else { } else {
axios axios.put(config.publicPath + '/recipe/recipe/' + recipe_id, data).then(
.put(config.publicPath + '/recipe/recipe/' + recipe_id, data) (Toast.success({
.then((this.loading = false)); message: '修改成功',
forbidClick: true,
duration: 500,
}),
(this.loading = false))
);
} }
}, },
onSubmitDelete(recipe_id) {
if (!this.form.name) {
return;
}
this.loading = true;
axios.delete(config.publicPath + '/recipe/recipe/' + recipe_id).then(
(Toast.success({
message: '删除成功',
forbidClick: true,
duration: 500,
}),
(this.loading = false))
);
},
}, },
}; };
</script> </script>
@ -130,4 +192,7 @@ export default {
.recipe-create { .recipe-create {
margin: 20px 16px; margin: 20px 16px;
} }
.submit-button {
width: 100%;
}
</style> </style>

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2022-02-04 16:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recipe', '0002_auto_20211002_1926'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='status',
field=models.CharField(default='active', max_length=32),
),
]

View File

@ -9,6 +9,7 @@ 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, default=const.RECIPE_TYPE_MEAT) recipe_type = models.CharField(max_length=32, default=const.RECIPE_TYPE_MEAT)
status = models.CharField(max_length=32, default=const.RECIPE_STATUS_ACTIVE)
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)
@ -54,7 +55,9 @@ class Recipe(models.Model):
difficulty = 0 difficulty = 0
else: else:
difficulty = 0 difficulty = 0
recipe = cls.objects.create(name=name, recipe_type=recipe_type, rate=rate, difficulty=difficulty) recipe = cls.objects.create(
name=name, recipe_type=recipe_type, rate=rate, difficulty=difficulty, status=const.RECIPE_STATUS_ACTIVE
)
return recipe return recipe
@property @property
@ -125,7 +128,12 @@ class DailyRecipe(models.Model):
# meat # meat
for x in range(0, 2): for x in range(0, 2):
while True: while True:
recipe = Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_MEAT).order_by('?').first() recipe = (
Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_MEAT)
.exclude(status=const.RECIPE_STATUS_DELETED)
.order_by('?')
.first()
)
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes: if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
recipes.append(recipe.id) recipes.append(recipe.id)
break break
@ -138,7 +146,12 @@ class DailyRecipe(models.Model):
# vegetable # vegetable
for x in range(0, 1): for x in range(0, 1):
while True: while True:
recipe = Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_VEGETABLE).order_by('?').first() recipe = (
Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_VEGETABLE)
.exclude(status=const.RECIPE_STATUS_DELETED)
.order_by('?')
.first()
)
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes: if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
recipes.append(recipe.id) recipes.append(recipe.id)
break break
@ -150,7 +163,12 @@ class DailyRecipe(models.Model):
# soup # soup
if random.randint(0, 2): if random.randint(0, 2):
recipe = Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_SOUP).order_by('?').first() recipe = (
Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_SOUP)
.exclude(status=const.RECIPE_STATUS_DELETED)
.order_by('?')
.first()
)
# if recipe not in recipes and recipe not in prev_recipes: # if recipe not in recipes and recipe not in prev_recipes:
if recipe: if recipe:
recipes.append(recipe.id) recipes.append(recipe.id)

View File

@ -12,7 +12,7 @@ import recipe.serializers
from utils import const from utils import const
class RecipeAPI(rest_framework.generics.RetrieveUpdateAPIView): class RecipeAPI(rest_framework.generics.RetrieveUpdateDestroyAPIView):
# authentication_classes = (authentication.TokenAuthentication, # authentication_classes = (authentication.TokenAuthentication,
# authentication.SessionAuthentication, # authentication.SessionAuthentication,
@ -21,6 +21,10 @@ class RecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
queryset = recipe.models.Recipe.objects.all() queryset = recipe.models.Recipe.objects.all()
serializer_class = recipe.serializers.RecipeSerializer serializer_class = recipe.serializers.RecipeSerializer
def perform_destroy(self, instance):
instance.status = const.RECIPE_STATUS_DELETED
instance.save(update_fields=['status'])
class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView): class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
@ -28,7 +32,7 @@ class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics
# 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.exclude(status=const.RECIPE_STATUS_DELETED)
serializer_class = recipe.serializers.RecipeSerializer serializer_class = recipe.serializers.RecipeSerializer
filterset_fields = { filterset_fields = {
'recipe_type': const.FILTER_EXACT, 'recipe_type': const.FILTER_EXACT,

View File

@ -0,0 +1,83 @@
# coding: utf-8
from datetime import datetime
from dateutil import tz
from email import encoders
from email.header import Header
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import os, tarfile
import smtplib
to_zone = tz.gettz('Asia/Shanghai')
localtime = datetime.now(tz=to_zone)
def make_targz(output_filename, source_dir):
"""
一次性打包目录为tar.gz
:param output_filename: 压缩文件名
:param source_dir: 需要打包的目录
:return:
"""
try:
with tarfile.open(output_filename, "w:gz") as tar:
tar.add(source_dir, arcname=os.path.basename(source_dir))
return True
except Exception as e:
print(e)
return False
def send_email(file_name):
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
smtp_server = 'smtp.gmail.com'
smtp_port = 587
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
# 剩下的代码和前面的一模一样:
# server.set_debuglevel(1)
from_addr = ''
to_addr = ''
to_zone = tz.gettz('Asia/Shanghai')
localtime = datetime.now(tz=to_zone)
msg = MIMEMultipart()
msg['From'] = _format_addr('Vaultwarden <%s>' % from_addr)
msg['To'] = _format_addr('<%s>' % to_addr)
msg['Subject'] = Header('bitwarden 备份 %s' % localtime.strftime('%Y-%m-%d'), 'utf-8').encode()
# 邮件正文是MIMEText:
msg.attach(MIMEText('backup file attached', 'plain', 'utf-8'))
# 添加附件就是加上一个MIMEBase从本地读取一个图片:
with open(file_name, 'rb') as f:
# 设置附件的MIME和文件名
mime = MIMEBase('tar.gz', 'tar.gz', filename=file_name)
# 加上必要的头信息:
mime.add_header('Content-Disposition', 'attachment', filename=file_name)
mime.add_header('Content-ID', '<0>')
mime.add_header('X-Attachment-Id', '0')
# 把附件的内容读进来:
mime.set_payload(f.read())
# 用Base64编码:
encoders.encode_base64(mime)
# 添加到MIMEMultipart:
msg.attach(mime)
server.login(from_email, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
if __name__ == '__main__':
file_name = '/root/develop/vaultwarden/backup/bitwarden-%s.tar.gz' % localtime.strftime('%Y-%m-%d')
if make_targz(file_name, '/root/develop/vaultwarden/vw-data'):
send_email(file_name)

View File

@ -224,6 +224,11 @@ class RequestHandler(BaseHTTPRequestHandler):
subprocess.call("/root/deploy/dsite_prepare.sh") subprocess.call("/root/deploy/dsite_prepare.sh")
subprocess.run(["supervisorctl", "restart", "dsite"]) subprocess.run(["supervisorctl", "restart", "dsite"])
self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_) self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_)
elif site_ == 'dodo':
self.msg_compoment(access_token, open_id, '🚧 %s 开始部署 🚧' % site_)
subprocess.run(["git", "pull"])
self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_)
subprocess.run(["supervisorctl", "restart", "dodo"])
else: else:
self.msg_compoment(access_token, open_id, '⚠️ %s 不存在 ⚠️' % site_) self.msg_compoment(access_token, open_id, '⚠️ %s 不存在 ⚠️' % site_)
elif orig_text.startswith('/菜谱 '): elif orig_text.startswith('/菜谱 '):

5
scripts/dsite_prepare.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
/root/.pyenv/versions/py37/bin/pip install -r /root/deploy/dsite/develop_requirements.txt
/root/.pyenv/versions/py37/bin/python /root/deploy/dsite/manage.py collectstatic --noinput
/root/.pyenv/versions/py37/bin/python /root/deploy/dsite/manage.py migrate

5
scripts/restart_dodo.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
supervisorctl restart dodo && \
curl -X POST -H "Content-Type: application/json" \
-d '{"msg_type":"text","content":{"text":"🎉 dodo 部署成功 🎉"}}' \
https://open.feishu.cn/open-apis/bot/v2/hook/57cfa603-6154-4055-a739-210028171d10

View File

@ -11,6 +11,9 @@ RECIPE_TYPE_MEAT = 'meat'
RECIPE_TYPE_VEGETABLE = 'vegetable' RECIPE_TYPE_VEGETABLE = 'vegetable'
RECIPE_TYPE_SOUP = 'soup' RECIPE_TYPE_SOUP = 'soup'
RECIPE_STATUS_ACTIVE = 'active'
RECIPE_STATUS_DELETED = 'deleted'
RECIPE_TYPE_CHOICE = [RECIPE_TYPE_MEAT, RECIPE_TYPE_VEGETABLE, RECIPE_TYPE_SOUP] RECIPE_TYPE_CHOICE = [RECIPE_TYPE_MEAT, RECIPE_TYPE_VEGETABLE, RECIPE_TYPE_SOUP]
FILTER_EXACT = ['exact'] FILTER_EXACT = ['exact']