python3 flask 多人答题(完整项目带源码与使用)

TopQB答题系统

2020/01/05
@pingfan

功能:
    1、多人同时答题系统
    2、在线查看个人得分与答题情况(解析)
    3、载入题库,随机抽取题目支持【单选题,多选题,判断题】
    4、自定义题目数量与题目分值,题目都是随机但数量不变总分不变
    5、在线实时查看总体得分情况,可导出excel
    6、适应场景: 各类考试答题等
    7、提示: 
        1、不需要外网,任意内网(局域网)机器运行均可,也可单机运行使用
        2、无毒,请添加信任
        3、系统要求: 64位win7、win10(当然linux是最好的)
使用说明:
    1、题库模板位于【static】->【qb.xlsx】题目索引值不可重复
    2、运行app.exe
    3、查看本机ip地址
    4、访问:
        1、答题地址:
            http://【本机ip】:9999
            如:http://192.168.1.2:9999
        2、设置地址:
            http://【本机ip】:9999/config
        3、得分地址:
            http://【本机ip】:9999/results
        4、管理地址:
            http://【本机ip】:9999/admin
    5、答题前请设置题目数量,题目得分
    6、实时查看总体得分情况刷新成绩页面即可

==========================================================================================

管理页面

python3 flask 多人答题(完整项目带源码与使用)_第1张图片

设置页面

python3 flask 多人答题(完整项目带源码与使用)_第2张图片

成绩汇总页面

python3 flask 多人答题(完整项目带源码与使用)_第3张图片

答题页面

python3 flask 多人答题(完整项目带源码与使用)_第4张图片

解析页面

python3 flask 多人答题(完整项目带源码与使用)_第5张图片

==========================================================================================

小小flask项目,就几个路由系统,没使用蓝图。

完整目录结构

python3 flask 多人答题(完整项目带源码与使用)_第6张图片

不使用数据库,轻应用读取excel中的题目缓存到内存中

需要一个excel.xlsx(2007以上的题库模板)放置到目录的static文件夹中命名为【qb.xlsx】(读取的是第一个工作表)

==========================================================================================

只依赖flask,提升并发的(协程)gevent库,以及pfExcel(基于openpyxl的封装)用于读取excel中的数据(前面的博客有提到)

前端没有使用任何框架,js都是原生

以下是依赖库的安装

# 安装flask
pip install flask

# 安装gevent
pip install gevent

# 安装openpyxl
pip install openpyxl

部署就是一个(pyinstaller库编译app.py)exe执行文件

# 安装pyinstaller
pip install pyinstaller
# 编译app.py

pyinstaller -F -i 你的logo文件.ico app.py

以下是pyExcel.py源码
 

"""
基于openpyxl的excel读写模块
提供更简单的读写方式
"""
from openpyxl import load_workbook, Workbook


class ExcelWork:

    def __init__(self, filePath):
        """
        初始化, 加载excel,默认选择第一个工作表
        :param filePath: str: 文件地址
        """
        self.filePath = filePath
        try:
            # 加载excel
            self.excel = load_workbook(self.filePath)
        except FileNotFoundError:
            # 创建excel
            self.excel = Workbook(self.filePath)
            # 创建sheet
            self.createSheet('Sheet1')
            # 保存excel
            self.close()
            # 加载excel
            self.excel = load_workbook(self.filePath)
        # sheet设置为第一个工作表
        self.sheet = self.excel.active

    def close(self):
        """
        保存并退出
        :return:
        """
        self.excel.save(self.filePath)

    def createSheet(self, sheetName):
        """
        创建工作表
        :param sheetName: str: 工作表名
        :return:
        """
        self.excel.create_sheet(sheetName)

    def getSheetTitle(self):
        """
        获取当前工作表名称
        :return:  str: 工作表名称
        """
        return self.sheet.title

    def getSheetTitles(self):
        """
        获取excel所有工作表的名称
        :return:  list: [工作表名称,]
        """
        return self.excel.sheetnames

    def delSheet(self):
        """
        删除当前工作表
        :return:
        """
        self.excel.remove(self.sheet)

    def selectSheet(self, sheetName):
        """
        选择工作表, 如果没有将创建
        :param sheetName: str: 工作表名
        :return:
        """
        if sheetName in self.getSheetTitles():
            # 选择工作表
            self.sheet = self.excel[sheetName]
        else:
            # 创建工作表
            self.createSheet(sheetName)
            # 选择工作表
            self.sheet = self.excel[sheetName]

    def setCell(self, r, c, var):
        """
        修改指定行, 列的单元格内容
        :param r: int: 行数
        :param c: int: 列数
        :param var: str: 修改内容
        :return:
        """
        self.sheet.cell(row=r, column=c, value=var)

    def getCell(self, r, c):
        """
        获取指定行, 列的单元格内容
        :param r: int: 行数
        :param c: int: 列数
        :return: str: 单元格内容
        """
        return self.sheet.cell(row=r, column=c).value

    def getRow(self, r):
        """
        获取指定行所有数据
        :param r: int: 行数
        :return: list: [数据,]
        """
        rowList = []
        for cell in self.sheet[r]:
            rowList.append(cell.value)
        return rowList

    def getColumn(self, c):
        """
        获取指定列所有数据
        :param c: int: 列数
        :return: list: [数据,]
        """
        columnList = []
        for temp in range(1, self.sheet.max_row + 1):
            columnList.append(self.getCell(temp, c))
        return columnList

