四则运算题目生成器
这篇文章详细讲述了需求分析当中功能需求第四阶段的实现过程和内容,对需求分析阶段感兴趣,请移步软件工程–四则运算表达式
对于网页之前的分析过程,请移步功能需求和设计阶段
网页的后端实现过程,请移步软件工程–四则运算表达式(4)
网页的前端实现过程,请移步软件工程–四则运算表达式(5)
做题和历史记录的设计过程
做题部分主要承担着以下功能
1.接受登录用户的做题请求
2.回传给用户一个表单用于填写做题信息
3.接收用户的表单,从数据库中取出相应的数据并且新建一个History记录保存用户的做题信息
4.给用户回传一个界面用于做题
5.接收用户做题信息并且对用户做题情况进行记录
6.返回用户答题成绩界面
7.可以让用户看到自己做错的题的信息
可以看到做题部分的功能是相对比较复杂的,而且里面需要进行的逻辑判断很多才能保证做题信息的严密性与安全性,尤其是对于如何保存用户的做题信息上,用户可能会多次提交同一个表单,多次提交刷分,要防止此类攻击的发生
而且在编写过程中也遇到了一些我目前难以解决的情况,比如如何实现表单长度的动态加载,以及对于其中的每一项进行赋值,网页的左右分栏问题等,接下来将介绍我是如何想尽各种办法绕开这些问题实现需求功能的
用例图
类图
编码过程
视图部分
出题部分
#question/views.py
def generator(request):
if not request.session.get('is_login', None):
return HttpResponse('您尚未登陆,无法操作')
if request.method == "POST":
form = DSform(request.POST)
if form.is_valid():
quantity = form.cleaned_data['quantity']
if_pow = form.cleaned_data['if_pow']
if_fra = form.cleaned_data['if_fra']
if_neg = form.cleaned_data['if_neg']
potp = form.cleaned_data['pow_type']
operator = form.cleaned_data['operators']
quest = get_ls(operator=operator, if_pow=if_pow, if_neg=if_neg)
user = User.objects.get(id=request.session.get('user_id', None))
i = 0
ot_ls = []
history = History.objects.create()
history.user = user
history.quantity = quantity
history.if_fra = if_fra
history.save()
id = history.id
while i < int(quantity):
rand = random.randint(0, quest.__len__() - 1)
quest[rand].question = string_change(quest[rand].question, potp)
ot_ls.append(quest[rand])
quest.pop(rand)
i += 1
i = 0
while i < int(quantity):
answer = Answer.objects.create()
answer.answer_id = history
answer.question = ot_ls[i]
answer.save()
i = i + 1
if int(quantity) == 5:
g_form = Form5
elif int(quantity) == 10:
g_form = Form10
else:
g_form = Form20
context = {'output_list': ot_ls, 'form': g_form, 'answer_id': id, 'quantity': quantity}
return render(request, 'question/Question_list.html', context)
form = DSform()
context = {'form': form}
return render(request, 'question/Question.html', context)
这里是视图中的出题部分,类似于搜题部分实现的出题功能,但是有所区别的是这里不仅保存了题目列表也保存了所出的题目的信息和做题的信息在history表和answer表中,history和answer是一对多的关系,至于为什么不将history和answer表和二为一的原因是如果将history和answer合二为一将导致数据库在检索一条做题记录的时候速度变慢,所以不得已使用了这种方法对数据库进行检索,相当于抽象了一层出来,但是这样也有好处,因为一次做题的信息都可以保存在history中而不需要在answer中重复存取,降低了数据库的存取冗余度
但是在两张表做join检索的时候可能会略微降低数据库的存取效率
而且在这里也渲染了不同类型的多个表单用于提供给用户提交做题信息
其实这里就涉及到了表单动态加载的问题,我希望让表单的长度可以动态变化,使用同一个表单类满足不同的出题需求,但是目前为止这个问题仍然没有解决,如果您有解决的方法请您务必告诉我,感谢!
所以也导致我不得不更改了DSForm,由SouSouSou的可以选择出题数量变味了用户只能从一个singlechoicebox中选择5、10、20三种不同的出题数量,这样虽然简化了操作但总觉得技术有所欠缺
检查用户提交答案部分
def confirm(request, num, answer_id):
if request.method == "POST":
if num == 5:
form = Form5(request.POST)
if form.is_valid():
answer = {}
answer[0] = form.cleaned_data['Kt1']
answer[1] = form.cleaned_data['Kt2']
answer[2] = form.cleaned_data['Kt3']
answer[3] = form.cleaned_data['Kt4']
answer[4] = form.cleaned_data['Kt5']
history = History.objects.get(id=answer_id)
if history.end_time != None:
return HttpResponse('您已经提交过,请勿重复提交!')
history.__set_end_time__()
time = history.end_time - history.start_time
as_list = Answer.objects.filter(answer_id=history)
i = 0
s = num
ot_ls = []
while i < answer.__len__():
if history.if_fra == True:
if not answer[i] == as_list[i].question.answer_Fraction:
if WrongAnswer.objects.filter(question=as_list[i].question):
s = s - 1
else:
wrong = WrongAnswer.objects.create()
wrong.question = as_list[i].question
wrong.answer_id = history
ot_ls.append(as_list[i].question)
wrong.save()
s = s - 1
else:
if not float(answer[i]) == float(as_list[i].question.answer_float):
if WrongAnswer.objects.filter(question=as_list[i].question):
s = s - 1
else:
wrong = WrongAnswer.objects.create()
wrong.question = as_list[i].question
wrong.answer_id = history
wrong.save()
ot_ls.append(as_list[i].question)
s = s - 1
i = i + 1
history.score = s
history.save()
context = {'output_list': ot_ls, 'score': s, 'time': time, 'if_fra': history.if_fra}
return render(request, 'question/your_score.html', context)
return HttpResponse('请重新填写表单')
elif num == 10:
form = Form10(request.POST)
print(request.POST)
if form.is_valid():
answer = {}
answer[0] = form.cleaned_data['Kt1']
answer[1] = form.cleaned_data['Kt2']
answer[2] = form.cleaned_data['Kt3']
answer[3] = form.cleaned_data['Kt4']
answer[4] = form.cleaned_data['Kt5']
answer[5] = form.cleaned_data['Kt6']
answer[6] = form.cleaned_data['Kt7']
answer[7] = form.cleaned_data['Kt8']
answer[8] = form.cleaned_data['Kt9']
answer[9] = form.cleaned_data['Kt10']
history = History.objects.get(id=answer_id)
if history.end_time != None:
return HttpResponse('您已经提交过,请勿重复提交!')
history.__set_end_time__()
time = history.end_time - history.start_time
as_list = Answer.objects.filter(answer_id=history)
i = 0
s = num
ot_ls = []
while i < answer.__len__():
if history.if_fra == True:
if not answer[i] == as_list[i].question.answer_Fraction:
if WrongAnswer.objects.filter(question=as_list[i].question):
s = s - 1
else:
wrong = WrongAnswer.objects.create()
wrong.question = as_list[i].question
wrong.answer_id = history
ot_ls.append(as_list[i].question)
wrong.save()
s = s - 1
else:
if not float(answer[i]) == float(as_list[i].question.answer_float):
if WrongAnswer.objects.filter(question=as_list[i].question):
s = s - 1
else:
wrong = WrongAnswer.objects.create()
wrong.question = as_list[i].question
wrong.answer_id = history
wrong.save()
ot_ls.append(as_list[i].question)
s = s - 1
i = i + 1
history.score = s
history.save()
context = {'output_list': ot_ls, 'score': s, 'time': time, 'if_fra': history.if_fra}
return render(request, 'question/your_score.html', context)
return HttpResponse('请重新填写表单')
if request.method == "POST":
if num == 20:
form = Form5(request.POST)
print(request.POST)
if form.is_valid():
answer = {}
answer[0] = form.cleaned_data['Kt1']
answer[1] = form.cleaned_data['Kt2']
answer[2] = form.cleaned_data['Kt3']
answer[3] = form.cleaned_data['Kt4']
answer[4] = form.cleaned_data['Kt5']
answer[5] = form.cleaned_data['Kt6']
answer[6] = form.cleaned_data['Kt7']
answer[7] = form.cleaned_data['Kt8']
answer[8] = form.cleaned_data['Kt9']
answer[9] = form.cleaned_data['Kt10']
answer[10] = form.cleaned_data['Kt11']
answer[11] = form.cleaned_data['Kt12']
answer[12] = form.cleaned_data['Kt13']
answer[13] = form.cleaned_data['Kt14']
answer[14] = form.cleaned_data['Kt15']
answer[15] = form.cleaned_data['Kt16']
answer[16] = form.cleaned_data['Kt17']
answer[17] = form.cleaned_data['Kt18']
answer[18] = form.cleaned_data['Kt19']
answer[19] = form.cleaned_data['Kt20']
history = History.objects.get(id=answer_id)
if history.end_time != None:
return HttpResponse('您已经提交过,请勿重复提交!')
history.__set_end_time__()
time = history.end_time - history.start_time
as_list = Answer.objects.filter(answer_id=history)
i = 0
s = num
ot_ls = []
while i < answer.__len__():
if history.if_fra == True:
if not answer[i] == as_list[i].question.answer_Fraction:
if WrongAnswer.objects.filter(question=as_list[i].question):
s = s - 1
else:
wrong = WrongAnswer.objects.create()
wrong.question = as_list[i].question
wrong.answer_id = history
ot_ls.append(as_list[i].question)
wrong.save()
s = s - 1
else:
if not float(answer[i]) == float(as_list[i].question.answer_float):
if WrongAnswer.objects.filter(question=as_list[i].question):
s = s - 1
else:
wrong = WrongAnswer.objects.create()
wrong.question = as_list[i].question
wrong.answer_id = history
wrong.save()
ot_ls.append(as_list[i].question)
s = s - 1
i = i + 1
history.score = s
history.save()
context = {'output_list': ot_ls, 'score': s, 'time': time, 'if_fra': history.if_fra}
return render(request, 'question/your_score.html', context)
return HttpResponse('请重新填写表单')
return HttpResponse('非法的提交操作')
这个函数虽然有点长,但是实现的功能在很大程度上是重复的,原因很简单,因为我不会动态扩充表单,所以选择了一种原始的傻大黑粗的土办法
我们可以看到用户提交的时候是连着answer_id一起提交的,answer_id就是对应的数据表history中保存的记录的主键,通过这样的方式可以从用户的历史记录和Answer表做join,提取用户一次做题的完整题目列表,正常情况下题目列表是按照主键递增的原则保存的,用户生成的题目清单也是按照主键递增原则生成的,所以可以直接按照默认的排序规则将数据库中的历史记录取出和用户返回的表单中的对应项比较,如果相同则说明回答正确,否则回答错误,这里比较的方法参考了第一阶段实现的内容(判断用户答题情况对错),如果是分数题直接通过字符串比较即可知道是否回答正确,如果是小数题将提交的答案转换为float型浮点数(提交上来的是str类型)和数据库中的小数答案(也是str类型)做对比
如果相同则说明用户回答正确,否则为用户回答错误,经过如上的操作错误的信息将被保存在WrongAnswer表中,正确的回答不作记录,而且对于每一个回答,会首先从错题数据库中查找是否已经存在同一个题,因为同一个题记录多次是没有意义的,所以要进行一次数据的筛选操作
而且因为如果数据已经提交过,就会有一个对应的end_time字段保存在history表中,那么就可以通过这个字段内容的有无判断是否提交过,如此实现了防止重复提交的功能
历史记录的函数的部分
def youtube(request):
if not request.session.get('is_login', None):
return HttpResponse('您尚未登录,无法操作')
user = User.objects.get(id=request.session.get('user_id', None))
history = History.objects.filter(user=user)
i = 0
ot_ls = []
while i < history.__len__():
wa = WrongAnswer.objects.filter(answer_id=history[i])
k = 0
while k < wa.__len__():
ot_ls.append(wa[k].question)
k = k + 1
i = i + 1
return render(request, 'question/history.html', context={'output_list': ot_ls})
这个函数首先判断用户是否存在,存在的话从数据库中取出用户的user信息,并且在history中筛选对应的history主键信息,然后再去WrongAnswer表中筛选对应的错题记录,并且将相应的内容以字段的形式保存在output_list中,返回相应的界面给用户
表单部分
表单部分内容没啥好说的,下面这段代码还是比较容易看懂的,毕竟也是重复的操作
一种出题表单
#Question/forms.py
from django import forms
class DSform(forms.Form):
qunt = (
('5', "给爷先来5个"),
('10', "少废话再来5个"),
('20', "20个让爷做到爽"),
)
quantity = forms.ChoiceField(label="想来几个题爽", choices=qunt)
optr = (
('2', "简单难度2"),
('3', "简单难度3"),
('4', "简单难度4"),
('5', "中等难度5"),
('6', "中等难度6"),
('7', "中等难度7"),
('8', "小学生都哭了的难度8"),
('9', "小学生都哭了的难度9"),
)
operators = forms.MultipleChoiceField(label="爷来几个符号", choices=optr)
if_pw = (
('True', "要"),
('False', "不要"),
('Both', "爷都要"),
)
if_ng = (
('True', "要"),
('False', "不要"),
('Both', "爷都要"),
)
if_fa = (
('True', "要"),
('False', "不要"),
)
potyp = (
('True', "^"),
('False', "**"),
)
if_pow = forms.ChoiceField(label="乘方", choices=if_pw)
if_neg = forms.ChoiceField(label="负数", choices=if_ng)
if_fra = forms.ChoiceField(label="分数", choices=if_fa)
pow_type = forms.ChoiceField(label="类型", choices=potyp)
三种做题表单
from django import forms
class Form5(forms.Form):
Kt1 = forms.CharField(max_length=128)
Kt2 = forms.CharField(max_length=128)
Kt3 = forms.CharField(max_length=128)
Kt4 = forms.CharField(max_length=128)
Kt5 = forms.CharField(max_length=128)
class Form10(forms.Form):
Kt1 = forms.CharField(max_length=128)
Kt2 = forms.CharField(max_length=128)
Kt3 = forms.CharField(max_length=128)
Kt4 = forms.CharField(max_length=128)
Kt5 = forms.CharField(max_length=128)
Kt6 = forms.CharField(max_length=128)
Kt7 = forms.CharField(max_length=128)
Kt8 = forms.CharField(max_length=128)
Kt9 = forms.CharField(max_length=128)
Kt10 = forms.CharField(max_length=128)
class Form20(forms.Form):
Kt1 = forms.CharField(max_length=128)
Kt2 = forms.CharField(max_length=128)
Kt3 = forms.CharField(max_length=128)
Kt4 = forms.CharField(max_length=128)
Kt5 = forms.CharField(max_length=128)
Kt6 = forms.CharField(max_length=128)
Kt7 = forms.CharField(max_length=128)
Kt8 = forms.CharField(max_length=128)
Kt9 = forms.CharField(max_length=128)
Kt10 = forms.CharField(max_length=128)
Kt11 = forms.CharField(max_length=128)
Kt12 = forms.CharField(max_length=128)
Kt13 = forms.CharField(max_length=128)
Kt14 = forms.CharField(max_length=128)
Kt15 = forms.CharField(max_length=128)
Kt16 = forms.CharField(max_length=128)
Kt17 = forms.CharField(max_length=128)
Kt18 = forms.CharField(max_length=128)
Kt19 = forms.CharField(max_length=128)
Kt20 = forms.CharField(max_length=128)
如此实现了总体上的实现过程,接下来将进入对代码进行测试和性能分析的阶段
来源:CSDN
作者:Sky blue water
链接:https://blog.csdn.net/qq_43513268/article/details/104007710