【python练习】选课系统(基于面向对象的)
程序功能:
本程序模拟实现了基于面向对象编写的一个选课系统程序:
1. 可以通过管理员创建学校、老师、学生、班级、课程: 2. 老师可以在管理员注册之后登陆,查看自己的信息,可以选择学校、班级、课程、为学生打分 3. 学生可以注册、登陆、查看自己的信息,可以缴纳学费
测试:
数据为空,需要手动创建添加,导入了一个prettytable包 1.需要先进入管理员,创建学校、老师、学生、班级、课程(需要先船舰课程,后创建班级) 2. 创建完老师,会自动注册(账号为手机号,密码为后6位) 3. 老师需要登陆后选择学校、班级(课程)
程序结构:
选课系统/ └── ├── README ├── bin # 选课系统 执行文件 目录 │ ├── init.py │ └── 选课系统.py # ATM 管理端 ├── conf # 配置文件 │ ├── init.py │ └── settings.py ├── core # 主要程序逻辑都 │ ├── init.py │ ├── db_handler.py # 数据的存储和加载 │ ├── logger.py # 日志记录模块 │ ├── login.py # 登陆模块 │ ├── main.py # 主逻辑交互程序 │ └── register.py # 注册模块 ├── db #用户数据存储的地方 │ ├── accounts # 存放登陆信息 │ └── data # 分别存放课程、班级、学校、老师、学生的文件夹 │ ├── courses # 课程 │ ├── grades # 班级 │ ├── schools # 学校 │ ├── students # 学生 │ └── teachers # 老师 └── logs #日志目录 ├── init.py └── access.log #用户访问和操作的相关日志
选课系统/bin/选课系统.py
# Author:q1.ang import os import sys BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) print(BASE_PATH) sys.path.append(BASE_PATH) from core import main if __name__ == '__main__': main.run()
选课系统/conf/settings.py
# Author:q1.ang import os import sys import logging sep = os.sep BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DB_PATH = '%s%sdb' % (BASE_PATH, sep) LOG_LEVEL = logging.INFO LOG_TYPES = { 'transaction': 'transaction.log', 'access': 'access.log' } # 两个tab的长度 tab = ' ' * 2 accounts_file = '' schools_file = 'schools' teachers_file = 'teachers' grades_file = 'grades' course_file = 'courses' students_file = 'students' pretty_table_school = [' ', '学校', '地址', '班级', '课程'] pretty_table_teacher_student = [' ', '姓名', '年龄', '性别', '电话号码', '学校', '班级', '课程'] pretty_table_grades = [' ', '班级', '学校', '讲师', '课程', '学员'] pretty_table_courses = [' ', '课程', '周期', '价格', '/'] pretty_table_student = [' ', '姓名', '年龄', '性别', '电话号码', '学校', '班级', '课程', '分数'] # 为Admin.info(*args)为Admin.del_instance(*args)传值 args = { '1': [schools_file, pretty_table_school], '2': [teachers_file, pretty_table_teacher_student], '3': [grades_file, pretty_table_grades], '4': [course_file, pretty_table_courses], '5': [students_file, pretty_table_teacher_student], }
选课系统/core/db_handle.py
import os import pickle from conf import settings tab = settings.tab # data = {'name': 'LuffySchool', 'address': '北京'} # # path = '{base_path}{sep}db{sep}data{sep}schools{sep}{file_name}.pkl'.format( # base_path=settings.BASE_PATH, sep=settings.sep, file_name='15667038228') def save(data, file, file_name): path = '{base_path}{sep}db{sep}data{sep}{file}{sep}{name}.pickle'.format( base_path=settings.BASE_PATH, sep=settings.sep, file=file, name=file_name) with open(path, 'wb') as f: pickle.dump(data, f) def read(file, file_name): path = '{base_path}{sep}db{sep}data{sep}{file}{sep}{name}.pickle'.format( base_path=settings.BASE_PATH, sep=settings.sep, file=file, name=file_name) with open(path, 'rb') as f: return pickle.load(f) def read_dir(file): path = '{base_path}{sep}db{sep}data{sep}{file}'.format( base_path=settings.BASE_PATH, sep=settings.sep, file=file) return os.listdir(r'%s' % path) def del_file(file, file_name): path = '{base_path}{sep}db{sep}data{sep}{file}{sep}{name}.pickle'.format( base_path=settings.BASE_PATH, sep=settings.sep, file=file, name=file_name) os.remove(path) print('删除成功')
选课系统/core/logger.py
import logging from conf import settings def logger(log_type): # 创建Logger logger = logging.getLogger(log_type) logger.setLevel(logging.INFO) # 创建handler ch = logging.StreamHandler() ch.setLevel(logging.INFO) path = '%s\\logs\\%s' % (settings.BASE_PATH,settings.LOG_TYPES[log_type]) fh = logging.FileHandler(path) fh.setLevel(logging.INFO) # 定义formatter formatter = logging.Formatter('%(asctime)s-%(name)s-%(levelname)s %(message)s') # 绑定formatter ch.setFormatter(formatter) fh.setFormatter(formatter) # 添加handle对象 # logger.addHandler(ch) logger.addHandler(fh) return logger
选课系统/core/login.py
# Author:q1.ang #!/usr/bin/env python # -*- coding: utf-8 -*- import os import pickle from core import logger BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def authentication(): ''' 用户认证程序 :return:登陆成功返回True ''' acc = '' auth_list = [] # 用于存放每次输入的用户名 print('- - - Login - - -') while auth_list.count(acc) < 3: # 当相同的用户名登陆失败3次,跳出循环 acc = input('Account:').strip() pwd = input('Password:').strip() path = '%s\\db\\accounts\\%s.pickle' % (BASE_PATH, acc) # 用户信息存放路径 if os.path.isfile(path): # 用户名是否存在 with open(path, 'rb') as f: acc_data = pickle.load(f) # 读取信息 if acc_data['password'] == pwd: print('Login success !') logger.logger('access').info('Login %s' % acc) return acc else: auth_list.append(acc) logger.logger('access').info('Login %s - Password Error' % acc) print('The password is error,you have %s times' % (3 - auth_list.count(acc))) else: auth_list.append(acc) print('The account is not exist...you have %s times' % (3 - auth_list.count(acc)))
选课系统/core/main.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import re from conf import settings from core import db_handle from core import register from core import login from prettytable import PrettyTable tab = settings.tab class School(object): def __init__(self, name, address): self.name = name self.address = address self.grade = [] self.course = [] self.student = [] self.save_name = self.name + '_' + self.address def info(self, num, table): table.add_row( [num, self.name, self.address , ','.join(list(map(lambda x: x.grade, self.grade))) , ','.join(list(map(lambda x: x.course, self.course)))]) def create_grade(self, grade): self.grade.append(Grade(grade)) def create_course(self, course, period, price): self.course.append(Course(course, period, price)) def bind_student(self): pass class People(object): def __init__(self, name, age, sex, phone): self.name = name self.age = age self.sex = sex self.phone = phone self.save_name = self.phone class Teachers(People): db_file = settings.teachers_file def __init__(self, name, age, sex, phone): super(Teachers, self).__init__(name, age, sex, phone) self.save_name = self.phone self.school = School('', '') self.grade = [] self.course = [] def info(self, num, table): table.add_row( [num, self.name , self.age, self.sex , self.phone, self.school.name + ' ' + self.school.address , ','.join(list(map(lambda x: x.grade, self.grade))) , ','.join(list(map(lambda x: x.course, self.course)))]) def bind_instance(self, bind_type, *args): """ 绑定实例 :param bind_type: 需要绑定的类型 :param args: 传入筛选后的实例 :return: 再次筛选后的实例 """ if len(args) > 0: # 如果传入筛选后的实例 if bind_type == '3': # '3'为班级 table = PrettyTable(settings.pretty_table_grades) # 实例化PrettyTable for index, obj in enumerate(args[0].grade, 1): # args[0].grade为筛选后实例的grade参数 obj.info(index, table) # 调用实例的info方法 print(table) # 打印输出 list_data = list(map(lambda x: x.grade, args[0].grade)) # 生成一个由文件名组成的列表 else: # 没有传入筛选后的实例,调用Admin.info_instance()打印输出并返回由文件名组成的列表 list_data = Admin.info_instance(settings.args[bind_type][0], settings.args[bind_type][1]) while True: inp = input('输入要关联的序号[q返回]:').strip() if inp == 'q': break elif inp.isdigit() and 0 < abs(int(inp)) <= len(list_data): instance = db_handle.read(settings.args[bind_type][0], list_data[int(inp) - 1]) # 读取需要管关联的实例 if bind_type == '1': self.school = instance # 此时instance为School的一个实例,self.school在每次实例Teacher的时候都会赋值 instance.teacher = self.name + ' ' + self.phone # 此时instance为Grade的一个实例 if db_handle.save(instance, settings.schools_file, instance.save_name) != 0: print('数据更新成功') elif bind_type == '3': self.grade.append(instance) # 为自己绑定班级 self.course.append(instance.course) instance.teacher = self.name+' '+self.phone # 此时instance为Grade的一个实例 if db_handle.save(instance, settings.grades_file, instance.save_name) != 0: print('数据更新成功') print('关联成功') return instance else: print('输入错误') class Students(People): db_file = settings.students_file def __init__(self, name, age, sex, phone): super(Students, self).__init__(name, age, sex, phone) self.save_name = self.phone # self.fees = [] # 学费,0代表未交学费 self.score = 0 self.grade = [] self.course = [] def info(self, num, table): table.add_row( [num, self.name , self.age, self.sex , self.phone, self.school.name + ' ' + self.school.address , ','.join(list(map(lambda x: x.grade, self.grade))) , ','.join(list(map(lambda x: x.course, self.course))) , self.score]) def bind_instance(self, bind_type, *args): """ 绑定实例 :param bind_type: 需要绑定的类型 :param args: 传入筛选后的实例 :return: 再次筛选后的实例 """ if len(args) > 0: # 如果传入筛选后的实例 if bind_type == '3': # '3'为班级 table = PrettyTable(settings.pretty_table_grades) # 实例化PrettyTable for index, obj in enumerate(args[0].grade, 1): # args[0].grade为筛选后实例的grade参数 obj.info(index, table) # 调用实例的info方法 print(table) # 打印输出 list_data = list(map(lambda x: x.grade, args[0].grade)) # 生成一个由文件名组成的列表 else: # 没有传入筛选后的实例,调用Admin.info_instance()打印输出并返回由文件名组成的列表 list_data = Admin.info_instance(settings.args[bind_type][0], settings.args[bind_type][1]) while True: inp = input('输入要关联的序号[q返回]:').strip() if inp == 'q': break elif inp.isdigit() and 0 < abs(int(inp)) <= len(list_data): instance = db_handle.read(settings.args[bind_type][0], list_data[int(inp) - 1]) # 读取需要管关联的实例 if bind_type == '1': self.school = instance # 此时instance为School的一个实例,self.school在每次实例Teacher的时候都会赋值 # 更新instance instance.student.append(self) # 此时instance为School的一个实例 if db_handle.save(instance, settings.schools_file, instance.save_name) != 0: print('数据更新成功') elif bind_type == '3': self.grade.append(instance) # 为自己绑定班级 self.course.append(instance.course) # 绑定课程 # self.fees.append(instance.course.price+' '+instance.course.price) # 添加课程学费 # 更新instance instance.student.append(self) # 此时instance为Grade的一个实例 if db_handle.save(instance, settings.grades_file, instance.save_name) != 0: print('数据更新成功') print('关联成功') return instance else: print('输入错误') class Course(object): def __init__(self, course, period, price): self.course = course self.period = period self.price = price self.fees_flag = ' ' self.save_name = self.course def info(self, num, table): table.add_row([num, self.course, self.period, self.price, self.fees_flag]) class Grade(object): def __init__(self, grade): self.grade = grade self.teacher = '' self.course = '' self.student = [] self.save_name = self.grade def info(self, num, table): table.add_row( [num, self.grade , self.school.name + ' ' + self.school.address , self.teacher, self.course.course , ','.join(list(map(lambda x: x.name, self.student)))]) class Admin(object): def __init__(self, admin): self.admin = admin self.menu = { '1-学校管理': { '1-浏览学校': 'info_instance', '2-创建学校': 'create_school', '3-删除学校': 'del_instance', 'q-退出': 'quit' }, '2-讲师管理': { '1-浏览讲师': 'info_instance', '2-创建讲师': 'create_teacher', '3-删除讲师': 'del_instance', 'q-退出': 'quit' }, '3-班级管理': { '1-浏览班级': 'info_instance', '2-创建班级': 'create_grades', '3-删除班级': 'del_instance', 'q-退出': 'quit' }, '4-课程管理': { '1-浏览课程': 'info_instance', '2-创建课程': 'create_course', '3-删除课程': 'del_instance', 'q-退出': 'quit' }, 'q-退出': 'quit' } def interactive(self): while True: # 第一级菜单显示 for i in self.menu.keys(): print(i, end=' ') inp = input('\n>>>').strip() # 第二级菜单显示 if inp.isdigit(): if 0 < int(inp) <= len(self.menu): new_menu = list(self.menu.items())[int(inp) - 1][1] for i1 in new_menu: print(i1, end=' ') else: print('\031[0;33m输入超出范围\033[0m') continue elif inp == 'q': return else: print('\031[0;33m输入错误,重新输入\033[0m') continue inp1 = input('\n>>>').strip() # 选择跳转 if inp1.isdigit(): inp1 = int(inp1) if 0 < inp1 <= len(new_menu): choose = list(new_menu.items())[inp1 - 1][1] getattr(Admin(self.admin), choose)(settings.args[inp][0], settings.args[inp][1]) elif inp1 == 'q': continue else: print('\031[0;33m输入错误,重新输入\033[0m') @staticmethod def info_instance(*args): instance_file = args[0] # 需要遍历的文件夹名称 pretty_table_instance = args[1] # 打印表格表头形式 table = PrettyTable(pretty_table_instance) # 实例化PrettyTable file_list = db_handle.read_dir(instance_file) # 返回文件夹内文件名的列表 num = 1 # b表格第一列的序号 list_data = [] # 保存文件的实例,用于返回 for i in file_list: instance = db_handle.read(instance_file, i.split('.')[0]) instance.info(num, table) list_data.append(instance.save_name) num += 1 print(table) return list_data def del_instance(self, *args): instance_file = args[0] pretty_table_instance = args[1] list_data = self.info_instance(instance_file, pretty_table_instance) while True: inp = input('\033[0;32m输入要删除的序号[q返回]:\033[0m').strip() if inp == 'q': break elif inp.isdigit() and 0 < abs(int(inp)) <= len(list_data): db_handle.del_file(instance_file, list_data[int(inp) - 1]) break else: print('\033[0;31m输入错误\033[0m') def create_school(self, *args): name = input('\033[0;32m学校:\033[0m').strip() address = input('\033[0;32m地址[北京\上海]:\033[0m').strip() # 实例化学校,保存对象 school = School(name, address) if db_handle.save(school, settings.schools_file, school.save_name) != 0: print('\033[0;33m创建成功\033[0m') def create_teacher(self, *args): print('\033[0;33m- - - 填写资料 - - - \033[0m') while True: name = input('\033[0;32m姓名[中文/英文]:\033[0m').strip() if not name.isalpha(): print('\033[0;31m格式错误[只能为中文/英文]\033[0m') continue else: break while True: age = input('\033[0;32m年龄[1~99数字]:\033[0m').strip() if age.isdigit() and (len(age) <= 2): break else: print('\033[0;31m格式错误[1~99数字]\033[0m') continue while True: sex = input('\033[0;32m性别[男/女]:\033[0m').strip() if sex == '男' or sex == '女': break else: print('\033[0;31m格式错误[男/女]\033[0m') continue if len(args) < 3: while True: phone = input('\033[0;32m电话号码[11位数字]:\033[0m').strip() if not re.fullmatch('\d{11}', phone): print('\033[0;31m格式错误[11位数字]\033[0m') continue else: break else: phone = args[3] # 实例化老师/学员,绑定学校 instance = Teachers(name, age, sex, phone) # print('选择学校:') # filter_instance = instance.bind_instance('1') # print('选择班级:') # instance.bind_instance('3', filter_instance) if db_handle.save(instance, instance.db_file, phone) != 0: print('\033[0;33m创建成功\033[0m') register.register(phone) print('\033[0;36m账号为手机号,密码为手机号后6位\033[0m') def create_grades(self, *args): list_data = self.info_instance(settings.schools_file, settings.pretty_table_school) while True: inp = input('\033[0;32m请选择学校:\033[0m').strip() if inp.isdigit() and 0 < abs(int(inp)) <= len(list_data): school = db_handle.read(settings.schools_file, list_data[int(inp) - 1]) # school.address,是一个参数 break else: print('\033[0;31m输入错误\033[0m') # 打印筛选后的school table = PrettyTable(settings.pretty_table_courses) # 实例化PrettyTable for index, obj in enumerate(school.course, 1): # 筛选后实例的course参数 obj.info(index, table) # 调用实例的info方法 print(table) # 打印输出 list_data = list(map(lambda x: x.course, school.course)) # 生成一个由文件名组成的列表 while True: inp = input('\033[0;32m请绑定课程:\033[0m').strip() if inp == 'q': return if inp.isdigit() and 0 < abs(int(inp)) <= len(list_data): course = db_handle.read(settings.course_file, list_data[int(inp) - 1]) # course.course,是一个参数 break else: print('\033[0;31m输入错误\033[0m') while True: grade = input('\033[0;32m班级[数字]:\033[0m').strip() if grade == 'q': return if grade.isdigit(): # 学校实例,保存对象 school.create_grade(grade) # 通过School()的实例创建班级 school.grade[-1].school = school # 为grade绑定学校信息,school.grade[-1]为Grade实例 school.grade[-1].course = course # 为grade绑定课程信息 if db_handle.save(school.grade[-1], settings.grades_file, grade) != 0: print('\033[0;33m创建成功\033[0m') if db_handle.save(school, settings.schools_file, school.save_name) != 0: print('\033[0;33m数据更新成功\033[0m') break else: print('\033[0;31m请输入数字\033[0m') def create_course(self, *args): list_data = self.info_instance(settings.schools_file, settings.pretty_table_school) while True: inp = input('\033[0;32m请选择学校[q返回]:\033[0m').strip() if inp == 'q': break elif inp.isdigit() and 0 < abs(int(inp)) <= len(list_data): school = db_handle.read(settings.schools_file, list_data[int(inp) - 1]) break else: print('\033[0;31m输入错误\033[0m') while True: course = input('\033[0;32m课程:\033[0m').strip() if course.isalnum(): if course.lower() == 'go' and school.address == '上海': break elif (course.lower() == 'python' or course.lower() == 'linux') and school.address == '北京': break else: print('\033[0;31m%s没有开设%s课程\033[0m' % (school.address, course)) continue else: print('\033[0;31m输入错误\033[0m') period = input('\033[0;32m周期[数字、月]:\033[0m').strip() price = input('\033[0;32m价格[数字]:\033[0m').strip() if period.isdigit() and price.isdigit(): # 实例化学校,保存对象 school.create_course(course, period, price) school.course[-1].school = school if db_handle.save(school.course[-1], settings.course_file, course) != 0: print('\033[0;33m创建成功\033[0m') if db_handle.save(school, settings.schools_file, school.save_name) != 0: print('\033[0;33m数据更新成功\033[0m') else: print('\033[0;31m请输入数字\033[0m') class TeacherInterface(object): def teacher_interactive(self): acc = login.authentication() if acc.isdigit(): instance = db_handle.read(settings.teachers_file, acc) while True: print('\n1-浏览信息 2-选择学校 3-选择班级 4-打分 q-退出') inp1 = input('>>>') if inp1 == '1': table = PrettyTable(settings.pretty_table_teacher_student) # 实例化PrettyTable instance.info(1, table) # 调用实例的info方法 print(table) # 打印输出 elif inp1 == '2': print('选择学校:') instance.bind_instance('1') db_handle.save(instance, settings.teachers_file, acc) continue elif inp1 == '3': print('选择班级:') instance.bind_instance('3', instance.school) db_handle.save(instance, settings.teachers_file, acc) continue elif inp1 == '4': self.give_score() elif inp1 == 'q': return else: print('输入错误') else: return def give_score(self): # 获得学员的实例列表 instance_list = Admin.info_instance(settings.students_file, settings.pretty_table_student) student = input('选择学生:').strip() if student == 'q': return elif student.isdigit() and 0 < abs(int(student)) <= len(instance_list): instance = db_handle.read(settings.args['5'][0], instance_list[int(student) - 1]) # 读取需要管关联的实例 score = input('分数:') instance.score = score # 此时instance为Student的一个实例 if db_handle.save(instance, settings.students_file, instance.save_name) != 0: print('数据更新成功') class StudentInterface(object): def student_interactive(self): while True: print('1-登陆 2-注册 q-退出') inp1 = input('>>>') if inp1 == '1': acc = login.authentication() if acc.isdigit(): instance = db_handle.read(settings.students_file, acc) while True: print('1-查看信息 2-缴纳学费 q-退出') inp2 = input('>>>') if inp2 == '1': table = PrettyTable(settings.pretty_table_student) # 实例化PrettyTable instance.info(1, table) # 调用实例的info方法 print(table) # 打印输出 elif inp2 == '2': table = PrettyTable(settings.pretty_table_courses) # 实例化PrettyTable for index, obj in enumerate(instance.course, 1): # args[0].grade为筛选后实例的grade参数 obj.info(index, table) # 调用实例的info方法 print(table) # 打印输出 inp3 = input('输入要缴纳的课程[q退出]:').strip() if inp3.isdigit() and 0 < int(inp3) <= len(instance.course): instance.course[int(inp3)-1].fees_flag = '已缴纳' db_handle.save(instance, settings.students_file, acc) print('缴纳成功') elif inp3 == 'q': break else: print('输入错误') elif inp2 == 'q': break else: print('输入错误') elif inp1 == '2': phone = register.register() self.create_student(phone) elif inp1 == 'q': return else: print('输入错误') def create_student(self, phone): print('- - - 填写资料 - - -') while True: name = input('姓名[中文/英文]:').strip() if not name.isalpha(): print('格式错误[只能为中文/英文]') continue else: break while True: age = input('年龄[1~99数字]:').strip() if age.isdigit() and (len(age) <= 2): break else: print('格式错误[1~99数字]') continue while True: sex = input('性别[男/女]:').strip() if sex == '男' or sex == '女': break else: print('格式错误[男/女]') continue # 实例化学员,绑定学校 instance = Students(name, age, sex, phone) print('选择学校:') filter_instance = instance.bind_instance('1') print('选择班级:') instance.bind_instance('3', filter_instance) if db_handle.save(instance, instance.db_file, phone) != 0: print('创建成功') def run(): print('\033[0;33m%s- - - 欢迎来到课程管理系统 - - - \033[0m' % tab) while True: inp = input(''' 1. 管理员 2. 讲师 3. 学生 - - - - - - >>>''').strip() if inp == '1': admin = Admin('admin') admin.interactive() elif inp == '2': teacher_interface = TeacherInterface() teacher_interface.teacher_interactive() elif inp == '3': student_interface = StudentInterface() student_interface.student_interactive()
选课系统/core/register.py
# Author:q1.ang from conf import settings import pickle import re import os tab = settings.tab def register(*args): print('- - - 注册账号 - - - ') if len(args) == 0: while True: acc = input('输入手机号:').strip() if not re.fullmatch('\d{11}', acc): print('格式错误[11位数字]') continue else: break while True: pwd = input('输入密码[大于6位的英文大小写和数字]:').strip() if len(pwd) < 6: print('密码应为大于6位的英文大小写和数字') continue else: break else: acc = args[0] pwd = args[0][-6:] # 后六位,默认密码 path = '{db_path}{sep}accounts{sep}{acc}.pickle'.format( db_path=settings.DB_PATH, sep=settings.sep, acc=acc) if os.path.isfile(path): cover_flag = input('账号已经存在,是否覆盖(Y/N)').strip() if cover_flag == 'N': return 0 elif cover_flag == 'Y': pass else: print('输入错误') data = { 'account': acc, 'password': pwd } with open(path, 'wb') as f: pickle.dump(data, f) print('注册成功') return acc
选课系统/db/accounts/...
选课系统/db/data/...
选课系统/logs/...