以下是qb.py源码

"""
用于操作题库数据
"""
import socket
from random import shuffle

from pfExcel import ExcelWork

# 问题设置
QUESTION_CONFIG = {
    'radioNum': 1,
    'checkboxNum': 1,
    'trueOrFalseNum': 1,
    'radioResult': 1,
    'checkboxResult': 1,
    'trueOrFalseResult': 1
}


def loadQb(qbPath):
    """
    加载题库数据
    :param qbPath: list: 题库地址
    :return: list: [[单选题,], [多选题,], [判断题,]]
    """
    # 单选题列表
    radioList = []
    # 多选题列表
    checkboxList = []
    # 判断题列表
    trueOrFalseList = []
    # 读取excel
    excel = ExcelWork(qbPath)
    # 获取所有数据
    for index, _ in enumerate(excel.getColumn(1)):
        # 每行数据: ['索引', '题型', '题目', '选项A', '选项B', '选项C', '选项D', '选项E', '选项F', '答案']
        rowList = excel.getRow(index + 1)[0:10]
        # 根据题型划分
        if rowList[1] == '单选题':
            radioList.append(rowList)
        elif rowList[1] == '多选题':
            checkboxList.append(rowList)
        elif rowList[1] == '判断题':
            trueOrFalseList.append(rowList)
    return [radioList, checkboxList, trueOrFalseList]


def randomQbData(qbData):
    """
    随机题库数据直接改变原始数据
    :param qbData: list: [[单选题,], [多选题,], [判断题,]]
    :return:
    """
    for tempData in qbData:
        # 打乱列表数据的顺序
        shuffle(tempData)


def getUserQbData(qbData):
    """
    获取用户题库数据并保存
    :param qbData: list: [[单选题,], [多选题,], [判断题,]]
    :return: list: [[题],]
    """
    radioList = qbData[0][:int(QUESTION_CONFIG['radioNum'])]
    checkboxList = qbData[1][:int(QUESTION_CONFIG['checkboxNum'])]
    trueOrFalseList = qbData[2][:int(QUESTION_CONFIG['trueOrFalseNum'])]
    return radioList + checkboxList + trueOrFalseList


def listToStr(dataList):
    """
    将列表中的值提取出来转为字符串
    :param dataList: list
    :return: str
    """
    tempStr = ''
    for temp in dataList:
        tempStr = tempStr + str(temp)
    return tempStr


def checkQbData(userQbData):
    """
    检查答案与用户作答数据进行比对为列表添加结果
    :param userQbData: list: [[索引, 题型, 题目, 选项A, 选项B, 选项C, 选项D, 选项E, 选项F, 答案, 用户作答],]
    :return: list: [[索引, 题型, 题目, 选项A, 选项B, 选项C, 选项D, 选项E, 选项F, 答案, 用户作答, 正确|错误],]
    """
    for qb in userQbData:
        # 比对结果
        if qb[9] == qb[10]:
            qb.append('正确')
        else:
            qb.append('错误')


def getUserResult(userQbData):
    """
    获取用户成绩
    :param userQbData: list: [[索引, 题型, 题目, 选项A, 选项B, 选项C, 选项D, 选项E, 选项F, 答案, 用户作答, 正确|错误],]
    :return: float: 用户成绩
    """
    result = 0
    for temp in userQbData:
        if temp[-1] == '正确':
            if temp[1] == '单选题':
                result = result + float(QUESTION_CONFIG['radioResult'])
            elif temp[1] == '多选题':
                result = result + float(QUESTION_CONFIG['checkboxResult'])
            elif temp[1] == '判断题':
                result = result + float(QUESTION_CONFIG['trueOrFalseResult'])
    return result


