├── .gitignore ├── README.md ├── api ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── serializers │ ├── __init__.py │ └── basic.py ├── service │ ├── __init__.py │ ├── exceptions.py │ ├── fields.py │ ├── pagination.py │ └── response.py ├── tests.py ├── urls.py └── views │ ├── __init__.py │ └── basic.py ├── init.json ├── manage.py ├── requirements.txt ├── static ├── css │ ├── element.css │ └── fonts │ │ └── element-icons.woff └── js │ ├── axios.js │ ├── echarts.js │ ├── element-ui.js │ ├── jquery.js │ └── vue.js ├── surveySystem ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── templates ├── components │ └── handle.html └── web │ ├── detail.html │ ├── index.html │ ├── login.html │ ├── public │ ├── base.html │ └── layout.html │ └── report.html └── web ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── tests.py ├── urls.py └── views ├── __init__.py └── backend.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | .venv 82 | venv/ 83 | ENV/ 84 | 85 | *~ 86 | .DS_Store 87 | */migrations 88 | db.sqlite3 89 | 90 | .idea/ 91 | 92 | .vscode/ 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # surveySystem 2 | 3 | Thank you to all the people who already contributed to this project! 4 | 5 | --- 6 | 7 | ## Introduction 8 | 9 | 后端使用 `django` & `DRF` 实现的 `restfulApi` 10 | 11 | 前端使用 `vue` & `element-ui` 12 | 13 | ## 开发环境 14 | 15 | - Python 3.6.8 16 | - Django 2.1.5 17 | - Drf 3.9.4 18 | 19 | ## Quick Tutorial 20 | 21 | ### 使用虚拟环境(virturalenv) 22 | 23 | ``` 24 | pip3 install virtualenv 25 | 26 | 切换到项目目录下, 执行下面的命令 27 | source venv/bin/activate 28 | 29 | pip install -r requirements.txt 30 | 31 | ``` 32 | 33 | ### 生成表结构 34 | 35 | > 默认使用 `sqlite` 数据库 36 | 37 | 如果您想使用 `mysql` 数据库,请移步至常见问题(配置完成后,继续如下操作)。 38 | 39 | ``` 40 | python manage.py makemigrations 41 | 42 | python manage.py migrate 43 | ``` 44 | 45 | ### 导入虚拟数据 46 | 47 | ``` 48 | python manage.py loaddata init.json 49 | ``` 50 | 51 | ### 启动项目 52 | 53 | ``` 54 | python manage.py runserver 0.0.0.0:8023 55 | 56 | 后台地址: 57 | 58 | 127.0.0.1:8023/admin/ 59 | 60 | 账号密码 root root123456 61 | ``` 62 | 63 | ### 效果图及代码结构图 64 | 65 | ![image](https://csrftoken.oss-cn-beijing.aliyuncs.com/github/6DBA4BE6-8EA5-4AA7-A673-457D30FE906A.png) 66 | 67 | ![image](https://csrftoken.oss-cn-beijing.aliyuncs.com/github/59A544B3-AE40-48DF-BA8A-2D58CB8CE9F2%2011.00.13.png) 68 | 69 | ## 常见问题 70 | 71 | ### mysql 数据库使用 72 | 73 | * 配置 74 | 75 | ``` 76 | # 在 settings.py 文件下找到 DATABASES 配置,修改为 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.mysql', 81 | 'NAME': '数据库名称', 82 | 'USER': '用户名', 83 | 'PASSWORD': '密码', 84 | 'HOST': '', # 默认 127.0.0.1 85 | 'PORT': '', # 默认 3306 86 | } 87 | } 88 | ``` 89 | 90 | 然后连接至您的数据库终端 91 | 92 | ``` 93 | $ mysql -u 用户名 -p 密码 94 | 创建数据库,记得指定编码 95 | >>> create database 数据库名称 default charset utf8; 96 | 97 | ``` 98 | 99 | ### 第一次执行这条语句报No changes detected 100 | 101 | * 问题 102 | 103 | ``` 104 | python manage.py makemigrations 105 | ``` 106 | 107 | * 解决 108 | 109 | 在 `web` 下面创建目录 migrations 并在里面创建__init__.py 110 | ``` 111 | mkdir web/migrations 112 | touch web/migrations/__init__.py 113 | ``` 114 | 115 | ### 模板渲染冲突 116 | 117 | * 问题 118 | 119 | ``` 120 | django模板与vue.js冲突问题 121 | 122 | django模板与vue.js的变量都是使用“{{”和“}}”包裹起来的,在渲染django模板时会先替代掉所有的“{{”和“}}”及被包裹在其中的内容,使得vue.js没有使用”{{“、”}}”来绑定变量。 123 | 124 | ``` 125 | 126 | * 解决 127 | 128 | ``` 129 | 1、修改vue.js的默认的绑定符号 130 | Vue.config.delimiters = ["[[", "]]"]; 131 | 132 | 2、使用模板的标签来输出`{{`、`}}` 133 | 详情参见: https://docs.djangoproject.com/en/2.1/ref/templates/builtins/ 134 | 135 | 3、禁用django模板渲染 136 | django标签 verbatim可以使包裹其中的代码不进行渲染保持原样输出 137 | {% verbatim %} 138 | {{ vue }} 139 | {% endverbatim %} 140 | ``` 141 | 142 | ### 文件下载 143 | 144 | * 问题 145 | 146 | ``` 147 | `response` 实现文件流下载, 且点击下载显示文件名称的问题 148 | ``` 149 | 150 | * 解决 151 | 152 | ``` 153 | response['Content-Type'] = 'application/octet-stream' 154 | response['Content-Disposition'] = 'attachment; {}'.format( 155 | "filename*=utf-8''{}".format(quote(self.file_name)) 156 | ) 157 | ``` 158 | 159 | ## 学习资料 160 | 161 | ``` 162 | # 我所认为的RESTful API最佳实践 163 | http://www.scienjus.com/my-restful-api-best-practices/ 164 | 165 | # `xlwt` 简单使用 166 | 167 | import xlwt 168 | 169 | xls = xlwt.Workbook(encoding="utf-8", style_compression=2) 170 | sheet = xls.add_sheet("唯一码", cell_overwrite_ok=True) 171 | sheet.write(0, 0, '号码') 172 | 173 | for index, code in enumerate(queryset.iterator(), 1): 174 | sheet.write(index, 0, code.unique_code) 175 | 176 | xls.save(`file_name`) 177 | 178 | ``` 179 | 180 | ## Donate 181 | 182 | 如果本仓库对你有帮助,可以请作者喝杯白开水或Star。 183 | 184 | Thanks ~ 185 | 186 | ![image](https://csrftoken.oss-cn-beijing.aliyuncs.com/github/F83BF8B1-998B-4818-BDA9-1FAADEEFD16F.png?x-oss-process=image/resize,w_200) 187 | 188 | ## Support 189 | 190 | ``` 191 | 2020 By Liuzhichao. 192 | ``` 193 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csrftoken/surveySystem/de98f07a55af13d31629de9d50afbc24371b4c7c/api/__init__.py -------------------------------------------------------------------------------- /api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /api/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | -------------------------------------------------------------------------------- /api/serializers/basic.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | from django.urls import reverse 6 | from django.template import loader 7 | from django.db.models import Count 8 | from django.db import transaction 9 | from django.utils.timezone import now 10 | 11 | from rest_framework import serializers 12 | 13 | from web import models 14 | 15 | from ..service.fields import ValueField 16 | 17 | 18 | class SurveyChoicesSerializer(serializers.ModelSerializer): 19 | 20 | class Meta: 21 | model = models.SurveyChoices 22 | fields = ( 23 | "content", 24 | "points", 25 | ) 26 | 27 | 28 | class SurveyItemSerializer(serializers.ModelSerializer): 29 | survey_item = serializers.CharField(source="pk") 30 | choices = SurveyChoicesSerializer(source="answers", many=True, required=False, allow_null=True) 31 | value = ValueField( 32 | source="answer_type", 33 | required=True, 34 | allow_null=False 35 | ) 36 | error = serializers.CharField(default="") 37 | 38 | class Meta: 39 | model = models.SurveyItem 40 | fields = ( 41 | "id", 42 | "name", 43 | "answer_type", 44 | "choices", 45 | "value", 46 | "survey_item", 47 | "error" 48 | ) 49 | 50 | def to_representation(self, instance): 51 | data = super(SurveyItemSerializer, self).to_representation(instance) 52 | 53 | data["survey_template"] = self.context.get("survey_id") 54 | 55 | return data 56 | 57 | 58 | class SurveyRecordSerializer(serializers.ModelSerializer): 59 | 60 | value = ValueField( 61 | required=False, 62 | error_messages={ 63 | "required": "该项为必填项", 64 | "blank": "该项为必填项" 65 | } 66 | ) 67 | 68 | class Meta: 69 | model = models.SurveyRecord 70 | fields = ( 71 | "survey_template", 72 | "survey_item", 73 | "value", 74 | ) 75 | 76 | def validate(self, data): 77 | 78 | survey_item = data.get("survey_item") 79 | value = data.pop("value") 80 | if survey_item.answer_type == "single": 81 | data["choices"] = survey_item.answers.filter(points=value) 82 | elif survey_item.answer_type == "multiple": 83 | data["choices"] = survey_item.answers.filter( 84 | points__in=value 85 | ) 86 | else: 87 | data["suggestion"] = value 88 | 89 | data["survey_code"] = self.context.get("unique_code") 90 | data["survey_id"] = self.context["view"].kwargs.get("pk") 91 | 92 | return data 93 | 94 | 95 | class SurveyDetailSerializer(serializers.ModelSerializer): 96 | 97 | class Meta: 98 | model = models.Survey 99 | fields = ( 100 | "id", 101 | "name", 102 | ) 103 | 104 | def to_representation(self, instance): 105 | 106 | data = super(SurveyDetailSerializer, self).to_representation(instance) 107 | 108 | self.context["survey_id"] = data["id"] 109 | data["questions"] = SurveyItemSerializer( 110 | instance.questions.all(), many=True, context=self.context 111 | ).data 112 | 113 | return data 114 | 115 | 116 | class SurveyTemplateCreateSerializer(serializers.Serializer): 117 | 118 | id = serializers.IntegerField() 119 | questions = SurveyRecordSerializer(many=True) 120 | 121 | def create(self, validated_data): 122 | return validated_data 123 | 124 | def update(self, instance, validated_data): 125 | return instance 126 | 127 | 128 | class SurveyCreateSerializer(serializers.Serializer): 129 | 130 | unique_code = serializers.CharField( 131 | error_messages={ 132 | "required": "唯一码不可为空", "null": "唯一码不可为空", "blank": "唯一码不可为空" 133 | } 134 | ) 135 | surveys = serializers.ListSerializer(child=SurveyTemplateCreateSerializer()) 136 | 137 | def validate_unique_code(self, value): 138 | 139 | codes = models.SurveyCode.objects.filter(unique_code=value) 140 | if not codes.exists(): 141 | raise serializers.ValidationError("无效的唯一码") 142 | 143 | if models.SurveyRecord.objects.filter(survey_code__unique_code=value).exists(): 144 | raise serializers.ValidationError("唯一码已使用") 145 | 146 | self.context["unique_code"] = codes.first() 147 | 148 | return self.context["unique_code"] 149 | 150 | @transaction.atomic 151 | def create(self, validated_data): 152 | 153 | # 创建数据 154 | for item in validated_data.get("surveys", []): 155 | 156 | survey_records = [] 157 | 158 | for question in item.get("questions", []): 159 | choices = question.pop("choices", []) 160 | record = models.SurveyRecord.objects.create(**question) 161 | record.choices.set(choices) 162 | 163 | models.SurveyRecord.objects.bulk_create(survey_records) 164 | 165 | # 修改唯一状态 166 | unique_code = validated_data.get("unique_code") 167 | unique_code.used = True 168 | unique_code.used_time = now() 169 | unique_code.save(update_fields=("used", "used_time", )) 170 | 171 | return {} 172 | 173 | def update(self, instance, validated_data): 174 | return instance 175 | 176 | 177 | class SurveySerializer(serializers.ModelSerializer): 178 | by_class = serializers.CharField(source="by_class.course") 179 | link = serializers.SerializerMethodField() 180 | handle = serializers.SerializerMethodField() 181 | 182 | surveys = SurveyDetailSerializer(many=True) 183 | 184 | HANDLE_TEMPLATE = "components/handle.html" 185 | 186 | class Meta: 187 | model = models.Survey 188 | fields = ( 189 | "name", 190 | "by_class", 191 | "number", 192 | "link", 193 | "handle", 194 | "date", 195 | "surveys", 196 | ) 197 | 198 | def get_link(self, instance): 199 | request = self.context["request"] 200 | link = "{}://{}{}".format( 201 | request.scheme, 202 | request.get_host(), 203 | reverse('survey-detail', args=(instance.pk, )) 204 | ) 205 | return "{link}".format(link=link) 206 | 207 | def get_handle(self, instance): 208 | """ 209 | 获取操作相关 210 | 211 | 查看报告 编辑 212 | """ 213 | handle_html = loader.render_to_string( 214 | self.HANDLE_TEMPLATE, 215 | context={ 216 | "pk": instance.pk 217 | } 218 | ) 219 | return handle_html 220 | 221 | def to_representation(self, instance): 222 | 223 | data = super(SurveySerializer, self).to_representation(instance) 224 | 225 | data["number"] = instance.surveyrecord_set.values("survey_code").annotate( 226 | count=Count("survey_code") 227 | ).count() 228 | 229 | return data 230 | -------------------------------------------------------------------------------- /api/service/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/8 4 | 5 | -------------------------------------------------------------------------------- /api/service/exceptions.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/20 4 | -------------------------------------------------------------------------------- /api/service/fields.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/8 4 | 5 | import time 6 | 7 | from rest_framework import fields 8 | 9 | 10 | class ValueField(fields.Field): 11 | 12 | default_error_messages = { 13 | "invalid": "不合法的数据", 14 | "required": "该项为必填项", 15 | "blank": "该项为必填项" 16 | } 17 | 18 | def __init__(self, **kwargs): 19 | self.allow_blank = kwargs.pop('allow_blank', False) 20 | super(ValueField, self).__init__(**kwargs) 21 | 22 | def to_representation(self, value): 23 | """区分单选及多选 24 | 25 | 单选默认值为空字符串 26 | 多选默认值为空数组 27 | 28 | """ 29 | return [] if value and value == "multiple" else "" 30 | 31 | def to_internal_value(self, value): 32 | 33 | if not value: 34 | self.fail("blank") 35 | 36 | return value 37 | 38 | 39 | class DurationDateField(fields.Field): 40 | """ 41 | 自定义日期字段 42 | """ 43 | def to_representation(self, value): 44 | # serializers.DateTimeField 45 | 46 | minute = 60 47 | hour = minute * 60 48 | day = hour * 24 49 | month = day * 30 50 | current_time = time.time() 51 | diff_val = current_time - time.mktime(value.timetuple()) 52 | if diff_val < 0: 53 | return 54 | month_c = diff_val / month 55 | week_c = diff_val / (7 * day) 56 | day_c = diff_val / day 57 | hour_c = diff_val / hour 58 | min_c = diff_val / minute 59 | if month_c >= 1: 60 | pub_date = "%s月前" % int(month_c) 61 | elif week_c >= 1: 62 | pub_date = "%s周前" % int(week_c) 63 | elif day_c >= 1: 64 | pub_date = "%s天前" % int(day_c) 65 | elif hour_c >= 1: 66 | pub_date = "%s小时前" % int(hour_c) 67 | elif min_c >= 1: 68 | pub_date = "%s分钟前" % int(min_c) 69 | else: 70 | pub_date = "刚刚" 71 | return pub_date 72 | 73 | def to_internal_value(self, data): 74 | if not data: 75 | return None 76 | 77 | return data 78 | 79 | 80 | class CustomCharField(fields.Field): 81 | 82 | def to_representation(self, value): 83 | return "{}第{}期".format(value.course, value.semester) 84 | 85 | def to_internal_value(self, data): 86 | return data 87 | -------------------------------------------------------------------------------- /api/service/pagination.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/8 4 | 5 | import re 6 | 7 | from collections import OrderedDict 8 | 9 | from rest_framework import pagination 10 | 11 | 12 | class CustomLimitOffsetPagination(pagination.LimitOffsetPagination): 13 | 14 | max_limit = 100 15 | 16 | def get_paginated_data(self, data): 17 | return OrderedDict([ 18 | ('count', self.count), 19 | ('next', self.get_next_link()), 20 | ('previous', self.get_previous_link()), 21 | ('results', data) 22 | ]) 23 | 24 | def get_next_link(self): 25 | next_link = super().get_next_link() 26 | return self._get_link(next_link) 27 | 28 | def get_previous_link(self): 29 | previous_link = super().get_previous_link() 30 | return self._get_link(previous_link) 31 | 32 | def _get_link(self, link): 33 | if link is not None: 34 | host = self.request.get_host() 35 | res = re.search(r"(http|https)://{}".format(host), link) 36 | return link.replace(res.group() if res else "", "") 37 | else: 38 | return link 39 | -------------------------------------------------------------------------------- /api/service/response.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/20 4 | 5 | from rest_framework.response import Response 6 | 7 | 8 | class CustomResponse(Response): 9 | 10 | def __init__(self, errcode=True, data=None, status=None, 11 | template_name=None, headers=None, 12 | exception=False, content_type=None): 13 | 14 | data = { 15 | "errcode": errcode, 16 | "data": data 17 | } 18 | 19 | super(CustomResponse, self).__init__(data, status, template_name, headers, exception, content_type) 20 | -------------------------------------------------------------------------------- /api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/urls.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | from django.urls import path 6 | 7 | from .views import basic 8 | 9 | 10 | urlpatterns = [ 11 | path("login/", basic.LoginApi.as_view()), 12 | path("surveys/", basic.SurveyApi.as_view()), 13 | path("surveys//", basic.SurveyDetailApi.as_view()), 14 | ] 15 | -------------------------------------------------------------------------------- /api/views/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | -------------------------------------------------------------------------------- /api/views/basic.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | from django.contrib.auth import login 6 | from django.contrib.auth import authenticate 7 | 8 | from rest_framework import generics 9 | from rest_framework.filters import SearchFilter 10 | from rest_framework.filters import OrderingFilter 11 | from rest_framework.response import Response 12 | 13 | from web import models 14 | 15 | from ..serializers import basic as basic_serializer 16 | from ..service.response import CustomResponse 17 | 18 | 19 | class LoginApi(generics.CreateAPIView): 20 | 21 | def create(self, request, *args, **kwargs): 22 | username = self.request.data.get('username') 23 | password = self.request.data.get('password') 24 | user = authenticate(username=username, password=password) 25 | 26 | if user is not None: 27 | login(request, user) 28 | return CustomResponse(errcode=True) 29 | else: 30 | return CustomResponse(errcode=False, data="用户名或密码错误") 31 | 32 | 33 | class SurveyApi(generics.ListAPIView): 34 | queryset = models.Survey.objects.all() 35 | serializer_class = basic_serializer.SurveySerializer 36 | 37 | filter_backends = (SearchFilter, OrderingFilter, ) 38 | search_fields = ("name",) 39 | 40 | fields = [ 41 | { 42 | 'prop': 'name', 43 | 'label': '问卷名称', 44 | }, 45 | { 46 | 'prop': 'by_class', 47 | 'label': '班级', 48 | }, 49 | { 50 | 'prop': 'number', 51 | 'label': '填写人数', 52 | }, 53 | { 54 | 'prop': 'link', 55 | 'label': '填写链接' 56 | }, 57 | { 58 | 'prop': 'date', 59 | 'label': '创建日期' 60 | }, 61 | { 62 | 'prop': 'handle', 63 | 'label': '操作' 64 | } 65 | ] 66 | 67 | def list(self, request, *args, **kwargs): 68 | queryset = self.filter_queryset(self.get_queryset()) 69 | page = self.paginate_queryset(queryset) 70 | if page is not None: 71 | serializer = self.get_serializer(page, many=True) 72 | paginated_data = self.paginator.get_paginated_data(serializer.data) 73 | return Response({ 74 | "fields": self.fields, 75 | "result": paginated_data, 76 | # "search_fields": self.search_fields 77 | }) 78 | 79 | serializer = self.get_serializer(queryset, many=True) 80 | return Response({ 81 | "fields": self.fields, 82 | "result": serializer.data, 83 | # "search_fields": self.search_fields 84 | }) 85 | 86 | 87 | class SurveyDetailApi(generics.CreateAPIView, generics.RetrieveAPIView): 88 | 89 | queryset = models.Survey.objects.all() 90 | pagination_class = None 91 | 92 | def get_serializer_class(self): 93 | if self.request.method == "POST": 94 | return basic_serializer.SurveyCreateSerializer 95 | else: 96 | return basic_serializer.SurveySerializer 97 | 98 | def get_serializer_context(self): 99 | context = super(SurveyDetailApi, self).get_serializer_context() 100 | 101 | context["unique_code"] = self.request.data.get("unique_code") 102 | 103 | return context 104 | 105 | def retrieve(self, request, *args, **kwargs): 106 | instance = self.get_object() 107 | serializer = self.get_serializer(instance) 108 | return Response(serializer.data) 109 | 110 | def create(self, request, *args, **kwargs): 111 | serializer = self.get_serializer(data=request.data) 112 | if serializer.is_valid(): 113 | self.perform_create(serializer) 114 | return CustomResponse(errcode=True) 115 | else: 116 | return CustomResponse(errcode=False, data=serializer.errors) 117 | -------------------------------------------------------------------------------- /init.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "contenttypes.contenttype", 4 | "pk": 1, 5 | "fields": { 6 | "app_label": "admin", 7 | "model": "logentry" 8 | } 9 | }, 10 | {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "web", "model": "classlist"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "web", "model": "survey"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "web", "model": "surveychoices"}}, {"model": "contenttypes.contenttype", "pk": 10, "fields": {"app_label": "web", "model": "surveycode"}}, {"model": "contenttypes.contenttype", "pk": 11, "fields": {"app_label": "web", "model": "surveyitem"}}, {"model": "contenttypes.contenttype", "pk": 12, "fields": {"app_label": "web", "model": "surveyrecord"}}, {"model": "contenttypes.contenttype", "pk": 13, "fields": {"app_label": "web", "model": "surveytemplate"}}, {"model": "sessions.session", "pk": "tiqwhldv3os456lkbcmbkpblaia3loj7", "fields": {"session_data": "ZWExMWExMWMxZWRlYzU5MzBiNmMyZDFiYTkyMjI3MGQ2ZDhhYTRjOTp7Il9hdXRoX3VzZXJfaWQiOiIxIiwiX2F1dGhfdXNlcl9iYWNrZW5kIjoiZGphbmdvLmNvbnRyaWIuYXV0aC5iYWNrZW5kcy5Nb2RlbEJhY2tlbmQiLCJfYXV0aF91c2VyX2hhc2giOiIwYTIxZDBiYzQyMmJiNmVhZDAwMTkwMDM1MTVkYjkwY2I4NGVmYmYyIn0=", "expire_date": "2020-04-22T09:43:09.888"}}, {"model": "web.classlist", "pk": 1, "fields": {"course": "\u8ba1\u7b97\u673a\u79d1\u5b66\u4e0e\u6280\u672f", "semester": 1, "memo": "xxx"}}, {"model": "web.surveyitem", "pk": 1, "fields": {"name": "\u8bb2\u5e08\u662f\u5426\u6e05\u6670\uff1f", "date": "2020-04-08", "answer_type": "single"}}, {"model": "web.surveyitem", "pk": 2, "fields": {"name": "\u6388\u8bfe\u6ee1\u610f\u5ea6\uff1f", "date": "2020-04-08", "answer_type": "multiple"}}, {"model": "web.surveyitem", "pk": 3, "fields": {"name": "\u5bf9ta\u7684\u5efa\u8bae", "date": "2020-04-08", "answer_type": "suggestion"}}, {"model": "web.surveychoices", "pk": 1, "fields": {"question": 2, "content": "\u903b\u8f91\u4e25\u8c28", "points": 3}}, {"model": "web.surveychoices", "pk": 2, "fields": {"question": 2, "content": "\u5c42\u6b21\u6e05\u6670", "points": 8}}, {"model": "web.surveychoices", "pk": 3, "fields": {"question": 2, "content": "\u6577\u884d\u4e86\u4e8b", "points": 0}}, {"model": "web.surveychoices", "pk": 4, "fields": {"question": 1, "content": "\u975e\u5e38\u597d", "points": 10}}, {"model": "web.surveychoices", "pk": 5, "fields": {"question": 1, "content": "\u8f83\u597d", "points": 8}}, {"model": "web.surveychoices", "pk": 6, "fields": {"question": 1, "content": "\u4e00\u822c", "points": 5}}, {"model": "web.surveychoices", "pk": 7, "fields": {"question": 1, "content": "\u5dee", "points": 3}}, {"model": "web.surveycode", "pk": 1, "fields": {"survey": 1, "unique_code": "rnR7bXtW", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 2, "fields": {"survey": 1, "unique_code": "G0isQEnF", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 3, "fields": {"survey": 1, "unique_code": "25b3yLnt", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 4, "fields": {"survey": 1, "unique_code": "3S7vjqu6", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 5, "fields": {"survey": 1, "unique_code": "kiDyxu34", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 6, "fields": {"survey": 1, "unique_code": "pxIbzNtx", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 7, "fields": {"survey": 1, "unique_code": "y16btUr9", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 8, "fields": {"survey": 1, "unique_code": "XiydBSDq", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 9, "fields": {"survey": 1, "unique_code": "bDfvtlOg", "used": false, "used_time": null, "date": "2020-04-08T09:50:46.264"}}, {"model": "web.surveycode", "pk": 10, "fields": {"survey": 1, "unique_code": "XTXAI5gm", "used": true, "used_time": "2020-04-08T10:29:24.481", "date": "2020-04-08T09:50:46.264"}}, {"model": "web.survey", "pk": 1, "fields": {"name": "\u8ba1\u7f51\u95ee\u5377", "by_class": 1, "number": 1, "quantity": 10, "date": "2020-04-08T09:50:46.258", "surveys": [2, 1]}}, {"model": "web.surveytemplate", "pk": 1, "fields": {"name": "\u8bb2\u5e08\u95ee\u5377\u6a21\u677f", "date": "2020-04-08T09:50:13.785", "questions": [2, 1, 3]}}, {"model": "web.surveytemplate", "pk": 2, "fields": {"name": "\u52a9\u6559\u6a21\u677f", "date": "2020-04-08T09:50:26.910", "questions": [3]}}, {"model": "web.surveyrecord", "pk": 1, "fields": {"survey": 1, "survey_template": 2, "survey_item": 3, "score": null, "suggestion": "sdlkfjl ", "survey_code": 10, "is_hide": false, "date": "2020-04-08T10:29:24.471", "choices": []}}, {"model": "web.surveyrecord", "pk": 2, "fields": {"survey": 1, "survey_template": 1, "survey_item": 2, "score": null, "suggestion": null, "survey_code": 10, "is_hide": false, "date": "2020-04-08T10:29:24.473", "choices": [1, 2]}}, {"model": "web.surveyrecord", "pk": 3, "fields": {"survey": 1, "survey_template": 1, "survey_item": 1, "score": null, "suggestion": null, "survey_code": 10, "is_hide": false, "date": "2020-04-08T10:29:24.476", "choices": [4]}}, {"model": "web.surveyrecord", "pk": 4, "fields": {"survey": 1, "survey_template": 1, "survey_item": 3, "score": null, "suggestion": "lkdsjf", "survey_code": 10, "is_hide": false, "date": "2020-04-08T10:29:24.479", "choices": []}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add log entry", "content_type": 1, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change log entry", "content_type": 1, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete log entry", "content_type": 1, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view log entry", "content_type": 1, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add permission", "content_type": 2, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change permission", "content_type": 2, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete permission", "content_type": 2, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view permission", "content_type": 2, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add group", "content_type": 3, "codename": "add_group"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change group", "content_type": 3, "codename": "change_group"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete group", "content_type": 3, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view group", "content_type": 3, "codename": "view_group"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add user", "content_type": 4, "codename": "add_user"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change user", "content_type": 4, "codename": "change_user"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete user", "content_type": 4, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view user", "content_type": 4, "codename": "view_user"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add content type", "content_type": 5, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change content type", "content_type": 5, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete content type", "content_type": 5, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view content type", "content_type": 5, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add session", "content_type": 6, "codename": "add_session"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change session", "content_type": 6, "codename": "change_session"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete session", "content_type": 6, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view session", "content_type": 6, "codename": "view_session"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add \u73ed\u7ea7\u5217\u8868", "content_type": 7, "codename": "add_classlist"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change \u73ed\u7ea7\u5217\u8868", "content_type": 7, "codename": "change_classlist"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete \u73ed\u7ea7\u5217\u8868", "content_type": 7, "codename": "delete_classlist"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view \u73ed\u7ea7\u5217\u8868", "content_type": 7, "codename": "view_classlist"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add survey", "content_type": 8, "codename": "add_survey"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change survey", "content_type": 8, "codename": "change_survey"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete survey", "content_type": 8, "codename": "delete_survey"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view survey", "content_type": 8, "codename": "view_survey"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can add \u95ee\u5377\u8c03\u67e5\u5019\u9009\u7b54\u6848", "content_type": 9, "codename": "add_surveychoices"}}, {"model": "auth.permission", "pk": 34, "fields": {"name": "Can change \u95ee\u5377\u8c03\u67e5\u5019\u9009\u7b54\u6848", "content_type": 9, "codename": "change_surveychoices"}}, {"model": "auth.permission", "pk": 35, "fields": {"name": "Can delete \u95ee\u5377\u8c03\u67e5\u5019\u9009\u7b54\u6848", "content_type": 9, "codename": "delete_surveychoices"}}, {"model": "auth.permission", "pk": 36, "fields": {"name": "Can view \u95ee\u5377\u8c03\u67e5\u5019\u9009\u7b54\u6848", "content_type": 9, "codename": "view_surveychoices"}}, {"model": "auth.permission", "pk": 37, "fields": {"name": "Can add survey code", "content_type": 10, "codename": "add_surveycode"}}, {"model": "auth.permission", "pk": 38, "fields": {"name": "Can change survey code", "content_type": 10, "codename": "change_surveycode"}}, {"model": "auth.permission", "pk": 39, "fields": {"name": "Can delete survey code", "content_type": 10, "codename": "delete_surveycode"}}, {"model": "auth.permission", "pk": 40, "fields": {"name": "Can view survey code", "content_type": 10, "codename": "view_surveycode"}}, {"model": "auth.permission", "pk": 41, "fields": {"name": "Can add \u8c03\u67e5\u95ee\u5377\u95ee\u9898\u5217\u8868", "content_type": 11, "codename": "add_surveyitem"}}, {"model": "auth.permission", "pk": 42, "fields": {"name": "Can change \u8c03\u67e5\u95ee\u5377\u95ee\u9898\u5217\u8868", "content_type": 11, "codename": "change_surveyitem"}}, {"model": "auth.permission", "pk": 43, "fields": {"name": "Can delete \u8c03\u67e5\u95ee\u5377\u95ee\u9898\u5217\u8868", "content_type": 11, "codename": "delete_surveyitem"}}, {"model": "auth.permission", "pk": 44, "fields": {"name": "Can view \u8c03\u67e5\u95ee\u5377\u95ee\u9898\u5217\u8868", "content_type": 11, "codename": "view_surveyitem"}}, {"model": "auth.permission", "pk": 45, "fields": {"name": "Can add \u95ee\u5377\u8bb0\u5f55", "content_type": 12, "codename": "add_surveyrecord"}}, {"model": "auth.permission", "pk": 46, "fields": {"name": "Can change \u95ee\u5377\u8bb0\u5f55", "content_type": 12, "codename": "change_surveyrecord"}}, {"model": "auth.permission", "pk": 47, "fields": {"name": "Can delete \u95ee\u5377\u8bb0\u5f55", "content_type": 12, "codename": "delete_surveyrecord"}}, {"model": "auth.permission", "pk": 48, "fields": {"name": "Can view \u95ee\u5377\u8bb0\u5f55", "content_type": 12, "codename": "view_surveyrecord"}}, {"model": "auth.permission", "pk": 49, "fields": {"name": "Can add \u8c03\u67e5\u95ee\u5377", "content_type": 13, "codename": "add_surveytemplate"}}, {"model": "auth.permission", "pk": 50, "fields": {"name": "Can change \u8c03\u67e5\u95ee\u5377", "content_type": 13, "codename": "change_surveytemplate"}}, {"model": "auth.permission", "pk": 51, "fields": {"name": "Can delete \u8c03\u67e5\u95ee\u5377", "content_type": 13, "codename": "delete_surveytemplate"}}, {"model": "auth.permission", "pk": 52, "fields": {"name": "Can view \u8c03\u67e5\u95ee\u5377", "content_type": 13, "codename": "view_surveytemplate"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$120000$EO8riiAdruzi$MVmIaqOVPWqHNHZ2wXY0ZTCLxyAOtlQ6aO5/AKYCvLI=", "last_login": "2020-04-08T09:43:09.874", "is_superuser": true, "username": "root", "first_name": "", "last_name": "", "email": "test@qq.com", "is_staff": true, "is_active": true, "date_joined": "2020-04-08T09:42:57.565", "groups": [], "user_permissions": []}}, {"model": "admin.logentry", "pk": 1, "fields": {"action_time": "2020-04-08T09:45:28.231", "user": 1, "content_type": 7, "object_id": "1", "object_repr": "\u8ba1\u7b97\u673a\u79d1\u5b66\u4e0e\u6280\u672f-\u7b2c1\u671f", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 2, "fields": {"action_time": "2020-04-08T09:46:05.395", "user": 1, "content_type": 11, "object_id": "1", "object_repr": "\u8bb2\u5e08\u662f\u5426\u6e05\u6670\uff1f", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 3, "fields": {"action_time": "2020-04-08T09:46:51.994", "user": 1, "content_type": 11, "object_id": "2", "object_repr": "\u6388\u8bfe\u6ee1\u610f\u5ea6\uff1f", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 4, "fields": {"action_time": "2020-04-08T09:47:07.064", "user": 1, "content_type": 11, "object_id": "3", "object_repr": "\u5bf9ta\u7684\u5efa\u8bae", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 5, "fields": {"action_time": "2020-04-08T09:47:31.745", "user": 1, "content_type": 9, "object_id": "1", "object_repr": "\u903b\u8f91\u4e25\u8c28", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 6, "fields": {"action_time": "2020-04-08T09:47:44.512", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "\u5c42\u6b21\u6e05\u6670", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 7, "fields": {"action_time": "2020-04-08T09:47:57.139", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "\u6577\u884d\u4e86\u4e8b", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 8, "fields": {"action_time": "2020-04-08T09:48:10.604", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "\u975e\u5e38\u597d", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 9, "fields": {"action_time": "2020-04-08T09:48:22.260", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "\u8f83\u597d", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 10, "fields": {"action_time": "2020-04-08T09:48:29.883", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "\u4e00\u822c", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 11, "fields": {"action_time": "2020-04-08T09:48:37.275", "user": 1, "content_type": 9, "object_id": "7", "object_repr": "\u5dee", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 12, "fields": {"action_time": "2020-04-08T09:50:13.789", "user": 1, "content_type": 13, "object_id": "1", "object_repr": "\u8bb2\u5e08\u95ee\u5377\u6a21\u677f", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 13, "fields": {"action_time": "2020-04-08T09:50:26.913", "user": 1, "content_type": 13, "object_id": "2", "object_repr": "\u52a9\u6559\u6a21\u677f", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 14, "fields": {"action_time": "2020-04-08T09:50:46.269", "user": 1, "content_type": 8, "object_id": "1", "object_repr": "\u8ba1\u7f51\u95ee\u5377", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}] -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "surveySystem.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | xlwt==1.3.0 2 | django==2.1.15 3 | djangorestframework==3.9.4 4 | pymysql 5 | -------------------------------------------------------------------------------- /static/css/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csrftoken/surveySystem/de98f07a55af13d31629de9d50afbc24371b4c7c/static/css/fonts/element-icons.woff -------------------------------------------------------------------------------- /static/js/axios.js: -------------------------------------------------------------------------------- 1 | /* axios v0.18.0 | (c) 2018 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(5),u=n(6),a=r(u);a.Axios=s,a.create=function(e){return r(o.merge(u,e))},a.Cancel=n(23),a.CancelToken=n(24),a.isCancel=n(20),a.all=function(e){return Promise.all(e)},a.spread=n(25),e.exports=a,e.exports.default=a},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"[object ArrayBuffer]"===R.call(e)}function i(e){return"undefined"!=typeof FormData&&e instanceof FormData}function s(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function u(e){return"string"==typeof e}function a(e){return"number"==typeof e}function c(e){return"undefined"==typeof e}function f(e){return null!==e&&"object"==typeof e}function p(e){return"[object Date]"===R.call(e)}function d(e){return"[object File]"===R.call(e)}function l(e){return"[object Blob]"===R.call(e)}function h(e){return"[object Function]"===R.call(e)}function m(e){return f(e)&&h(e.pipe)}function y(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function w(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function g(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function v(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n 6 | * @license MIT 7 | */ 8 | e.exports=function(e){return null!=e&&(n(e)||r(e)||!!e._isBuffer)}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new s,response:new s}}var o=n(6),i=n(2),s=n(17),u=n(18);r.prototype.request=function(e){"string"==typeof e&&(e=i.merge({url:arguments[0]},arguments[1])),e=i.merge(o,{method:"get"},this.defaults,e),e.method=e.method.toLowerCase();var t=[u,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},i.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(i.merge(n||{},{method:e,url:t}))}}),i.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(i.merge(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function o(){var e;return"undefined"!=typeof XMLHttpRequest?e=n(8):"undefined"!=typeof process&&(e=n(8)),e}var i=n(2),s=n(7),u={"Content-Type":"application/x-www-form-urlencoded"},a={adapter:o(),transformRequest:[function(e,t){return s(t,"Content-Type"),i.isFormData(e)||i.isArrayBuffer(e)||i.isBuffer(e)||i.isStream(e)||i.isFile(e)||i.isBlob(e)?e:i.isArrayBufferView(e)?e.buffer:i.isURLSearchParams(e)?(r(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):i.isObject(e)?(r(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300}};a.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){a.headers[e]={}}),i.forEach(["post","put","patch"],function(e){a.headers[e]=i.merge(u)}),e.exports=a},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(9),i=n(12),s=n(13),u=n(14),a=n(10),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(15);e.exports=function(e){return new Promise(function(t,f){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest,h="onreadystatechange",m=!1;if("undefined"==typeof window||!window.XDomainRequest||"withCredentials"in l||u(e.url)||(l=new window.XDomainRequest,h="onload",m=!0,l.onprogress=function(){},l.ontimeout=function(){}),e.auth){var y=e.auth.username||"",w=e.auth.password||"";d.Authorization="Basic "+c(y+":"+w)}if(l.open(e.method.toUpperCase(),i(e.url,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l[h]=function(){if(l&&(4===l.readyState||m)&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in l?s(l.getAllResponseHeaders()):null,r=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:r,status:1223===l.status?204:l.status,statusText:1223===l.status?"No Content":l.statusText,headers:n,config:e,request:l};o(t,f,i),l=null}},l.onerror=function(){f(a("Network Error",e,null,l)),l=null},l.ontimeout=function(){f(a("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=n(16),v=(e.withCredentials||u(e.url))&&e.xsrfCookieName?g.read(e.xsrfCookieName):void 0;v&&(d[e.xsrfHeaderName]=v)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),e.withCredentials&&(l.withCredentials=!0),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),f(e),l=null)}),void 0===p&&(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(10);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(11);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e}},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(o.isURLSearchParams(t))i=t.toString();else{var s=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),s.push(r(t)+"="+r(e))}))}),i=s.join("&")}return i&&(e+=(e.indexOf("?")===-1?"?":"&")+i),e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t){"use strict";function n(){this.message="String contains an invalid character"}function r(e){for(var t,r,i=String(e),s="",u=0,a=o;i.charAt(0|u)||(a="=",u%1);s+=a.charAt(63&t>>8-u%1*8)){if(r=i.charCodeAt(u+=.75),r>255)throw new n;t=t<<8|r}return s}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";n.prototype=new Error,n.prototype.code=5,n.prototype.name="InvalidCharacterError",e.exports=r},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var u=[];u.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&u.push("expires="+new Date(n).toGMTString()),r.isString(o)&&u.push("path="+o),r.isString(i)&&u.push("domain="+i),s===!0&&u.push("secure"),document.cookie=u.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";function r(){this.handlers=[]}var o=n(2);r.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},r.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},r.prototype.forEach=function(e){o.forEach(this.handlers,function(t){null!==t&&e(t)})},e.exports=r},function(e,t,n){"use strict";function r(e){e.cancelToken&&e.cancelToken.throwIfRequested()}var o=n(2),i=n(19),s=n(20),u=n(6),a=n(21),c=n(22);e.exports=function(e){r(e),e.baseURL&&!a(e.url)&&(e.url=c(e.baseURL,e.url)),e.headers=e.headers||{},e.data=i(e.data,e.headers,e.transformRequest),e.headers=o.merge(e.headers.common||{},e.headers[e.method]||{},e.headers||{}),o.forEach(["delete","get","head","post","put","patch","common"],function(t){delete e.headers[t]});var t=e.adapter||u.adapter;return t(e).then(function(t){return r(e),t.data=i(t.data,t.headers,e.transformResponse),t},function(t){return s(t)||(r(e),t&&t.response&&(t.response.data=i(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)})}},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t,n){return r.forEach(n,function(n){e=n(e,t)}),e}},function(e,t){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); 9 | //# sourceMappingURL=axios.min.map -------------------------------------------------------------------------------- /static/js/vue.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Vue.js v2.5.17 3 | * (c) 2014-2018 Evan You 4 | * Released under the MIT License. 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Vue=t()}(this,function(){"use strict";var y=Object.freeze({});function M(e){return null==e}function D(e){return null!=e}function S(e){return!0===e}function T(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function P(e){return null!==e&&"object"==typeof e}var r=Object.prototype.toString;function l(e){return"[object Object]"===r.call(e)}function i(e){var t=parseFloat(String(e));return 0<=t&&Math.floor(t)===t&&isFinite(e)}function t(e){return null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function F(e){var t=parseFloat(e);return isNaN(t)?e:t}function s(e,t){for(var n=Object.create(null),r=e.split(","),i=0;ie.id;)n--;bt.splice(n+1,0,e)}else bt.push(e);Ct||(Ct=!0,Ze(At))}}(this)},St.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||P(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Fe(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},St.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},St.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},St.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||f(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var Tt={enumerable:!0,configurable:!0,get:$,set:$};function Et(e,t,n){Tt.get=function(){return this[t][n]},Tt.set=function(e){this[t][n]=e},Object.defineProperty(e,n,Tt)}function jt(e){e._watchers=[];var t=e.$options;t.props&&function(n,r){var i=n.$options.propsData||{},o=n._props={},a=n.$options._propKeys=[];n.$parent&&ge(!1);var e=function(e){a.push(e);var t=Ie(e,r,i,n);Ce(o,e,t),e in n||Et(n,"_props",e)};for(var t in r)e(t);ge(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]=null==t[n]?$:v(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;l(t=e._data="function"==typeof t?function(e,t){se();try{return e.call(t,t)}catch(e){return Fe(e,t,"data()"),{}}finally{ce()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var o=n[i];r&&p(r,o)||(void 0,36!==(a=(o+"").charCodeAt(0))&&95!==a&&Et(e,"_data",o))}var a;we(t,!0)}(e):we(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=Y();for(var i in t){var o=t[i],a="function"==typeof o?o:o.get;r||(n[i]=new St(e,a||$,$,Nt)),i in e||Lt(e,i,o)}}(e,t.computed),t.watch&&t.watch!==G&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;iparseInt(this.max)&&bn(a,s[0],s,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};$n=hn,Cn={get:function(){return j}},Object.defineProperty($n,"config",Cn),$n.util={warn:re,extend:m,mergeOptions:Ne,defineReactive:Ce},$n.set=xe,$n.delete=ke,$n.nextTick=Ze,$n.options=Object.create(null),k.forEach(function(e){$n.options[e+"s"]=Object.create(null)}),m(($n.options._base=$n).options.components,kn),$n.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(-1=a&&l()};setTimeout(function(){c\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,oo="[a-zA-Z_][\\w\\-\\.]*",ao="((?:"+oo+"\\:)?"+oo+")",so=new RegExp("^<"+ao),co=/^\s*(\/?)>/,lo=new RegExp("^<\\/"+ao+"[^>]*>"),uo=/^]+>/i,fo=/^",""":'"',"&":"&"," ":"\n"," ":"\t"},go=/&(?:lt|gt|quot|amp);/g,_o=/&(?:lt|gt|quot|amp|#10|#9);/g,bo=s("pre,textarea",!0),$o=function(e,t){return e&&bo(e)&&"\n"===t[0]};var wo,Co,xo,ko,Ao,Oo,So,To,Eo=/^@|^v-on:/,jo=/^v-|^@|^:/,No=/([^]*?)\s+(?:in|of)\s+([^]*)/,Lo=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,Io=/^\(|\)$/g,Mo=/:(.*)$/,Do=/^:|^v-bind:/,Po=/\.[^.]+/g,Fo=e(eo);function Ro(e,t,n){return{type:1,tag:e,attrsList:t,attrsMap:function(e){for(var t={},n=0,r=e.length;n]*>)","i")),n=i.replace(t,function(e,t,n){return r=n.length,ho(o)||"noscript"===o||(t=t.replace(//g,"$1").replace(//g,"$1")),$o(o,t)&&(t=t.slice(1)),d.chars&&d.chars(t),""});a+=i.length-n.length,i=n,A(o,a-r,a)}else{var s=i.indexOf("<");if(0===s){if(fo.test(i)){var c=i.indexOf("--\x3e");if(0<=c){d.shouldKeepComment&&d.comment(i.substring(4,c)),C(c+3);continue}}if(po.test(i)){var l=i.indexOf("]>");if(0<=l){C(l+2);continue}}var u=i.match(uo);if(u){C(u[0].length);continue}var f=i.match(lo);if(f){var p=a;C(f[0].length),A(f[1],p,a);continue}var _=x();if(_){k(_),$o(v,i)&&C(1);continue}}var b=void 0,$=void 0,w=void 0;if(0<=s){for($=i.slice(s);!(lo.test($)||so.test($)||fo.test($)||po.test($)||(w=$.indexOf("<",1))<0);)s+=w,$=i.slice(s);b=i.substring(0,s),C(s)}s<0&&(b=i,i=""),d.chars&&b&&d.chars(b)}if(i===e){d.chars&&d.chars(i);break}}function C(e){a+=e,i=i.substring(e)}function x(){var e=i.match(so);if(e){var t,n,r={tagName:e[1],attrs:[],start:a};for(C(e[0].length);!(t=i.match(co))&&(n=i.match(io));)C(n[0].length),r.attrs.push(n);if(t)return r.unarySlash=t[1],C(t[0].length),r.end=a,r}}function k(e){var t=e.tagName,n=e.unarySlash;m&&("p"===v&&ro(t)&&A(v),g(t)&&v===t&&A(t));for(var r,i,o,a=y(t)||!!n,s=e.attrs.length,c=new Array(s),l=0;l-1"+("true"===d?":("+l+")":":_q("+l+","+d+")")),Ar(c,"change","var $$a="+l+",$$el=$event.target,$$c=$$el.checked?("+d+"):("+v+");if(Array.isArray($$a)){var $$v="+(f?"_n("+p+")":p)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+Er(l,"$$a.concat([$$v])")+")}else{$$i>-1&&("+Er(l,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+Er(l,"$$c")+"}",null,!0);else if("input"===$&&"radio"===w)r=e,i=_,a=(o=b)&&o.number,s=Or(r,"value")||"null",Cr(r,"checked","_q("+i+","+(s=a?"_n("+s+")":s)+")"),Ar(r,"change",Er(i,s),null,!0);else if("input"===$||"textarea"===$)!function(e,t,n){var r=e.attrsMap.type,i=n||{},o=i.lazy,a=i.number,s=i.trim,c=!o&&"range"!==r,l=o?"change":"range"===r?Pr:"input",u="$event.target.value";s&&(u="$event.target.value.trim()"),a&&(u="_n("+u+")");var f=Er(t,u);c&&(f="if($event.target.composing)return;"+f),Cr(e,"value","("+t+")"),Ar(e,l,f,null,!0),(s||a)&&Ar(e,"blur","$forceUpdate()")}(e,_,b);else if(!j.isReservedTag($))return Tr(e,_,b),!1;return!0},text:function(e,t){t.value&&Cr(e,"textContent","_s("+t.value+")")},html:function(e,t){t.value&&Cr(e,"innerHTML","_s("+t.value+")")}},isPreTag:function(e){return"pre"===e},isUnaryTag:to,mustUseProp:Sn,canBeLeftOpenTag:no,isReservedTag:Un,getTagNamespace:Vn,staticKeys:(Go=Wo,Go.reduce(function(e,t){return e.concat(t.staticKeys||[])},[]).join(","))},Qo=e(function(e){return s("type,tag,attrsList,attrsMap,plain,parent,children,attrs"+(e?","+e:""))});function ea(e,t){e&&(Zo=Qo(t.staticKeys||""),Xo=t.isReservedTag||O,function e(t){t.static=function(e){if(2===e.type)return!1;if(3===e.type)return!0;return!(!e.pre&&(e.hasBindings||e.if||e.for||c(e.tag)||!Xo(e.tag)||function(e){for(;e.parent;){if("template"!==(e=e.parent).tag)return!1;if(e.for)return!0}return!1}(e)||!Object.keys(e).every(Zo)))}(t);if(1===t.type){if(!Xo(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var n=0,r=t.children.length;n|^function\s*\(/,na=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,ra={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},ia={esc:"Escape",tab:"Tab",enter:"Enter",space:" ",up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete"]},oa=function(e){return"if("+e+")return null;"},aa={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:oa("$event.target !== $event.currentTarget"),ctrl:oa("!$event.ctrlKey"),shift:oa("!$event.shiftKey"),alt:oa("!$event.altKey"),meta:oa("!$event.metaKey"),left:oa("'button' in $event && $event.button !== 0"),middle:oa("'button' in $event && $event.button !== 1"),right:oa("'button' in $event && $event.button !== 2")};function sa(e,t,n){var r=t?"nativeOn:{":"on:{";for(var i in e)r+='"'+i+'":'+ca(i,e[i])+",";return r.slice(0,-1)+"}"}function ca(t,e){if(!e)return"function(){}";if(Array.isArray(e))return"["+e.map(function(e){return ca(t,e)}).join(",")+"]";var n=na.test(e.value),r=ta.test(e.value);if(e.modifiers){var i="",o="",a=[];for(var s in e.modifiers)if(aa[s])o+=aa[s],ra[s]&&a.push(s);else if("exact"===s){var c=e.modifiers;o+=oa(["ctrl","shift","alt","meta"].filter(function(e){return!c[e]}).map(function(e){return"$event."+e+"Key"}).join("||"))}else a.push(s);return a.length&&(i+="if(!('button' in $event)&&"+a.map(la).join("&&")+")return null;"),o&&(i+=o),"function($event){"+i+(n?"return "+e.value+"($event)":r?"return ("+e.value+")($event)":e.value)+"}"}return n||r?e.value:"function($event){"+e.value+"}"}function la(e){var t=parseInt(e,10);if(t)return"$event.keyCode!=="+t;var n=ra[e],r=ia[e];return"_k($event.keyCode,"+JSON.stringify(e)+","+JSON.stringify(n)+",$event.key,"+JSON.stringify(r)+")"}var ua={on:function(e,t){e.wrapListeners=function(e){return"_g("+e+","+t.value+")"}},bind:function(t,n){t.wrapData=function(e){return"_b("+e+",'"+t.tag+"',"+n.value+","+(n.modifiers&&n.modifiers.prop?"true":"false")+(n.modifiers&&n.modifiers.sync?",true":"")+")"}},cloak:$},fa=function(e){this.options=e,this.warn=e.warn||$r,this.transforms=wr(e.modules,"transformCode"),this.dataGenFns=wr(e.modules,"genData"),this.directives=m(m({},ua),e.directives);var t=e.isReservedTag||O;this.maybeComponent=function(e){return!t(e.tag)},this.onceId=0,this.staticRenderFns=[]};function pa(e,t){var n=new fa(t);return{render:"with(this){return "+(e?da(e,n):'_c("div")')+"}",staticRenderFns:n.staticRenderFns}}function da(e,t){if(e.staticRoot&&!e.staticProcessed)return va(e,t);if(e.once&&!e.onceProcessed)return ha(e,t);if(e.for&&!e.forProcessed)return f=t,v=(u=e).for,h=u.alias,m=u.iterator1?","+u.iterator1:"",y=u.iterator2?","+u.iterator2:"",u.forProcessed=!0,(d||"_l")+"(("+v+"),function("+h+m+y+"){return "+(p||da)(u,f)+"})";if(e.if&&!e.ifProcessed)return ma(e,t);if("template"!==e.tag||e.slotTarget){if("slot"===e.tag)return function(e,t){var n=e.slotName||'"default"',r=_a(e,t),i="_t("+n+(r?","+r:""),o=e.attrs&&"{"+e.attrs.map(function(e){return g(e.name)+":"+e.value}).join(",")+"}",a=e.attrsMap["v-bind"];!o&&!a||r||(i+=",null");o&&(i+=","+o);a&&(i+=(o?"":",null")+","+a);return i+")"}(e,t);var n;if(e.component)a=e.component,c=t,l=(s=e).inlineTemplate?null:_a(s,c,!0),n="_c("+a+","+ya(s,c)+(l?","+l:"")+")";else{var r=e.plain?void 0:ya(e,t),i=e.inlineTemplate?null:_a(e,t,!0);n="_c('"+e.tag+"'"+(r?","+r:"")+(i?","+i:"")+")"}for(var o=0;o':'
',0 2 | 查看报告 3 | 4 | 5 | 下载 6 | -------------------------------------------------------------------------------- /templates/web/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'web/public/layout.html' %} 2 | {% block app-page %} 3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 |
12 |
13 |

