四则运算题目生成器
这篇文章详细讲述了第三阶段的需求分析、概要设计详细设计和编码阶段的工作
如果对上一阶段实现的功能感兴趣,请移步
软件工程–四则表达式(2)
如果对可行性分析和问题定义、PSP表格感兴趣,请移步
软件工程–四则运算表达式
第三阶段:网站部分
概述
剩下的主要是所搭网站的前端后端部分
所使用的架构是Django架构,同样是python写的开源服务器架构
Django版本2.27 没有使用最新的3.0版本因为3.0版本当中据说有未修复的bug
对于先前已经实现的功能,引入只需要将源码打包在一个文件夹里,就可以在后端轻松引入
网站的MVC模型
这个项目是严格遵循MVC设计模式实现的,所以在此介绍一下Django中的MVC模型
Model模型
Django中的模型是在每一个app中的models构成的,通过在models中建立class就相当于在数据库中建立了一张表,这些表由Django维护,对于这些表的调用一般是在Views中进行
View视图
Django中的视图是每一个app中的views文件中的函数,view和model的引用是通过import models中的class实现的,通过这种方法可以轻松的实现对数据的增删改查
Controller控制器
Django的控制器是用的urls文件,通过对urls.py的配置将用户请求映射到处理函数,urls.py文件存在于项目的根文件和app文件中,通过对url的匹配规则的修改调用不同apps中的不同视图函数实现对用户输入的控制并且向模型发送数据
第三阶段需求分析详细说明
a)首先就是希望网页端可以实现用户输入参数从题库中随机生成题目的功能,这个功能因为有数据库的辅助实现起来还是比较简单的;
b) 其次就是希望可以做到用户管理,用户可以注册可以登录如果忘记密码可以通过预留的邮箱找回密码,这个功能也是可以实现的;
c) 第三个就是希望可以像第三阶段的第一个要求让用户在线答题,并且由于有用户管理的准备工作可以生成用户答题的历史记录,并且记录错题信息
概要设计和详细设计
用例图模型
类图模型
以下类图模型为了方便理解,用包将统一作用的类囊括起来,但本质上还是类与类之间的关系
login 的类图模型
reset_password类图模型
question类图模型
顺序图
顺序图描述了几个较为重要的功能动态模型,有的只描述了操作正确的情况,对于某些操作(比如重置密码)中出现的其他情况没有绘制
登录功能
出题功能
重置密码功能
编码过程(部分)
以下详细讲述了项目的编码过程,并且对代码的运行流程进行了解释性说明
创建项目
首先用如下命令创建一个新项目
django -admin startproject QuestGeneratorWebsite
接下来创建一个新的服务器端应用
python manage.py startapp login
创建完毕并进行了一段时间的开发后代码结构大致如下所示:
.
├── QuestGeneratorWebsite
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── SouSouSou
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── models.py
│ ├── templates
│ │ └── SouSouSou
│ │ ├── Generator.html
│ │ └── main.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── db.sqlite3
├── generate
│ ├── OriginRequest.py
│ ├── main.py
│ └── solve.py
├── login
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── migrations
│ ├── models.py
│ ├── templates
│ │ └── login
│ │ ├── login.html
│ │ └── register.html
│ ├── tests.py
│ └── views.py
├── manage.py
├── out.txt
├── question
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── inti_data.py
│ ├── migrations
│ │ ├── __init__.py
│ ├── models.py
│ ├── templates
│ │ └── question
│ │ ├── Question.html
│ │ └── Question_list.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── reset_passowrd
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── models.py
│ ├── templates
│ │ └── reset_passowrd
│ │ ├── _message.html
│ │ ├── confirm.html
│ │ ├── pass.html
│ │ └── reset.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── templates
├── base.html
├── footer.html
└── top.html
这个是已经进行了一段时间的开发之后的项目规模,可以看到代码的层次结构还是很清晰的,generate中就是我们在前两步已经开发完成的工程文件,后端对以上阶段实现的功能的调用是通过接口实现的
数据库结构概览
数据库用的是SQLite,Django默认的数据库,可视化软件用的Navicat
在数据库初始化的过程中会调用main中的main函数,给数据库填充初始化数据,在批量生成数据的过程中,也同时会在控制台输出所生成的所有表达式的值,填充进数据库的内容有表达式,里面有几个操作符,有没有乘方,有没有负数,分数结果和小数结果,对于没有指定主键的Model,Django会自动生成一个id作为Model的主键,正如Django在这个工程里做的一样
网站的主要控制器部分 (MVC当中的C )
项目的主要部件
首先是QuestGeneratorWebsite,这是项目的主要文件,其他的都是通过manage.py生成的应用文件或者是上一阶段开发完成的工程文件接口,其中在QuestGeneratorWebsite中最重要的就是url 的匹配规则文件urls 和settings 里面的配置信息,urls文件配置如下:
#QuestGeneratorWebsite/urls
from django.contrib import admin
from django.urls import path, include
from login import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', views.login),
path('logout/', views.logout),
path('register/', views.register),
path('reset_password/', include('reset_passowrd.urls')),
path(" captcha/", include("captcha.urls")),
path('sou/', include("SouSouSou.urls")),
path('generate/', include("question.urls")),
]
从这里可以看出我们从login里导入了views(视图文件)对应MVC当中的V,urls这个正则匹配规则代表MVC当中的C,通过输入的url地址进行正则匹配选择调用的视图函数
settings是这个项目的基本设置,settings部分文件如下:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'login',
'captcha',
'question',
'reset_passowrd',
'SouSouSou',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
这里没有放数据库的配置等信息,主要看STATIC_URL 部分和INSTALLED_APPS 部分
这里面因为register用到了验证码,我们用的是django-simple-captcha ,版本是0.5.12
这个库是需要额外安装的
static_url标示了静态文件所属的位置,这里面一般是网页样式信息,包括CSS,BootStrap和JQuery等
templates是因为在非Pycharm环境创建工程下可能会没有templates文件夹,文件夹里是基础网页,其他网页绝大部分是在base.html上的拓展
在网页中通过Django提供的{% load staticfiles %}去获取网页的模版信息并且渲染到base等网页上
接下来大致叙述一下数据库模型(MVC)中的M和用户登录注册等功能的实现过程
用户的登录注册是参考了用Django实现用户的注册登录在此深表感谢
网站数据库(模型)部分
这部分对应了MVC模型中的M部分
login、reset_password、question和SouSouSou这四个app都是自己建立起来的,这四个的models如下
1.login模型部分
#login/models.py
from django.db import models
# Create your models here.
class User(models.Model):
gender = (
('male', '男'),
('female', '女'),
)
name = models.CharField(max_length=128, unique=True)
password = models.CharField(max_length=256)
email = models.EmailField(unique=True)
sex = models.CharField(max_length=32, choices=gender, default='男')
c_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Meta:
ordering = ['c_time']
verbose_name = '用户'
verbose_name_plural = '用户'
2.reset_password模型部分
#reset_password/models.py
from django.db import models
from login.models import User
# Create your models here.
class ConfirmString(models.Model):
code = models.CharField(max_length=256)
user = models.OneToOneField(User, models.CASCADE)
c_time = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.user.name + ': ' + self.code
class Meta:
ordering = ['-c_time']
verbose_name = '确认码'
verbose_name_plural = '确认码'
可以看到user是对User里面的modelsUser的1对1关系,这么做的考虑是因为一个用户可能会同时发送多个重置密码请求,导致收到多封带着重置密码验证码的邮件,这样导致后端无法正确识别一个用户对应的验证码或者有多个验证码每一个都可以识别,所以为了避免这种情况,采用了一对一的数据库模型设计
这个是真的会发邮件的!!!!!我开启了QQ邮箱的SMTP服务,和Django的SMTP服务配合,就可以发邮件到指定的邮箱了(所以真·别乱填邮箱)
3.SouSouSou模型部分
#SouSouSou/models.py
from django.db import models
from login.models import User
class History(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
start_time = models.DateTimeField(auto_now_add=True)
end_time = models.DateTimeField(null=True)
score = models.IntegerField(default=0)
quantity = models.IntegerField(default=0)
if_fra = models.BooleanField(default=False)
def __get_answer_id__(self):
return self.answer_id
def __answer_id_increase__(self):
self.answer_id = self.answer_id + 1
这个是用户答题的历史记录模型,可以看到user对应了这个模型的ForeignKey(在Django中一个ForeignKey默认的是一对多关系),一个用户可以答多个题嘛,然后为了避免数据库冗余,我在question的models设置了一个answer模型,专门用来记录用户答题的信息,answer模型的外键就是History模型的主键,同样是一对多关系,因为一个历史记录可以含多个题目
4.question模型部分
#question/models.py
from django.db import models
from login.models import User
from SouSouSou.models import History
# Create your models here.
class Question(models.Model):
question = models.CharField(max_length=128, unique=True)
answer_float = models.CharField(max_length=128)
answer_Fraction = models.CharField(max_length=128)
question_if_pow = models.BooleanField()
question_if_negative = models.BooleanField()
question_operators_num = models.IntegerField()
c_time = models.DateTimeField(auto_now_add=True)
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, null=True)
answer_id = models.ForeignKey(History, on_delete=models.CASCADE, null=True)
class WrongAnswer(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, null=True)
answer_id = models.ForeignKey(History, on_delete=models.CASCADE, null=True)
answer_if_answered_again = models.BooleanField(default=False)
type = models.BooleanField(default=False)
Question是主要的模型,这里面是一个题目的信息,数据库初始化调用main函数接口生成的数据都被注入到这个模型里,可以看到为了确保题目没有重复,这里也开了一个unique=True约束,因为多次的migrate会为数据库填充多次信息,这样就可能导致question有重复,answer_float是一个char字段,用来保存小数结果,为什么要转变为char型呢,因为这样输出的时候是一个str型,可以对str类型进行各种类型转换,方便和用户作答结果比较,answer_Fraction用来保存分数结果的,if_pow就是是否有乘方运算,if_neg判断表达式是否有负数
另外两个models一个是Answer,记录某次历史记录的全部答题信息,可以看到两个其中的question和answer_id字段都是外键分别对应Question模型和History模型,WrongAnswer记录用户答题信息,里面是错题,但是现阶段还没有用到这两个模型,但是为了避免日后对Django数据库进行迁移可能需要对数据初始化的情况,先将两个模型建立完毕以待备用
数据库初始化操作,和上一部分的代码连接
下面的代码块里面是数据库数据的初始化操作,我们可以看到对于每种情况进行循环,每个种类的题目生成20次,并且调用main函数接口,main函数返回一个list型的列表,这个列表最后经过处理以后创建一个Question类型的对象,这个对象就是题目对应的数据库,然后把这个对象保存在数据库
#question/init_data.py
from .models import Question
from generate.main import main
def init_data():
if_pow = 0
while if_pow <= 1:
if_negative = 0
while if_negative <= 1:
operators = 2
while operators < 10:
return_list = main(20, operators, if_negative, if_pow, 9)
i = 0
while i < return_list.__len__():
quest = return_list[i]
i += 1
answer_fraction = return_list[i]
i += 1
answer_flo = return_list[i]
if Question.objects.filter(question=quest):
operators -= 1
break
Question.objects.create(question=quest, question_if_negative=if_negative, question_if_pow=if_pow,
answer_float=answer_flo, answer_Fraction=answer_fraction,
question_operators_num=operators)
i = i + 1
operators += 1
if_negative += 1
if_pow += 1
print('finished')
def inti_data():
print('Initializing data ')
init_data()
因为是数据库初始化过程中填充数据,所以数据填充发生在python manage.py migrate之后,在控制台会显示生成的所有题目信息
同样的我们也需要更改init.py,init.py负责把python工程初始化,init.py如下所示:
#question/__init__.py
from os import path
def get_current_app_name(file):
return path.dirname(file).replace('\\', '/').split('/')[-1]
current_app_name = get_current_app_name(__file__)
default_app_config = current_app_name + '.apps.' + current_app_name.capitalize() + 'Config'
来源:CSDN
作者:Sky blue water
链接:https://blog.csdn.net/qq_43513268/article/details/103993024