def getIp():
    """
    查询本机ip地址
    :return: str: 本机ip
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(('8.8.8.8', 80))
    ip = s.getsockname()[0]
    s.close()
    return ip

以下是app.py源码

from gevent import monkey

monkey.patch_all()  # 猴子补丁

from gevent import pywsgi
from copy import deepcopy
import webbrowser

from flask import Flask, request, render_template, redirect
from qb import loadQb, randomQbData, getUserQbData, listToStr, checkQbData, getUserResult, getIp, QUESTION_CONFIG

# app实例
app = Flask(__name__)

"""
缓存
QB_DATA缓存excel表格题库数据
USER_QB_DATA缓存用户随机的题库数据
"""
# 题库数据缓存[[单选题,], [多选题,], [判断题,]]
QB_DATA = loadQb('static/qb.xlsx')
# 用户题库数据缓存 {ip: [单选题, 多选题, 判断题],}
USER_QB_DATA = {}
# 成绩缓存[[name, class, result],]
RESULT_DATA = []

# 打开管理页面
webbrowser.open('http://127.0.0.1:9999/admin')


@app.route('/', methods=['GET', 'POST'])
def question():
    """
    答题|解析
    :return:
    """
    if request.method == 'GET':
        # 深拷贝数据,保证原始数据不被修改
        data = deepcopy(QB_DATA)
        # 随机题库数据
        randomQbData(data)
        # 获取用户题库数据
        userQbData = getUserQbData(data)
        # 缓存用户题库数据 {ip: [[单选题],[多选题],[判断题],],}
        USER_QB_DATA[request.remote_addr] = userQbData
        # 组合数据
        cont = {
            'data': userQbData
        }
        return render_template('question.html', **cont)
    else:
        # 获取用户
        name = request.form.get('name')
        # 获取班级
        qcls = request.form.get('qcls')
        # 获取用户题库缓存数据
        userQbData = USER_QB_DATA[request.remote_addr]
        # 清除用户缓存
        del USER_QB_DATA[request.remote_addr]
        # 用户题库缓存数据后追加用户答题结果
        for qb in userQbData:
            qb.append(listToStr(request.form.getlist(str(qb[0]))))
        # 检查答题结果
        checkQbData(userQbData)
        # 获取成绩
        result = getUserResult(userQbData)
        # 添加成绩缓存
        RESULT_DATA.append([name, qcls, result])
        # 组合数据
        cont = {
            'data': userQbData,
            'name': name,
            'qcls': qcls,
            'result': result
        }
        return render_template('result.html', **cont)


@app.route('/admin')
def admin():
    cont = {
        'url': f'http://{getIp()}:9999'
    }
    return render_template('admin.html', **cont)


@app.route('/results')
def results():
    """
    成绩
    :return:
    """
    cont = {
        'data': RESULT_DATA
    }
    return render_template('results.html', **cont)


@app.route('/config', methods=['GET', 'POST'])
def config():
    """
    设置
    :return:
    """
    if request.method == 'GET':
        cont = {
            'data': QUESTION_CONFIG
        }
        return render_template('config.html', **cont)
    else:
        data = request.form.to_dict()
        for k, v in data.items():
            QUESTION_CONFIG[k] = v
        return redirect('/config')


if __name__ == '__main__':
    print('-----答题系统服务已启动-----\n')
    print('-----后台管理-----\n')
    print('http://127.0.0.1:9999/admin\n')
    print('#' * 30)
    server = pywsgi.WSGIServer(('0.0.0.0', 9999), app)
    server.serve_forever()

==========================================================================================

前端页面放置到templates文件夹中

以下是admin.html页面源码




    
    答题系统
    


答题系统
答题地址:{ { url }}

以下是config.html源码




    
    答题系统
    


答题系统

以下是results.html源码




    
    答题系统
    


答题系统
{% for tempList in data %} {% for temp in tempList %} {% endfor %} {% endfor %}
姓名 班级 成绩
{ { temp }}

以下是question.html源码




    
    答题系统
    


答题系统
{% for temp in data %} {# 判断题型 #} {% if temp[1] != '多选题' %} {# ['索引', '题型', '题目', '选项A', '选项B', '选项C', '选项D', '选项E', '选项F', '答案'] #}
  • { { temp[1] }}
  • { { temp[2] }}
  • {% if temp[3] !=None %}
  • {% endif %} {% if temp[4] !=None %}
  • {% endif %} {% if temp[5] !=None %}
  • {% endif %} {% if temp[6] !=None %}
  • {% endif %} {% if temp[7] !=None %}
  • {% endif %} {% if temp[8] !=None %}
  • {% endif %}
{% else %}
  • { { temp[1] }}
  • { { temp[2] }}
  • {% if temp[3] !=None %}
  • {% endif %} {% if temp[4] !=None %}
  • {% endif %} {% if temp[5] !=None %}
  • {% endif %} {% if temp[6] !=None %}
  • {% endif %} {% if temp[7] !=None %}
  • {% endif %} {% if temp[8] !=None %}
  • {% endif %}
{% endif %} {% endfor %}

以下是result.html源码




    
    答题系统
    


答题系统
{% for temp in data %} {# 判断题型 #} {# ['索引', '题型', '题目', '选项A', '选项B', '选项C', '选项D', '选项E', '选项F', '答案', '用户作答', '正确|错误'] #}
  • { { temp[1] }}
  • { { temp[2] }}
  • {% if temp[3] !=None %}
  • {% endif %} {% if temp[4] !=None %}
  • {% endif %} {% if temp[5] !=None %}
  • {% endif %} {% if temp[6] !=None %}
  • {% endif %} {% if temp[7] !=None %}
  • {% endif %} {% if temp[8] !=None %}
  • {% endif %}
{% endfor %}

将源码按照文件名保存,运行编译都可以(进行过真实环境测试,效果还可以,写的啰嗦求大佬指正,互相学习!

当然也可以直接拿来用,用的便捷用的完成要求啦,麻烦点个赞哈~~

以下是pyinstaller编译后的目录 9.9M多一点点,够小了没有通过pyinstaller把静态文件与模板打包到exe中(其实是可以的但不利于直接修改模板题库)

python3 flask 多人答题(完整项目带源码与使用)_第7张图片

templates目录

你可能感兴趣的:(python,web,excel,html)