创建正式考试

This commit is contained in:
caoqianming 2021-03-14 22:00:30 +08:00
parent b920e6f701
commit 9554132a66
16 changed files with 572 additions and 12 deletions

View File

@ -0,0 +1,29 @@
import request from '@/utils/request'
export function getexamlist(query) {
return request({
url: '/examtest/exam/',
method: 'get',
params: query
})
}
export function createexam(data) {
return request({
url: '/examtest/exam/',
method: 'post',
data
})
}
export function updateexam(id, data) {
return request({
url: `/examtest/exam/${id}/`,
method: 'put',
data
})
}
export function deleteexam(id) {
return request({
url: `/examtest/exam/${id}/`,
method: 'delete',
})
}

View File

@ -35,10 +35,11 @@ export function deleteTestRule(id) {
})
}
export function getWorkScopeAll() {
export function getWorkScopeAll(query) {
return request({
url: '/examtest/workscope/',
method: 'get',
params: query
})
}

View File

@ -247,6 +247,21 @@ export const asyncRoutes = [
},
]
},
{
path: '/exammanage',
component: Layout,
redirect: '/exammanage/index',
name: 'Exammanage',
meta: { title: '考试管理', icon: '', perms: []},
children: [
{
path: 'index',
name: 'exam',
component: () => import('@/views/exam/index.vue'),
meta: { title: '考试管理', icon: 'component', perms: ['exam_view'] }
},
]
},
{
path: '/analyse',
component: Layout,
@ -268,6 +283,7 @@ export const asyncRoutes = [
},
]
},
{
path: '/qtest',
component: Layout,

View File

@ -64,9 +64,61 @@ div:focus {
padding: 10px;
}
.el-table--medium td,   .el-table--medium th {
padding: 2px 0;
}
.el-form-item {
margin-bottom: 16px;
}
.el-card, .el-message {
border-radius: 0px;
overflow: hidden;
}
.el-card__body {
padding: 6px;
}
.el-card__header {
padding: 6px;
}
}
.el-tabs--border-card>.el-tabs__content {
padding: 6px;
}
.el-dialog__header {
padding: 10px 10px 6px;
}
// .el-dialog{
// display: flex;
// flex-direction: column;
// margin:0 !important;
// position:absolute;
// top:50%;
// left:50%;
// transform:translate(-50%,-50%);
// /*height:600px;*/
// max-height:calc(100% - 30px);
// max-width:calc(100% - 30px);
// }
.el-dialog .el-dialog__body{
// flex:1;
// overflow: auto;
padding: 8px 12px;
}
.el-form--label-top .el-form-item__label {
line-height: 16px;
}
.el-button+.el-button {
margin-left: 1px;
}
.el-tabs__header {
margin: 0 0 6px;
}
.pagination-container {
padding: 0px 0px;
}
body .el-table th.gutter{
display: table-cell!important;
}
.el-dialog__footer{
padding: 6px 6px 6px;
}

View File