{% templatetag openvariable %}survey.name{% templatetag closevariable %}

14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | {% templatetag openvariable %}choice.content{% templatetag closevariable %} 23 | 24 | 25 | 26 | 27 | {% templatetag openvariable %}choice.content{% templatetag closevariable %} 28 | 29 | 30 | 31 | 32 |
33 | 创建问卷调查 34 |
35 | {% endblock %} 36 | {% block js %} 37 | 98 | {% endblock %} -------------------------------------------------------------------------------- /templates/web/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'web/public/layout.html' %} 2 | {% block app-page %} 3 | 38 | {% endblock %} 39 | {% block js %} 40 | 113 | {% endblock %} -------------------------------------------------------------------------------- /templates/web/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'web/public/layout.html' %} 2 | {% block app-page %} 3 | {% csrf_token %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 登录 13 | 14 | 15 | {% endblock %} 16 | {% block js %} 17 | 72 | {% endblock %} -------------------------------------------------------------------------------- /templates/web/public/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 问卷调查管理系统 9 | 10 | 11 | 12 | {# #} 13 | 14 | 19 | {% block css %}{% endblock %} 20 | 21 | 22 |
23 | {% block body %}{% endblock %} 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% block js %}{% endblock %} 34 | 35 | -------------------------------------------------------------------------------- /templates/web/public/layout.html: -------------------------------------------------------------------------------- 1 | {% extends 'web/public/base.html' %} 2 | {% block body %} 3 |
4 |
{% block panel-title %}{{ title }}{% endblock %}
5 |
6 | {% block app-page %}{% endblock %} 7 |
8 | 9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /templates/web/report.html: -------------------------------------------------------------------------------- 1 | {% extends 'web/public/layout.html' %} 2 | {% load staticfiles %} 3 | {% block css %} 4 | 15 | {% endblock %} 16 | {% block app-page %} 17 | {% csrf_token %} 18 |
19 |
20 | 共收到有效问卷调查 21 | {{ count }} 22 | 份 23 |
24 |
25 | 26 | 36 | 37 |
38 | {% for item in result %} 39 |
40 |
41 | 总得分占比 {{ item.percent }}% 42 |
43 |
44 |
45 |
46 | 57 |
58 |
59 |
60 | {% for code, value in item.answers.items %} 61 |
62 | 63 |

64 | 唯一码:{{ code }}    总分:{{ value.score_sum }}

65 |
    66 | {% for item in value.questions %} 67 |
  • 68 | 问题:{{ item.name }} 69 |      70 | 总分:{{ item.score }} 71 |
  • 72 | {% endfor %} 73 |
74 |
75 | {% endfor %} 76 |
77 | 78 |
79 |
80 |
81 |
82 |
83 |
    84 | {% for pk, val in item.choices.items %} 85 |
  • 86 | 题目: 87 | {{ val.name }} 88 | 总分: 89 | {{ val.score }} 90 | 实际得分: 91 | {{ val.real_score }} 92 |
  • 93 | {% endfor %} 94 |
95 |
96 |
97 |
98 |
99 |
    100 | {% for suggestion in item.suggestions %} 101 |
  • {{ suggestion }}
  • 102 | {% endfor %} 103 |
104 |
105 |
106 |
107 |
108 | {% endfor %} 109 | 110 |
111 | {% for name, data in questions.items %} 112 |
113 | {% endfor %} 114 |
115 |
116 |
117 | {% endblock %} 118 | {% block js %} 119 | 120 | 301 | {% endblock %} 302 | -------------------------------------------------------------------------------- /web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csrftoken/surveySystem/de98f07a55af13d31629de9d50afbc24371b4c7c/web/__init__.py -------------------------------------------------------------------------------- /web/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from . import models 4 | 5 | 6 | class SurveyAdmin(admin.ModelAdmin): 7 | 8 | filter_horizontal = ("surveys", ) 9 | 10 | 11 | admin.site.register(models.ClassList) 12 | admin.site.register(models.SurveyItem) 13 | admin.site.register(models.SurveyRecord) 14 | admin.site.register(models.Survey, SurveyAdmin) 15 | admin.site.register(models.SurveyChoices) 16 | admin.site.register(models.SurveyCode) 17 | admin.site.register(models.SurveyTemplate) 18 | -------------------------------------------------------------------------------- /web/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WebConfig(AppConfig): 5 | name = 'web' 6 | -------------------------------------------------------------------------------- /web/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | # Create your models here. 5 | 6 | 7 | class ClassList(models.Model): 8 | course = models.CharField("课程名称", max_length=64) 9 | semester = models.IntegerField("学期") 10 | memo = models.CharField('说明', blank=True, null=True, max_length=100) 11 | 12 | def __str__(self): 13 | return "{}-第{}期".format(self.course, self.semester) 14 | 15 | class Meta: 16 | verbose_name = "班级列表" 17 | 18 | 19 | class SurveyItem(models.Model): 20 | name = models.CharField("调查问题", max_length=255) 21 | date = models.DateField(auto_now_add=True) 22 | answer_type_choices = (('single', "单选"), ('multiple', '多选'), ('suggestion', "建议")) 23 | answer_type = models.CharField("问题类型", choices=answer_type_choices, default='single', max_length=32) 24 | 25 | def __str__(self): 26 | return f"{self.name}-{self.get_answer_type_display()}" 27 | 28 | class Meta: 29 | verbose_name = '调查问卷问题列表' 30 | verbose_name_plural = "调查问卷问题列表" 31 | ordering = ["answer_type", ] 32 | 33 | 34 | class SurveyChoices(models.Model): 35 | question = models.ForeignKey("SurveyItem", verbose_name='问题', related_name='answers', on_delete=models.CASCADE) 36 | content = models.CharField(verbose_name='答案内容', max_length=256, ) 37 | points = models.IntegerField(verbose_name='分值', ) 38 | 39 | def __str__(self): 40 | return f"{self.question}-{self.content}-{self.points}" 41 | 42 | class Meta: 43 | verbose_name = '问卷调查候选答案' 44 | verbose_name_plural = '问卷调查候选答案' 45 | 46 | 47 | class SurveyCode(models.Model): 48 | """ 49 | 问卷唯一码 50 | """ 51 | survey = models.ForeignKey("Survey", on_delete=models.CASCADE) 52 | unique_code = models.CharField(max_length=32, unique=True) 53 | used = models.BooleanField(default=False, verbose_name="使用状态") 54 | used_time = models.DateTimeField(blank=True, null=True, verbose_name='使用时间') 55 | date = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") 56 | 57 | def __str__(self): 58 | return self.unique_code 59 | 60 | 61 | class Survey(models.Model): 62 | """问卷模板 63 | 64 | """ 65 | name = models.CharField(verbose_name="问卷名称", max_length=128, help_text="可以写详细一些") 66 | surveys = models.ManyToManyField( 67 | "SurveyTemplate", verbose_name="问题模板", 68 | help_text="针对哪几个角色进行问卷调查,可以几个角色一起做问卷一起做" 69 | ) 70 | by_class = models.ForeignKey("ClassList", verbose_name="班级", on_delete=models.CASCADE) 71 | number = models.IntegerField(null=True, blank=True, verbose_name="第几次问卷调查") 72 | quantity = models.PositiveIntegerField("数量", default=1, help_text="生成唯一码的数量") 73 | date = models.DateTimeField(auto_now_add=True, verbose_name="问卷创建日期") 74 | 75 | def __str__(self): 76 | return self.name 77 | 78 | @staticmethod 79 | def _get_random_string(): 80 | from django.utils.crypto import get_random_string 81 | code = get_random_string(8) 82 | while True: 83 | if not SurveyCode.objects.filter(unique_code=code).exists(): 84 | return code 85 | 86 | def save(self, *args, **kwargs): 87 | """ 88 | 调查问卷记录 89 | """ 90 | super().save(*args, **kwargs) 91 | bulk_list = [] 92 | 93 | for item in range(self.quantity): 94 | code = self._get_random_string() 95 | bulk_list.append(SurveyCode(survey=self, unique_code=code)) 96 | 97 | SurveyCode.objects.bulk_create(bulk_list) 98 | 99 | class Meta: 100 | verbose_name = "问卷调查" 101 | verbose_name_plural = "问卷调查" 102 | 103 | 104 | class SurveyTemplate(models.Model): 105 | name = models.CharField("问卷模板名称", max_length=128, unique=True) 106 | questions = models.ManyToManyField("SurveyItem", verbose_name="选择要调查的问题列表") 107 | date = models.DateTimeField(auto_now_add=True, verbose_name="问卷创建日期") 108 | 109 | def __str__(self): 110 | return self.name 111 | 112 | class Meta: 113 | verbose_name = "问卷调查模板" 114 | verbose_name_plural = "问卷调查模板" 115 | 116 | ordering = ["-date", ] 117 | 118 | 119 | class SurveyRecord(models.Model): 120 | survey = models.ForeignKey("Survey", verbose_name="问卷", on_delete=models.CASCADE) 121 | survey_template = models.ForeignKey("SurveyTemplate", verbose_name="针对具体角色的问卷", on_delete=models.CASCADE) 122 | survey_item = models.ForeignKey("SurveyItem", verbose_name="调查项", on_delete=models.CASCADE) 123 | score = models.IntegerField("评分", help_text="打分为0至10,0为非常不满意,10为非常满意,请自行斟酌", blank=True, null=True) 124 | suggestion = models.TextField("建议", max_length=1024, blank=True, null=True) 125 | choices = models.ManyToManyField("SurveyChoices", verbose_name='选择项', blank=True) 126 | survey_code = models.ForeignKey("SurveyCode", verbose_name="唯一码", blank=True, null=True, on_delete=models.CASCADE) 127 | is_hide = models.BooleanField(default=False, help_text="是否不进行统计,为True表示不进行统计") 128 | date = models.DateTimeField(auto_now_add=True, verbose_name="答题日期") 129 | 130 | class Meta: 131 | verbose_name = "问卷记录" 132 | verbose_name_plural = "问卷记录" 133 | -------------------------------------------------------------------------------- /web/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /web/urls.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | from django.urls import re_path 6 | from django.urls import include 7 | from django.contrib.auth.decorators import login_required 8 | 9 | from .views import backend 10 | 11 | 12 | survey_urlpatterns = [ 13 | 14 | re_path('^$', login_required(backend.SurveyIndexView.as_view())), 15 | re_path('^login/$', backend.SurveyLoginView.as_view()), 16 | re_path(r'^(?P\d+)/report/$', login_required(backend.SurveyReportView.as_view()), name="survey-report"), 17 | re_path(r'^(?P\d+)/$', backend.SurveyDetailView.as_view(), name='survey-detail'), 18 | re_path(r'^(?P\d+)/download/$', login_required(backend.SurveyDownloadView.as_view()), name='survey-download'), 19 | ] 20 | 21 | 22 | # 路由分发 23 | urlpatterns = [ 24 | re_path('^', include(survey_urlpatterns)), 25 | ] 26 | -------------------------------------------------------------------------------- /web/views/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | -------------------------------------------------------------------------------- /web/views/backend.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Date: 2018/12/6 4 | 5 | import os 6 | import json 7 | import xlwt 8 | import operator 9 | 10 | from urllib.parse import quote 11 | 12 | from django.conf import settings 13 | from django.views import View 14 | from django.views.generic import TemplateView 15 | from django.db.models import Count 16 | from django.http.response import StreamingHttpResponse 17 | from django.http.response import JsonResponse 18 | # from django.http.response import FileResponse 19 | 20 | from .. import models 21 | 22 | 23 | class SurveyLoginView(TemplateView): 24 | 25 | template_name = "web/login.html" 26 | 27 | extra_context = { 28 | "title": "问卷调查" 29 | } 30 | 31 | 32 | class SurveyIndexView(TemplateView): 33 | 34 | template_name = "web/index.html" 35 | 36 | extra_context = { 37 | "title": "欢迎使用问卷调查系统" 38 | } 39 | 40 | 41 | class SurveyDetailView(TemplateView): 42 | 43 | template_name = "web/detail.html" 44 | 45 | extra_context = { 46 | "title": "问卷调查" 47 | } 48 | 49 | 50 | class SurveyReportView(TemplateView): 51 | 52 | template_name = "web/report.html" 53 | 54 | extra_context = { 55 | "title": "问卷调查报告" 56 | } 57 | 58 | queryset = models.SurveyRecord.objects.all() 59 | 60 | @staticmethod 61 | def get_survey_data(survey, survey_template): 62 | records = models.SurveyRecord.objects.filter( 63 | **{ 64 | "survey": survey, "survey_template": survey_template 65 | } 66 | ).select_related() 67 | suggestions, answers = [], {} 68 | 69 | # 获取填写的记录 70 | for record in records.iterator(): 71 | # 将单选的选项进行封装 72 | unique_code = record.survey_code.unique_code 73 | 74 | # 如果是建议 75 | if not record.choices.exists(): 76 | suggestions.append(record.suggestion) 77 | continue 78 | 79 | score = sum(choice.points for choice in record.choices.iterator()) 80 | if unique_code in answers: 81 | answers[unique_code]["questions"].append({ 82 | "id": record.survey_item.pk, 83 | "name": record.survey_item.name, 84 | "score": score 85 | }) 86 | answers[unique_code]["score_sum"] = answers[unique_code]["score_sum"] + score 87 | else: 88 | answers[unique_code] = { 89 | "questions": [ 90 | { 91 | "id": record.survey_item.pk, 92 | "name": record.survey_item.name, 93 | "score": score 94 | } 95 | ], 96 | "is_hide": record.is_hide, 97 | "score_sum": score 98 | } 99 | 100 | _answers = list(filter(lambda item: not item["is_hide"], answers.values())) 101 | 102 | # 去除极值问题, 最高分和最低分 103 | if len(_answers) > 2: 104 | sorted_data = [ 105 | {"code": code, "score_sum": value["score_sum"]} for code, value in answers.items() 106 | ] 107 | sorted_data.sort(key=operator.itemgetter("score_sum")) 108 | # 去除最低分 109 | answers[sorted_data[0]["code"]]["is_hide"] = True 110 | # 去除最高分 111 | answers[sorted_data[-1]["code"]]["is_hide"] = True 112 | 113 | choices = { 114 | question.pk: { 115 | "name": question.name, "real_score": 0, 116 | "score": getattr( 117 | question.answers.order_by("-points").only("points").first(), "points", 0 118 | ) * (len(_answers) - 2 if len(_answers) > 2 else len(_answers)) 119 | } 120 | for question in survey_template.questions.exclude(answer_type="suggestion").iterator() 121 | } 122 | 123 | score = 0 124 | real_score = 0 125 | 126 | for _, value in answers.items(): 127 | 128 | # 如果不计入统计项目, 则不进行统计 129 | if value.get("is_hide", True): 130 | continue 131 | 132 | for question in value.get("questions", []): 133 | if question.get("id") in choices: 134 | choices[question["id"]]["real_score"] += question["score"] 135 | 136 | for value in choices.values(): 137 | 138 | score += value["score"] 139 | real_score += value["real_score"] 140 | 141 | return choices, suggestions, answers, "{:.2f}".format(real_score / (score or 1) * 100) 142 | 143 | def get_context_data(self, **kwargs): 144 | context = super(SurveyReportView, self).get_context_data(**kwargs) 145 | 146 | pk = kwargs.get("pk") 147 | 148 | instance = models.Survey.objects.filter(pk=pk).first() 149 | 150 | result = [] 151 | 152 | # 构造数据结构体 153 | for item in instance.surveys.iterator(): 154 | choices, suggestions, answers, percent = self.get_survey_data(instance, item) 155 | 156 | result.append( 157 | { 158 | "id": "survey-{}".format(item.pk), 159 | "percent": percent, 160 | "name": item.name, 161 | "answers": answers, 162 | "suggestions": suggestions, 163 | "choices": choices, 164 | } 165 | ) 166 | 167 | context["result"] = result 168 | 169 | # 问卷份数 170 | context["count"] = instance.surveyrecord_set.values("survey_code").annotate( 171 | count=Count("survey_code") 172 | ).count() 173 | 174 | # 问卷问题 175 | questions = {} 176 | records = instance.surveyrecord_set.filter(survey_item__answer_type__in=( 177 | "single", "multiple" 178 | )) 179 | for record in records.iterator(): 180 | name = record.survey_item.name 181 | choices = record.choices.all() 182 | if name not in questions: 183 | questions[name] = {} 184 | for choice in choices: 185 | if choice.content not in questions[name]: 186 | questions[name][choice.content] = 1 187 | else: 188 | questions[name][choice.content] += 1 189 | context["questions"] = { 190 | key: json.dumps(val) 191 | for key, val in questions.items() 192 | } 193 | return context 194 | 195 | def post(self, request, **kwargs): 196 | hide_codes = request.POST.getlist("hide_codes") 197 | show_codes = request.POST.getlist("show_codes") 198 | queryset = self.queryset.filter( 199 | survey_id=kwargs.get("pk"), survey_template_id=request.POST.get("survey_id", "")[-1] 200 | ) 201 | queryset.filter(survey_code__unique_code__in=hide_codes).update(is_hide=True) 202 | queryset.filter(survey_code__unique_code__in=show_codes).update(is_hide=False) 203 | return JsonResponse({"status": True}) 204 | 205 | 206 | class SurveyDownloadView(View): 207 | 208 | """ 209 | 1、将唯一码写入文件 210 | 2、下载`xls`文件 211 | """ 212 | queryset = models.SurveyCode.objects.all() 213 | 214 | file_name = "唯一码.xls" 215 | 216 | def get(self, *args, **kwargs): 217 | _ = args[0] 218 | queryset = self.queryset.filter(survey_id=kwargs.get("pk")) 219 | 220 | xls = xlwt.Workbook(encoding="utf-8", style_compression=2) 221 | sheet = xls.add_sheet("唯一码", cell_overwrite_ok=True) 222 | 223 | for index, code in enumerate(queryset.iterator(), ): 224 | sheet.write(index, 0, code.unique_code) 225 | 226 | xls.save(self.file_name) 227 | 228 | file_path = os.path.join(settings.BASE_DIR, self.file_name) 229 | 230 | def iter_file(path, size=1024): 231 | 232 | with open(path, "rb", ) as f: 233 | for data in iter(lambda: f.read(size), b''): 234 | yield data 235 | 236 | response = StreamingHttpResponse(iter_file(file_path)) 237 | response['Content-Type'] = 'application/octet-stream' 238 | response['Content-Disposition'] = 'attachment; {}'.format( 239 | "filename*=utf-8''{}".format(quote(self.file_name)) 240 | ) 241 | 242 | return response 243 | --------------------------------------------------------------------------------