題目要求
利用規范化目錄結構完成一個學生選課系統。
角色:學生、管理員。
功能分析:
用戶登錄之后就可以直接判斷用戶身份,是學生還是管理員。
學生登錄之后有以下幾個功能:
查看所有課程。
選擇課程。
查看所選課程。
退出程序。
管理員登錄之后有以下幾個功能:
創建課程(需要記錄日志)。
創建學生賬號(需要記錄日志)。
查看所有課程。
查看所有學生。
查看所有學生的選課情況。
退出程序。
課程屬性:課程名,價格,周期,老師。
學生屬性:姓名,所選課程。
管理員屬性:姓名。
start
import os
import sys
BATH_DIR = os.path.dirname(os.path.dirname(file))
sys.path.append(BATH_DIR)
添加搜索路徑 確定當前工程的搜索路徑 定位到 老師版本學生選課系統,可千萬不能變成每周大作業了 為了查找具體的路徑
from core import src
from core.src import Course
from core.src import Student
if name == 'main':
src.main()
src
import sys
import os
import pickle
from lib import common
from conf import settings
class Base: #
def show_courses(self):
with open(settings.COURSE_PATH, mode='rb') as f:
num = 0
while 1:
try:
num += 1
obj = pickle.load(f) # # obj獲取每行的課程 每行課程是一個對象
print(f'{num}: {obj.name} {obj.price} {obj.period}')# 打印對應的課程以及序列號
'''
1: Python周末班 12000 6個月
2: python脫產班 23000 6個月
3: 大數據 13000 4個月
4: java 12000 5個月
5: 測試 12000 6個月
6: 算法工程師 36000 6個月
7: 前端 12000 4個月
8: 平面設計 2345 六個月
'''
except EOFError: # 讀完之后直接停止,防止循環讀內容報錯
break
def exit(self): # 使用這個函數退出,並且打印退出提示信息
sys.exit(f'\033[0;32m感謝{self.name}使用選課系統!\033[0m')
# sys.exit所用是退出並且打印內容,是sys模塊里面的一個功能
class Student(Base):
operate_lst = [('查看可選課程', 'show_courses'),
('選擇課程', 'select_course'),
('查看所選課程', 'show_selected_course'),
('退出', 'exit')] # 靜態屬性
def __init__(self, name):
self.name = name # 定義兩個屬性
self.courses = [] # 空列表,為了后面添加課程
def select_course(self):
"""選擇課程"""
self.show_courses()# 先把課程打印出來 調用父類方法
try:
choice_num = input('\033[0;32m請輸入要選擇的課程序號:\033[0m').strip()
# 輸入要添加的課程序號
with open(settings.COURSE_PATH, mode='rb') as f:
for i in range(int(choice_num)):
# 根據輸入的序號確定循環次數最終鎖定選擇的課程對象
# 比如 range(5) 循環 5 次
obj = pickle.load(f) ## 拿出最后一個 最后被覆蓋 # load讀出來 用obj接收所選的課程對象
self.courses.append(obj) # 將課程添加到里面
print(f'\033[0;32m您已經成功添加了{obj.name}課程\033[0m')
# 把所選的課程添加到學生對象所對應的課程列表中
except Exception:
print('輸入有誤....')
def show_selected_course(self):
"""查看所選課程"""
print(f'\033[0;32m您已報名如下課程\033[0m')
for obj_course in self.courses:
# 循環此學生對象的列表,然后把所有課程的打印,每個課程也是一個對象
print(f'\033[0;32m課程名:{obj_course.name},課程價格:{obj_course.price} 課程周期:{obj_course.period}\033[0m')
def exit(self):
"""退出之前要將學生選擇的課程添加上"""
with open(settings.STUDENT_PATH, mode='rb') as f1, \
open(f'{settings.STUDENT_PATH}_bak', mode='wb') as f2:
'''打開兩個文件,讀舊文件,寫新文件'''
while 1: # 循環讀內容
try:
obj = pickle.load(f1)
pickle.dump(self if obj.name == self.name else obj, f2)
''' 把相應的對象名修改成現在的,其他的對象名直接寫入
'''
except EOFError:
break
os.remove(settings.STUDENT_PATH)
os.rename(f'{settings.STUDENT_PATH}_bak', settings.STUDENT_PATH) # 改名字
super().exit()
@classmethod
def get_obj(cls, username):
"""此方法是區別學生與管理員登錄,學生登錄就會去文件中取對象,管理員登錄則直接userinfo獲取管理員用戶名密碼"""
with open(settings.STUDENT_PATH, mode='rb') as f1:
while 1:
try:
obj = pickle.load(f1)
print(obj)
if username == obj.name:
return obj
except EOFError:
break
class Manager(Base):
operate_lst = [('創建課程', 'create_course'),
('創建學生', 'create_student'),
('查看可選課程', 'show_courses'),
('查看所有學生', 'show_students'),
('查看所有學生選課情況', 'show_students_courses'),
('退出', 'exit')]
def __init__(self, name):
self.name = name
def create_course(self):
"""創建課程"""
course = getattr(sys.modules[__name__], 'Course')
name, price, period = input('請依次輸入課程名,價格以及課程周期,以|分割').strip().split('|')
obj = course(name, price, period)
with open(settings.COURSE_PATH, mode='ab') as f1:
pickle.dump(obj, f1)
logger = common.record_logger()
logger.info(f'成功創建{name}課程')
def create_student(self):
"""創建學生"""
student_username = input('\033[0;32m 請輸入學生姓名:\033[0m').strip()
student_password = input('\033[0;32m 請輸入學生密碼:\033[0m').strip()
student_pwd_md5 = common.hashlib_md5(student_password)
with open(settings.USERINFO_PATH, encoding='utf-8', mode='a') as f1:
f1.write(f'\n{student_username}|{student_pwd_md5}|Student')
with open(settings.STUDENT_PATH, mode='ab') as f:
obj = getattr(sys.modules[__name__], 'Student')(student_username)
pickle.dump(obj, f)
logger = common.record_logger()
logger.info(f'成功您已成功創建學生賬號:{student_username},初始密碼:{student_password}')
def show_students(self):
"""查看所有學生"""
with open(settings.STUDENT_PATH, mode='rb') as f1:
while 1:
try:
obj = pickle.load(f1)
print(obj.name)
except EOFError:
break
def show_students_courses(self):
"""查看所有學生選課情況"""
with open(settings.STUDENT_PATH, mode='rb') as f1:
while 1:
try:
obj = pickle.load(f1)
print(f'\033[0;32m學生:{obj.name},所選課程:\
{["%s-%s-%s" %(course.name,course.price,course.period) for course in obj.courses]}\033[0m')
except EOFError:
break
def exit(self):
"""退出"""
super().exit()
@classmethod
def get_obj(cls, username):
return Manager(username) # 實例化的過程
class Course:
def init(self, name, price, period):
self.name = name
self.price = price
self.period = period
self.teacher = None
def login():
"""登陸邏輯,此處是用了單次登陸驗證,你也可以根據自己的需求改成三次登陸失敗才返回False"""
count = 1
while count < 4:
username = input('請輸入用戶名:').strip()
password = input('請輸入密碼:').strip()
pwd_md5 = common.hashlib_md5(password) # 這一步是加密
with open(settings.USERINFO_PATH,mode='r', encoding='utf-8') as f1:
for line in f1: # 一行一行讀取
if not line.strip(): # 如果是空字符串,不空就是 True 繼續執行
continue # 繼續走 for
user, pwd, identify = line.strip().split('|') #去掉結尾\n 分割之后是列表
if user == username and pwd == pwd_md5:
return {'username': user, 'identify': identify, 'auth': True} # 成功了 這個狀態就是True
else:
print('用戶名或者密碼錯誤,請重新輸入')
count += 1
return {'username': username, 'identify': None, 'auth': False} # 失敗了就把狀態改為 False
def main():
print('\033[0;32m歡迎訪問選課系統,請先登錄\033[0m')
dict_auth = login()
print(f"\033[0;32m登陸成功,歡迎{dict_auth['username']},您的身份是{dict_auth['identify']}\033[0m")
if dict_auth['auth']:
'''根據不同的身份,進行相應的操作,登錄成功狀態就改為 True'''
if hasattr(sys.modules[name], dict_auth['identify']): # 必須找到這個類,才能調用后續的方法
'''反射到本模塊,找到 identify 對應的類 反射 判斷對象是否存在,存在就是返回 True 否則 False'''
cls = getattr(sys.modules[name], dict_auth['identify']) # 得到類名地址
'''
如果是管理者登錄:
obj = cls(dict_auth['username'])
如果是學生登錄:
從Student文件中 讀取該學生對象並返回:
打開文件,讀取文件: pickle.load(f) dic_auth['username'] == obj.name
obj = 文件中讀取的學生對象
'''
obj = cls.get_obj(dict_auth['username']) # 管理員與學生都定義了此方法,鴨子類型.
while 1:
for num, option in enumerate(cls.operate_lst, 0): # 類名. 這個萬能的點 # num 將元組解構
'''
operate_lst = [('創建課程', 'create_course'),
('創建學生', 'create_student'),
('查看可選課程', 'show_courses'),
('查看所有學生', 'show_students'),
('查看所有學生選課情況', 'show_students_courses'),
('退出', 'exit')]
'''
print(f'{num+1}: {option[0]}')
'''
輸出是這個東東
1: 創建課程
2: 創建學生
3: 查看可選課程
4: 查看所有學生
5: 查看所有學生選課情況
6: 退出
num 的作用就是解構,坤坤說這些都是舊的知識點,我的媽呀,難受,竟然都不知道
'''
choice_num = int(input('\033[0;32m 請輸入選項:\033[0m').strip())
getattr(obj, cls.operate_lst[choice_num - 1][1])() # getattr(obj,'create_course')() 已經調用這個函數了
else:
print('三次驗證失敗,系統自動退出')
return False
settings
import os
import logging.config
BATH_DIR = os.path.dirname(os.path.dirname(file))
USERINFO_PATH = os.path.join(BATH_DIR, 'db', 'user_info')
COURSE_PATH = os.path.join(BATH_DIR, 'db', 'Course')
STUDENT_PATH = os.path.join(BATH_DIR, 'db', 'Student')
LOGGING_PATH = os.path.join(BATH_DIR, 'log', 'admin.log')
SIMPLE_FORMAT = '[%(asctime)s] %(message)s'
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': SIMPLE_FORMAT,
},
},
'filters': {},
'handlers': {
# 打印到終端的日志
'stream': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
# 打印到文件的日志,收集info及以上的日志
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'simple',
'filename': LOGGING_PATH, # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小 5M
'backupCount': 5,
'encoding': 'utf-8', # 日志文件的編碼,再也不用擔心中文log亂碼了
},
},
'loggers': {
# logging.getLogger(__name__)拿到的logger配置
'': {
'handlers': ['stream', 'file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
common
import hashlib
import logging.config
from conf.settings import LOGGING_DIC
def hashlib_md5(password):
"""密碼加密"""
ret = hashlib.md5()
ret.update(password.encode('utf-8'))
return ret.hexdigest()
def record_logger():
"""記錄日志"""
logging.config.dictConfig(LOGGING_DIC) # 導入上面定義的logging配置
logger = logging.getLogger() # 生成一個log實例
return logger
