This commit is contained in:
shijing 2022-11-11 17:05:46 +08:00
commit 6a9fd2207c
27 changed files with 1342 additions and 85 deletions

View File

@ -3,8 +3,8 @@ ENV = 'development'
# base api
#VUE_APP_BASE_API = 'http://10.0.11.127:8000/api'
#VUE_APP_BASE_API = 'http://127.0.0.1:8000/api'
VUE_APP_BASE_API = 'https://testsearch.ctc.ac.cn/api'
VUE_APP_BASE_API = 'http://127.0.0.1:2222/api'
#VUE_APP_BASE_API = 'https://testsearch.ctc.ac.cn/api'
#VUE_APP_BASE_API = 'http://47.95.0.242:9101/api'

View File

@ -60,4 +60,5 @@
<style lang="scss">
@import "uview-ui/index.scss";
@import "common/demo.scss";
@import "common/uni-ui.scss";
</style>

View File

@ -44,6 +44,10 @@ const install = (Vue, vm) => {
let getVideos = (params={})=>vm.$u.get('/vod/video/', params);//点播视频
let getDickey = (params={})=>vm.$u.get('/system/dict/', params);//查询字典
let putMyVideoView =(id)=>vm.$u.put(`/vod/video/${id}/myview/`);//更新本人观看信息
//考试有关
let getExamList = (params={})=>vm.$u.get('/exam/exam/', params);//考试列表
vm.$u.api = {getUserInfo,
getCode,
codeLogin,
@ -64,7 +68,9 @@ const install = (Vue, vm) => {
getDocument,
getVideos,
getDickey,
putMyVideoView
putMyVideoView,
getExamList
};
}

View File

@ -0,0 +1,119 @@
.uni-flex {
display: flex;
}
.uni-flex-row {
@extend .uni-flex;
flex-direction: row;
box-sizing: border-box;
}
.uni-flex-column {
@extend .uni-flex;
flex-direction: column;
}
.uni-color-gary {
color: #3b4144;
}
/* 标题 */
.uni-title {
display: flex;
margin-bottom: $uni-spacing-col-base;
font-size: $uni-font-size-lg;
font-weight: bold;
color: #3b4144;
}
.uni-title-sub {
display: flex;
// margin-bottom: $uni-spacing-col-base;
font-size: $uni-font-size-base;
font-weight: 500;
color: #3b4144;
}
/* 描述 额外文本 */
.uni-note {
margin-top: 10px;
color: #999;
font-size: $uni-font-size-sm;
}
/* 列表内容 */
.uni-list-box {
@extend .uni-flex-row;
flex: 1;
margin-top: 10px;
}
/* 略缩图 */
.uni-thumb {
flex-shrink: 0;
margin-right: $uni-spacing-row-base;
width: 125px;
height: 75px;
border-radius: $uni-border-radius-lg;
overflow: hidden;
border: 1px #f5f5f5 solid;
image {
width: 100%;
height: 100%;
}
}
.uni-media-box {
@extend .uni-flex-row;
// margin-bottom: $uni-spacing-col-base;
border-radius: $uni-border-radius-lg;
overflow: hidden;
.uni-thumb {
margin: 0;
margin-left: 4px;
flex-shrink: 1;
width: 33%;
border-radius:0;
&:first-child {
margin: 0;
}
}
}
/* 内容 */
.uni-content {
@extend .uni-flex-column;
justify-content: space-between;
}
/* 列表footer */
.uni-footer {
@extend .uni-flex-row;
justify-content: space-around;
margin-top: $uni-spacing-col-lg;
}
.uni-footer-text {
font-size: $uni-font-size-sm;
color: $uni-text-color-grey;
margin-left: 5px;
}
/* 标签 */
.uni-tag {
flex-shrink: 0;
padding: 0 5px;
border: 1px $uni-border-color solid;
margin-right: $uni-spacing-row-sm;
border-radius: $uni-border-radius-base;
background: $uni-bg-color-grey;
color: $uni-text-color;
font-size: $uni-font-size-sm;
}
/* 链接 */
.uni-link {
margin-left: 10px;
color: $uni-text-color;
text-decoration: underline;
}

View File

@ -124,7 +124,52 @@
"path" : "pages/vod/video",
"style" :
{
"navigationBarTitleText": "云点播",
"navigationBarTitleText": "视频观看",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/index",
"style" :
{
"navigationBarTitleText": "考试列表",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/preview",
"style" :
{
"navigationBarTitleText": "考试须知",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/main",
"style" :
{
"navigationBarTitleText": "答题中",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/record",
"style" :
{
"navigationBarTitleText": "答题记录",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/result",
"style" :
{
"navigationBarTitleText": "答题结束",
"enablePullDownRefresh": false
}
@ -147,12 +192,18 @@
"selectedIconPath": "static/common/homec.png",
"text": "主页"
},
/* {
{
"pagePath": "pages/exam/index",
"iconPath": "static/common/play.png",
"selectedIconPath": "static/common/playc.png",
"text": "答题"
},
{
"pagePath": "pages/vod/video",
"iconPath": "static/common/play.png",
"selectedIconPath": "static/common/playc.png",
"text": "点播"
}, */
},
{
"pagePath": "pages/my/my",
"iconPath": "static/common/me.png",

View File

@ -0,0 +1,22 @@
<template>
<view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,60 @@
<template>
<view>
<uni-list>
<!-- 垂直排列无略缩图主标题+副标题显示 -->
<uni-list-item direction="column" :key="item.id" v-for="(item, index) in examList">
<template v-slot:header>
<view class="uni-title">{{item.name}}</view>
</template>
<template v-slot:body>
<view class="uni-list-box">
<view class="uni-content">
<view class="uni-title-sub uni-ellipsis-2">开启时间: {{item.open_time}}{{item.close_time}}</view>
<view class="uni-note">考试机会: {{item.chance}}</view>
</view>
</view>
</template>
<template v-slot:footer>
<view class="uni-footer">
<u-button size="mini" type="primary" @click="attendExam(item)">我要参加</u-button>
<u-button size="mini" type="info">成绩排名</u-button>
</view>
</template>
</uni-list-item>
</uni-list>
</view>
</template>
<script>
export default {
data() {
return {
query:{
page: 1
},
examList: []
}
},
onLoad() {
this.getExamList();
},
methods: {
getExamList(){
this.$u.api.getExamList(this.query).then(res=>{
this.examList = res.data.results
})
},
attendExam(val){
console.log(val)
uni.setStorageSync('currentExam', val)
uni.navigateTo({
url:"/pages/exam/preview"
})
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,22 @@
<template>
<view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,76 @@
<template>
<view class='wrap'>
<view class="subTitle">
考试信息
</view>
<view class="examContent">
<view>考试名称<text>{{currentExam.name}}</text> </view>
<view>考试总分<text>{{currentExam.paper_.total_score}}</text></view>
<view v-if="currentExam.limit>0">考试时长<text>{{currentExam.limit}}</text>分钟</view>
<view v-else>考试时长<text>不限时长</text></view>
<view>题目分布单选<text>{{currentExam.paper_.danxuan_count}}</text>;多选<text>{{currentExam.paper_.duoxuan_count}}</text>;判断<text>{{currentExam.paper_.panduan_count}}</text></view>
<view>判分规则单选{{currentExam.paper_.danxuan_score}},多选{{currentExam.paper_.duoxuan_score}},多选{{currentExam.paper_.panduan_score}},错选少选均不得分</view>
</view>
<view class="subTitle">答题须知</view>
<view class="tipsArea">
<ul>
<li>1.进入答题后请不要后退或返回</li>
<li>2.可点击上一题/下一题切换</li>
<li>3.可点击答题卡复查</li>
<li>4.请合理安排时间答题,可提前交卷</li>
</ul>
</view>
<u-button type="primary" @click="start()">开始答题</u-button>
</view>
</template>
<script>
export default {
data() {
return {
currentExam:{}
}
},
onLoad() {
this.currentExam = uni.getStorageSync('currentExam')
},
methods: {
start(){
uni.reLaunch({
url:'/pages/exam/main'
})
}
}
}
</script>
<style lang="scss" scoped>
.wrap{
padding: 24rpx;
font-size: 32rpx;
}
.subTitle {
font-size: $u-font-size-title;
font-weight: bold;
color: $theme-color;
text-align: center;
margin: 40rpx auto 20rpx auto;
}
.examContent {
line-height: 50rpx;
margin-left: 80rpx;
text {
font-weight: bold;
color:$u-type-warning-dark;
}
}
.tipsArea {
padding: 0 40rpx;
margin-left: 0rpx;
margin-bottom: 30rpx;
}
ul {
margin: 0 30rpx;
line-height: 50rpx;
}
</style>

View File

@ -0,0 +1,133 @@
<template>
<view>
<uni-list>
<uni-list-item v-for="item in list" :key="item.id" @click="goDetail(item.id)" :clickable="true" link>
<!-- 自定义 header -->
<!-- <view slot="header" class="slot-box">
<view>{{item.name}}</view>
</view> -->
<!-- 自定义 body -->
<template slot="body" style="display: block;">
<view>
<text v-if="item.type=='自助模考'">模拟练习</text>
<text v-else style="font-weight: bold;color:orange">正式竞赛</text>
<text v-if="item.name.indexOf('(补)') != -1">()</text>
</view>
<view style="color:gray;font-size: 26rpx;">
<span>耗时:
<span style="color:darkblue;font-weight: bold;">{{item.took}}</span>
s</span>
-
<text>提交时间:{{item.create_time}}</text>
</view>
<view style="color:gray;font-size: 26rpx;">
<span>总分:{{item.total_score}}</span>
-
<span>得分:
<span style="color:green;font-weight: bold;">{{item.score}}</span>
</span>
</view>
</template>
<!-- 自定义 footer-->
<!-- <template slot="footer">
<image class="slot-image" src="/static/logo.png" mode="widthFix"></image>
</template> -->
</uni-list-item>
</uni-list>
<!-- <uni-card :title="item.name" :isFull="true" isShadow='true' extra="查看详情" @click="clickCard" v-for="item in list" :key="item.id">
<view>
<text>{{item.type}}</text>
</view>
<view>
<text>耗时:{{item.took}}</text>
<text>开始答题:{{item.start_time}}</text>
</view>
</uni-card> -->
<view style="color:gray;text-align: center;margin-top:20upx">{{loadingText}}</view>
</view>
</template>
<script>
export default {
data() {
return {
listQuery: {
page: 1,
page_size: 20
},
list: [],
loadingText: '加载中...'
}
},
methods: {
getList() {
var that = this
that.$u.api.getMyExamRecord(that.listQuery).then(res => {
uni.stopPullDownRefresh()
uni.setNavigationBarTitle({
title: res.data.count + '条答题记录'
})
if (that.listQuery.page == 1) {
if (res.data.results.length == 0) {
that.loadingText = '暂无答题记录'
} else {
that.loadingText = ''
that.list = res.data.results
}
} else {
that.loadingText = ''
that.list = that.list.concat(res.data.results)
}
}).catch(res => {
uni.stopPullDownRefresh()
if (res.code == 404) {
that.loadingText = '到底了'
}
})
},
goDetail(id) {
uni.showLoading({
title:"正在获取答题详情",
})
this.$u.api.getExamRecordDetail(id).then(res=>{
uni.hideLoading()
uni.setStorageSync('currentExam', res.data)
if (res.data.questions.length>0){
uni.redirectTo({
url:'/pages/exam/detail?examrecord='+id
})
}
else{
uni.showToast({
title:'获取失败',
icon:'none'
})
return
}
}).catch(e=>{
})
}
},
onLoad() {
this.listQuery.page = 1
this.getList()
},
onPullDownRefresh() {
this.listQuery.page = 1
this.getList()
},
onReachBottom() {
this.listQuery.page = this.listQuery.page + 1
this.getList()
}
}
</script>
<style lang='scss'>
page {
background-color: $u-bg-color;
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<view>
<image class="examImage" :src="imageSrc" mode="aspectFit"></image>
<view class="finishText">
恭喜您完成考试
</view>
<view class="finishText">
<view><text>{{currentExam.name}}</text></view>
<!-- 练习类型{{paperRecord.paperName}} -->
<view>总分<text>{{currentExam.total_score}}</text></view>
<!-- <view>答对<text>{{paperRecord.rightQueNum}}</text></view> -->
<view>得分<text>{{currentExam.score}}</text> </view>
</view>
<view class="btnArea">
<u-button class="btnClass" type="primary" :ripple="true" shape="circle" @click="goDetail">查看答卷</u-button>
<u-button class="btnClass" :ripple="true" shape="circle" @click="backToHome">返回首页</u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
imageSrc: '/static/exam/result.png',
currentExam:{}
}
},
methods: {
goDetail(){
uni.showLoading({
title:"正在获取答题详情",
})
this.$u.api.getExamRecordDetail(this.currentExam.id).then(res=>{
uni.hideLoading()
uni.setStorageSync('currentExam', res.data)
uni.redirectTo({
url:'/pages/exam/detail?examrecord='+this.currentExam.id
})
}).catch(e=>{
})
},
backToHome(){
uni.reLaunch({
url:'/pages/index/index'
})
}
},
onLoad(options){
this.currentExam = uni.getStorageSync('currentExam')
},
}
</script>
<style lang="scss" scoped>
.examImage {
width: 100%;
display: flex;
justify-content: center;
// background-color: #000000;
margin-top: 100rpx;
height: 300rpx;
}
.finishText {
// margin: 100rpx 200rpx;
padding: 50rpx;
letter-spacing: 0.2em;
font-size: 32rpx;
display: flex;
flex-direction: column;
align-items: center;
text {
color: $theme-color;
font-weight: bold;
}
}
.btnArea {
display: flex;
margin-top: 50rpx;
justify-content: center;
align-items: center;
.btnClass {
width: 300rpx;
}
}
.infoArea {
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@ -31,7 +31,7 @@
</video>
<view style="color:darkblue;margin-left:4upx">
<text>{{video.name}}</text>
<text style="float:right;color:gray;margin-right: 8upx;">{{video.viewsp}}人观看</text>
<text style="float:right;color:gray;margin-right: 8upx;">{{video.views}}次播放</text>
</view>
</view>
</view>

View File

@ -2,6 +2,7 @@
// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中造成微信程序包太大
// 故uni.scss只建议放scss变量名相关样式其他的样式可以通过main.js或者App.vue引入
$theme-color:#0080d1;
$u-main-color: #303133;
$u-content-color: #606266;
$u-tips-color: #909399;
@ -36,3 +37,9 @@ $u-type-info-light: #f4f4f5;
$u-form-item-height: 70rpx;
$u-form-item-border-color: #dcdfe6;
/* 文字尺寸 */
$u-font-size-sm:24rpx;
$u-font-size-lg:32rpx;
$u-font-size-base:28rpx;
$u-font-size-title:36rpx;

View File

@ -19,6 +19,7 @@ from apps.supervision.models import Content, Record
from apps.supervision.serializers import ContentSerializer, RecordCreateSerializer, RecordSerializer
from apps.system.mixins import CreateUpdateCustomMixin
from utils.queryset import get_child_queryset2
from apps.system.permission_data import RbacFilterSet
from django.utils import timezone
from apps.supervision.permission import RecordPermission
from django.utils.decorators import method_decorator
@ -60,7 +61,7 @@ class AbilityContentViewSet(CreateUpdateCustomMixin, ModelViewSet):
def perform_update(self, serializer):
serializer.save(update_by = self.request.user)
class AbilityRecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
class AbilityRecordViewSet(RbacFilterSet, PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
perms_map = {'get': '*', 'post': '*',
'put': '*', 'delete': '*'}
queryset = Record.objects.filter(content__cate=2)
@ -70,25 +71,6 @@ class AbilityRecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
ordering = ['-task', 'content__sortnum', '-create_time']
filterset_fields = ['content','content__cate', 'belong_dept', 'state']
def get_queryset(self):
queryset = self.queryset
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset)
if self.request.user.is_superuser:
pass
if hasattr(queryset.model, 'belong_dept'):
user = self.request.user
roles = user.roles
data_range = roles.values_list('datas', flat=True)
if '全部' in data_range:
pass
elif '本级及以下' in data_range:
belong_depts = get_child_queryset2(user.dept)
queryset = queryset.filter(belong_dept__in = belong_depts)
elif '本级' in data_range:
queryset = queryset.filter(belong_dept = user.dept)
return queryset
def filter_queryset(self, queryset):
if not self.request.query_params.get('pageoff', None):
queryset = queryset.exclude(state='待发布')

View File

@ -13,6 +13,7 @@ from rest_framework.exceptions import ParseError
from rest_framework import serializers
from rest_framework.exceptions import ParseError
from utils.queryset import get_child_queryset2
from apps.system.permission_data import RbacFilterSet
class QualiLibViewSet(PageOrNot, ListModelMixin, GenericViewSet):
@ -112,7 +113,7 @@ def cal_count(qtask, org):
qorg.save()
class QActionViewSet(PageOrNot, ListModelMixin, DestroyModelMixin, RetrieveModelMixin, GenericViewSet):
class QActionViewSet(RbacFilterSet, PageOrNot, ListModelMixin, DestroyModelMixin, RetrieveModelMixin, GenericViewSet):
perms_map = {'get': '*', 'delete': 'qaction_delete'}
queryset = QAction.objects.select_related(
'file', 'atype', 'afield', 'qtask', 'belong_dept', 'create_by')
@ -126,24 +127,7 @@ class QActionViewSet(PageOrNot, ListModelMixin, DestroyModelMixin, RetrieveModel
return QActionDetailSerializer
return super().get_serializer_class()
def get_queryset(self):
queryset = self.queryset
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset)
if self.request.user.is_superuser:
pass
if hasattr(queryset.model, 'belong_dept'):
user = self.request.user
roles = user.roles
data_range = roles.values_list('datas', flat=True)
if '全部' in data_range:
pass
elif '本级及以下' in data_range:
belong_depts = get_child_queryset2(user.dept)
queryset = queryset.filter(belong_dept__in=belong_depts)
elif '本级' in data_range:
queryset = queryset.filter(belong_dept=user.dept)
return queryset
# @action(methods=['get'], detail=False, perms_map={'get': '*'})
# def my(self, request, *args, **kwargs):

View File

@ -0,0 +1,20 @@
from django_filters import rest_framework as filters
from .models import ExamRecord, Exam
class ExamRecordFilter(filters.FilterSet):
class Meta:
model = ExamRecord
fields = {
'start_time': ['exact', 'gte', 'lte'],
'is_pass': ['exact'],
'type': ['exact'],
'is_submited': ['exact'],
}
class ExamFilter(filters.FilterSet):
class Meta:
model = Exam
fields = {
'close_time': ['exact', 'gte', 'lte'],
'paper': ['exact'],
'code': ['exact']
}

View File

@ -0,0 +1,153 @@
# Generated by Django 3.0.5 on 2022-11-07 05:56
from django.conf import settings
import django.contrib.postgres.fields.jsonb
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),
('exam', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AnswerDetail',
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_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('user_answer', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)),
('score', models.FloatField(default=0, verbose_name='本题得分')),
('is_right', models.BooleanField(default=False, verbose_name='是否正确')),
],
options={
'verbose_name': '答题记录',
'verbose_name_plural': '答题记录',
},
),
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_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('code', models.CharField(blank=True, max_length=100, null=True, unique=True, verbose_name='考试编号')),
('name', models.CharField(max_length=100, verbose_name='名称')),
('place', models.CharField(blank=True, max_length=100, null=True, verbose_name='考试地点')),
('open_time', models.DateTimeField(blank=True, null=True, verbose_name='开启时间')),
('close_time', models.DateTimeField(blank=True, null=True, verbose_name='关闭时间')),
('proctor_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='监考人姓名')),
('proctor_phone', models.CharField(blank=True, max_length=100, null=True, verbose_name='监考人联系方式')),
('chance', models.IntegerField(default=3, verbose_name='考试机会')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exam_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Paper',
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_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=200, verbose_name='名称')),
('limit', models.IntegerField(default=0, verbose_name='限时(分钟)')),
('total_score', models.FloatField(default=0, verbose_name='满分')),
('pass_score', models.FloatField(default=0, verbose_name='通过分数')),
('danxuan_count', models.IntegerField(default=0, verbose_name='单选数量')),
('danxuan_score', models.FloatField(default=2, verbose_name='单选分数')),
('duoxuan_count', models.IntegerField(default=0, verbose_name='多选数量')),
('duoxuan_score', models.FloatField(default=4, verbose_name='多选分数')),
('panduan_count', models.IntegerField(default=0, verbose_name='判断数量')),
('panduan_score', models.FloatField(default=2, verbose_name='判断分数')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paper_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
],
options={
'verbose_name': '押题卷',
'verbose_name_plural': '押题卷',
},
),
migrations.CreateModel(
name='PaperQuestion',
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_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('total_score', models.FloatField(default=0, verbose_name='单题满分')),
('sort', models.PositiveSmallIntegerField(default=1)),
('paper', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.Paper', verbose_name='试卷')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.Question', verbose_name='试题')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='paper',
name='questions',
field=models.ManyToManyField(through='exam.PaperQuestion', to='exam.Question'),
),
migrations.AddField(
model_name='paper',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paper_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.CreateModel(
name='ExamRecord',
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_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=200, verbose_name='名称')),
('type', models.CharField(choices=[('自助模考', '自助模考'), ('押卷模考', '押卷模考'), ('正式考试', '正式考试')], default='自助模考', max_length=50, verbose_name='考试类型')),
('limit', models.IntegerField(default=0, verbose_name='限时(分钟)')),
('total_score', models.FloatField(default=0, verbose_name='总分')),
('score', models.FloatField(default=0, verbose_name='得分')),
('took', models.IntegerField(default=0, verbose_name='耗时(秒)')),
('start_time', models.DateTimeField(verbose_name='开始答题时间')),
('end_time', models.DateTimeField(verbose_name='结束答题时间')),
('is_pass', models.BooleanField(default=True, verbose_name='是否通过')),
('questions', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=list, verbose_name='下发的题目列表')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='examrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('detail', models.ManyToManyField(through='exam.AnswerDetail', to='exam.Question', verbose_name='答题记录')),
('exam', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='exam.Exam', verbose_name='关联的正式考试')),
('paper', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='exam.Paper', verbose_name='所用试卷')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='examrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '考试记录',
'verbose_name_plural': '考试记录',
},
),
migrations.AddField(
model_name='exam',
name='paper',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='exam.Paper', verbose_name='使用的试卷'),
),
migrations.AddField(
model_name='exam',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exam_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AddField(
model_name='answerdetail',
name='examrecord',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.ExamRecord'),
),
migrations.AddField(
model_name='answerdetail',
name='question',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exam.Question'),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 3.0.5 on 2022-11-08 01:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('exam', '0002_auto_20221107_1356'),
]
operations = [
migrations.RemoveField(
model_name='examrecord',
name='questions',
),
migrations.RemoveField(
model_name='paperquestion',
name='sort',
),
migrations.AddField(
model_name='answerdetail',
name='total_score',
field=models.FloatField(default=0, verbose_name='该题满分'),
),
migrations.AddField(
model_name='examrecord',
name='is_submited',
field=models.BooleanField(default=False),
),
]

View File

@ -1,6 +1,7 @@
from django.db import models
from apps.system.models import CommonAModel
from django.contrib.postgres.fields import JSONField
from utils.model import BaseModel
# Create your models here.
class Questioncat(CommonAModel):
@ -45,3 +46,89 @@ class Question(CommonAModel):
def __str__(self):
return self.name
class Paper(CommonAModel):
name = models.CharField(max_length=200, verbose_name='名称')
questions = models.ManyToManyField(Question, through='PaperQuestion')
limit = models.IntegerField(default=0, verbose_name='限时(分钟)')
total_score = models.FloatField(default=0, verbose_name='满分')
pass_score = models.FloatField(default=0, verbose_name='通过分数')
danxuan_count = models.IntegerField(default=0, verbose_name='单选数量')
danxuan_score = models.FloatField(default=2, verbose_name='单选分数')
duoxuan_count = models.IntegerField(default=0, verbose_name='多选数量')
duoxuan_score = models.FloatField(default=4, verbose_name='多选分数')
panduan_count = models.IntegerField(default=0, verbose_name='判断数量')
panduan_score = models.FloatField(default=2, verbose_name='判断分数')
class Meta:
verbose_name = '押题卷'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class PaperQuestion(BaseModel):
paper = models.ForeignKey(Paper, on_delete=models.CASCADE, verbose_name='试卷')
question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name='试题')
total_score = models.FloatField(default=0, verbose_name='单题满分')
class Exam(CommonAModel):
"""
组织的正式考试
"""
code = models.CharField('考试编号', max_length=100, null=True, blank=True, unique=True)
name = models.CharField('名称', max_length=100)
place = models.CharField('考试地点', max_length=100, null=True, blank=True)
open_time = models.DateTimeField('开启时间', null=True, blank=True)
close_time = models.DateTimeField('关闭时间', null=True, blank=True)
proctor_name = models.CharField('监考人姓名', max_length=100, null=True, blank=True)
proctor_phone = models.CharField('监考人联系方式', max_length=100, null=True, blank=True)
chance = models.IntegerField('考试机会', default=3)
paper = models.ForeignKey(Paper, verbose_name='使用的试卷', on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return self.name
class ExamRecord(CommonAModel):
'''
考试记录表
'''
type_choices = (
('自助模考', '自助模考'),
('押卷模考', '押卷模考'),
('正式考试', '正式考试')
)
name = models.CharField(max_length=200, verbose_name='名称')
type = models.CharField(max_length=50, default='自助模考',choices = type_choices, verbose_name='考试类型')
limit = models.IntegerField(default=0, verbose_name='限时(分钟)')
paper = models.ForeignKey(Paper, on_delete=models.SET_NULL, verbose_name='所用试卷', null=True, blank=True)
total_score = models.FloatField(default=0, verbose_name='总分')
score = models.FloatField(default=0, verbose_name='得分')
took = models.IntegerField(default=0, verbose_name='耗时(秒)')
start_time = models.DateTimeField(verbose_name='开始答题时间')
end_time = models.DateTimeField(verbose_name='结束答题时间')
detail = models.ManyToManyField(Question, verbose_name='答题记录', through='AnswerDetail')
is_pass = models.BooleanField(default=True, verbose_name='是否通过')
exam = models.ForeignKey(Exam, verbose_name='关联的正式考试', null=True, blank=True, on_delete= models.SET_NULL)
is_submited = models.BooleanField(default=False)
class Meta:
verbose_name = '考试记录'
verbose_name_plural = verbose_name
class AnswerDetail(BaseModel):
examrecord = models.ForeignKey(ExamRecord, on_delete=models.CASCADE)
total_score = models.FloatField(default=0, verbose_name='该题满分')
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user_answer = JSONField(null=True,blank=True)
score = models.FloatField(default=0, verbose_name='本题得分')
is_right = models.BooleanField(default=False, verbose_name='是否正确')
class Meta:
verbose_name = '答题记录'
verbose_name_plural = verbose_name

View File

@ -1,6 +1,7 @@
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import ModelSerializer, CharField, Serializer, SerializerMethodField, FloatField
from rest_framework import serializers
from apps.exam.models import Question, Questioncat
from apps.exam.models import Question, Questioncat, Paper, Exam, PaperQuestion, ExamRecord, AnswerDetail
class QuestioncatSerializer(ModelSerializer):
@ -10,6 +11,174 @@ class QuestioncatSerializer(ModelSerializer):
class QuestionSerializer(ModelSerializer):
questioncat_name = serializers.CharField(source='questioncat.name', read_only=True)
class Meta:
model = Question
fields = '__all__'
class PaperSerializer(ModelSerializer):
class Meta:
model = Paper
exclude = ('questions',)
class PaperQuestionSerializer(ModelSerializer):
class Meta:
model = PaperQuestion
fields = ['question', 'total_score']
class PaperCreateUpdateSerializer(ModelSerializer):
questions_ = PaperQuestionSerializer(many=True)
class Meta:
model = Paper
fields = '__all__'
class PaperQuestionDetailSerializer(ModelSerializer):
name = serializers.ReadOnlyField(source='question.name')
options = serializers.ReadOnlyField(source='question.options')
right = serializers.ReadOnlyField(source='question.right')
type = serializers.ReadOnlyField(source='question.type')
img = serializers.ReadOnlyField(source='question.img')
questioncat_name = serializers.ReadOnlyField(
source='question.questioncat.name')
level = serializers.ReadOnlyField(source='question.level')
class Meta:
model = PaperQuestion
fields = ('id', 'name', 'options', 'right', 'type', 'level',
'total_score', 'questioncat_name', 'img', 'question')
class PaperQuestionShortSerializer(ModelSerializer):
class Meta:
model = PaperQuestion
fields = '__all__'
class PaperDetailSerializer(ModelSerializer):
questions_ = SerializerMethodField()
class Meta:
model = Paper
fields = '__all__'
def get_questions_(self, instance):
pqs = PaperQuestion.objects.filter(paper=instance)
return PaperQuestionDetailSerializer(pqs, many=True).data
class ExamCreateUpdateSerializer(ModelSerializer):
class Meta:
model = Exam
fields = ['name', 'place', 'open_time',
'close_time', 'proctor_name', 'proctor_phone', 'chance', 'paper']
class ExamListSerializer(ModelSerializer):
create_by_name = CharField(source='create_by.name', read_only=True)
paper_ = PaperSerializer(source='paper', read_only=True)
class Meta:
model = Exam
fields = '__all__'
class ExamDetailSerializer(ModelSerializer):
create_by_name = CharField(source='create_by.name', read_only=True)
paper_ = PaperSerializer(source='paper', read_only=True)
class Meta:
model = Exam
fields = '__all__'
class ExamAttendSerializer(Serializer):
code = CharField(label="考试编号")
class ExamRecordListSerializer(serializers.ModelSerializer):
"""
考试列表序列化
"""
took_format = serializers.SerializerMethodField()
create_by_name = serializers.CharField(
source='create_by.name', read_only=True)
class Meta:
model = ExamRecord
exclude = ('detail',)
def get_took_format(self, obj):
m, s = divmod(obj.took, 60)
h, m = divmod(m, 60)
return "%02d:%02d:%02d" % (h, m, s)
class ExamRecordDetailSerializer(serializers.ModelSerializer):
"""
考试详情序列化
"""
took_format = serializers.SerializerMethodField()
questions_ = serializers.SerializerMethodField()
class Meta:
model = ExamRecord
exclude = ('detail',)
def get_took_format(self, obj):
m, s = divmod(obj.took, 60)
h, m = divmod(m, 60)
return "%02d:%02d:%02d" % (h, m, s)
def get_questions_(self, obj):
objs = AnswerDetail.objects.select_related('question').filter(
examrecord=obj).order_by('question__type')
return AnswerDetailSerializer(instance=objs, many=True).data
class AnswerDetailUpdateSerializer(serializers.Serializer):
id = serializers.CharField(label='下发ID')
user_answer = serializers.JSONField(label='作答')
class ExamRecordSubmitSerializer(serializers.ModelSerializer):
questions_ = AnswerDetailUpdateSerializer(many=True)
class Meta:
model = ExamRecord
fields = ['questions_']
class AnswerDetailSerializer(ModelSerializer):
name = serializers.ReadOnlyField(source='question.name')
options = serializers.ReadOnlyField(source='question.options')
right = serializers.ReadOnlyField(source='question.right')
type = serializers.ReadOnlyField(source='question.type')
img = serializers.ReadOnlyField(source='question.img')
questioncat_name = serializers.ReadOnlyField(
source='question.questioncat.name')
level = serializers.ReadOnlyField(source='question.level')
class Meta:
model = PaperQuestion
fields = ['id', 'question', 'name', 'options', 'right', 'type', 'level',
'total_score', 'questioncat_name', 'img', 'user_answer', 'score', 'is_right']
class AnswerDetailOutSerializer(ModelSerializer):
name = serializers.ReadOnlyField(source='question.name')
options = serializers.ReadOnlyField(source='question.options')
type = serializers.ReadOnlyField(source='question.type')
img = serializers.ReadOnlyField(source='question.img')
questioncat_name = serializers.ReadOnlyField(
source='question.questioncat.name')
level = serializers.ReadOnlyField(source='question.level')
class Meta:
model = PaperQuestion
fields = ['id', 'question', 'name', 'options', 'type', 'level',
'total_score', 'questioncat_name', 'img', 'user_answer', 'score', 'is_right']

View File

@ -1,13 +1,16 @@
from django.urls import path, include
from rest_framework import routers
from apps.exam.views import QuestionViewSet, QuestioncatViewSet
from apps.exam.views import QuestionViewSet, QuestioncatViewSet, PaperViewSet, ExamViewSet, ExamRecordViewSet
API_BASE_URL = 'api/exam/'
HTML_BASE_URL = 'exam/'
router = routers.DefaultRouter()
router.register('questioncat', QuestioncatViewSet, basename='questioncat')
router.register('question', QuestionViewSet, basename="question")
router.register('paper', PaperViewSet, basename="paper")
router.register('exam', ExamViewSet, basename="exam")
router.register('examrecord', ExamRecordViewSet, basename="examrecord")
urlpatterns = [
path(API_BASE_URL, include(router.urls))
]

View File

@ -1,50 +1,95 @@
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import ListModelMixin, DestroyModelMixin
from apps.exam.exports import export_question
from apps.exam.models import Question, Questioncat
from apps.exam.serializers import QuestionSerializer, QuestioncatSerializer
from apps.exam.models import Question, Questioncat, PaperQuestion
from apps.exam.serializers import (QuestionSerializer, QuestioncatSerializer, PaperSerializer, ExamDetailSerializer, ExamRecordDetailSerializer, ExamListSerializer,
ExamCreateUpdateSerializer, ExamListSerializer, ExamRecordSubmitSerializer, PaperDetailSerializer, PaperCreateUpdateSerializer, AnswerDetailOutSerializer, ExamRecordListSerializer)
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import ParseError
from openpyxl import Workbook, load_workbook
from django.conf import settings
from apps.exam.models import Paper, Exam, ExamRecord, AnswerDetail
from django.utils import timezone
from django.db import transaction
from rest_framework.serializers import Serializer
from datetime import datetime
from apps.exam.filters import ExamRecordFilter, ExamFilter
from datetime import timedelta
from apps.system.mixins import CreateUpdateCustomMixin
# Create your views here.
class QuestioncatViewSet(ModelViewSet):
perms_map = {'*': '*'}
def enctry(s):
k = 'ez9z3a4m*$%srn9ve_t71yd!v+&xn9@0k(e(+l6#g1h=e5i4da'
encry_str = ""
for i, j in zip(s, k):
# i为字符j为秘钥字符
temp = str(ord(i)+ord(j))+'_' # 加密字符 = 字符的Unicode码 + 秘钥的Unicode码
encry_str = encry_str + temp
return encry_str
# 解密
def dectry(p):
k = 'ez9z3a4m*$%srn9ve_t71yd!v+&xn9@0k(e(+l6#g1h=e5i4da'
dec_str = ""
for i, j in zip(p.split("_")[:-1], k):
# i 为加密字符j为秘钥字符
# 解密字符 = (加密Unicode码字符 - 秘钥字符的Unicode码)的单字节字符
temp = chr(int(i) - ord(j))
dec_str = dec_str+temp
return dec_str
class QuestioncatViewSet(CreateUpdateCustomMixin, ModelViewSet):
perms_map = {'get': '*', 'post':'question', 'put':'question', 'delete':'question'}
queryset = Questioncat.objects.all()
serializer_class = QuestioncatSerializer
filterset_fields = ['parent']
search_fields = ['name']
class QuestionViewSet(ModelViewSet):
perms_map = {'*': '*'}
class QuestionViewSet(CreateUpdateCustomMixin, ModelViewSet):
perms_map = {'get': '*', 'post':'question', 'put':'question', 'delete':'question'}
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': '*'}])
url_path='export', url_name='export_question', perms_map=[{'get': '*'}], serializer_class=Serializer)
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])
@action(methods=['post'], detail=False, url_name='enable_question', perms_map={'post': 'question'}, serializer_class=Serializer)
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': '*'}])
url_path='import', url_name='import_question', perms_map={'post': 'question'}, serializer_class=Serializer)
def import_question(self, request):
"""
导入题目
导入题目
"""
xlsxpath = request.data['path']
@ -167,3 +212,207 @@ class QuestionViewSet(ModelViewSet):
obj.save()
i = i + 1
return Response(notinlist, status=200)
class PaperViewSet(ModelViewSet):
"""
试卷增删改查
"""
perms_map = {'get': '*', 'post':'paper', 'put':'paper', 'delete':'paper'}
queryset = Paper.objects.all()
serializer_class = PaperSerializer
ordering = ['id']
search_fields = ('name',)
def get_serializer_class(self):
if self.action in ['retrieve']:
return PaperDetailSerializer
elif self.action in ['create', 'update']:
return PaperCreateUpdateSerializer
return super().get_serializer_class()
def create(self, request, *args, **kwargs):
sr = PaperCreateUpdateSerializer(data=request.data)
sr.is_valid(raise_exception=True)
vdata = sr.validated_data
vdata['create_by'] = request.user
questions_ = vdata.pop('questions_')
paper = Paper.objects.create(**vdata)
q_list = []
for i in questions_:
q_list.append(PaperQuestion(question=i['question'], total_score=i['total_score'], paper=paper))
PaperQuestion.objects.bulk_create(q_list)
return Response(status=201)
def update(self, request, *args, **kwargs):
# 有考试在执行,不可更新
now = timezone.now()
paper = self.get_object()
if Exam.objects.filter(close_time__gte=now, paper=paper).exists():
raise ParseError('存在考试,不可编辑')
sr = PaperCreateUpdateSerializer(instance=paper, data=request.data)
sr.is_valid(raise_exception=True)
vdata = sr.validated_data
questions_ = vdata.pop('questions_')
vdata['update_by'] = request.user
Paper.objects.filter(id=paper.id).update(**vdata)
q_list = []
for i in questions_:
q_list.append(PaperQuestion(question=i['question'], total_score=i['total_score'], paper=paper))
PaperQuestion.objects.filter(paper=paper).delete()
PaperQuestion.objects.bulk_create(q_list)
return Response()
class ExamViewSet(CreateUpdateCustomMixin, ModelViewSet):
perms_map = {'get': '*', 'post':'exam', 'put':'exam', 'delete':'exam'}
queryset = Exam.objects.all().select_related('paper', 'create_by')
ordering = ['-id']
search_fields = ('name',)
serializer_class = ExamListSerializer
filterset_class = ExamFilter
def get_serializer_class(self):
if self.action in ['create', 'update']:
return ExamCreateUpdateSerializer
elif self.action in ['retrieve']:
return ExamDetailSerializer
return super().get_serializer_class()
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if ExamRecord.objects.filter(exam=instance).exists():
raise ParseError('存在考试记录,禁止删除')
instance.delete(soft=False)
return Response(status=204)
@action(methods=['post'], detail=True, perms_map=[{'post': '*'}], serializer_class=Serializer, permission_classes = [IsAuthenticated])
def start(self, request, *args, **kwargs):
"""
开始考试
开始考试具体题目信息
"""
exam = self.get_object()
now = timezone.now()
if now < exam.open_time or now > exam.close_time:
raise ParseError('不在考试时间范围')
tests = ExamRecord.objects.filter(
exam=exam, create_by=request.user)
chance_used = tests.count()
if chance_used > exam.chance:
raise ParseError('考试机会已用完')
if exam.paper:
er = ExamRecord()
er.type = '正式考试'
er.name = '正式考试' + now.strftime('%Y%m%d%H%M')
er.limit = exam.paper.limit
er.paper = exam.paper
er.total_score = exam.paper.total_score
er.start_time = now
er.is_pass = False
er.exam = exam
er.save()
ret = {}
ret['examrecord'] = er.id
pqs = PaperQuestion.objects.filter(paper=exam.paper).order_by('id')
details = []
for i in pqs:
details.append(AnswerDetail(examrecord=er, question=i.question, total_score=i.total_score))
AnswerDetail.objects.bulk_create(details)
ads = AnswerDetail.objects.select_related('question').filter(examrecord=er).order_by('id')
ret['questions_'] = AnswerDetailOutSerializer(instance=ads, many=True).data
return Response(ret)
raise ParseError('暂不支持')
class ExamRecordViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
"""
考试记录列表和详情
"""
perms_map = {'get': 'examrecord', 'post': '*', 'delete':'examrecord'}
queryset = ExamRecord.objects.select_related('create_by')
serializer_class = ExamRecordListSerializer
ordering_fields = ['create_time', 'score', 'took', 'update_time']
ordering = ['-update_time']
search_fields = ('create_by__name', 'create_by__username')
filterset_class = ExamRecordFilter
def get_serializer_class(self):
if self.action == 'retrieve':
return ExamRecordDetailSerializer
return super().get_serializer_class()
def perform_destroy(self, instance): # 考试记录物理删除
instance.delete(soft=False)
@action(methods=['post'], detail=False, perms_map=[{'post': '*'}], serializer_class=Serializer, permission_classes = [IsAuthenticated])
def clear(self, request, pk=None):
"""
清除七日前未提交的考试记录
清除七日前未提交的考试记录
"""
now = timezone.now
days7_ago = now - timedelta(days=7)
ExamRecord.objects.filter(create_time__lte=days7_ago, is_submited=False).delete(soft=False)
return Response(status=False)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def self(self, request, pk=None):
'''
个人考试记录
个人考试记录
'''
queryset = ExamRecord.objects.filter(create_by=request.user).order_by('-update_time')
page = self.paginate_queryset(queryset)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
@action(methods=['post'], detail=True, perms_map=[{'post': '*'}], serializer_class=ExamRecordSubmitSerializer, permission_classes = [IsAuthenticated])
def submit(self, request, pk=None):
'''
提交答卷
提交答卷
'''
er = self.get_object()
now = timezone.now()
if er.create_by != request.user:
raise ParseError('提交人有误')
exam = er.exam
if not exam:
raise ParseError('暂不支持')
if now > exam.close_time + timedelta(minutes=30):
raise ParseError('考试时间已过, 提交失败')
serializer = ExamRecordSubmitSerializer(data = request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
questions_ = vdata['questions_']
# 后端判卷
ads = AnswerDetail.objects.select_related('question').filter(examrecord=er).order_by('id').values('id', 'question__right', 'total_score', 'question__type')
total_score = 0
try:
for index, ad in enumerate(ads):
ad.user_answer = questions_[index]['user_answer']
if ad.question__type == '多选':
if set(ad.question__right) == set(ad.user_answer):
ad.is_right = True
ad.score = ad.total_score
else:
if ad.question__right == ad.user_answer:
ad.is_right = True
ad.score = ad.total_score
ad.save()
total_score = total_score + ad.score
except Exception as e:
raise ParseError('判卷失败, 请检查试卷:' + str(e))
er.score = total_score
if er.score > 0.6*er.total_score:
er.is_pass = True
er.took = (now - er.create_time).total_seconds()
er.end_time = now
er.is_submited = True
er.save()
return Response()

View File

@ -98,7 +98,7 @@ class SubtaskViewSet(PageOrNot, CreateUpdateCustomMixin, OptimizationMixin, Mode
if has_permission('inspecttask_create', self.request.user):
return queryset
else:
return queryset.filter(team_subtask__member=self.request.user).exclude(state='待发布')
return queryset.filter(team_subtask__member__id=self.request.user.id).exclude(state='待发布')
@action(methods=['get'], detail=False, perms_map = {'get':'*'})
def self(self, request, *args, **kwargs):

View File

@ -209,7 +209,7 @@ from utils.queryset import get_child_queryset2
from .permission import RecordPermission
class RecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
class RecordViewSet(RbacFilterSet, PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
perms_map = {'get': '*', 'post': 'record_create',
'put': 'record_update', 'delete': 'record_delete'}
queryset = Record.objects.filter(content__cate=1)
@ -219,25 +219,6 @@ class RecordViewSet(PageOrNot, CreateUpdateCustomMixin, ModelViewSet):
ordering = ['-task', 'content__sortnum', '-create_time']
filter_class = RecordFilter # 过滤类
def get_queryset(self):
queryset = self.queryset
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset)
if self.request.user.is_superuser:
pass
if hasattr(queryset.model, 'belong_dept'):
user = self.request.user
roles = user.roles
data_range = roles.values_list('datas', flat=True)
if '全部' in data_range:
pass
elif '本级及以下' in data_range:
belong_depts = get_child_queryset2(user.dept)
queryset = queryset.filter(belong_dept__in = belong_depts)
elif '本级' in data_range:
queryset = queryset.filter(belong_dept = user.dept)
return queryset
def filter_queryset(self, queryset):
if not self.request.query_params.get('pageoff', None):
queryset = queryset.exclude(state='待发布')

View File

@ -12,7 +12,7 @@ def get_permission_list(user):
perms_list = ['admin']
else:
perms = Permission.objects.none()
roles = user.roles.all()
roles = user.roles.all() if hasattr(user, 'roles') else None
if roles:
for i in roles:
perms = perms | i.perms.all()

View File

@ -34,7 +34,9 @@ class RbacFilterSet(object):
if hasattr(queryset.model, 'belong_dept'):
user = self.request.user
roles = user.roles
roles = user.roles if hasattr(user, 'roles') else []
if not roles:
return queryset.none()
data_range = roles.values_list('datas', flat=True)
if '全部' in data_range:
return queryset
@ -57,6 +59,8 @@ class RbacFilterSet(object):
elif '仅本人' in data_range:
queryset = queryset.filter(Q(create_by=user)|Q(update_by=user))
return queryset
else:
return queryset.none()
return queryset

View File

@ -133,7 +133,7 @@ class MyViewRecordAPIView(APIView):
video.save()
if request.data.get('current', None):
record.current = request.data.get('current')
if timezone.now() > record.update_time + timedelta(hours=6):
if timezone.now() > record.update_time + timedelta(minutes=30):
record.views = record.views + 1
video.views = video.views + 1
video.save()