def generate(name: str, courses: Dict[Tuple[int, int], List[Dict]], semester: Semester, ics_token: str): """ 生成 ics 文件并保存到目录 :param name: 姓名 :param courses: 参与的课程 :param semester: 当前导出的学期 :param ics_token: ics 令牌 :return: None """ semester_string = semester.to_str(simplify=True) semester = semester.to_tuple() # 创建 calender 对象 cal = Calendar() cal.add('prodid', '-//Admirable//EveryClass//EN') cal.add('version', '2.0') cal.add('calscale', 'GREGORIAN') cal.add('method', 'PUBLISH') cal.add('X-WR-CALNAME', name + '的' + semester_string + '课表') cal.add('X-WR-TIMEZONE', 'Asia/Shanghai') # 时区 tzc.add_component(tzs) cal.add_component(tzc) # 创建 events for time in range(1, 7): for day in range(1, 8): if (day, time) in courses: for course in courses[(day, time)]: for week in course['week']: dtstart = _get_datetime(week, day, get_time(time)[0], semester) dtend = _get_datetime(week, day, get_time(time)[1], semester) if dtstart.year == 1984: continue cal.add_component( _build_event(course_name=course['name'], times=(dtstart, dtend), classroom=course['classroom'], teacher=course['teacher'], week_string=course['week_string'], current_week=week, cid=course['cid'])) # 写入文件 import os with open( os.path.join(os.path.dirname(__file__), '../../../calendar_files/{}.ics'.format(ics_token)), 'w') as f: f.write(cal.to_ical().decode(encoding='utf-8'))
def get_classes_for_student(student_id: str, sem: Semester) -> dict: """ 获得一个学生在指定学期的全部课程 如学生不存在当前学期则引出 NoStudentException :param student_id: 学号 :param sem: 学期,`Semester` 类型对象 :return: dict,键为 (day, time),值为课程列表,每一节课为一个包含了课程名称、老师、上课时间、地点信息的 dict """ from everyclass.server.exceptions import NoStudentException, IllegalSemesterException db = get_connection() cursor = db.cursor() # 初步合法性检验 if sem.to_tuple() not in app.config['AVAILABLE_SEMESTERS']: raise IllegalSemesterException('No such semester for the student') mysql_query = "SELECT classes FROM ec_students_" + sem.to_db_code( ) + " WHERE xh=%s" cursor.execute(mysql_query, (student_id, )) result = cursor.fetchall() if not result: cursor.close() raise NoStudentException(student_id) else: courses_list = json.loads(result[0][0]) courses = dict() for classes in courses_list: mysql_query = "SELECT clsname,day,time,teacher,duration,week,location,id FROM {} WHERE id=%s" \ .format("ec_classes_" + sem.to_db_code()) cursor.execute(mysql_query, (classes, )) result = cursor.fetchall() if (result[0][1], result[0][2]) not in courses: courses[(result[0][1], result[0][2])] = list() courses[(result[0][1], result[0][2])].append( dict(name=result[0][0], teacher=result[0][3], duration=result[0][4], week=result[0][5], location=result[0][6], id=result[0][7])) cursor.close() return courses
def get_ics(student_id, semester_str): """ iCalendar service """ from everyclass.server.calendar import ics_generator from everyclass.server.db.dao import check_if_stu_exist, get_my_semesters, get_classes_for_student from everyclass.server.db.model import Semester from everyclass.server.exceptions import IllegalSemesterException from everyclass.server import logger # TODO: generate ics here and return it to user, instead of generating .ics files in other places. # 临时 fix place = student_id.find('-') semester_str = student_id[place + 1:len(student_id)] + '-' + semester_str student_id = student_id[:place] # 学号检测 if not check_if_stu_exist(student_id): flash("{} 学号不存在".format(student_id)) logger.warning("[ics] {} 学号不存在".format(student_id)) return redirect(url_for("main.main")) # 学期检测 my_available_semesters, student_name = get_my_semesters(student_id) try: semester = Semester(semester_str) except IllegalSemesterException: flash("{} 学期格式错误".format(semester_str)) logger.warning("{} 学期格式错误".format(semester_str)) return redirect(url_for("main.main")) if semester not in my_available_semesters: flash("{} 学期不适用于此学生".format(semester_str)) logger.warning("{} 学期不适用于此学生".format(semester_str)) return redirect(url_for("main.main")) student_classes = get_classes_for_student(student_id, semester) ics_generator.generate(student_id, student_name, student_classes, semester.to_str(simplify=True), semester.to_tuple()) return send_from_directory("../../calendar_files", student_id + "-" + semester_str + ".ics", as_attachment=True, mimetype='text/calendar')
def batch_generate(): """生成当前学期所有学生的 ics 文件,每次更新当前学期数据后使用""" from everyclass.server import create_app from everyclass.server.db.dao import get_all_students, get_classes_for_student from everyclass.server.db.model import Semester config = get_config() now_semester = Semester(config.DEFAULT_SEMESTER) now_semester_str = str(now_semester) with create_app(offline=True).app_context(): students = get_all_students() print("Total {} students".format(len(students))) for each in students: if now_semester_str in each[2]: print("Generate .ics for [{}]{}...".format(each[0], each[1])) student_classes = get_classes_for_student( each[0], now_semester) generate(student_id=each[0], student_name=each[1], student_classes=student_classes, semester_string=now_semester.to_str(simplify=True), semester=now_semester.to_tuple()) print("Done.")
def query(): """ 查询本人课表视图函数 正常情况应该是 post 方法,但是也兼容 get 防止意外情况,提高用户体验 """ from flask import request, render_template, redirect, url_for, session from flask import current_app as app import elasticapm from everyclass.server.tools import is_chinese_char from everyclass.server.exceptions import NoStudentException, IllegalSemesterException from everyclass.server.db.mysql import get_local_conn from everyclass.server.db.dao import faculty_lookup from everyclass.server.db.dao import class_lookup from everyclass.server.db.dao import get_classes_for_student from everyclass.server.db.model import Semester from everyclass.server.db.dao import get_privacy_settings from everyclass.server.db.dao import get_my_semesters from everyclass.server.db.dao import check_if_stu_exist # if under maintenance, return to maintenance.html if app.config["MAINTENANCE"]: return render_template("maintenance.html") db = get_local_conn() cursor = db.cursor() # 如 URL 中有 id 参数,判断是姓名还是学号,然后赋学号给student_id if request.values.get('id'): id_or_name = request.values.get('id') # 首末均为中文,判断为人名 if is_chinese_char(id_or_name[0:1]) and is_chinese_char( id_or_name[-1:]): # 使用人名查询打点 elasticapm.tag(ec_query_method='by_name') mysql_query = "SELECT name,xh FROM ec_students WHERE name=%s" cursor.execute(mysql_query, (id_or_name, )) result = cursor.fetchall() if cursor.rowcount > 1: # 查询到多个同名,进入选择界面 students_list = list() for each_student in result: students_list.append([ each_student[0], each_student[1], faculty_lookup(each_student[1]), class_lookup(each_student[1]) ]) return render_template("query_same_name.html", count=cursor.rowcount, student_info=students_list) elif cursor.rowcount == 1: # 仅能查询到一个人,则赋值学号 student_id = result[0][1] else: # 查无此人 elasticapm.tag(ec_query_not_found=True) return _no_student_handle(id_or_name) # id 不为中文,则为学号 else: # 学号查询打点 elasticapm.tag(ec_query_method='by_id') student_id = request.values.get('id') # 判断学号是否有效 if not check_if_stu_exist(student_id): elasticapm.tag(ec_query_not_found=True) return _no_student_handle(student_id) # 写入 session 的学号一定有效 session['stu_id'] = student_id # url 中没有 id 参数但 session 中有 elif session.get('stu_id', None): elasticapm.tag(ec_query_method='by_session') student_id = session['stu_id'] # 既没有 id 参数也没有 session,无法知道需要查询谁的课表,返回主页 else: elasticapm.tag(ec_query_method='exception') return redirect(url_for('main.main')) # 查询学生本人的可用学期 my_available_semesters, student_name = get_my_semesters(student_id) # 如果没有学期,则直接返回 if not my_available_semesters: logger.warning('Not any semester in ec_student', stack=True) return _no_student_handle() # 如URL参数中包含学期,判断有效性后更新 session if request.values.get('semester'): try: sem = Semester(request.values.get('semester')) if sem in my_available_semesters: session['semester'] = sem.to_tuple() if app.config['DEBUG']: print('[query.query] updated session semester to', Semester(session['semester']).to_str()) # 用户指定的学期格式不合法 except IllegalSemesterException: if app.config['DEBUG']: print('[query.query] IllegalSemesterException handled.' + Semester(session['semester']).to_str()) session['semester'] = my_available_semesters[-1].to_tuple() cursor.close() # 关闭数据库连接 # 如果 session 中无学期或学期无效,回落到本人可用最新学期 # session 中学期使用 tuple 保存,因为 Semester 对象无法被序列化 semester = session.get('semester', None) if not semester or Semester(semester) not in my_available_semesters: session['semester'] = my_available_semesters[-1].to_tuple() try: student_classes = get_classes_for_student(student_id=student_id, sem=Semester( session['semester'])) except NoStudentException: return _no_student_handle(student_id) else: # 空闲周末判断,考虑到大多数人周末都是没有课程的 empty_weekend = True for cls_time in range(1, 7): for cls_day in range(6, 8): if (cls_day, cls_time) in student_classes: empty_weekend = False # 空闲课程判断,考虑到大多数人11-12节都是没有课程的 empty_6 = True for cls_day in range(1, 8): if (cls_day, 6) in student_classes: empty_6 = False empty_5 = True for cls_day in range(1, 8): if (cls_day, 5) in student_classes: empty_5 = False # available_semesters 为当前学生所能选择的学期,是一个list。 # 当中每一项又是一个包含两项的list,第一项为学期string,第二项为True/False表示是否为当前学期。 available_semesters = [] for each_semester in my_available_semesters: if session['semester'] == each_semester: available_semesters.append([each_semester, True]) else: available_semesters.append([each_semester, False]) # Privacy settings # Available privacy settings: "show_table_on_page", "import_to_calender", "major" privacy_settings = get_privacy_settings(student_id) # privacy on if "show_table_on_page" in privacy_settings: return render_template( 'blocked.html', name=student_name, falculty=faculty_lookup(student_id), class_name=class_lookup(student_id), stu_id=student_id, available_semesters=available_semesters, no_import_to_calender=True if "import_to_calender" in privacy_settings else False) # privacy off return render_template('query.html', name=student_name, falculty=faculty_lookup(student_id), class_name=class_lookup(student_id), stu_id=student_id, classes=student_classes, empty_wkend=empty_weekend, empty_6=empty_6, empty_5=empty_5, available_semesters=available_semesters)