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
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-05 14:02 +0000
1"""
2Handles routes for the student module.
3"""
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
18def add_student_routes(app):
19 """Add student routes."""
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"] = []
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"] = ""
53 student["skills"] = []
54 student["attempted_skills"] = []
55 student["has_car"] = False
56 student["placement_duration"] = []
58 student["_id"] = uuid.uuid4().hex
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"])
67 return Student().add_student(student)
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"]
83 if not handlers.allowed_file(file.filename, ["xlsx", "xls"]):
84 return jsonify({"error": "Invalid file type"}), 400
86 return Student().import_from_xlsx(base_email, file)
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 )
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)
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)
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")
129 @app.route("/students/otp", methods=["POST"])
130 def student_otp():
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
140 session["student_logged_in"] = True
142 return jsonify({"message": "OTP verified"}), 200
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")
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
157 if session["student"]["student_id"] != str(student_id):
158 handlers.clear_session_save_theme()
159 return redirect("/students/login")
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}")
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)
179 if total_skills > max_skills:
180 skills = skills[: max_skills - len(attempted_skills)]
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()
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"]]
217 return Student().update_student_by_id(student_id, student)
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 )
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(",")
250 if student["skills"] == [""]:
251 student["skills"] = []
252 student["attempted_skills"] = request.form.get("attempted_skills").split(
253 ","
254 )
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(",")
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(",")
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()
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"]]
297 return Student().update_student_by_uuid(uuid, student)
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 )
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
319 if "student" not in session:
320 return redirect("/students/login")
322 if session["student"]["student_id"] != str(student_id):
323 handlers.clear_session_save_theme()
324 return redirect("/students/login")
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")
333 if not DEADLINE_MANAGER.is_past_details_deadline():
334 return redirect("/students/details/" + str(student_id))
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 )
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")
376 @app.route("/students/download", methods=["GET"])
377 @handlers.login_required
378 def download_all_students():
379 """Download students."""
380 return Student().download_students()
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()