add operation and deployment module
This commit is contained in:
+4
-3
@@ -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/*
|
||||
@@ -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
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
__author__ = 'HymanLu'
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -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)
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
__author__ = 'HymanLu'
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
class CasesConfig(AppConfig):
|
||||
name = 'questions'
|
||||
verbose_name = '问题'
|
||||
@@ -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='案例类目'),
|
||||
),
|
||||
]
|
||||
@@ -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")
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
__author__ = 'HymanLu'
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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 = [
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = {};
|
||||
// 利用jQuery的extend函数实现深拷贝
|
||||
$.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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Binary file not shown.
+97
-86
@@ -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('回退成功')
|
||||
+10
-12
@@ -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)
|
||||
Reference in New Issue
Block a user