Coverage for model/course.py: 100%

158 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-11 18:37 +0000

1from typing import Optional 

2from flask import Blueprint, request 

3 

4from mongo import * 

5from .auth import * 

6from .utils import * 

7from mongo.utils import * 

8from mongo.course import * 

9from mongo import engine 

10from datetime import datetime 

11 

12__all__ = ['course_api'] 

13 

14course_api = Blueprint('course_api', __name__) 

15 

16 

17@course_api.get('/') 

18@login_required 

19def get_courses(user): 

20 data = [{ 

21 'course': c.course_name, 

22 'teacher': c.teacher.info, 

23 } for c in Course.get_user_courses(user)] 

24 return HTTPResponse('Success.', data=data) 

25 

26 

27@course_api.get('/summary') 

28@identity_verify(0) 

29def get_courses_summary(user): 

30 courses = [Course(c) for c in Course.get_all()] 

31 summary = {"courseCount": len(courses), "breakdown": []} 

32 

33 for course in courses: 

34 # The user is admin, it won't filter out any problems (it's required) 

35 problems = Problem.get_problem_list(user, course=course.course_name) 

36 course_summary = course.get_course_summary(problems) 

37 course_summary["problemCount"] = len(problems) 

38 summary["breakdown"].append(course_summary) 

39 

40 return HTTPResponse("Success.", data=summary) 

41 

42 

43@course_api.route('/', methods=['POST', 'PUT', 'DELETE']) 

44@Request.json('course', 'new_course', 'teacher') 

45@identity_verify(0, 1) 

46def modify_courses(user, course, new_course, teacher): 

47 r = None 

48 if user.role == 1: 

49 teacher = user.username 

50 try: 

51 if request.method == 'POST': 

52 r = Course.add_course(course, teacher) 

53 if request.method == 'PUT': 

54 co = Course(course) 

55 co.edit_course(user, new_course, teacher) 

56 if request.method == 'DELETE': 

57 co = Course(course) 

58 co.delete_course(user) 

59 except ValueError: 

60 return HTTPError('Not allowed name.', 400) 

61 except NotUniqueError: 

62 return HTTPError('Course exists.', 400) 

63 except PermissionError: 

64 return HTTPError('Forbidden.', 403) 

65 except engine.DoesNotExist as e: 

66 return HTTPError(f'{e} not found.', 404) 

67 return HTTPResponse('Success.') 

68 

69 

70@course_api.route('/<course_name>', methods=['GET', 'PUT']) 

71@login_required 

72def get_course(user, course_name): 

73 course = Course(course_name) 

74 

75 if not course: 

76 return HTTPError('Course not found.', 404) 

77 

78 if not course.permission(user, Course.Permission.VIEW): 

79 return HTTPError('You are not in this course.', 403) 

80 

81 @Request.json('TAs', 'student_nicknames') 

82 def modify_course(TAs, student_nicknames): 

83 if not course.permission(user, Course.Permission.MODIFY): 

84 return HTTPError('Forbidden.', 403) 

85 else: 

86 tas = [] 

87 for ta in TAs: 

88 permit_user = User(ta).obj 

89 if not User(ta): 

90 return HTTPResponse(f'User: {ta} not found.', 404) 

91 tas.append(permit_user) 

92 

93 for permit_user in set(course.tas) - set(tas): 

94 course.remove_user(permit_user) 

95 for permit_user in set(tas) - set(course.tas): 

96 course.add_user(permit_user) 

97 course.tas = tas 

98 

99 try: 

100 course.update_student_namelist(student_nicknames) 

101 except engine.DoesNotExist as e: 

102 return HTTPError(str(e), 404) 

103 return HTTPResponse('Success.') 

104 

105 if request.method == 'GET': 

106 return HTTPResponse( 

107 'Success.', 

108 data={ 

109 "teacher": course.teacher.info, 

110 "TAs": [ta.info for ta in course.tas], 

111 "students": 

112 [User(name).info for name in course.student_nicknames] 

113 }, 

114 ) 

115 else: 

116 return modify_course() 

117 