@ -0,0 +1,273 @@
<template>
<div class="app-container">
<div style="margin-top:10px">
<el-input
v-model="listQuery.search"
placeholder="输入考试名称进行搜索"
style="width: 300px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button type="primary" @click="handleAdd" icon="el-icon-plus" v-if ="checkPermission(['exam_create'])">新增</el-button>
<el-button
class="filter-item"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>刷新重置</el-button>
</div>
<el-table
:data="tableData.results"
style="width: 100%;margin-top:10px;"
border
fit
v-loading="listLoading"
highlight-current-row
max-height="600"
row-key="id"
default-expand-all
>
<el-table-column label="考试名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="考试编号">
<template slot-scope="scope">{{ scope.row.code }}</template>
</el-table-column>
<el-table-column label="考试地点">
<template slot-scope="scope">{{ scope.row.place }}</template>
</el-table-column>
<el-table-column label="工作类别">
<template slot-scope="scope">{{ scope.row.workscope }}</template>
</el-table-column>
<el-table-column label="开启时间">
<template slot-scope="scope">{{ scope.row.opentime }}</template>
</el-table-column>
<el-table-column label="关闭时间">
<template slot-scope="scope">{{ scope.row.closetime }}</template>
</el-table-column>
<el-table-column label="创建人">
<template slot-scope="scope">
<span>{{ scope.row.create_admin_name }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作" fixed="right">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
@click="handleView(scope)"
:disabled="!checkPermission(['exam_view'])"
>详情</el-button>
<el-button
size="small"
@click="handleEdit(scope)"
:disabled="!checkPermission(['exam_update'])"
>编辑</el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(scope)"
:disabled="!checkPermission(['exam_delete'])"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.count>0"
:total="tableData.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
<el-dialog :visible.sync="dialogVisible" :title="dialogType==='edit'?'编辑考试':'新增考试'" >
<el-form :model="exam" label-width="80px" :rules="rule1" ref="examForm">
<el-form-item label="名称" prop="name">
<el-input v-model="exam.name" placeholder="名称" />
</el-form-item>
<el-form-item label="考试地点" prop="place">
<el-input v-model="exam.place" placeholder="考试地点" />
</el-form-item>
<el-form-item label="工作类别" prop="workscope" >
<el-select v-model="exam.workscope" placeholder="请选择工作类别" style="width:100%">
<el-option
v-for="item in workscopeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="开启时间" prop="opentime">
<el-date-picker
v-model="exam.opentime"
type="datetime"
placeholder="开启时间"
style="width:100%">
</el-date-picker>
</el-form-item>
<el-form-item label="关闭时间" prop="closetime">
<el-date-picker
v-model="exam.closetime"
type="datetime"
placeholder="关闭时间"
style="width:100%"
></el-date-picker>
</el-form-item>
<el-form-item label="监考人姓名" prop="proctor_name" label-width="120px">
<el-input v-model="exam.proctor_name" placeholder="监考人姓名" />
</el-form-item>
<el-form-item label="监考人联系方式" prop="proctor_phone" label-width="120px">
<el-input v-model="exam.proctor_phone" placeholder="监考人联系方式" />
</el-form-item>
</el-form>
<div style="text-align:right;">
<el-button type="danger" @click="dialogVisible=false">取消</el-button>
<el-button type="primary" @click="confirmexam('examForm')">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getexamlist, createexam, deleteexam, updateexam} from "@/api/exam";
import { getWorkScopeAll } from "@/api/examtest"
import { deepClone } from "@/utils";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination"
const defaultexam = {
id: "",
name: "",
place: "",
opentime: null,
closetime: null,
};
const listQuery = {
page: 1,
limit: 20,
search: ""
}
export default {
components: { Pagination },
data() {
return {
selects:[],
exam: {
id: "",
name: "",
},
listQuery:listQuery,
tableData: {count:0},
listLoading: true,
dialogVisible: false,
dialogType: "new",
workscopeOptions:[],
rule1: {
name: [{ required: true, message: "请输入", trigger: "blur" }],
place: [{ required: true, message: "请输入", trigger: "change" }],
workscope: [{ required: true, message: "请选择", trigger: "change" }],
opentime: [{ required: true, message: "请选择", trigger: "change" }],
closetime: [{ required: true, message: "请选择", trigger: "change" }],
proctor_name: [{ required: true, message: "请输入", trigger: "change" }],
proctor_phone: [{ required: true, message: "请输入", trigger: "change" }]
},
};
},
computed: {},
created() {
this.getList();
this.getworkscopeOptions();
},
methods: {
checkPermission,
getworkscopeOptions(){
getWorkScopeAll({can_exam:true}).then(res=>{
this.workscopeOptions = res.data
})
},
getList() {
this.listLoading = true
getexamlist(this.listQuery).then(response => {
this.tableData = response.data
this.listLoading = false
});
},
resetFilter() {
this.listQuery = {
page: 1,
limit: 20,
search: ""
};
this.getList();
},
handleFilter() {
this.listQuery.page = 1;
this.getList();
},
handleAdd() {
this.exam = Object.assign({}, defaultexam);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["examForm"].clearValidate();
});
},
handleEdit(scope) {
this.exam = Object.assign({}, scope.row); // copy obj
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["examForm"].clearValidate();
});
},
handleDelete(scope) {
this.$confirm("确认删除该考试吗?将丢失数据!", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error"
})
.then(async () => {
await deleteexam(scope.row.id);
this.getList()
this.$message({
type: "success",
message: "成功删除!"
});
})
.catch(err => {
// console.error(err);
});
},
handleView(scope){
},
async confirmexam(form) {
this.$refs[form].validate(valid => {
if (valid) {
const isEdit = this.dialogType === "edit";
if (isEdit) {
updateexam(this.exam.id, this.exam).then(() => {
this.getList();
this.dialogVisible = false;
this.$message.success('成功')
});
} else {
createexam(this.exam).then(res => {
// this.exam = res.data
// this.tableData.unshift(this.exam)
this.getList();
this.dialogVisible = false;
this.$message.success('成功')
});
}
} else {
return false;
}
});
},
}
};
</script>

