[Python语言程序设计]课程选课系统

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Python语言程序设计]课程选课系统相关的知识,希望对你有一定的参考价值。

项目文件:课程选课系统
开发环境:PyCharm 2021.1.1

文章目录

一、任务描述及数据

主要任务:三人自由组合完成一个课程系统

角色: 学校、学员、课程、讲师、管理员

1.1 要求

  1. 创建武汉、长沙 2 所学校 (管理员创建学校)
  2. 创建 Linux、Python、C 语言 3 门课程 , Linux、Python 在武汉开课, C 语言在长沙开课
  3. 课程包含:周期、价格(通过学校创建课程)
  4. 提供两个角色接口:创建讲师、创建学员时需选择学校,关联班级
  5. 学员视图,可以注册,选择课程(等同于选择班级)
  6. 讲师视图,讲师可管理自己的课程,上课时选择班级,查看班级学员列表 , 修改所管理的学员的成绩
  7. 管理视图,创建讲师, 创建班级,创建课程
  8. 上面的操作产生的数据都通过pickle序列化保存到文件里(pickle 可以帮我们保存对象)

二、需求分析及实现路线

2.1 管理视图

  1. 注册

    (1)用户在视图层输入用户名与密码交给接口层

    (2)接口层调用数据层的 models.select 进行校验

    (3)若不存在,则创建,并将注册成功返回给用户视图层

  2. 登录

    (1)用户在视图层输入用户名与密码交给接口层

    (2)接口层调用数据层的 models.select 进行校验

    (3)若存在则校验密码,并将登录成功返回给用户视图层

  3. 创建学校

    (1)让用户输入学校名与学校地址

    (2)调用管理员创建学校接口

    (3)判断学校是否存在,若存在,不让创建

    (4)若不存在,则调用接口层创建学校,获取对象的创建学校方法,保存学校

    (5)将结果返回给视图层

  4. 创建课程(先选择学校)

    (1)获取所有学校并打印,让用户选择

    (2)获取用户选择的学校与创建的课程交给接口层

    (3)接口层调用管理员对象中的创建课程方法,保存课程对象

    (4)课程需要绑定给学校对象,最终将创建成功的结果返回给视图层

  5. 创建讲师

    (1)让用户输入老师的名称

    (2)调用接口层,接口层中设置默认密码 123,调用数据层

    (3)判断老师是否存在,不存在则调用管理员对象中的创建老师方法

    (4)保存老师对象并将结果返回给视图层

2.2 学员视图

  1. 注册

  2. 登录

  3. 选择校区

    (1)获取所有学校让学生选择,将选择的学校传给接口层

    (2)接口层判断当前学生是否选择学校

    (3)若没有选择,则调用学生对象中的添加学校方法

    (4)将添加后的消息返回给视图层

  4. 选择课程(先选择校区,再选择校区中的某一门课程),学生与课程互相选择

    (1)先获取当前学生所在学校的所有课程并选择

    (2)接口层将选择后的学生课程,调用数据层的添加课程方法,保存

    (3)学生对象中的课程列表添加该课程,设置课程分数默认为 0

    (4)最终将结果返回给视图层

  5. 查看分数

    (1)直接调用接口层

    (2)接口层调用数据层的查看成绩方法

    (3)返回成绩给视图层并打印

2.3 讲师视图

  1. 登录

  2. 查看授课课程

    (1)直接调用接口层,获取老师对象下课程列表数据

    (2)若有则打印,没有则退出

  3. 选择教授课程

    (1)调用接口层中的选择教授课程接口,调用数据层中该课程下所有的学生返回给视图层

    (2)打印所有的课程让老师选择,若老师课程中有该课程则不让添加

    (3)没有则调用老师中的添加课程方法进行添加

  4. 查看课程下的学生名单

    (1)直接获取老师对象下所有的课程,选择课程

    (2)从老师对象中,调用查看课程下学生方法,获取课程对象下所有的学生,返回给视图层

    (3)视图层打印,该课程下所有的学生

  5. 修改学生分数

    (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类,视图类继承之,方便传参。

# BaseView.py
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
               
# 学生视图 student.py
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)

# 角色基类 Human
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

# 管理员类 Admin
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()

# 学校类 School
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()

# 课程类 Course
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()

# 教师类 Teacher
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()

# 学生类 Student
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 登录功能设计

每个角色都有登录需求,因此应抽取一个公用的登录逻辑接口层。不过因为数据存放格式的限制,这里妥协一下。每个登录视图层还是直接调用各自的登录逻辑接口,然后从各自的逻辑接口层中调用公用逻辑接口层的核心登录逻辑判断。这里在角色的登录接口中做一个中转的目的是为了给登录用户设置一个登录角色。并且这个角色的字符串名字和类的名字保持一致,为了方便在公共登录接口中使用反射判断。以下为设计样例:

# admin_interface.py
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

# common_interface.py
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 属性的名字。这样做的好处是方便对象数据的读取和保存,并且对象间没有使用组合的方式,避免数据的重复保存。但这样做也有着不足:每个类下面的对象不能重名。这个问题需要重新组织数据管理方式,让其更实际化。

# json 格式数据存放格式
school:course:'teacher': teacher, 'grade': grade

3.7 程序结构图

四、测试结果

4.1 管理员功能

  1. 注册

  2. 登录

  3. 创建学校

  4. 创建课程

  5. 创建讲师

4.2 教师功能

  1. 教师登录

  2. 教师选择并查看课程

  3. 教师查看课程下学生名单

  4. 教师修改学生分数

    Python开发程序:选课系统

    Python开发程序:选课系统-改良版

    Python选课系统

    Python 选课系统

    python模拟选课系统

    选课系统作业