add operation and deployment module

This commit is contained in:
unknown
2017-12-09 20:28:53 +08:00
parent 810e60eda7
commit 2d74257a9e
50 changed files with 694 additions and 1644 deletions
+4 -3
View File
@@ -2,12 +2,13 @@
*.idea*
*__pycache__*
*.pyc
*.swp
back-end-src/media/*
back-end-src/tool_scripts/data/setting/local.py
back-end-src/tool_scripts/data/setting/remote.py
front-end-src/dist
front-end-src/node_modules
front-end-src/bower_components
fabfile_conf/production.py
dist/*
*.swp
operation-and-deployment-src/fabfile_conf/production.py
operation-and-deployment-src/dist/*
operation-and-deployment-src/backup/*
+37 -20
View File
@@ -1,35 +1,52 @@
# 框架概述
# 一、后端项目(back-end-src目录)
1. 后端Xadmin+Django2全家桶(①django;②django-REST-framework;③django-filter;④django-simple-captcha;⑤django-REST-framework-jwt )项目框架(附注释)
2. 前端webpack+Vue全家桶(①Vue2;②Vue-router;③axios;④Vuex+Bootstrap3的前端项目框架(附注释)
3. 后端部署脚本使用了fabric
4. 前端表单验证使用了1000hz-bootstrap-validator,文件上传使用了bootstrap-fileinput,富文本编辑使用了tinyMCE,图片编辑使用了cropper
5. `github_commit.sh`是一个非常强大的脚本,只要在命令行输入`.github_commit.sh "你的注释"`即可完成代码的提交(最重要的是把所有敏感的服务器和数据库配置信息统统替换掉,然后再上传到github,上传完成后再还原回来)
## 1. 架构说明
# 使用说明
1. Xadmin+Django2全家桶(①django;②django-REST-framework;③django-filter;④django-simple-captcha;⑤django-REST-framework-jwt
1. 后台环境搭建。安装好`Python3.5`或者`Python3.6`,在本项目根目录打开命令行,并输入:`pip install -r python-lib-requirements.txt`(注意,请保证你使用的Django版本要至少为2.0
## 2. 使用教程(下面的命令皆以back-end-src为当前目录运行
2. 前端环境搭建。安装好`node.js`,进入`/front-end-src`目录,打开命令行并输入:`npm install`
1. 环境搭建。安装好`Python3.5`或者`Python3.6`后,命令行运行`pip install -r python-lib-requirements.txt`(注意,请保证你使用的Django版本要至少为2.0)
2. 数据库生成。修改`APP_Inventor_case_base/settings.py`中的DATABASES为你自己的Mysql数据库配置,在`/back-end-src`下依次运行`python manage.py makemigrations``python manage.py migrate`
3. 启动后台程序,命令行运行`python manage.py runserver`
3. 数据库生成。修改`/back-end-src/APP_Inventor_case_base/settings.py`中的DATABASES为你自己的Mysql数据库配置,在`/back-end-src`下依次运行`python manage.py makemigrations``python manage.py migrate`
## 3. 工具脚本说明:
4. 启动后台程序,在`/back-end-src`下运行`python manage.py runserver`
`tool_scripts/clean_useless_files.py`用于删除/media下已经失效的用户上传图片文件(完全没有被当前任何数据引用到的);
5. 启动前端程序,进入进入`/front-end-src`目录,运行`npm run dev`
`tool_scripts/import_category_data.py`用于往数据表中插入案例的类别数据,用于初始化数据库;
6. `fabfile`为fabric部署脚本,在根目录下命令行运行`fab 函数名`即可执行对应操作
`tool_scripts/switch_setting.py`用于将`tool_scripts/data/db_backup`目录下制定文件(如`local.py`)替换到`APP_Inventor_case_base/settings.py`
7. 后台工具脚本说明:
# 二、前端项目(front-end-src目录)
`./back-end-src/tool_scripts/clean_useless_files.py`用于删除/media下已经失效的用户上传图片文件(完全没有被当前任何数据引用到的);
## 1. 架构说明
`./back-end-src/tool_scripts/import_category_data.py`用于往数据表中插入案例的类别数据,用于初始化数据库;
1. webpack+Vue全家桶(①Vue2;②Vue-router;③axios;④Vuex+Bootstrap3的前端项目框架(附注释)
2. 前端表单验证使用了1000hz-bootstrap-validator;文件上传使用了bootstrap-fileinput;富文本编辑使用了tinyMCE;片编辑使用了cropper
`./back-end-src/tool_scripts/init_migrations.py`用于删除所有的migration文件,然后重新进行`makemigrations`操作;
## 2. 使用教程(下面的命令皆以front-end-src为当前目录运行)
`./back-end-src/tool_scripts/backup.py`用于备份数据库;
1. 环境搭建。安装好`node.js`后,命令行运行`npm install`
2. 启动前端程序。命令行运行`npm run dev`
`./back-end-src/tool_scripts/restore.py`用于利用备份数据进行数据库还原;
# 三、运维和部署脚本(operation-and-deployment-src目录)
`./back-end-src/tool_scripts/switch_setting.py`用于将`./back-end-src/tool_scripts/data/db_backup`目录下制定文件(如`local.py`)替换到`./back-end-src/APP_Inventor_case_base/settings.py`
## 1. 架构说明
1. fabric
## 2. 使用教程(下面的命令皆以operation-and-deployment-src为当前目录运行)
1. `fabfile.py`为fabric部署脚本,命令行运行`fab 函数名`即可执行对应操作
2. `fab remoteBackupDB`将**服务器数据库**进行备份(仅对`_BACKUP_TABLES`中包含的表),然后把备份数据打包下载到本地
3. `fab localBackupDB`将**本地数据库**进行备份(仅对`_BACKUP_TABLES`中包含的表)
4. `fab localRestoreDB`利用数据库备份文件和`Django``migration`功能对**本地数据库**进行恢复
5. `fab remoteRestoreDB`利用数据库备份文件和`Django``migration`功能对**服务器数据库**进行恢复
6. `fab build`将本地项目代码进行打包,用作备份或者上传服务器进行部署
7. `fab deploy`将在本地打包好的项目代码上传到服务器然后再解包,然后利用supervisor启动服务,nginx进行请求转发
8. `fab rollback`项目代码回退到上一个版本,并重启任务
# 四、补充说明
1. `github_commit.sh`是一个非常强大的脚本,只要在命令行输入`.github_commit.sh "你的注释"`即可完成代码的提交(最重要的是把所有敏感的服务器和数据库配置信息统统替换掉,然后再上传到github,上传完成后再还原回来)
@@ -32,6 +32,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'users.apps.UsersConfig',
'cases.apps.CasesConfig',
'questions.apps.CasesConfig',
'user_operation.apps.UserOperationConfig',
'DjangoUeditor',
'crispy_forms',
@@ -10,6 +10,7 @@ from rest_framework.routers import DefaultRouter
from rest_framework.authtoken import views
from rest_framework_jwt.views import obtain_jwt_token
from cases.views import CasesListViewSet, CategoryViewset, HotSearchsViewset, BannerViewset
from questions.views import QuestionsListViewSet, QuestionStateViewset
from users.views import SmsCodeViewset, UserViewset, PictureCodeView
from user_operation.views import UserFavViewset
from django.views.generic import TemplateView
@@ -18,6 +19,8 @@ from utils.file_views import UploadFileView, DeleteFileView
router = DefaultRouter() # 利用了ViewSet的重载as_view()方法进行请求方法和处理函数的动态绑定(简单好用,只需要逐一register,然后urls函数即可)
router.register(r'cases', CasesListViewSet, base_name="cases") # 案例列表的相关操作
router.register(r'categorys', CategoryViewset, base_name="categorys") # 案例类别的相关操作
router.register(r'questions', QuestionsListViewSet, base_name="questions") # 问题列表的相关操作
router.register(r'question-states', QuestionStateViewset, base_name="question_states") # 问题状态的相关操作
router.register(r'smscodes', SmsCodeViewset, base_name="smscodes") # 短信验证码的相关操作
router.register(r'hotsearchs', HotSearchsViewset, base_name="hotsearchs") # 热搜案例的相关操作
router.register(r'users', UserViewset, base_name="users") # 用户的相关操作
@@ -1,4 +1,4 @@
# Generated by Django 2.0 on 2017-12-06 11:33
# Generated by Django 2.0 on 2017-12-09 19:58
import datetime
from django.db import migrations, models
+1 -1
View File
@@ -38,7 +38,7 @@ class CasesListViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.Re
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
filter_class = CasesFilter
search_fields = ('name', 'cases_brief', 'cases_desc')
ordering_fields = ('sold_num', 'shop_price')
ordering_fields = ('click_num', 'add_time')
def get_serializer_class(self):
if self.action == "retrieve":
-40
View File
@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
from django.views.generic.base import View
from cases.models import Cases
# from django.views.generic import ListView
class CasesListView(View):
def get(self, request):
"""
通过django的view实现案例列表页
:param request:
:return:
"""
json_list = []
cases = Cases.objects.all()[:10]
# for case in cases:
# json_dict = {}
# json_dict["name"] = case.name
# json_dict["category"] = case.category.name
# json_dict["market_price"] = case.market_price
# json_dict["add_time"] = case.add_time
# json_list.append(json_dict)
# from django.forms.models import model_to_dict
# for case in cases:
# json_dict = model_to_dict(case)
# json_list.append(json_dict)
import json
from django.core import serializers
json_data = serializers.serialize('json', cases)
json_data = json.loads(json_data)
from django.http import HttpResponse, JsonResponse
return JsonResponse(json_data, safe=False)
+6
View File
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
from django.contrib import admin
# Register your models here.
+23
View File
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
import xadmin
from .models import Questions, QuestionState
class CasesAdmin(object):
list_display = ["name", "click_num", "fav_num",
"cases_brief", "cases_desc", "add_time"]
search_fields = ['name', ]
list_filter = ["name", "click_num", "fav_num",
"add_time", "category__name"]
style_fields = {"cases_desc": "ueditor"}
class QuestionStateAdmin(object):
list_display = ["name", "add_time"]
list_filter = ["name"]
search_fields = ['name']
xadmin.site.register(Questions, CasesAdmin)
xadmin.site.register(QuestionState, QuestionStateAdmin)
+8
View File
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
from django.apps import AppConfig
class CasesConfig(AppConfig):
name = 'questions'
verbose_name = '问题'
+23
View File
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
import django_filters
from django.db.models import Q
from .models import Questions
class CasesFilter(django_filters.rest_framework.FilterSet):
"""
问题的过滤类
"""
top_category = django_filters.NumberFilter(method='top_category_filter')
def top_category_filter(self, queryset, name, value):
return queryset.filter(Q(category_id=value)|Q(category__parent_category_id=value)|Q(category__parent_category__parent_category_id=value))
class Meta:
model = Questions
fields = []
@@ -0,0 +1,52 @@
# Generated by Django 2.0 on 2017-12-09 19:58
import datetime
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Questions',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='问题名')),
('click_num', models.IntegerField(default=0, verbose_name='点击数')),
('fav_num', models.IntegerField(default=0, verbose_name='收藏数')),
('reply_num', models.IntegerField(default=0, verbose_name='回复数')),
('cases_brief', models.TextField(max_length=500, verbose_name='问题简短描述')),
('cases_desc', models.TextField(max_length=50000, verbose_name='问题正文内容')),
('add_time', models.DateTimeField(default=datetime.datetime.now, verbose_name='添加时间')),
],
options={
'verbose_name': '问题',
'verbose_name_plural': '问题',
},
),
migrations.CreateModel(
name='QuestionState',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='', help_text='类别名', max_length=30, verbose_name='类别名')),
('code', models.CharField(default='', help_text='类别code', max_length=30, verbose_name='类别code')),
('desc', models.TextField(default='', help_text='类别描述', verbose_name='类别描述')),
('add_time', models.DateTimeField(default=datetime.datetime.now, verbose_name='添加时间')),
],
options={
'verbose_name': '问题状态',
'verbose_name_plural': '问题状态',
},
),
migrations.AddField(
model_name='questions',
name='state',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='questions.QuestionState', verbose_name='案例类目'),
),
]
+42
View File
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
from datetime import datetime
from django.db import models
class QuestionState(models.Model):
"""
问题状态
"""
name = models.CharField(default="", max_length=30, verbose_name="类别名", help_text="类别名")
code = models.CharField(default="", max_length=30, verbose_name="类别code", help_text="类别code")
desc = models.TextField(default="", verbose_name="类别描述", help_text="类别描述")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "问题状态"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Questions(models.Model):
"""
问题
"""
state = models.ForeignKey(QuestionState, verbose_name="案例类目", on_delete=models.CASCADE)
name = models.CharField(max_length=100, verbose_name="问题名")
click_num = models.IntegerField(default=0, verbose_name="点击数")
fav_num = models.IntegerField(default=0, verbose_name="收藏数")
reply_num = models.IntegerField(default=0, verbose_name="回复数")
cases_brief = models.TextField(max_length=500, verbose_name="问题简短描述")
cases_desc = models.TextField(max_length=50000, verbose_name="问题正文内容")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = '问题'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
from rest_framework import serializers
from django.db.models import Q
from questions.models import Questions, QuestionState
class QuestionStateSerializer(serializers.ModelSerializer):
class Meta:
model = QuestionState
fields = "__all__"
class GetQuestionsSerializer(serializers.ModelSerializer):
question_state = QuestionStateSerializer()
class Meta:
model = Questions
fields = "__all__"
class PostQuestionsSerializer(serializers.ModelSerializer):
question_state_id = serializers.IntegerField(required=True, write_only=True, label="问题状态ID", help_text="问题状态ID")
def validate(self, attrs):
question_state_id = attrs['question_state_id']
question_state = QuestionState.objects.get(Q(id=question_state_id))
attrs['state'] = question_state
del attrs['question_state_id']
return attrs
class Meta:
model = Questions
fields = "__all__"
fields = ("question_state_id", "name", "cases_brief", "cases_desc")
+6
View File
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
from django.test import TestCase
# Create your tests here.
+79
View File
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
__author__ = 'HymanLu'
from rest_framework.response import Response
from rest_framework import mixins
from rest_framework import filters
from rest_framework.pagination import PageNumberPagination
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework.throttling import UserRateThrottle
from rest_framework_extensions.cache.mixins import CacheResponseMixin
from .models import Questions, QuestionState
from .filters import CasesFilter
from .serializers import GetQuestionsSerializer, PostQuestionsSerializer, QuestionStateSerializer
from rest_framework import status
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly
# Create your views here.
class QuestionsPagination(PageNumberPagination):
page_size = 12
page_size_query_param = 'page_size'
page_query_param = "page"
max_page_size = 100
class QuestionsListViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
"""
问题列表页, 分页, 搜索, 过滤, 排序
"""
throttle_classes = (UserRateThrottle, )
queryset = Questions.objects.all()
serializer_class = GetQuestionsSerializer
pagination_class = QuestionsPagination
authentication_classes = (JSONWebTokenAuthentication, )
permission_classes = (IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
filter_class = CasesFilter
search_fields = ('name', 'cases_brief', 'cases_desc')
ordering_fields = ('click_num', 'add_time')
def get_serializer_class(self):
if self.action == "retrieve":
return GetQuestionsSerializer
# 当进行的是注册新用户时
elif self.action == "create":
return PostQuestionsSerializer
elif self.action == "update":
return PostQuestionsSerializer
return GetQuestionsSerializer
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
instance.click_num += 1
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
print("---serializer---")
print(serializer)
case = serializer.save() # 注册成功,将SQL记录插入语句提交到数据库执行
re_dict = serializer.data
re_dict['id'] = case.id
headers = self.get_success_headers(serializer.data)
return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)
class QuestionStateViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""
list:
问题状态列表数据
retrieve:
获取问题状态详情
"""
queryset = QuestionState.objects.all()
serializer_class = QuestionStateSerializer
@@ -1,4 +1,4 @@
# Generated by Django 2.0 on 2017-12-06 11:33
# Generated by Django 2.0 on 2017-12-09 19:58
import datetime
from django.db import migrations, models
@@ -1,4 +1,4 @@
# Generated by Django 2.0 on 2017-12-06 11:33
# Generated by Django 2.0 on 2017-12-09 19:58
from django.conf import settings
from django.db import migrations, models
@@ -10,8 +10,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('user_operation', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@@ -13,7 +13,7 @@ class UserFavDetailSerializer(serializers.ModelSerializer):
class Meta:
model = UserFav
fields = ("goods", "id")
fields = ("cases", "id")
class UserFavSerializer(serializers.ModelSerializer):
@@ -26,9 +26,9 @@ class UserFavSerializer(serializers.ModelSerializer):
validators = [
UniqueTogetherValidator(
queryset=UserFav.objects.all(),
fields=('user', 'goods'),
fields=('user', 'cases'),
message="已经收藏"
)
]
fields = ("user", "goods", "id")
fields = ("user", "cases", "id")
@@ -1,4 +1,4 @@
# Generated by Django 2.0 on 2017-12-06 11:33
# Generated by Django 2.0 on 2017-12-09 19:58
import datetime
import django.contrib.auth.models
@@ -1,4 +1,4 @@
# Generated by Django 2.0 on 2017-12-06 11:33
# Generated by Django 2.0 on 2017-12-09 19:58
from django.conf import settings
from django.db import migrations, models
@@ -11,8 +11,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
+1 -1
View File
@@ -4,7 +4,7 @@ __author__ = 'HymanLu'
import os
import sys
# 常用参数:makemigrations、migrate、runserver、startapp
# 常用参数:makemigrations、migrate、runserver 0.0.0.0:8000、startapp
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "APP_Inventor_case_base.settings")
try:
-31
View File
@@ -1,31 +0,0 @@
import os
import sys
if __name__ == "__main__":
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'APP_Inventor_case_base'))
from settings import DATABASES
default_conf = DATABASES['default']
db_backup_file_dir = os.path.dirname(os.path.abspath(__file__))
db_backup_file_dir = os.path.join(db_backup_file_dir, 'data')
db_backup_file_dir = os.path.join(db_backup_file_dir, 'db_backup')
version_counter = 0
for db_backup_file_name in os.listdir(db_backup_file_dir):
if not db_backup_file_name.endswith('.sql'):
print('---存在非法文件---')
print(db_backup_file_name)
exit()
version = int(db_backup_file_name[:-4])
if version > version_counter:
version_counter = version
version_counter = version_counter + 1
db_backup_file_path = os.path.join(db_backup_file_dir, str(version_counter) + '.sql')
mysqldump_procedure_path = '"C:/MySoftware/MySQL Server 5.7/bin/mysqldump"'
user_name = default_conf['USER']
password = default_conf['PASSWORD']
db_name = default_conf['NAME']
cmdString = mysqldump_procedure_path + ' -u' + user_name + ' -p' + password + ' ' + db_name + '>' + db_backup_file_path
print(cmdString)
os.system(cmdString)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-29
View File
@@ -1,29 +0,0 @@
import os
import sys
if __name__ == "__main__":
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'APP_Inventor_case_base'))
from settings import DATABASES
default_conf = DATABASES['default']
db_backup_file_dir = os.path.dirname(os.path.abspath(__file__))
db_backup_file_dir = os.path.join(db_backup_file_dir, 'data')
db_backup_file_dir = os.path.join(db_backup_file_dir, 'db_backup')
version_counter = 0
for db_backup_file_name in os.listdir(db_backup_file_dir):
if not db_backup_file_name.endswith('.sql'):
print('---存在非法文件---')
print(db_backup_file_name)
exit()
version = int(db_backup_file_name[:-4])
if version > version_counter:
version_counter = version
db_backup_file_path = os.path.join(db_backup_file_dir, str(version_counter) + '.sql')
mysql_procedure_path = '"C:/MySoftware/MySQL Server 5.7/bin/mysql"'
user_name = default_conf['USER']
password = default_conf['PASSWORD']
db_name = default_conf['NAME']
cmdString = mysql_procedure_path + ' -u' + user_name + ' -p' + password + ' ' + db_name + '<' + db_backup_file_path
print(cmdString)
os.system(cmdString)
+1 -1
View File
@@ -43,6 +43,6 @@
<!-- 加载bootstrap-fileinput中文语言包 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.5/js/locales/zh.js"></script>
<!-- 引入tinyMCE -->
<script src="https://cloud.tinymce.com/stable/tinymce.min.js?apiKey=w1jnifw6jyuhsi04yjizbcmplt1s0w5zks3u7xeaitd7samr"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.7.4/tinymce.min.js?apiKey=w1jnifw6jyuhsi04yjizbcmplt1s0w5zks3u7xeaitd7samr"></script>
</body>
</html>
+4 -59
View File
@@ -2,7 +2,7 @@
<div class="row">
<template v-if="caseOutlineList.length > 0">
<div class="col-sm-12">
<ul class="list-group" v-on:switchCaseCategory="getcaseOutlineList">
<ul class="list-group">
<li v-for="(caseOutline, index) in caseOutlineList" class="list-group-item"><case-outline-media-object v-bind="caseOutline"></case-outline-media-object></li>
</ul>
</div>
@@ -21,7 +21,6 @@
<script>
import caseOutlineMediaObject from './caseOutlineMediaObject.vue';
import paginationNav from './paginationNav.vue';
import $ from 'jquery';
export default {
data() {
return {
@@ -32,65 +31,11 @@
caseOutlineMediaObject,
paginationNav
},
computed: {
listenDataDict () {
return {
caseFilterCondition: this.$store.state.caseFilterCondition,
forcedRequestCounter: this.$store.state.forcedRequestCounter,
currPageNum: this.$store.state.currPageNum,
caseCountPerPage: this.$store.state.caseCountPerPage
};
}
},
methods: {
//
getcaseOutlineList: function () {
var queryDict = {};
// jQueryextend
$.extend(true, queryDict, this.listenDataDict.caseFilterCondition);
queryDict['page_size'] = this.listenDataDict.caseCountPerPage;
queryDict['page'] = this.listenDataDict.currPageNum;
var requestURL = 'cases/?';
for (var key in queryDict) {
requestURL = requestURL + key + '=' + queryDict[key] + '&';
}
requestURL = requestURL.slice(0, -1);
var path = this.$route.path;
this.$axios.get(requestURL)
.then((res) => {
if (path !== this.$route.path) {
return;
}
this.$store.commit('setPreviousPageUrl', res.data['previous']);
this.$store.commit('setNextPageUrl', res.data['next']);
this.$store.commit('setPageListByCount', res.data['count']);
this.caseOutlineList = res.data.results;
for (var index in this.caseOutlineList) {
this.caseOutlineList[index]['category_name'] = this.caseOutlineList[index]['category']['name'];
delete this.caseOutlineList[index]['category'];
delete this.caseOutlineList[index]['cases_desc'];
}
}, (err) => {
var errorReasonDict = err.body;
console.log('---errorReasonDict---');
console.log(errorReasonDict);
});
}
},
mounted: function () {
this.$nextTick(function () {
if (!$.isEmptyObject(this.listenDataDict.caseFilterCondition)) {
this.getcaseOutlineList(this.listenDataDict.caseFilterCondition);
}
});
this.$store.commit('setCaseListComponent', this);
},
watch: {
listenDataDict: {
handler: function (val, oldVal) {
this.getcaseOutlineList();
},
deep: true
}
destroyed: function () {
this.$store.commit('setCaseListComponent', null);
}
};
</script>
+4 -4
View File
@@ -2,9 +2,9 @@
<form data-toggle="validator" role="form" id="loginForm" class="form-horizontal">
<div class="form-group has-feedback has-success has-error">
<label for="usernameInput" class="col-sm-3 control-label"><i class="glyphicon glyphicon-user" aria-hidden="true"></i> 用户名</label>
<label for="loginUsernameInput" class="col-sm-3 control-label"><i class="glyphicon glyphicon-user" aria-hidden="true"></i> 用户名</label>
<div class="col-sm-9">
<input type="text" id="usernameInput" class="form-control" name="username" placeholder="请输入用户名" data-pattern-error="用户名长度不足6位" data-required-error="用户名不可以为空" pattern="^.{6,}$" required data-serverValidation>
<input type="text" id="loginUsernameInput" class="form-control" name="username" placeholder="请输入用户名" data-pattern-error="用户名长度不足6位" data-required-error="用户名不可以为空" pattern="^.{6,}$" required data-serverValidation>
<span class="glyphicon glyphicon-ok form-control-feedback"></span>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
@@ -12,9 +12,9 @@
</div>
<div class="form-group has-feedback has-success has-error">
<label for="passwordInput" class="col-sm-3 control-label"><i class="glyphicon glyphicon-lock" aria-hidden="true"></i> 密码</label>
<label for="loginPasswordInput" class="col-sm-3 control-label"><i class="glyphicon glyphicon-lock" aria-hidden="true"></i> 密码</label>
<div class="col-sm-9">
<input type="password" id="passwordInput" class="form-control" name="password" placeholder="请输入密码" data-pattern-error="密码长度不足6位" data-required-error="密码不可以为空" pattern="^.{6,}$" required>
<input type="password" id="loginPasswordInput" class="form-control" name="password" placeholder="请输入密码" data-pattern-error="密码长度不足6位" data-required-error="密码不可以为空" pattern="^.{6,}$" required>
<span class="glyphicon glyphicon-ok form-control-feedback"></span>
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
</div>
@@ -0,0 +1,41 @@
<template>
<div class="row">
<template v-if="questionOutlineList.length > 0">
<div class="col-sm-12">
<ul class="list-group">
<li v-for="(caseOutline, index) in questionOutlineList" class="list-group-item"><question-outline-media-object v-bind="caseOutline"></question-outline-media-object></li>
</ul>
</div>
<div class="col-sm-8 col-sm-offset-4">
<pagination-nav></pagination-nav>
</div>
</template>
<template v-else>
<div class="col-sm-12">
<h1>暂无相关的提问</h1>
</div>
</template>
</div>
</template>
<script>
import questionOutlineMediaObject from './questionOutlineMediaObject.vue';
import paginationNav from './paginationNav.vue';
export default {
data() {
return {
questionOutlineList: []
};
},
components: {
questionOutlineMediaObject,
paginationNav
},
mounted: function () {
this.$store.commit('setCaseListComponent', this);
},
destroyed: function () {
this.$store.commit('setCaseListComponent', null);
}
};
</script>
@@ -0,0 +1,87 @@
<template>
<div>
<router-link class="button expanded" v-bind:to="editCaseURL" data-toggle="tooltip" data-original-title="编辑">编辑</router-link>
<button class="button expanded" v-on:click="deleteCase">删除</button>
<div id="richTextViewerDiv" v-html="cases_desc">
</div>
</div>
</template>
<script>
import $ from 'jquery';
export default {
props: {
caseId: {
type: String,
default: ''
}
},
data() {
return {
category_name: '',
name: '',
click_num: 0,
fav_num: 0,
reply_num: 0,
cases_brief: '',
cases_desc: '',
cases_front_image: '',
add_time: '',
id: 0
};
},
methods: {
//
getCaseDetail: function () {
if (this.caseId.length <= 0) {
console.log('---请传入案例ID作为query---');
} else {
this.$axios.get('cases/' + this.caseId + '/')
.then((res) => {
this.category_name = res.data['category']['name'];
this.name = res.data['name'];
this.click_num = res.data['click_num'];
this.fav_num = res.data['fav_num'];
this.reply_num = res.data['reply_num'];
this.cases_brief = res.data['cases_brief'];
this.cases_desc = res.data['cases_desc'];
this.cases_front_image = res.data['cases_front_image'];
this.add_time = res.data['add_time'];
this.id = res.data['id'];
}, (err) => {
var errorReasonDict = err.body;
console.log('---errorReasonDict---');
console.log(errorReasonDict);
});
}
},
deleteCase: function () {
if (this.caseId.length <= 0) {
console.log('---请传入案例ID作为query---');
} else {
this.$axios.delete('cases/' + this.caseId + '/')
.then((res) => {
console.log('---删除案例成功---');
this.$store.commit('increaseForcedRequestCounter');
this.$root.jumpToThisPage('/showAllCases');
}, (err) => {
var errorReasonDict = err.body;
console.log('---errorReasonDict---');
console.log(errorReasonDict);
});
}
}
},
mounted: function () {
this.$nextTick(function () {
this.getCaseDetail();
$('[data-toggle=tooltip]').tooltip();
});
},
computed: {
editCaseURL () {
return '/editCase?id=' + this.id;
}
}
};
</script>
+1 -1
View File
@@ -20,7 +20,7 @@
<li><router-link to="/index">首页<!--<span class="sr-only">(current)</span>--></router-link></li>
<li><router-link to="/showAllCases">案例</router-link></li>
<li><router-link to="/editCase">发表</router-link></li>
<li><router-link to="/index">问答</router-link></li>
<li><router-link to="/showAllQuestions">问答</router-link></li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
+8
View File
@@ -50,6 +50,14 @@ const routers = [{
resolve(require('./views/showAllCases.vue'));
});
}
}, {
path: '/showAllQuestions',
name: 'showAllQuestions',
component(resolve) {
require.ensure(['./views/showAllQuestions.vue'], () => {
resolve(require('./views/showAllQuestions.vue'));
});
}
}, {
path: '*',
component(resolve) {
+20
View File
@@ -53,3 +53,23 @@ exports.convertBase64UrlToBlob = function (urlData) {
}
return new Blob([ab], {type: 'image/png'});
};
exports.isTwoObjectsEqual = function (objectA, objectB) {
if (!objectA && !objectB) {
return true;
}
if (!objectA || !objectB) {
return false;
}
for (var keyA in objectA) {
if (objectA[keyA] !== objectB[keyA]) {
return false;
}
}
for (var keyB in objectB) {
if (objectA[keyB] !== objectB[keyB]) {
return false;
}
}
return true;
};
+1 -1
View File
@@ -25,7 +25,7 @@
mounted: function () {
this.$nextTick(function () {
if ($.isEmptyObject(this.$route.query)) {
this.$store.commit('setCaseFilterCondition', {page: 1});
this.$store.commit('setCaseFilterCondition', {page_size: 1, page: 1});
} else {
this.$store.commit('setCaseFilterCondition', this.$route.query);
}
@@ -0,0 +1,38 @@
<template>
<div>
<div class="row">
<div class="col-sm-12">
<top-bar activeIndex=1>
</top-bar>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<question-list></question-list>
</div>
</div>
</div>
</template>
<script>
import topBar from '../components/topBar.vue';
import questionList from '../components/questionList.vue';
import $ from 'jquery';
export default {
components: {
topBar,
questionList
},
mounted: function () {
this.$nextTick(function () {
if ($.isEmptyObject(this.$route.query)) {
this.$store.commit('setCaseFilterCondition', {page_size: 1, page: 1});
} else {
this.$store.commit('setCaseFilterCondition', this.$route.query);
}
});
}
};
</script>
<style lang="scss">
</style>
+4 -4
View File
@@ -8,19 +8,19 @@
</div>
<div class="row">
<div class="col-sm-12">
<rich-text-viewer v-bind:caseId="caseId">
</rich-text-viewer>
<case-viewer v-bind:caseId="caseId">
</case-viewer>
</div>
</div>
</div>
</template>
<script>
import topBar from '../components/topBar.vue';
import richTextViewer from '../components/richTextViewer.vue';
import caseViewer from '../components/caseViewer.vue';
export default {
components: {
topBar,
richTextViewer
caseViewer
},
computed: {
caseId () {
+48 -28
View File
@@ -6,43 +6,63 @@ import Utils from '../utils';
const store = new Vuex.Store({
state: {
userName: '',
caseFilterCondition: {},
forcedRequestCounter: 0, // 用于在caseFilterCondition不变的情况下,强制进行数据请求
currPageNum: 1,
caseCountPerPage: 1,
pageNumToShowList: [1],
caseFilterCondition: {
page_size: 1,
page: 1
},
lastCaseFilterCondition: null,
caseListComponent: null,
pageNumToShowList: [],
nextPageUrl: null,
previousPageUrl: null
previousPageUrl: null,
questionListComponent: null
},
// 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
mutations: {
setUserName(state, userName) {
state.userName = userName;
},
setCaseListComponent(state, caseListComponent) {
state.lastCaseFilterCondition = null;
state.caseListComponent = caseListComponent;
},
setCaseFilterCondition(state, caseFilterCondition) {
if (Utils.isTwoObjectsEqual(caseFilterCondition, state.lastCaseFilterCondition)) {
return;
}
var self = state.caseListComponent;
state.caseFilterCondition = caseFilterCondition;
},
addCaseFilterCondition(state, conditionKey, conditionValue) {
state.caseFilterCondition[conditionKey] = conditionValue;
},
increaseForcedRequestCounter(state) {
state.forcedRequestCounter = state.forcedRequestCounter + 1;
},
setCurrPageNum(state, currPageNum) {
state.currPageNum = currPageNum;
},
setPageListByCount(state, count) {
var sumPageNum = Math.ceil(count / state.caseCountPerPage);
state.pageNumToShowList = Utils.rangeArray(1, sumPageNum);
},
setNextPageUrl(state, nextPageUrl) {
state.nextPageUrl = nextPageUrl;
},
setPreviousPageUrl(state, previousPageUrl) {
state.previousPageUrl = previousPageUrl;
},
setCaseCountPerPage(state, caseCountPerPage) {
state.caseCountPerPage = caseCountPerPage;
var requestURL = 'cases/?';
for (var key in caseFilterCondition) {
requestURL = requestURL + key + '=' + caseFilterCondition[key] + '&';
}
requestURL = requestURL.slice(0, -1);
console.log('---requestURL---');
console.log(requestURL);
state.lastCaseFilterCondition = caseFilterCondition;
self.$axios.get(requestURL)
.then((res) => {
if (state.caseFilterCondition['page_size']) {
state.previousPageUrl = res.data['previous'];
state.nextPageUrl = res.data['next'];
var sumPageNum = Math.ceil(res.data['count'] / state.caseFilterCondition['page_size']);
state.pageNumToShowList = Utils.rangeArray(1, sumPageNum);
} else {
state.pageNumToShowList = [];
state.nextPageUrl = null;
state.previousPageUrl = null;
}
self.caseOutlineList = res.data.results;
for (var index in self.caseOutlineList) {
self.caseOutlineList[index]['category_name'] = self.caseOutlineList[index]['category']['name'];
delete self.caseOutlineList[index]['category'];
delete self.caseOutlineList[index]['cases_desc'];
}
}, (err) => {
var errorReasonDict = err.body;
console.log('---errorReasonDict---');
console.log(errorReasonDict);
});
}
}
});
+1 -1
View File
@@ -3,7 +3,7 @@
# 获取该脚本文件本身的绝对路径(必须在cd前执行此操作,因为.和..依赖于当前工作目录,必须在没改变前获取之)
script_file_path=$(readlink -f $0)
base_dir=$(dirname ${script_file_path})
switch_fabfile_env_script_path=${base_dir}"/switch_fabfile_env.py"
switch_fabfile_env_script_path=${base_dir}"/operation-and-deployment-src/switch_fabfile_env.py"
switch_setting_script_path=${base_dir}"/back-end-src/tool_scripts/switch_setting.py"
python "${switch_fabfile_env_script_path}" "github"
python "${switch_setting_script_path}" "initial"
+97 -86
View File
@@ -2,10 +2,11 @@
__author__ = 'HymanLu'
from fabric.api import *
import datetime
from datetime import datetime
import os
import sys
import re
from reinit_migrations import deleteAllMigrationFiles, makemigrationAndMigrate
env.user = '服务器用户名'
env.password = '对应密码'
@@ -18,43 +19,127 @@ _REMOTE_TMP_DIR = '/tmp'
_REMOTE_TMP_TAR = '%s/%s' % (_REMOTE_TMP_DIR, _TAR_FILE)
_REMOTE_BASE_DIR = '/srv/APP_Inventor_case_base_website'
_CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, os.path.join(os.path.join(_CURRENT_DIR, 'back-end-src'), 'APP_Inventor_case_base'))
_ROOT_DIR = os.path.dirname(_CURRENT_DIR)
sys.path.insert(0, os.path.join(os.path.join(_ROOT_DIR, 'back-end-src'), 'APP_Inventor_case_base'))
from settings import DATABASES
_DB_USER_NAME = DATABASES['default']['USER']
_DB_PASSWORD = DATABASES['default']['PASSWORD']
_DB_NAME = DATABASES['default']['NAME']
_BACKUP_TABLES = ['cases_banner', 'cases_cases', 'cases_casescategory', 'users_userprofile']
def _now():
return datetime.now().strftime('%y-%m-%d_%H.%M.%S')
def remoteBackup():
def remoteBackupDB():
'''
备份服务器端的数据库
服务器数据库 进行备份仅对_BACKUP_TABLES中包含的表然后把备份数据打包下载到本地
'''
dt = _now()
f = 'remote-backup-APP_Inventor_case_base_website-%s.sql' % dt
with cd(_REMOTE_TMP_DIR):
cmd_string = 'mysqldump --user=%s --password=%s --skip-opt --add-drop-table --default-character-set=utf8 --quick %s > %s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, f)
cmd_string = 'mysqldump --user=%s --password=%s -t --default-character-set=utf8 --quick %s %s > %s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, ' '.join(_BACKUP_TABLES), f)
run(cmd_string)
run('tar -czvf %s.tar.gz %s' % (f, f))
get('%s.tar.gz' % f, '%s/backup/' % _CURRENT_DIR)
run('rm -f %s' % f)
run('rm -f %s.tar.gz' % f)
def localBackup():
def localBackupDB():
'''
备份本地数据库
本地数据库 进行备份仅对_BACKUP_TABLES中包含的表
'''
dt = _now()
f = 'local-backup-APP_Inventor_case_base_website-%s.sql' % dt
backup_dir = os.path.join(_CURRENT_DIR, 'backup')
with lcd(backup_dir):
cmd_string = 'mysqldump --user=%s --password=%s --skip-opt --add-drop-table --default-character-set=utf8 --quick %s > %s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, f)
cmd_string = 'mysqldump --user=%s --password=%s -t --default-character-set=utf8 --quick %s %s > %s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, ' '.join(_BACKUP_TABLES), f)
local(cmd_string)
local('tar -czvf %s.tar.gz %s' % (f, f))
local('rm -f %s' % f)
def _selectBackupFile():
'''
选择要进行还原的数据库备份文件
'''
backup_dir = os.path.join(_CURRENT_DIR, 'backup')
fs = os.listdir(backup_dir)
files = [f for f in fs if f.startswith('remote-backup-') and f.endswith('.sql.tar.gz')]
files.extend([f for f in fs if f.startswith('local-backup-') and f.endswith('.sql.tar.gz')])
files.sort()
if len(files) == 0:
print('找不到任何备份文件')
return
print('一共找到了%s个备份文件:' % len(files))
print('==================================================')
n = 0
for f in files:
print('%s: %s' % (n, f))
n = n + 1
print('==================================================')
print('')
try:
num = int(input('请输入要还原的数据库备份档案: '))
except ValueError:
print('非法数字')
return
restore_file = files[num]
yn = input('是否进行还原 %s: %s? y/N ' % (num, restore_file))
if yn != 'y' and yn != 'Y':
print('还原操作被取消')
return
print('开始进行还原操作...')
return restore_file
def localRestoreDB():
'''
利用数据库备份文件和Django的migration功能对 本地数据库 进行恢复
'''
restore_file = _selectBackupFile()
backup_dir = os.path.join(_CURRENT_DIR, 'backup')
p = input('请输入Mysql的root用户密码: ')
sqls = [
'drop database if exists ' + _DB_NAME + ';',
'create database ' + _DB_NAME + ';',
# 'grant select, insert, update, delete on awesome.* to \'%s\'@\'localhost\' identified by \'%s\';' % (_DB_USER_NAME, _DB_PASSWORD)
]
for sql in sqls:
local(r'mysql -uroot -p%s -e "%s"' % (p, sql))
with lcd(backup_dir):
local('tar zxvf %s' % restore_file)
deleteAllMigrationFiles()
makemigrationAndMigrate()
cmd_string = 'mysql -u%s -p%s -f %s < %s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, restore_file[:-7])
local(cmd_string)
print('echo %s' % restore_file[:-7])
local('rm -f %s' % restore_file[:-7])
def remoteRestoreDB():
'''
利用数据库备份文件和Django的migration功能对 服务器数据库 进行恢复
'''
restore_file_name = _selectBackupFile()
p = input('请输入Mysql的root用户密码: ')
sqls = [
'drop database if exists ' + _DB_NAME + ';',
'create database ' + _DB_NAME + ';',
# 'grant select, insert, update, delete on awesome.* to \'%s\'@\'localhost\' identified by \'%s\';' % (_DB_USER_NAME, _DB_PASSWORD)
]
for sql in sqls:
run(r'mysql -uroot -p%s -e "%s"' % (p, sql))
with lcd(_CURRENT_DIR):
put('./backup/%s' % restore_file_name, '%s/%s' % (_REMOTE_TMP_DIR, restore_file_name))
with cd(_REMOTE_TMP_DIR):
run('tar zxvf %s' % restore_file_name)
deleteAllMigrationFiles()
makemigrationAndMigrate()
cmd_string = 'mysql -u%s -p%s -f %s < %s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, restore_file_name[:-7])
run(cmd_string)
run('rm -f %s' % restore_file_name[:-7])
run('rm -f %s' % restore_file_name)
def build():
'''
将本地代码进行打包
将本地项目代码进行打包用作备份或者上传服务器进行部署
'''
includes = ['back-end-src', 'front-end-src']
excludes = ['*__pycache__*', '*.idea*', '*.pyc']
@@ -67,7 +152,7 @@ def build():
def deploy():
'''
将在本地打包好的代码上传到服务器然后再解包然后利用supervisor启动服务nginx进行请求转发
将在本地打包好的项目代码上传到服务器然后再解包然后利用supervisor启动服务nginx进行请求转发
'''
newdir = 'src-%s' % _now()
run('rm -f %s' % _REMOTE_TMP_TAR)
@@ -92,7 +177,7 @@ RE_FILES = re.compile('\r?\n')
def rollback():
'''
代码回退到上一个版本并重启任务
项目代码回退到上一个版本并重启任务
'''
with cd(_REMOTE_BASE_DIR):
r = run('ls -p -1')
@@ -135,78 +220,4 @@ def rollback():
sudo('supervisorctl stop awesome')
sudo('supervisorctl start awesome')
sudo('/etc/init.d/nginx reload')
print('回退成功')
def _selectBackupFile():
'''
选择要进行还原的数据库备份文件
'''
backup_dir = os.path.join(_CURRENT_DIR, 'backup')
fs = os.listdir(backup_dir)
files = [f for f in fs if f.startswith('remote-backup-') and f.endswith('.sql.tar.gz')]
files.extend([f for f in fs if f.startswith('local-backup-') and f.endswith('.sql.tar.gz')])
files.sort(cmp=lambda s1, s2: 1 if s1 < s2 else -1)
if len(files) == 0:
print('找不到任何备份文件')
return
print('一共找到了%s个备份文件:' % len(files))
print('==================================================')
n = 0
for f in files:
print('%s: %s' % (n, f))
n = n + 1
print('==================================================')
print('')
try:
num = int(input('请输入要还原的数据库备份档案: '))
except ValueError:
print('非法数字')
return
restore_file = files[num]
yn = input('是否进行还原 %s: %s? y/N ' % (num, restore_file))
if yn != 'y' and yn != 'Y':
print('还原操作被取消')
return
print('开始进行还原操作...')
return restore_file
def restore2local():
'''
将数据库备份还原到本地
'''
restore_file = _selectBackupFile()
backup_dir = os.path.join(_CURRENT_DIR, 'backup')
p = input('请输入Mysql的root用户密码: ')
sqls = [
'drop database if exists ' + _DB_NAME + ';',
'create database ' + _DB_NAME + ';',
# 'grant select, insert, update, delete on awesome.* to \'%s\'@\'localhost\' identified by \'%s\';' % (_DB_USER_NAME, _DB_PASSWORD)
]
for sql in sqls:
local(r'mysql -uroot -p%s -e "%s"' % (p, sql))
with lcd(backup_dir):
local('tar zxvf %s' % restore_file)
cmd_string = 'mysql -u%s -p%s %s < backup/%s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, restore_file[:-7])
local(cmd_string)
local('rm -f %s' % restore_file[:-7])
def restore2remote():
'''
将数据库备份还原到本地
'''
restore_file_name = _selectBackupFile()
p = input('请输入Mysql的root用户密码: ')
sqls = [
'drop database if exists ' + _DB_NAME + ';',
'create database ' + _DB_NAME + ';',
# 'grant select, insert, update, delete on awesome.* to \'%s\'@\'localhost\' identified by \'%s\';' % (_DB_USER_NAME, _DB_PASSWORD)
]
for sql in sqls:
run(r'mysql -uroot -p%s -e "%s"' % (p, sql))
with lcd(_CURRENT_DIR):
put('./backup/%s' % restore_file_name, '%s/%s' % (_REMOTE_TMP_DIR, restore_file_name))
with cd(_REMOTE_TMP_DIR):
run('tar zxvf %s' % restore_file_name)
cmd_string = 'mysql -u%s -p%s %s < backup/%s' % (_DB_USER_NAME, _DB_PASSWORD, _DB_NAME, restore_file_name[:-7])
run(cmd_string)
run('rm -f %s' % restore_file_name[:-7])
print('回退成功')
@@ -1,12 +1,12 @@
import os
import shutil
import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BACK_END_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'back-end-src')
def deleteAllMigrationFiles():
app_parent_dir_list = []
app_parent_dir_list.append(os.path.join(BASE_DIR, 'apps'))
app_parent_dir_list.append(os.path.join(BASE_DIR, 'extra_apps'))
app_parent_dir_list.append(os.path.join(BACK_END_BASE_DIR, 'apps'))
app_parent_dir_list.append(os.path.join(BACK_END_BASE_DIR, 'extra_apps'))
for app_parent_dir in app_parent_dir_list:
for app_name in os.listdir(app_parent_dir):
app_dir = os.path.join(app_parent_dir, app_name)
@@ -20,10 +20,8 @@ def deleteAllMigrationFiles():
elif os.path.isdir(migration_file_path) and migration_file_name == '__pycache__':
shutil.rmtree(migration_file_path)
def makeMigration():
os.chdir(BASE_DIR)
print('---os.getcwd()---')
print(os.getcwd())
def makemigrationAndMigrate():
os.chdir(BACK_END_BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "APP_Inventor_case_base.settings")
try:
from django.core.management import execute_from_command_line
@@ -40,8 +38,8 @@ def makeMigration():
"forget to activate a virtual environment?"
)
raise
execute_from_command_line('makemigrations')
if __name__ == "__main__":
deleteAllMigrationFiles()
makeMigration()
manage_script_path = os.path.join(BACK_END_BASE_DIR, 'manage.py')
argv = [manage_script_path, 'makemigrations']
execute_from_command_line(argv)
argv = [manage_script_path, 'migrate']
execute_from_command_line(argv)