Coverage for students/routes_student.py: 84%

227 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-05 14:02 +0000

1""" 

2Handles routes for the student module. 

3""" 

4 

5from html import escape 

6import uuid 

7from dotenv import load_dotenv 

8from flask import jsonify, redirect, render_template, request, session 

9from itsdangerous import URLSafeSerializer 

10from core import handlers, shared 

11from courses.models import Course 

12from employers.models import Employers 

13from skills.models import Skill 

14from course_modules.models import Module 

15from .models import Student 

16 

17 

18def add_student_routes(app): 

19 """Add student routes.""" 

20 

21 @app.route("/students/add_student", methods=["GET", "POST"]) 

22 @handlers.login_required 

23 def register_student_attempt(): 

24 """Adding new student.""" 

25 if request.method == "GET": 

26 return render_template( 

27 "student/add_student.html", 

28 courses=Course().get_courses(), 

29 modules=Module().get_modules(), 

30 skills=Skill().get_skills(), 

31 user_type="admin", 

32 page="students", 

33 ) 

34 student = request.json 

35 if isinstance(student["modules"], str): 

36 student["modules"] = student["modules"].split(",") 

37 if student["modules"] == [""]: 

38 student["modules"] = [] 

39 

40 if not student["student_id"]: 

41 return jsonify({"error": "Please enter a student ID"}), 400 

42 if not student["first_name"]: 

43 return jsonify({"error": "Please enter a first name"}), 400 

44 if not student["last_name"]: 

45 return jsonify({"error": "Please enter a last name"}), 400 

46 if not student["email"]: 

47 return jsonify({"error": "Please enter an email"}), 400 

48 if not student["course"]: 

49 return jsonify({"error": "Please select a course"}), 400 

50 if not student["comments"]: 

51 student["comments"] = "" 

52 

53 student["skills"] = [] 

54 student["attempted_skills"] = [] 

55 student["has_car"] = False 

56 student["placement_duration"] = [] 

57 

58 student["_id"] = uuid.uuid4().hex 

59 

60 student["student_id"] = escape(student["student_id"]) 

61 student["first_name"] = escape(student["first_name"]) 

62 student["last_name"] = escape(student["last_name"]) 

63 student["email"] = escape(student["email"]) 

64 student["course"] = escape(student["course"]) 

65 student["comments"] = escape(student["comments"]) 

66 

67 return Student().add_student(student) 

68 

69 @app.route("/students/upload", methods=["GET", "POST"]) 

70 @handlers.login_required 

71 def upload_page(): 

72 """Route to upload students from a XLSX file.""" 

73 if request.method == "GET": 

74 return render_template( 

75 "/student/upload_student_data.html", user_type="admin", page="students" 

76 ) 

77 load_dotenv() 

78 base_email = shared.getenv("BASE_EMAIL_FOR_STUDENTS") 

79 if "file" not in request.files: 

80 return jsonify({"error": "No file part"}), 400 

81 file = request.files["file"] 

82 

83 if not handlers.allowed_file(file.filename, ["xlsx", "xls"]): 

84 return jsonify({"error": "Invalid file type"}), 400 

85 

86 return Student().import_from_xlsx(base_email, file) 

87 

88 @app.route("/students/search") 

89 @handlers.login_required 

90 def search_page(): 

91 """Getting student.""" 

92 students = Student().get_students() 

93 for student in students: 

94 if "preferences" in student: 

95 student["ranked"] = True 

96 return render_template( 

97 "student/search_student.html", 

98 skills_map=Skill().get_skills_map(), 

99 skills=Skill().get_skills(), 

100 courses_map=Course().get_courses_map(), 

101 courses=Course().get_courses(), 

102 modules_map=Module().get_modules_map(), 

103 modules=Module().get_modules(), 

104 students=students, 

105 user_type="admin", 

106 page="students", 

107 ) 

108 

109 @app.route("/students/delete_student/<int:student_id>", methods=["DELETE"]) 

110 @handlers.login_required 

111 def delete_student(student_id): 

112 """Delete student.""" 

113 return Student().delete_student_by_id(student_id) 

114 

115 @app.route("/students/login", methods=["GET", "POST"]) 

116 def login_student(): 

117 """Logins a student""" 

118 handlers.clear_session_save_theme() 

119 if request.method == "POST": 

120 student_id = request.form.get("student_id") 

121 return Student().student_login(student_id) 

122 

123 if "student" in session and "student_logged_in" in session: 

124 return redirect( 

125 "/students/details/" + str(session["student"]["student_id"]) 

126 ) 

127 return render_template("student/student_login.html") 

128 

129 @app.route("/students/otp", methods=["POST"]) 

130 def student_otp(): 

131 

132 if "student" not in session: 

133 return jsonify({"error": "Employer not logged in."}), 400 

134 otp_serializer = URLSafeSerializer(str(shared.getenv("SECRET_KEY", "secret"))) 

135 if "OTP" not in session: 

136 return jsonify({"error": "OTP not sent."}), 400 

