项目文件:课程选课系统 开发环境:PyCharm 2021.1.1
一、任务描述及数据
主要任务:三人自由组合完成一个课程系统
角色: 学校、学员、课程、讲师、管理员
1.1 要求
- 创建武汉、长沙 2 所学校 (管理员创建学校)
- 创建 Linux、Python、C 语言 3 门课程 , Linux、Python 在武汉开课, C 语言在长沙开课
- 课程包含:周期、价格(通过学校创建课程)
- 提供两个角色接口:创建讲师、创建学员时需选择学校,关联班级
- 学员视图,可以注册,选择课程(等同于选择班级)
- 讲师视图,讲师可管理自己的课程,上课时选择班级,查看班级学员列表 , 修改所管理的学员的成绩
- 管理视图,创建讲师, 创建班级,创建课程
- 上面的操作产生的数据都通过pickle序列化保存到文件里(pickle 可以帮我们保存对象)
二、需求分析及实现路线
2.1 管理视图
-
注册 (1)用户在视图层输入用户名与密码交给接口层 (2)接口层调用数据层的 models.select 进行校验 (3)若不存在,则创建,并将注册成功返回给用户视图层 -
登录 (1)用户在视图层输入用户名与密码交给接口层 (2)接口层调用数据层的 models.select 进行校验 (3)若存在则校验密码,并将登录成功返回给用户视图层 -
创建学校 (1)让用户输入学校名与学校地址 (2)调用管理员创建学校接口 (3)判断学校是否存在,若存在,不让创建 (4)若不存在,则调用接口层创建学校,获取对象的创建学校方法,保存学校 (5)将结果返回给视图层 -
创建课程(先选择学校) (1)获取所有学校并打印,让用户选择 (2)获取用户选择的学校与创建的课程交给接口层 (3)接口层调用管理员对象中的创建课程方法,保存课程对象 (4)课程需要绑定给学校对象,最终将创建成功的结果返回给视图层 -
创建讲师 (1)让用户输入老师的名称 (2)调用接口层,接口层中设置默认密码 123,调用数据层 (3)判断老师是否存在,不存在则调用管理员对象中的创建老师方法 (4)保存老师对象并将结果返回给视图层
2.2 学员视图
-
注册 -
登录 -
选择校区 (1)获取所有学校让学生选择,将选择的学校传给接口层 (2)接口层判断当前学生是否选择学校 (3)若没有选择,则调用学生对象中的添加学校方法 (4)将添加后的消息返回给视图层 -
选择课程(先选择校区,再选择校区中的某一门课程),学生与课程互相选择 (1)先获取当前学生所在学校的所有课程并选择 (2)接口层将选择后的学生课程,调用数据层的添加课程方法,保存 (3)学生对象中的课程列表添加该课程,设置课程分数默认为 0 (4)最终将结果返回给视图层 -
查看分数 (1)直接调用接口层 (2)接口层调用数据层的查看成绩方法 (3)返回成绩给视图层并打印
2.3 讲师视图
-
登录 -
查看授课课程 (1)直接调用接口层,获取老师对象下课程列表数据 (2)若有则打印,没有则退出 -
选择教授课程 (1)调用接口层中的选择教授课程接口,调用数据层中该课程下所有的学生返回给视图层 (2)打印所有的课程让老师选择,若老师课程中有该课程则不让添加 (3)没有则调用老师中的添加课程方法进行添加 -
查看课程下的学生名单 (1)直接获取老师对象下所有的课程,选择课程 (2)从老师对象中,调用查看课程下学生方法,获取课程对象下所有的学生,返回给视图层 (3)视图层打印,该课程下所有的学生 -
修改学生分数 (1)直接获取老师对象下所有的课程,选择课程 (2)从老师对象中,调用查看课程下学生方法,获取课程对象下所有的学生,返回给视图层 (3)视图层打印,该课程下所有的学生,并让用户选择需要修改分数的学生 (4)调用老师修改分数接口,获取老师对象,调用对象中的修改分数方法 (5)获取学生对象中的 score_dict 分数字典进行修改
三、模块设计及实现
实现思路:项目采用三层架构设计,基于面向对象封装角色数据和功能。面向过程和面向对象搭配使用。程序开始,用户选择角色,进入不同的视图层,展示每个角色的功能,供用户选择。进入具体角色视图后,调用功能,对接逻辑接口层获取数据并展示给用户视图层。逻辑接口层需要调用数据处理层的类,获取类实例化对象,进而实现数据的增删改查。
3.1 视图层
用于与用户进行交互,如小的逻辑判断,比如注册功能中两次密码是否一致的校验
core:src.py(主视图)、admin.py(管理员视图)、student.py(学生视图)、teacher.py(教师视图)
视图层封装成视图类
之所以想要将视图层封装成视图类,主要是为了简化代码和避免手动编写用户的功能函数字典。采用视图类之后,可以将功能函数做成视图类的对象的绑定方法,采用反射,可以自动获取并调用。但这里需要做一个处理:用户选择角色后,如何获取并显示这个角色的功能函数函数列表?这里需要在视图类里面做一个显示功能的方法start ,这个方法要在用户选择先显示所有的功能,在此之前,还需要一个收集角色功能的方法auto_get_func_menu ,这个函数必须在对象使用时就立即工作,最后,还要配合一个装饰器my_func ,让收集函数知道搜集那些功能,保存下来func_list ,让显示函数获取。上述这个过程涉及的方法是每个视图类都要有的,因此抽象出来一个基础视图类BaseViewer 。最后,视图类需要用到一些公用工具(lib/tool.py),将它封装成一个ToolsMixin 类,视图类继承之,方便传参。
from functools import wraps
class BaseViewer:
name = None
role = None
func_list = []
def __init__(self):
self.auto_get_func_menu()
def auto_get_func_menu(self):
"""
自动调用功能函数触发装饰器的执行,将功能函数添加到类属性 func_list中
:return:
"""
not_this = ['auto_get_func_menu', 'my_func', 'start']
all_funcs = {k: v for k, v in self.__class__.__dict__.items()
if callable(v) and not k.startswith('__') and k not in not_this}
for func in all_funcs.values():
func()
def start(self):
"""
开始函数,功能菜单显示,供管理员选择
:return:
"""
while 1:
for index, func_name in enumerate(self.func_list, 1):
print('tttttt', index, func_name[0], sep='t')
choice = input('>>>(Q退出):').strip().lower()
if choice == 'q':
self.func_list.clear()
break
if not choice.isdigit() or int(choice) not in range(1, len(self.func_list) +1):
print('编号不存在, 请重新输入')
continue
func = self.func_list[int(choice) 1][1]
func(self)
@staticmethod
def my_func(desc):
"""
装饰器,实现功能函数自动添加到类的func_list中
:return:
"""
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
BaseViewer.func_list.append((desc, func))
return inner
return wrapper
@staticmethod
def auth(role):
"""
装饰器,登录校验
:return:
"""
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
if BaseViewer.name and BaseViewer.role == role:
res = func(*args, **kwargs)
return res
else:
print('您未登录或没有该功能的使用权限')
return inner
return wrapper
def login(self, role_interface):
while 1:
print('登录页面'.center(50, ''))
name = input('请输入用户名(Q退出):').strip().lower()
if name == 'q':
break
pwd = input('请输入密码:').strip()
if self.is_none(name, pwd):
print('用户名或密码不能为空')
continue
flag, msg = role_interface.login_interface(name, self.hash_md5(pwd))
print(msg)
if flag:
BaseViewer.name = name
break
from core.baseview import BaseViewer as Base
from lib.tools import ToolsMixin
from interface import student_interface, common_interface
class StudentViewer(ToolsMixin, Base):
@Base.my_func('登录')
def login(self):
Base.role = 'Student'
super().login(student_interface)
@Base.my_func('选择课程')
@Base.auth('Student')
def select_course(self):
while 1:
school_name = student_interface.get_my_school_interface(self.name)
flag, course_list = common_interface.get_course_list_from_school(school_name)
if not flag:
print(course_list)
break
print('待选课程列表'.center(30, ''))
flag2, course_name = self.select_item(course_list)
if not flag2:
break
flag3, msg = student_interface.select_course_interface(course_name, self.name)
print(msg)
@Base.my_func('我的课程')
@Base.auth('Student')
def check_my_course(self):
flag, course_list = student_interface.check_my_course_interface(self.name)
if not flag:
print(course_list)
return
print('我的课程:'.center(30, ''))
for index, course_name in enumerate(course_list, 1):
print(index, course_name)
@Base.my_func('我的分数')
@Base.auth('Student')
def check_my_score(self):
flag, score_dict = student_interface.check_score_interface(self.name)
if not flag:
print(score_dict)
else:
print('课程分数列表')
for index, course_name in enumerate(score_dict, 1):
score = score_dict[course_name]
print(index, course_name, score)
@Base.my_func('修改密码')
@Base.auth('Student')
def edit_my_pwd(self):
self.edit_pwd(common_interface.edit_pwd_interface)
3.2 逻辑接口层
核心业务逻辑的处理
interface:admin_interface.py、student_interface.py、teacher_interface.py
3.3 数据处理层
做数据的 CRUD 处理
models.py(存放所有的类:学校类、学院类、课程类、讲师类、管理员类)
db_handler.py(用户保存对象与获取对象,存放 json 格式的数据)
3.4 角色类的设计
从管理员、学生、老师角色中抽象出Human 类,有用户基本数据属性和密码相关的公共属性。为了方便角色数据的读取和保存,定义了一个接口类FileMixin ,用于对象数据的读取和保存。FileMixin 类中设置一个绑定类的方法,这样每个继承FileMixin 的类都可以通过对象名判断这个对象的存在与否(多继承时遵循Mixins 规范)。对象初始化后立即保存数据,每个功能操作后,也跟一个save_obj 方法,这样类的调用起来较为方便。在用户类中设置角色的方法属性,这样直接在逻辑接口层中在获取对象后,直接调用对象的方法即可。这样做是为了保证面向对象的完整性,每个对象都对应其现实意义。
class FileMixin:
@classmethod
def get_obj(cls, name):
return db_handle.get_obj(cls, name)
def save_obj(self):
db_handle.save_obj(self)
class Human:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
self.__pwd = settings.INIT_PWD
self.role = self.__class__.__name__
@property
def pwd(self):
return self.__pwd
@pwd.setter
def pwd(self, new_pwd):
self.__pwd = new_pwd
class Admin(FileMixin, Human):
def __init__(self, name, age, sex):
super().__init__(name, age, sex)
self.save_obj()
@staticmethod
def create_school(school_name, school_addr):
School(school_name, school_addr)
@staticmethod
def create_course(school_name, course_name, course_period, course_price):
Course(course_name, course_period, course_price, school_name)
@staticmethod
def create_teacher(teacher_name, teacher_age, teacher_sex, teacher_level):
Teacher(teacher_name, teacher_age, teacher_sex, teacher_level)
@staticmethod
def create_student(stu_name, stu_age, stu_sex, school_name, homeland):
Student(stu_name, stu_age, stu_sex, school_name, homeland)
@staticmethod
def reset_user_pwd(name, role):
obj = getattr(sys.modules[__name__], role).get_obj(name)
obj.pwd = settings.INIT_PWD
obj.save_obj()
class School(FileMixin):
def __init__(self, name, addr):
self.name = name
self.addr = addr
self.course_list = []
self.save_obj()
def relate_course(self, course_name):
self.course_list.append(course_name)
self.save_obj()
class Course(FileMixin):
def __init__(self, name, period, price, school_name):
self.name = name
self.period = period
self.price = price
self.school = school_name
self.teacher = None
self.student_list = []
self.save_obj()
def relate_teacher(self, teacher_name):
self.teacher = teacher_name
self.save_obj()
def relate_student(self, stu_name):
self.student_list.append(stu_name)
self.save_obj()
class Teacher(FileMixin, Human):
def __init__(self, name, age, sex, level):
super().__init__(name, age, sex)
self.level = level
self.course_list = []
self.save_obj()
def select_course(self, course_name):
self.course_list.append(course_name)
self.save_obj()
course_obj = Course.get_obj(course_name)
course_obj.relate_teacher(self.name)
def check_my_courses(self):
return self.course_list
@staticmethod
def check_my_student(course_name):
course_obj = Course.get_obj(course_name)
return course_obj.student_list
@staticmethod
def set_score(stu_name, course_name, score):
stu_obj = Student.get_obj(stu_name)
stu_obj.score_dict[course_name] = int(score)
stu_obj.save_obj()
class Student(FileMixin, Human):
def __init__(self, name, age, sex, school_name, homeland):
super().__init__(name, age, sex)
self.school = school_name
self.homeland = homeland
self.course_list = []
self.score_dict = {}
self.save_obj()
def select_course(self, course_name):
self.course_list.append(course_name)
self.score_dict[course_name] = None
self.save_obj()
course_obj = Course.get_obj(course_name)
course_obj.relate_student(self.name)
def check_my_course(self):
return self.course_list
def check_my_score(self):
return self.score_dict
3.5 登录功能设计
每个角色都有登录需求,因此应抽取一个公用的登录逻辑接口层。不过因为数据存放格式的限制,这里妥协一下。每个登录视图层还是直接调用各自的登录逻辑接口,然后从各自的逻辑接口层中调用公用逻辑接口层的核心登录逻辑判断。这里在角色的登录接口中做一个中转的目的是为了给登录用户设置一个登录角色。并且这个角色的字符串名字和类的名字保持一致,为了方便在公共登录接口中使用反射判断。以下为设计样例:
def login_interface(name, pwd):
"""
登录接口
:param name:
:param pwd: 密码,密文
:return:
"""
from interface import common_interface
role = 'Admin'
flag, msg = common_interface.common_login_interface(name, pwd, role)
return flag, msg
def common_login_interface(name, pwd, role):
"""
登录接口
:param name:
:param pwd: 密码,密文
:param role: 角色,如,Admin|Teacher|Student
:return:
"""
if hasattr(models, role):
obj = getattr(models, role).get_obj(name)
if not obj:
return False, f'用户名[{name}]不存在'
if pwd != obj.pwd:
return False, '用户名或密码错误'
return True, '登录成功'
else:
return False, '您没有权限登录'
3.6 数据存放格式
将一个类实例化对象按照类型保存在不同的文件夹中,文件夹名与类名相同,文件名为对象的 name 属性的名字。这样做的好处是方便对象数据的读取和保存,并且对象间没有使用组合的方式,避免数据的重复保存。但这样做也有着不足:每个类下面的对象不能重名。这个问题需要重新组织数据管理方式,让其更实际化。
{school:{course:{'teacher': teacher, 'grade': grade}}}
3.7 程序结构图
四、测试结果
4.1 管理员功能
-
注册 -
登录 -
创建学校 -
创建课程 -
创建讲师
4.2 教师功能
-
教师登录 -
教师选择并查看课程 -
教师查看课程下学生名单 -
教师修改学生分数
4.3 学生功能
-
学生注册 -
学生登录 -
学生选择学校 -
学生选择课程 -
学生查看分数
五、总结
对于刚学习 python,拿到需求,首先要经过自己分析,确实搞不定了,可以查看一些优秀的代码学习总结归纳,从而转化为自己的能力。对于写程序,实现逻辑思路是很重要的,写项目前一定要先分析需求,再构思设计,最后开始编码。角色设计时,需要全面考虑角色之间的关系方便接口设计。基于反射可以做很多动态判断,避免使用ifelifelse 多级判断。三层架构的设计,明确每层职责,系统耦合度低,方便易开发。
六、参考文献
[1] 张艳,顾晨.实践课程网上选课系统的设计与实现[J].实验室研究与探索,2003(03):75-77.
[2] 韩春英. 高校选课系统的设计与实现[D].华东师范大学,2010.
[3] 盛蒙蒙. Python 程序设计课程综合实验案例设计[J]. 现代计算机, 2020, (20):70-73,95.
|