View File

@ -20,14 +20,20 @@
row-key="id"
>
<el-table-column type="index" width="50"></el-table-column>
<el-table-column align="center" label="名称">
<el-table-column label="名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column align="center" label="所属学科">
<el-table-column label="所属学科">
<template slot-scope="scope">
<el-tag type="success" >{{scope.row.subject_name}}</el-tag>
</template>
</el-table-column>
<el-table-column label="是否可组织考试">
<template slot-scope="scope">
<el-tag v-if="scope.row.can_exam"></el-tag>
<el-tag type="warning" v-else></el-tag>
</template>
</el-table-column>
<el-table-column label="创建日期">
<template slot-scope="scope">
<span>{{ scope.row.create_time }}</span>

View File

@ -10,6 +10,9 @@
<el-form-item label="工作类别" prop="name">
<el-input v-model="Form.name" style="width:400px"></el-input>
</el-form-item>
<el-form-item label="是否可组织考试" prop="can_exam" label-width="120px">
<el-switch v-model="Form.can_exam"></el-switch>
</el-form-item>
<el-form-item label="所属学科" prop="subject">
<el-select v-model="Form.subject" placeholder="请选择所属学科" style="width:400px">
<el-option
@ -57,6 +60,7 @@ export default {
return {
Form: {
name: "",
can_exam:false,
subject: null,
questioncat: [],
},

View File

@ -10,6 +10,9 @@
<el-form-item label="工作类别" prop="name">
<el-input v-model="Form.name" style="width:400px"></el-input>
</el-form-item>
<el-form-item label="是否可组织考试" prop="can_exam" label-width="120px">
<el-switch v-model="Form.can_exam"></el-switch>
</el-form-item>
<el-form-item label="所属学科" prop="subject">
<el-select v-model="Form.subject" placeholder="请选择所属学科" style="width:400px">
<el-option
@ -57,6 +60,7 @@ export default {
return {
Form: {
name: "",
can_exam:false,
subject: null,
questioncat: [],
},

View File

@ -0,0 +1,53 @@
# Generated by Django 3.0.4 on 2021-03-14 09:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('examtest', '0024_auto_20200402_2353'),
]
operations = [
migrations.AddField(
model_name='workscope',
name='can_exam',
field=models.BooleanField(default=False, verbose_name='是否可组织考试'),
),
migrations.AlterField(
model_name='examtest',
name='type',
field=models.CharField(choices=[('自助模考', '自助模考'), ('押卷模考', '押卷模考'), ('正式考试', '正式考试')], default='自助模考', max_length=50, verbose_name='考试类型'),
),
migrations.CreateModel(
name='Exam',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_delete', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('code', models.CharField(blank=True, max_length=100, null=True, verbose_name='考试编号')),
('name', models.CharField(max_length=100, verbose_name='名称')),
('place', models.CharField(blank=True, max_length=100, null=True, verbose_name='考试地点')),
('opentime', models.DateTimeField(blank=True, null=True, verbose_name='开启时间')),
('closetime', models.DateTimeField(blank=True, null=True, verbose_name='关闭时间')),
('proctor_name', models.CharField(max_length=100, verbose_name='监考人姓名')),
('proctor_phone', models.CharField(max_length=100, verbose_name='监考人联系方式')),
('create_admin', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exam_create_admin', to=settings.AUTH_USER_MODEL)),
('workscope', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='examtest.WorkScope', verbose_name='工作类别')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='examtest',
name='exam',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='examtest_exam', to='examtest.Exam', verbose_name='关联的正式考试'),
),
]

View File

@ -1,5 +1,5 @@
from django.db import models
from rbac.models import SoftCommonModel, CommonModel
from rbac.models import SoftCommonModel, CommonModel, UserProfile
from django.contrib.postgres.fields import JSONField, ArrayField
from question.models import Questioncat, Question
from crm.models import Consumer
@ -7,7 +7,20 @@ from .models_paper import WorkScope, Paper
class Exam(CommonModel):
pass
"""
组织的正式考试
"""
code = models.CharField('考试编号', max_length=100, null=True, blank=True)
name = models.CharField('名称', max_length=100)
place = models.CharField('考试地点', max_length=100, null=True, blank=True)
workscope = models.ForeignKey(WorkScope, verbose_name='工作类别', on_delete= models.DO_NOTHING)
opentime = models.DateTimeField('开启时间', null=True, blank=True)
closetime = models.DateTimeField('关闭时间', null=True, blank=True)
proctor_name = models.CharField('监考人姓名', max_length=100)
proctor_phone = models.CharField('监考人联系方式', max_length=100)
create_admin = models.ForeignKey(UserProfile, on_delete=models.SET_NULL, null=True, blank=True, related_name='exam_create_admin')
class ExamTest(CommonModel):
@ -17,6 +30,7 @@ class ExamTest(CommonModel):
type_choices = (
('自助模考', '自助模考'),
('押卷模考', '押卷模考'),
('正式考试', '正式考试')
)
name = models.CharField(max_length=200, verbose_name='名称')
type = models.CharField(max_length=50, default='自助模考',choices = type_choices, verbose_name='考试类型')
@ -31,6 +45,8 @@ class ExamTest(CommonModel):
end_time = models.DateTimeField(verbose_name='结束答题时间')
detail = models.ManyToManyField(Question, related_name='答题记录', through='AnswerDetail')
is_pass = models.BooleanField(default=True, verbose_name='是否通过')
exam = models.ForeignKey(Exam, verbose_name='关联的正式考试', null=True, blank=True, related_name='examtest_exam', on_delete= models.SET_NULL)
class Meta:
verbose_name = '模拟考试'
verbose_name_plural = verbose_name

View File

@ -1,4 +1,5 @@
from django.db import models
from django.template.defaultfilters import default
from rbac.models import SoftCommonModel, CommonModel
from django.contrib.postgres.fields import JSONField, ArrayField
from question.models import Questioncat, Question
@ -31,6 +32,7 @@ class WorkScope(CommonModel):
subject = models.ForeignKey(Questioncat, verbose_name='所属学科', on_delete=models.CASCADE , related_name='workscope_subject')
questioncat = models.ManyToManyField(Questioncat, verbose_name='所选科目')
rule = models.ForeignKey(TestRule, on_delete=models.CASCADE, verbose_name='试卷结构')
can_exam = models.BooleanField('是否可组织考试', default=False)
class Meta:
verbose_name = '工作类别'

View File

@ -1,7 +1,7 @@
from rest_framework import serializers
from question.models import Questioncat
from crm.models import Consumer
from .models import ExamTest, AnswerDetail, Banner
from .models import Exam, ExamTest, AnswerDetail, Banner
from .models_paper import TestRule, WorkScope, Paper, PaperQuestions
from question.serializers import QuestionSerializer
@ -163,3 +163,14 @@ class PaperQuestionsCreateSerializer(serializers.ModelSerializer):
model = PaperQuestions
fields = '__all__'
class ExamCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Exam
fields = ['name', 'place', 'opentime', 'closetime', 'proctor_name', 'proctor_phone', 'workscope']
class ExamListSerializer(serializers.ModelSerializer):
create_admin_username = serializers.StringRelatedField(source='create_admin')
workscope_name = serializers.StringRelatedField(source='workscope')
class Meta:
model = Exam
fields = '__all__'

View File

@ -1,5 +1,5 @@
from django.urls import path,include
from .views import TestRuleViewSet, AnswerDetailView, WorkScopeViewSet, BannerViewSet, ExamTestViewSet, PaperViewSet
from .views import TestRuleViewSet, AnswerDetailView, WorkScopeViewSet, BannerViewSet, ExamTestViewSet, PaperViewSet, ExamViewSet
from rest_framework import routers
@ -9,6 +9,7 @@ router.register('workscope', WorkScopeViewSet, basename="workscope")
router.register('banner', BannerViewSet, basename='banner')
router.register('examtest', ExamTestViewSet, basename='examtest')
router.register('paper', PaperViewSet, basename='paper')
router.register('exam', ExamViewSet, basename='exam')
urlpatterns = [
path('answerdetail/', AnswerDetailView.as_view()),

View File

@ -18,18 +18,59 @@ from question.models import Question
from question.serializers import QuestionSerializer
from server import settings
from utils.custom import CommonPagination
from utils.mixins import OptimizationMixin
from .exports import export_test
from .models import AnswerDetail, Banner, ExamTest
from .models import AnswerDetail, Banner, ExamTest, Exam
from .models_paper import Paper, PaperQuestions, TestRule, WorkScope
from .serializers import (
AnswerDetailCreateSerializer, AnswerDetailSerializer, BannerSerializer,
ExamTestListSerializer, MoniTestSerializer, PaperDetailSerializer,
PaperQuestionsCreateSerializer, PaperSerializer, TestRuleSerializer,
WorkScopeSerializer)
WorkScopeSerializer, ExamCreateUpdateSerializer, ExamListSerializer)
# Create your views here.
class ExamViewSet(ModelViewSet):
"""
正式考试增删改查
"""
perms_map = [
{'get': 'exam_view'}, {'post': 'exam_create'},
{'put': 'exam_update'}, {'delete': 'exam_delete'}]
pagination_class = CommonPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
ordering = ['-id']
search_fields = ['name']
def get_queryset(self):
queryset = Exam.objects.all()
if not self.request.user.is_superuser:
queryset = queryset.filter(create_admin = self.request.user)
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化
return queryset
def get_serializer_class(self):
if self.action in ['create', 'update']:
return ExamCreateUpdateSerializer
return ExamListSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save(create_admin=request.user)
instance.code = instance.pk + 10000
instance.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if ExamTest.objects.filter(exam=instance).exists():
return Response({'error':'存在考试记录,禁止删除'})
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class AnswerDetailView(APIView):
authentication_classes = []
@ -56,8 +97,8 @@ class WorkScopeViewSet(ModelViewSet):
ordering_fields = ('id',)
ordering = ['id']
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['subject']
search_fields = ('^name',)
filterset_fields = ['subject', 'can_exam']
search_fields = ('name',)
def get_queryset(self):
queryset = self.queryset
@ -102,6 +143,7 @@ class WorkScopeViewSet(ModelViewSet):
b3_set = queryset.filter(questioncat=questioncats[2], type='多选').order_by('?')[:b3]
question_queryset = question_queryset|a1_set|a2_set|a3_set|b1_set|b2_set|b3_set
elif workscope.name == '辐射安全管理':
# 辐射安全管理出卷规则
queryset = Question.objects.filter(is_delete=0)
a1_set = queryset.filter(questioncat=questioncats[0], type='单选').order_by('?')[:16]
a2_set = queryset.filter(questioncat=questioncats[1], type='单选').order_by('?')[:24]
@ -109,6 +151,7 @@ class WorkScopeViewSet(ModelViewSet):
b2_set = queryset.filter(questioncat=questioncats[1], type='多选').order_by('?')[:6]
question_queryset = question_queryset|a1_set|a2_set|b1_set|b2_set
elif workscope.name == '科研、生产及其他':
# 科研、生产及其他出卷规则
queryset = Question.objects.filter(is_delete=0)
a1_set = queryset.filter(questioncat=questioncats[0], type='单选').order_by('?')[:24]
a2_set = queryset.filter(questioncat=questioncats[1], type='单选').order_by('?')[:16]

View File

@ -0,0 +1,48 @@
from django.db.models.query import QuerySet
class CreateUpdateModelAMixin:
"""
业务用基本表A用
"""
def perform_create(self, serializer):
serializer.save(create_by = self.request.user)
def perform_update(self, serializer):
serializer.save(update_by = self.request.user)
class CreateUpdateModelBMixin:
"""
业务用基本表B用
"""
def perform_create(self, serializer):
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept)
def perform_update(self, serializer):
serializer.save(update_by = self.request.user)
class CreateUpdateCustomMixin:
"""
整合
"""
def perform_create(self, serializer):
if hasattr(self.queryset.model, 'belong_dept'):
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept)
else:
serializer.save(create_by = self.request.user)
def perform_update(self, serializer):
serializer.save(update_by = self.request.user)
class OptimizationMixin:
"""
性能优化,需要在序列化器里定义setup_eager_loading,可在必要的View下继承
"""
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, QuerySet):
# Ensure queryset is re-evaluated on each request.
queryset = queryset.all()
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化
return queryset

1
test_server/x.py Normal file
View File

@ -0,0 +1 @@
from sympy import *