Coverage for model/course.py: 100%
158 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-11 18:37 +0000
« 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
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
12__all__ = ['course_api']
14course_api = Blueprint('course_api', __name__)
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)
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": []}
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)
40 return HTTPResponse("Success.", data=summary)
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.')
70@course_api.route('/<course_name>', methods=['GET', 'PUT'])
71@login_required
72def get_course(user, course_name):
73 course = Course(course_name)
75 if not course:
76 return HTTPError('Course not found.', 404)
78 if not course.permission(user, Course.Permission.VIEW):
79 return HTTPError('You are not in this course.', 403)
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)
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
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.')
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()
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)
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)
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, [])])
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.')
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.')
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.')
193 methods = {
194 'GET': get_score,
195 'POST': add_score,
196 'PUT': modify_score,
197 'DELETE': delete_score
198 }
199 return methods[request.method]()
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)
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)
230 if not course.permission(user, Course.Permission.GRADE):
231 return HTTPError('Permission denied', 403)
233 ret = course.get_scoreboard(pids, start, end)
235 return HTTPResponse(
236 'Success.',
237 data=ret,
238 )