118 

119@course_api.route('/<course_name>/grade/<student>', 

120 methods=['GET', 'POST', 'PUT', 'DELETE']) 

121@login_required 

122def grading(user, course_name, student): 

123 course = Course(course_name) 

124 

125 if not course: 

126 return HTTPError('Course not found.', 404) 

127 if not course.permission(user, Course.Permission.VIEW): 

128 return HTTPError('You are not in this course.', 403) 

129 if student not in course.student_nicknames.keys(): 

130 return HTTPError('The student is not in the course.', 404) 

131 if course.permission(user, Course.Permission.SCORE) and \ 

132 (user.username != student or request.method != 'GET'): 

133 return HTTPError('You can only view your score.', 403) 

134 

135 def get_score(): 

136 return HTTPResponse( 

137 'Success.', 

138 data=[{ 

139 'title': score['title'], 

140 'content': score['content'], 

141 'score': score['score'], 

142 'timestamp': score['timestamp'].timestamp() 

143 } for score in course.student_scores.get(student, [])]) 

144 

145 @Request.json('title', 'content', 'score') 

146 def add_score(title, content, score): 

147 score_list = course.student_scores.get(student, []) 

148 if title in [score['title'] for score in score_list]: 

149 return HTTPError('This title is taken.', 400) 

150 score_list.append({ 

151 'title': title, 

152 'content': content, 

153 'score': score, 

154 'timestamp': datetime.now() 

155 }) 

156 course.student_scores[student] = score_list 

157 course.save() 

158 return HTTPResponse('Success.') 

159 

160 @Request.json('title', 'new_title', 'content', 'score') 

161 def modify_score(title, new_title, content, score): 

162 score_list = course.student_scores.get(student, []) 

163 title_list = [score['title'] for score in score_list] 

164 if title not in title_list: 

165 return HTTPError('Score not found.', 404) 

166 index = title_list.index(title) 

167 if new_title is not None: 

168 if new_title in title_list: 

169 return HTTPError('This title is taken.', 400) 

170 title = new_title 

171 score_list[index] = { 

172 'title': title, 

173 'content': content, 

174 'score': score, 

175 'timestamp': datetime.now() 

176 } 

177 course.student_scores[student] = score_list 

178 course.save() 

179 return HTTPResponse('Success.') 

180 

181 @Request.json('title') 

182 def delete_score(title): 

183 score_list = course.student_scores.get(student, []) 

184 title_list = [score['title'] for score in score_list] 

185 if title not in title_list: 

186 return HTTPError('Score not found.', 404) 

187 index = title_list.index(title) 

188 del score_list[index] 

189 course.student_scores[student] = score_list 

190 course.save() 

191 return HTTPResponse('Success.') 

192 

193 methods = { 

194 'GET': get_score, 

195 'POST': add_score, 

196 'PUT': modify_score, 

197 'DELETE': delete_score 

198 } 

199 return methods[request.method]() 

200 

201 

202@course_api.route('/<course_name>/scoreboard', methods=['GET']) 

203@login_required 

204@Request.args('pids: str', 'start', 'end') 

205@Request.doc('course_name', 'course', Course) 

206def get_course_scoreboard( 

207 user, 

208 pids: str, 

209 start: Optional[str], 

210 end: Optional[str], 

211 course: Course, 

212): 

213 try: 

214 pids = pids.split(',') 

215 pids = [int(pid.strip()) for pid in pids] 

216 except: 

217 return HTTPError('Error occurred when parsing `pids`.', 400) 

218 

219 if start: 

220 try: 

221 start = float(start) 

222 except: 

223 return HTTPError('Type of `start` should be float.', 400) 

224 if end: 

225 try: 

226 end = float(end) 

227 except: 

228 return HTTPError('Type of `end` should be float.', 400) 

229 

230 if not course.permission(user, Course.Permission.GRADE): 

231 return HTTPError('Permission denied', 403) 

232 

233 ret = course.get_scoreboard(pids, start, end) 

234 

235 return HTTPResponse( 

236 'Success.', 

237 data=ret, 

238 )