137 if request.form.get("otp") != otp_serializer.loads(session["OTP"]): 

138 return jsonify({"error": "Invalid OTP."}), 400 

139 

140 session["student_logged_in"] = True 

141 

142 return jsonify({"message": "OTP verified"}), 200 

143 

144 @app.route("/students/passed_deadline") 

145 def past_deadline(): 

146 """Page for when the deadline has passed.""" 

147 handlers.clear_session_save_theme() 

148 return render_template("student/past_deadline.html") 

149 

150 @app.route("/students/details/<int:student_id>", methods=["GET", "POST"]) 

151 @handlers.student_login_required 

152 def student_details(student_id): 

153 """Get or update student details.""" 

154 from app import DEADLINE_MANAGER 

155 from app import CONFIG_MANAGER 

156 

157 if session["student"]["student_id"] != str(student_id): 

158 handlers.clear_session_save_theme() 

159 return redirect("/students/login") 

160 

161 # Handle deadlines (applicable to students only) 

162 if ( 

163 DEADLINE_MANAGER.is_past_student_ranking_deadline() 

164 and request.method == "GET" 

165 ): 

166 return redirect("/students/passed_deadline") 

167 if DEADLINE_MANAGER.is_past_details_deadline() and request.method == "GET": 

168 return redirect(f"/students/rank_preferences/{student_id}") 

169 

170 # Handle POST request for updating details 

171 if request.method == "POST": 

172 student = {} 

173 student["comments"] = request.form.get("comments") 

174 skills = request.form.get("skills").split(",") 

175 attempted_skills = request.form.get("attempted_skills").split(",") 

176 max_skills = CONFIG_MANAGER.get_max_num_of_skills() 

177 total_skills = len(skills) + len(attempted_skills) 

178 

179 if total_skills > max_skills: 

180 skills = skills[: max_skills - len(attempted_skills)] 

181 

182 student["skills"] = [skill for skill in skills if skill] 

183 student["attempted_skills"] = [skill for skill in attempted_skills if skill] 

184 student["has_car"] = request.form.get("has_car") 

185 student["placement_duration"] = request.form.get( 

186 "placement_duration" 

187 ).split(",") 

188 if ( 

189 student["placement_duration"] == [""] 

190 or student["placement_duration"] == [] 

191 ): 

192 return jsonify({"error": "Please select a placement duration"}), 400 

193 valid_durations = set( 

194 ["1_day", "1_week", "1_month", "3_months", "6_months", "12_months"] 

195 ) 

196 if not set(student["placement_duration"]).issubset(valid_durations): 

197 return jsonify({"error": "Invalid placement duration"}), 400 

198 student["modules"] = request.form.get("modules").split(",") 

199 if student["modules"] == [""]: 

200 student["modules"] = [] 

201 student["course"] = request.form.get("course") 

202 if student["course"] == "": 

203 return jsonify({"error": "Please select a course"}), 400 

204 student["course"] = student["course"].upper() 

205 

206 student["comments"] = escape(student["comments"]) 

207 student["skills"] = [escape(skill) for skill in student["skills"]] 

208 student["attempted_skills"] = [ 

209 escape(skill) for skill in student["attempted_skills"] 

210 ] 

211 student["has_car"] = escape(student["has_car"]) 

212 student["placement_duration"] = [ 

213 escape(duration) for duration in student["placement_duration"] 

214 ] 

215 student["modules"] = [escape(module) for module in student["modules"]] 

216 

217 return Student().update_student_by_id(student_id, student) 

218 

219 # Render the template 

220 return render_template( 

221 "student/student_details.html", 

222 student=session["student"], 

223 skills=Skill().get_skills(), 

224 courses=Course().get_courses(), 

225 modules=Module().get_modules(), 

226 attempted_skill_map={ 

227 skill["_id"]: skill for skill in Skill().get_list_attempted_skills() 

228 }, 

229 user_type="student", 

230 max_num_skills=CONFIG_MANAGER.get_max_num_of_skills(), 

231 ) 

232 

233 @app.route("/students/update_student", methods=["GET", "POST"]) 

234 @handlers.login_required 

235 def update_student(): 

236 """Update student for admins.""" 

237 if request.method == "POST": 

238 student = {} 

239 uuid = request.args.get("uuid") 

240 if not uuid or uuid == "": 

241 return jsonify({"error": "Invalid request"}), 404 

242 student["student_id"] = request.form.get("student_id") 

243 student["first_name"] = request.form.get("first_name") 

244 student["last_name"] = request.form.get("last_name") 

245 student["email"] = request.form.get("email") 

246 student["course"] = request.form.get("course") 

247 student["comments"] = request.form.get("comments") 

248 student["skills"] = request.form.get("skills").split(",") 

249 

250 if student["skills"] == [""]: 

251 student["skills"] = [] 

252 student["attempted_skills"] = request.form.get("attempted_skills").split( 

253 "," 

254 ) 

255 

256 if student["attempted_skills"] == [""]: 

257 student["attempted_skills"] = [] 

