diff --git a/server/apps/exam/exports.py b/server/apps/exam/exports.py new file mode 100644 index 0000000..e509192 --- /dev/null +++ b/server/apps/exam/exports.py @@ -0,0 +1,27 @@ +from openpyxl.workbook import Workbook +from django.conf import settings +from datetime import datetime +from openpyxl.styles import Font, Fill +import json +import os + +def export_question(questions): + ''' + params: serializer questions + return: xlsx path + ''' + wb = Workbook() + ws1 = wb.active + ws1.title = '题目表' + ws1.append(['分类','题型', '题干', '选项', '正确答案', '解析']) + row = ws1.row_dimensions[1] + row.font = Font(bold=True) + for i in questions: + ws1.append([i.questioncat.name, i.type, i.name, json.dumps(i.options, ensure_ascii=False), ''.join(sorted(i.right)), i.resolution]) + filename = 'questions' + datetime.now().strftime("%Y%m%d%H%M%S") +'.xlsx' + path = '/media/temp/' + full_path = settings.BASE_DIR + '/media/temp/' + if not os.path.exists(full_path): + os.makedirs(full_path) + wb.save(full_path+filename) + return path + filename \ No newline at end of file diff --git a/server/apps/exam/models.py b/server/apps/exam/models.py index 71a8362..bb71fd5 100644 --- a/server/apps/exam/models.py +++ b/server/apps/exam/models.py @@ -1,3 +1,47 @@ from django.db import models +from apps.system.models import CommonAModel +from django.contrib.postgres.fields import JSONField # Create your models here. +class Questioncat(CommonAModel): + name = models.CharField(max_length=200, verbose_name='名称') + parent = models.ForeignKey('self', verbose_name='父', null=True, blank=True, on_delete=models.CASCADE, related_name='questioncat_parent') + class Meta: + verbose_name = '题目分类' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + @property + def tmtotal(self): + return self.questioncat.count() + + +class Question(CommonAModel): + type_choices = ( + ('单选', '单选'), + ('多选', '多选'), + ('判断', '判断'), + ) + level_choices = ( + ('低', '低'), + ('中', '中'), + ('高', '高'), + ) + name = models.TextField(verbose_name='题干') + img = models.CharField(max_length=1000, null=True, blank=True, verbose_name='题干图片') + type = models.CharField(max_length=50, default='单选', choices=type_choices, verbose_name='题型') + level = models.CharField(max_length=50, default='低', choices=level_choices, verbose_name='难度') + questioncat = models.ForeignKey(Questioncat, blank=True, null=True, on_delete=models.SET_NULL, verbose_name='所属题库', related_name='questioncat') + options = JSONField(verbose_name='选项') + right = JSONField(verbose_name='正确答案') + resolution = models.TextField(verbose_name='解析', blank=True) + enabled = models.BooleanField('是否启用', default=False) + year = models.IntegerField('真题年份', null=True, blank=True) + class Meta: + verbose_name = '题目' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name \ No newline at end of file diff --git a/server/apps/exam/serializers.py b/server/apps/exam/serializers.py new file mode 100644 index 0000000..5363655 --- /dev/null +++ b/server/apps/exam/serializers.py @@ -0,0 +1,15 @@ +from rest_framework.serializers import ModelSerializer + +from apps.exam.models import Question, Questioncat + + +class QuestioncatSerializer(ModelSerializer): + class Meta: + model = Questioncat + fields = '__all__' + + +class QuestionSerializer(ModelSerializer): + class Meta: + model = Question + fields = '__all__' \ No newline at end of file diff --git a/server/apps/exam/urls.py b/server/apps/exam/urls.py index e625e34..86eb1b5 100644 --- a/server/apps/exam/urls.py +++ b/server/apps/exam/urls.py @@ -1,6 +1,13 @@ -from django.urls import path +from django.urls import path, include +from rest_framework import routers + +from apps.exam.views import QuestionViewSet, QuestioncatViewSet API_BASE_URL = 'api/exam/' HTML_BASE_URL = 'exam/' +router = routers.DefaultRouter() +router.register('questioncat', QuestioncatViewSet, basename='questioncat') +router.register('question', QuestionViewSet, basename="question") urlpatterns = [ -] \ No newline at end of file + path(API_BASE_URL, include(router.urls)) +] diff --git a/server/apps/exam/views.py b/server/apps/exam/views.py index 91ea44a..4b9ba88 100644 --- a/server/apps/exam/views.py +++ b/server/apps/exam/views.py @@ -1,3 +1,169 @@ from django.shortcuts import render +from rest_framework.viewsets import ModelViewSet +from apps.exam.exports import export_question +from apps.exam.models import Question, Questioncat +from apps.exam.serializers import QuestionSerializer, QuestioncatSerializer +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from openpyxl import Workbook, load_workbook +from django.conf import settings # Create your views here. + + +class QuestioncatViewSet(ModelViewSet): + perms_map = {'*': '*'} + queryset = Questioncat.objects.all() + serializer_class = QuestioncatSerializer + filterset_fields = ['parent'] + search_fields = ['name'] + + +class QuestionViewSet(ModelViewSet): + perms_map = {'*': '*'} + queryset = Question.objects.all() + serializer_class = QuestionSerializer + filterset_fields = ['level', 'type', 'year'] + search_fields = ['name', 'options', 'resolution'] + + @action(methods=['get'], detail=False, + url_path='export', url_name='export_question', perms_map=[{'get': '*'}]) + def export_question(self, request): + queryset = self.filter_queryset(self.get_queryset()) + path = export_question(queryset) + return Response({'path': path}) + + @action(methods=['post'], detail=False, url_name='enable_question', permission_classes=[IsAuthenticated]) + def enable(self, request): + ids = request.data.get('ids', None) + if ids: + Question.objects.filter(pk__in=ids).update(enabled=True) + return Response(status=200) + + @action(methods=['post'], detail=False, + url_path='import', url_name='import_question', perms_map=[{'post': '*'}]) + def import_question(self, request): + """ + 导入题目 + """ + xlsxpath = request.data['path'] + fullpath = settings.BASE_DIR + xlsxpath + wb = load_workbook(fullpath) + sheet = wb.worksheets[0] + qlist = ['A', 'B', 'C', 'D', 'E', 'F'] + leveldict = {'低': '低', '中': '中', '高': '高'} + notinlist = [] + # 验证文件内容 + if sheet['a2'].value != '题目类型': + return Response({"error": "类型列错误!"}) + if sheet['b2'].value != '分类': + return Response({"error": "分类列错误!"}) + if sheet['c2'].value != '题目': + return Response({"error": "题目列错误!"}) + questioncatdict = {} + questioncats = Questioncat.objects.all() + for i in questioncats: + questioncatdict[i.name] = i.id + i = 3 + while sheet['c'+str(i)].value: + type = sheet['a'+str(i)].value.replace(' ', '') + questioncat = sheet['b'+str(i)].value + if questioncat: + questioncat = questioncat.replace(' ', '') + else: + return Response(str(i)+'行没有分类', status=400) + name = sheet['c'+str(i)].value + + answer = {} + if sheet['d'+str(i)].value: + answer['A'] = sheet['d'+str(i)].value + if sheet['e'+str(i)].value: + answer['B'] = sheet['e'+str(i)].value + if sheet['f'+str(i)].value: + answer['C'] = sheet['f'+str(i)].value + if sheet['g'+str(i)].value: + answer['D'] = sheet['g'+str(i)].value + if sheet['h'+str(i)].value: + answer['E'] = sheet['h'+str(i)].value + if sheet['i'+str(i)].value: + answer['F'] = sheet['i'+str(i)].value + right = sheet['j'+str(i)].value + if right: + right = right.replace(' ', '') + else: + return Response(str(i)+'行没有答案', status=400) + resolution = sheet['k'+str(i)].value + level = sheet['l'+str(i)].value + year = sheet['m' + str(i)].value + if level: + level = level.replace(' ', '') + cateobj = None + if questioncat not in questioncatdict: + return Response(str(i)+"行不存在分类("+questioncat+")!请先新建", status=400) + else: + cateobj = Questioncat.objects.get( + id=questioncatdict[questioncat]) + if type == '单选': + if Question.objects.filter(type='单选', name=name, year=year, options=answer, questioncat=cateobj).exists(): + notinlist.append(i) + else: + if right in ['A', 'B', 'C', 'D', 'E', 'F']: + obj = Question() + obj.type = '单选' + if cateobj: + obj.questioncat = cateobj + obj.name = name + obj.options = answer + obj.right = right + obj.resolution = resolution if resolution else '' + obj.year = year if year else None + if level in leveldict: + obj.level = leveldict[level] + else: + obj.level = '低' + obj.save() + elif type == '多选': + right = list(right.strip()) + if Question.objects.filter(type='多选', name=name, year=year, options=answer, questioncat=cateobj).exists(): + notinlist.append(i) + else: + if [False for c in right if c not in qlist]: + pass + else: + obj = Question() + obj.type = '多选' + obj.questioncat = cateobj + obj.name = name + obj.options = answer + obj.right = right + obj.resolution = resolution if resolution else '' + obj.year = year if year else None + if level in leveldict: + obj.level = leveldict[level] + else: + obj.level = '低' + obj.save() + elif type == '判断': + if right == 'A' or right == '对' or right == '正确': + right = 'A' + else: + right = 'B' + if Question.objects.filter(type='判断', name=name, is_delete=0, options={'A': '对', 'B': '错'}, questioncat=cateobj).exists(): + notinlist.append(i) + else: + obj = Question() + obj.type = '判断' + obj.questioncat = cateobj + obj.name = name + obj.options = {'A': '对', 'B': '错'} + obj.right = right + obj.resolution = resolution if resolution else '' + obj.year = year if year else None + if level in leveldict: + obj.level = leveldict[level] + else: + obj.level = '低' + obj.save() + i = i + 1 + return Response(notinlist, status=200) diff --git a/server/server/settings_dev.py b/server/server/settings_dev.py index 0e60099..28f1715 100644 --- a/server/server/settings_dev.py +++ b/server/server/settings_dev.py @@ -1,21 +1,21 @@ from .settings import * DEBUG = True DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'cma', - 'USER': 'postgres', - 'PASSWORD': 'zctest1234', - 'HOST': '47.95.0.242', - 'PORT': '5432', - }, -# 'default': { -# 'ENGINE': 'django.db.backends.postgresql', -# 'NAME': 'cma', -# 'USER': 'cma', -# 'PASSWORD': 'cma123', -# 'HOST': '172.16.80.102', -# # 'HOST': '1.203.161.102', -# 'PORT': '5432', -# } + # 'default': { + # 'ENGINE': 'django.db.backends.postgresql', + # 'NAME': 'cma', + # 'USER': 'postgres', + # 'PASSWORD': 'zctest1234', + # 'HOST': '47.95.0.242', + # 'PORT': '5432', + # }, + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'cma', + 'USER': 'cma', + 'PASSWORD': 'cma123', + 'HOST': '172.16.80.102', + # 'HOST': '1.203.161.102', + 'PORT': '5432', + } }