258 student["has_car"] = request.form.get("has_car") 

259 student["placement_duration"] = request.form.get( 

260 "placement_duration" 

261 ).split(",") 

262 

263 if ( 

264 student["placement_duration"] == [""] 

265 or student["placement_duration"] == [] 

266 ): 

267 return jsonify({"error": "Please select a placement duration"}), 400 

268 valid_durations = set( 

269 ["1_day", "1_week", "1_month", "3_months", "6_months", "12_months"] 

270 ) 

271 if not set(student["placement_duration"]).issubset(valid_durations): 

272 return jsonify({"error": "Invalid placement duration"}), 400 

273 student["modules"] = request.form.get("modules").split(",") 

274 

275 if student["modules"] == [""]: 

276 student["modules"] = [] 

277 if student["course"] == "": 

278 return jsonify({"error": "Please select a course"}), 400 

279 student["course"] = student["course"].upper() 

280 

281 student["student_id"] = escape(student["student_id"]) 

282 student["first_name"] = escape(student["first_name"]) 

283 student["last_name"] = escape(student["last_name"]) 

284 student["email"] = escape(student["email"]) 

285 student["course"] = escape(student["course"]) 

286 student["comments"] = escape(student["comments"]) 

287 student["skills"] = [escape(skill) for skill in student["skills"]] 

288 student["attempted_skills"] = [ 

289 escape(skill) for skill in student["attempted_skills"] 

290 ] 

291 student["has_car"] = escape(student["has_car"]) 

292 student["placement_duration"] = [ 

293 escape(duration) for duration in student["placement_duration"] 

294 ] 

295 student["modules"] = [escape(module) for module in student["modules"]] 

296 

297 return Student().update_student_by_uuid(uuid, student) 

298 

299 uuid = request.args.get("uuid") 

300 student = Student().get_student_by_uuid(uuid) 

301 return render_template( 

302 "student/update_student.html", 

303 student=student, 

304 skills=Skill().get_skills(), 

305 courses=Course().get_courses(), 

306 modules=Module().get_modules(), 

307 attempted_skills=Skill().get_list_attempted_skills(), 

308 user_type="admin", 

309 page="students", 

310 ) 

311 

312 @app.route("/students/rank_preferences/<int:student_id>", methods=["GET", "POST"]) 

313 @handlers.student_login_required 

314 def rank_preferences(student_id): 

315 """Rank preferences.""" 

316 from app import DEADLINE_MANAGER 

317 from app import CONFIG_MANAGER 

318 

319 if "student" not in session: 

320 return redirect("/students/login") 

321 

322 if session["student"]["student_id"] != str(student_id): 

323 handlers.clear_session_save_theme() 

324 return redirect("/students/login") 

325 

326 if ( 

327 DEADLINE_MANAGER.is_past_student_ranking_deadline() 

328 and request.method == "GET" 

329 ): 

330 handlers.clear_session_save_theme() 

331 render_template("student/past_deadline.html") 

332 

333 if not DEADLINE_MANAGER.is_past_details_deadline(): 

334 return redirect("/students/details/" + str(student_id)) 

335 

336 if "modules" not in session["student"]: 

337 return render_template( 

338 "error_page.html", 

339 code="400", 

340 name="Missing details", 

341 msg="You have not filled in your details and the deadline has passed. If you see this page contact your admin to fill them in for you.", 

342 user_type="student", 

343 ) 

344 opportunities = Student().get_opportunities_by_student(student_id) 

345 if request.method == "POST": 

346 preferences = [a[5:].strip() for a in request.form.get("ranks").split(",")] 

347 min_ranked = CONFIG_MANAGER.get_min_num_ranking_student_to_opportunities() 

348 if len(opportunities) > min_ranked: 

349 if len(preferences) < min_ranked: 

350 return ( 

351 jsonify( 

352 { 

353 "error": "Please rank at least " 

354 + str(min_ranked) 

355 + " opportunities" 

356 } 

357 ), 

358 400, 

359 ) 

360 return Student().rank_preferences(student_id, preferences) 

361 return render_template( 

362 "student/student_rank_opportunities.html", 

363 opportunities=opportunities, 

364 employers_col=Employers().get_employer_by_id, 

365 user_type="student", 

366 min_ranked=CONFIG_MANAGER.get_min_num_ranking_student_to_opportunities(), 

367 ) 

368 

369 @app.route("/students/update_success") 

370 @handlers.student_login_required 

371 def student_update_successful(): 

372 """Routing to deal with success""" 

373 handlers.clear_session_save_theme() 

374 return render_template("student/update_successful_page.html") 

375 

376 @app.route("/students/download", methods=["GET"]) 

377 @handlers.login_required 

378 def download_all_students(): 

379 """Download students.""" 

380 return Student().download_students() 

381 

382 @app.route("/students/delete_all", methods=["DELETE"]) 

383 @handlers.login_required 

384 def delete_all_students(): 

385 """Delete all students.""" 

386 return Student().delete_all_students()