app.admin.routes
Admin routes for user and training module management.
This file defines all /admin/... endpoints for creating, editing,
viewing and deleting users and training modules. All routes ensure that the
current user is authenticated and has the “admin” role.
1"""Admin routes for user and training module management. 2 3This file defines all `/admin/...` endpoints for creating, editing, 4viewing and deleting users and training modules. All routes ensure that the 5current user is authenticated and has the “admin” role. 6""" 7from datetime import datetime 8 9from flask import render_template, flash, redirect, url_for, request 10from flask_login import current_user, login_required 11 12from app import app, db 13from app.admin.forms import CreateUserForm, EditUserForm, CreateTrainingModuleForm 14from app.models import ( 15 User, 16 Role, 17 Department, 18 TrainingModule, 19 Question, 20 Option, 21 OnboardingPath, 22 OnboardingStep 23) 24 25 26@app.route('/admin/register', methods = ['GET', 'POST']) 27def register_user(): 28 """Register a new user. 29 30 Displays a form for creating a user, validates input, determines the 31 appropriate onboarding path based on department, sets the password, 32 and commits the new User record to the database. 33 34 Args: 35 None (form data submitted via POST). 36 37 Returns: 38 - Redirects to logout if the user is not an admin. 39 - Re-renders the registration form on GET or validation failure. 40 - Redirects to manage_users on successful creation (with a flash 41 message). 42 """ 43 if not current_user.is_authenticated or current_user.role.role_name != 'admin': 44 return redirect(url_for('logout')) 45 46 form = CreateUserForm() 47 48 form.role.choices = [(0, 'Select Role')] + [ 49 (role.id, role.role_name) for role in Role.query.all() 50 ] 51 form.department.choices = [(0, 'Select Department')] + [ 52 (dept.id, dept.department_name) for dept in Department.query.all() 53 ] 54 55 manager_role = Role.query.filter_by(role_name='manager').first() 56 manager_choices = [(0, 'None')] 57 if manager_role: 58 manager_choices += [ 59 (u.id, f"{u.first_name.title()} {u.surname.title()}") 60 for u in User.query.filter_by(role_id=manager_role.id) 61 ] 62 form.manager.choices = manager_choices 63 64 if form.validate_on_submit(): 65 department_id = int(form.department.data) 66 67 if Department.query.filter_by( 68 id=department_id, 69 department_name = "office", 70 ).first(): 71 onboarding_path = OnboardingPath.query.filter_by( 72 path_name = "office", 73 ).first() 74 else: 75 onboarding_path = OnboardingPath.query.filter_by( 76 path_name = "operational", 77 ).first() 78 79 user = User( 80 first_name = form.firstName.data, 81 surname = form.surname.data, 82 username = form.username.data, 83 role_id = int(form.role.data), 84 is_onboarding = (form.is_onboarding.data == 'yes'), 85 manager_id = int(form.manager.data) if form.manager.data else None, 86 department_id = int(form.department.data), 87 onboarding_path_id = onboarding_path.id if onboarding_path else None, 88 dateStarted = datetime.now(), 89 job_title = form.job_title.data 90 ) 91 92 user.set_password(form.password.data) 93 db.session.add(user) 94 db.session.commit() 95 flash( 96 f'User {user.first_name} {user.surname} ' 97 'has been successfully registered!' 98 ) 99 return redirect(url_for('manage_users')) 100 101 return render_template( 102 'admin/createUser.html', 103 title='Register User', 104 form=form, 105 ) 106 107 108@app.route('/admin/dashboard') 109@login_required 110def admin_dashboard(): 111 """Display the admin dashboard. 112 113 Returns: 114 - Rendered admin dashboard template. 115 - Redirects to logout if the user is not an admin. 116 """ 117 if current_user.role.role_name!= "admin": 118 return redirect(url_for('logout')) 119 120 return render_template( 121 'admin/dashboard.html', 122 title='Admin Dashboard', 123 ) 124 125 126@app.route('/admin/manage_users', methods = ['GET']) 127@login_required 128def manage_users(): 129 """Display all users in the system. 130 131 Returns: 132 - Rendered template with a list of all users. 133 - Redirects to the admin dashboard with a flash message if an error 134 occurs. 135 - Redirects to logout if the user is not an admin. 136 """ 137 if current_user.role.role_name != "admin": 138 return redirect(url_for('logout')) 139 140 try: 141 users = User.query.all() 142 return render_template( 143 'admin/manageUsers.html', 144 title='Manage Users', 145 users=users, 146 ) 147 except Exception as e: 148 print('Error: ' + str(e)) 149 flash("An error occured contact support.") 150 return redirect(url_for('admin_dashboard')) 151 152 153@app.route('/admin/view_user/<int:user_id>', methods=['GET']) 154@login_required 155def view_user(user_id): 156 """View details of a specific user. 157 158 Args: 159 user_id (int): ID of the user to view. 160 161 Returns: 162 - Rendered template with user details. 163 - Redirects to logout if the user is not an admin. 164 """ 165 if current_user.role.role_name != "admin": 166 return redirect(url_for('logout')) 167 168 user = User.query.get_or_404(user_id) 169 170 return render_template( 171 'admin/viewUser.html', 172 title='View User', 173 user=user, 174 ) 175 176 177@app.route('/admin/edit_user/<int:user_id>', methods = ['GET', 'POST']) 178@login_required 179def edit_user(user_id): 180 """Edit details of a specific user. 181 182 Displays a form pre-populated with the user's current details. 183 Validates the form on submission, updating the user record in the database. 184 185 Details: 186 - If `password` is left blank, the existing hashed password is unchanged. 187 - The `manager` dropdown uses 0 to represent “None” (no manager). 188 189 Args: 190 user_id (int): ID of the user to edit. 191 192 Returns: 193 - Rendered template with the edit user form. 194 - Redirects to logout if the user is not an admin. 195 - Redirects to manage_users on successful update with a flash message. 196 """ 197 if current_user.role.role_name != "admin": 198 return redirect(url_for('logout')) 199 200 user = User.query.get_or_404(user_id) 201 form = EditUserForm(obj=user) 202 form.user_id = user.id 203 204 form.role.choices = [ 205 (role.id, role.role_name) for role in Role.query.all() 206] 207 form.department.choices = [ 208 (dept.id, dept.department_name) for dept in Department.query.all() 209 ] 210 211 manager_role = Role.query.filter_by(role_name='manager').first() 212 manager_choices = [(0, 'None')] 213 if manager_role: 214 manager_choices += [ 215 (u.id, f"{u.first_name.title()} {u.surname.title()}") 216 for u in User.query.filter_by(role_id=manager_role.id) 217 ] 218 form.manager.choices = manager_choices 219 220 if request.method == 'GET': 221 form.role.data = user.role_id 222 form.department.data = user.department_id 223 form.manager.data = user.manager_id or 0 224 form.is_onboarding.data = 'yes' if user.is_onboarding else 'no' 225 226 if form.validate_on_submit(): 227 user.first_name = form.first_name.data 228 user.surname = form.surname.data 229 user.username = form.username.data 230 user.role_id = int(form.role.data) 231 user.is_onboarding = (form.is_onboarding.data == 'yes') 232 user.manager_id = int(form.manager.data) if form.manager.data else None 233 user.department_id = int(form.department.data) 234 user.job_title = form.job_title.data 235 236 if form.password.data: 237 user.set_password(form.password.data) 238 239 if form.dateStarted.data: 240 user.dateStarted = form.dateStarted.data 241 242 db.session.commit() 243 244 flash( 245 f'User {user.first_name} {user.surname} ' 246 'user details have been updated') 247 return redirect(url_for('manage_users')) 248 249 return render_template( 250 'admin/editUser.html', 251 title='Edit User', 252 form=form, 253 user=user, 254 ) 255 256 257@app.route('/admin/delete_user/<int:user_id>', methods=['POST']) 258@login_required 259def delete_user(user_id): 260 """Delete a user from the system. 261 262 Details: 263 - Users are prevented from deleting their own account. 264 265 Args: 266 user_id (int): ID of the user to delete. 267 268 Raises: 269 404 error if the user does not exist. 270 271 Returns: 272 - Redirects to manage_users on successful deletion with a flash 273 message. 274 - Redirects to manage_users with a flash message if the user tries 275 to delete their own account. 276 - Redirects to logout if the user is not an admin. 277 """ 278 if current_user.role.role_name != 'admin': 279 return redirect(url_for('logout')) 280 281 user = User.query.get_or_404(user_id) 282 283 if user.id == current_user.id: 284 flash("You cannot delete your own account.") 285 return redirect(url_for('manage_users')) 286 287 db.session.delete(user) 288 db.session.commit() 289 flash(f"User {user.first_name} {user.surname} has been deleted.") 290 291 return redirect(url_for('manage_users')) 292 293 294@app.route('/admin/create_training_module', methods = ['GET', 'POST']) 295@login_required 296def create_training_module(): 297 """Create a new training module. 298 299 This route allows an admin to create a new training module, including 300 questions and options. It handles form submission, validation, and 301 associates the module with onboarding pathways. 302 303 Details: 304 - A POST with `add_question` in the form will append an extra 305 question entry and re-render the form without saving. 306 307 Args: 308 None (form data submitted via POST). 309 310 Returns: 311 - Redirects to manage_training_modules with a flash message on 312 successful creation. 313 - Re-renders the “create” template if fields are invalid or when 314 dynamically adding a question. 315 - Redirects to logout if the user is not an admin. 316 """ 317 if current_user.role.role_name != "admin": 318 return redirect(url_for('logout')) 319 320 form = CreateTrainingModuleForm() 321 form.pathways.choices = [ 322 (path.id, path.path_name) 323 for path in OnboardingPath.query.all() 324 ] 325 326 if request.method == 'POST' and 'add_question' in request.form: 327 form.questions.append_entry() 328 return render_template( 329 'admin/create_training_module.html', 330 title = 'Create Training Module', 331 form = form 332 ) 333 334 if form.validate_on_submit(): 335 try: 336 training_module = TrainingModule( 337 module_title = form.module_title.data, 338 module_description = form.module_description.data, 339 module_instructions = form.module_instructions.data, 340 video_url = form.video_url.data or None 341 ) 342 db.session.add(training_module) 343 db.session.flush() 344 345 for pathway_id in form.pathways.data: 346 pathway = OnboardingPath.query.get(pathway_id) 347 if pathway: 348 onboarding_step = OnboardingStep( 349 step_name = training_module.module_title, 350 path = pathway, 351 training_module = training_module 352 ) 353 db.session.add(onboarding_step) 354 355 if not form.questions.data: 356 flash("You must add at least one question.") 357 db.session.rollback() 358 return redirect(url_for('create_training_module')) 359 360 for question_form in form.questions: 361 question = Question( 362 question_text = question_form.question_text.data, 363 training_module = training_module 364 ) 365 db.session.add(question) 366 db.session.flush() 367 for option_form in ( 368 question_form.option1, 369 question_form.option2, 370 question_form.option3, 371 question_form.option4, 372 ): 373 option = Option( 374 option_text = option_form.option_text.data, 375 is_correct = option_form.is_correct.data, 376 question = question, 377 ) 378 db.session.add(option) 379 380 db.session.commit() 381 flash( 382 f'Training module "{training_module.module_title}" has been ' 383 'successfully created!') 384 return redirect(url_for('manage_training_modules')) 385 386 except Exception as e: 387 db.session.rollback() 388 print(f'Error: {str(e)}') 389 flash("An error occurred, please contact support.") 390 return redirect('admin/create_training_module.html', title='Create Training Module', form=form) 391 392 return render_template('admin/create_training_module.html', title='Create Training Module', form=form) 393 394 395 396@app.route('/admin/manage_training_modules', methods = ['GET']) 397@login_required 398def manage_training_modules(): 399 """Display all active training modules. 400 401 Returns: 402 - Rendered template with a list of all active training modules. 403 - Redirects to logout if the user is not an admin. 404 """ 405 if current_user.role.role_name != "admin": 406 return redirect(url_for('logout')) 407 408 modules = TrainingModule.query.filter_by(active=True).all() 409 410 return render_template( 411 'admin/manage_training_modules.html', 412 title = 'Manage Training Modules', 413 modules = modules 414 ) 415 416 417@app.route('/admin/details_training_module/<int:module_id>', methods = ['GET']) 418@login_required 419def details_training_module(module_id): 420 """Display details of a specific training module. 421 422 Args: 423 module_id (int): ID of the training module to view. 424 425 Raises: 426 404 error if the module does not exist. 427 428 Returns: 429 - Rendered template with training module details and associated 430 questions. 431 - Redirects to logout if the user is not an admin. 432 """ 433 if current_user.role.role_name != "admin": 434 return redirect(url_for('logout')) 435 436 module = TrainingModule.query.get_or_404(module_id) 437 438 questions = Question.query.filter_by(training_module_id=module_id).all() 439 440 return render_template( 441 'admin/details_training_module.html', 442 title=f'{module.module_title} Details', 443 module=module, 444 questions=questions 445 ) 446 447 448@app.route('/admin/edit_training_module/<int:module_id>', methods = ['GET', 'POST']) 449@login_required 450def edit_training_module(module_id): 451 """Edit an existing training module. 452 453 This route allows an admin to edit the details of a training module, 454 including its title, description, instructions, video URL, and associated 455 questions and options. It also allows the admin to assign the module to 456 specific onboarding pathways. 457 458 Details: 459 - OnboardingStep entries are cleared and re-created based on the form 460 submission. 461 - Questions and options are updated based on the form data. 462 463 Args: 464 module_id (int): ID of the training module to edit. 465 466 Raises: 467 404 error if the module does not exist. 468 469 Returns: 470 - Rendered template with the edit form pre-populated with the 471 module's current details. 472 - Redirects to manage_training_modules on successful update with a 473 flash message. 474 - Re-renders the edit form if validation fails or on GET request. 475 - Redirects to logout if the user is not an admin. 476 """ 477 if current_user.role.role_name != "admin": 478 return redirect(url_for('logout')) 479 480 module = TrainingModule.query.get_or_404(module_id) 481 482 form = CreateTrainingModuleForm(obj=module) 483 form.pathways.choices = [ 484 (path.id, path.path_name) for path in OnboardingPath.query.all() 485 ] 486 487 if request.method == 'GET': 488 form.questions.entries.clear() 489 for question in module.questions: 490 question_form = form.questions.append_entry() 491 question_form.question_text.data = question.question_text 492 for i, option in enumerate(question.options): 493 sub = getattr(question_form, f'option{i+1}') 494 sub.option_text.data = option.option_text 495 sub.is_correct.data = option.is_correct 496 497 form.pathways.data = [ 498 step.onboarding_path_id 499 for step in module.onboarding_steps 500 ] 501 502 if form.validate_on_submit(): 503 module.module_title = form.module_title.data 504 module.module_description = form.module_description.data 505 module.module_instructions = form.module_instructions.data 506 module.video_url = form.video_url.data or None 507 508 OnboardingStep.query.filter_by(training_module_id = module.id).delete() 509 for path_id in form.pathways.data: 510 path = OnboardingPath.query.get(path_id) 511 db.session.add(OnboardingStep( 512 step_name=module.module_title, 513 path=path, 514 training_module=module 515 )) 516 for i in range(len(module.questions)): 517 question_obj = module.questions[i] 518 question_form = form.questions[i] 519 520 question_obj.question_text = question_form.question_text.data 521 522 for j, option_obj in enumerate(question_obj.options): 523 option = getattr(question_form, f'option{j+1}') 524 option_obj.option_text = option.option_text.data 525 option_obj.is_correct = option.is_correct.data 526 527 db.session.commit() 528 flash(f'"{module.module_title}" has been updated.') 529 return redirect(url_for('manage_training_modules')) 530 elif request.method == 'POST': 531 flash('Please check form for errors.') 532 533 return render_template( 534 'admin/edit_training_module.html', 535 title=f'Edit Module: {module.module_title}', 536 form=form, 537 module=module 538 ) 539 540 541@app.route('/admin/delete_training_module/<int:module_id>', methods=['POST']) 542@login_required 543def delete_training_module(module_id): 544 """Deactivate a training module. 545 546 This marks the module as inactive so it no longer appears in listings. 547 It does not delete the record from the database. 548 549 Args: 550 module_id (int): ID of the training module to be deleted. 551 552 Raises: 553 404 error if the module does not exist. 554 555 Returns: 556 - Redirect to the training module management page with a success message. 557 - Redirects to logout if the user is not an admin. 558 """ 559 if current_user.role.role_name != 'admin': 560 return redirect(url_for('logout')) 561 562 module = TrainingModule.query.get_or_404(module_id) 563 module.active = False 564 565 try: 566 db.session.commit() 567 flash(f'Module "{module.module_title}" has been deleted.') 568 except Exception as e: 569 db.session.rollback() 570 print(f'Failed to deactivate module {module_id}: {e}') 571 flash('An error occurred while deleting the module. Contact support.') 572 573 return redirect(url_for('manage_training_modules'))
27@app.route('/admin/register', methods = ['GET', 'POST']) 28def register_user(): 29 """Register a new user. 30 31 Displays a form for creating a user, validates input, determines the 32 appropriate onboarding path based on department, sets the password, 33 and commits the new User record to the database. 34 35 Args: 36 None (form data submitted via POST). 37 38 Returns: 39 - Redirects to logout if the user is not an admin. 40 - Re-renders the registration form on GET or validation failure. 41 - Redirects to manage_users on successful creation (with a flash 42 message). 43 """ 44 if not current_user.is_authenticated or current_user.role.role_name != 'admin': 45 return redirect(url_for('logout')) 46 47 form = CreateUserForm() 48 49 form.role.choices = [(0, 'Select Role')] + [ 50 (role.id, role.role_name) for role in Role.query.all() 51 ] 52 form.department.choices = [(0, 'Select Department')] + [ 53 (dept.id, dept.department_name) for dept in Department.query.all() 54 ] 55 56 manager_role = Role.query.filter_by(role_name='manager').first() 57 manager_choices = [(0, 'None')] 58 if manager_role: 59 manager_choices += [ 60 (u.id, f"{u.first_name.title()} {u.surname.title()}") 61 for u in User.query.filter_by(role_id=manager_role.id) 62 ] 63 form.manager.choices = manager_choices 64 65 if form.validate_on_submit(): 66 department_id = int(form.department.data) 67 68 if Department.query.filter_by( 69 id=department_id, 70 department_name = "office", 71 ).first(): 72 onboarding_path = OnboardingPath.query.filter_by( 73 path_name = "office", 74 ).first() 75 else: 76 onboarding_path = OnboardingPath.query.filter_by( 77 path_name = "operational", 78 ).first() 79 80 user = User( 81 first_name = form.firstName.data, 82 surname = form.surname.data, 83 username = form.username.data, 84 role_id = int(form.role.data), 85 is_onboarding = (form.is_onboarding.data == 'yes'), 86 manager_id = int(form.manager.data) if form.manager.data else None, 87 department_id = int(form.department.data), 88 onboarding_path_id = onboarding_path.id if onboarding_path else None, 89 dateStarted = datetime.now(), 90 job_title = form.job_title.data 91 ) 92 93 user.set_password(form.password.data) 94 db.session.add(user) 95 db.session.commit() 96 flash( 97 f'User {user.first_name} {user.surname} ' 98 'has been successfully registered!' 99 ) 100 return redirect(url_for('manage_users')) 101 102 return render_template( 103 'admin/createUser.html', 104 title='Register User', 105 form=form, 106 )
Register a new user.
Displays a form for creating a user, validates input, determines the appropriate onboarding path based on department, sets the password, and commits the new User record to the database.
Arguments:
- None (form data submitted via POST).
Returns:
- Redirects to logout if the user is not an admin.
- Re-renders the registration form on GET or validation failure.
- Redirects to manage_users on successful creation (with a flash message).
109@app.route('/admin/dashboard') 110@login_required 111def admin_dashboard(): 112 """Display the admin dashboard. 113 114 Returns: 115 - Rendered admin dashboard template. 116 - Redirects to logout if the user is not an admin. 117 """ 118 if current_user.role.role_name!= "admin": 119 return redirect(url_for('logout')) 120 121 return render_template( 122 'admin/dashboard.html', 123 title='Admin Dashboard', 124 )
Display the admin dashboard.
Returns:
- Rendered admin dashboard template.
- Redirects to logout if the user is not an admin.
127@app.route('/admin/manage_users', methods = ['GET']) 128@login_required 129def manage_users(): 130 """Display all users in the system. 131 132 Returns: 133 - Rendered template with a list of all users. 134 - Redirects to the admin dashboard with a flash message if an error 135 occurs. 136 - Redirects to logout if the user is not an admin. 137 """ 138 if current_user.role.role_name != "admin": 139 return redirect(url_for('logout')) 140 141 try: 142 users = User.query.all() 143 return render_template( 144 'admin/manageUsers.html', 145 title='Manage Users', 146 users=users, 147 ) 148 except Exception as e: 149 print('Error: ' + str(e)) 150 flash("An error occured contact support.") 151 return redirect(url_for('admin_dashboard'))
Display all users in the system.
Returns:
- Rendered template with a list of all users.
- Redirects to the admin dashboard with a flash message if an error occurs.
- Redirects to logout if the user is not an admin.
154@app.route('/admin/view_user/<int:user_id>', methods=['GET']) 155@login_required 156def view_user(user_id): 157 """View details of a specific user. 158 159 Args: 160 user_id (int): ID of the user to view. 161 162 Returns: 163 - Rendered template with user details. 164 - Redirects to logout if the user is not an admin. 165 """ 166 if current_user.role.role_name != "admin": 167 return redirect(url_for('logout')) 168 169 user = User.query.get_or_404(user_id) 170 171 return render_template( 172 'admin/viewUser.html', 173 title='View User', 174 user=user, 175 )
View details of a specific user.
Arguments:
- user_id (int): ID of the user to view.
Returns:
- Rendered template with user details.
- Redirects to logout if the user is not an admin.
178@app.route('/admin/edit_user/<int:user_id>', methods = ['GET', 'POST']) 179@login_required 180def edit_user(user_id): 181 """Edit details of a specific user. 182 183 Displays a form pre-populated with the user's current details. 184 Validates the form on submission, updating the user record in the database. 185 186 Details: 187 - If `password` is left blank, the existing hashed password is unchanged. 188 - The `manager` dropdown uses 0 to represent “None” (no manager). 189 190 Args: 191 user_id (int): ID of the user to edit. 192 193 Returns: 194 - Rendered template with the edit user form. 195 - Redirects to logout if the user is not an admin. 196 - Redirects to manage_users on successful update with a flash message. 197 """ 198 if current_user.role.role_name != "admin": 199 return redirect(url_for('logout')) 200 201 user = User.query.get_or_404(user_id) 202 form = EditUserForm(obj=user) 203 form.user_id = user.id 204 205 form.role.choices = [ 206 (role.id, role.role_name) for role in Role.query.all() 207] 208 form.department.choices = [ 209 (dept.id, dept.department_name) for dept in Department.query.all() 210 ] 211 212 manager_role = Role.query.filter_by(role_name='manager').first() 213 manager_choices = [(0, 'None')] 214 if manager_role: 215 manager_choices += [ 216 (u.id, f"{u.first_name.title()} {u.surname.title()}") 217 for u in User.query.filter_by(role_id=manager_role.id) 218 ] 219 form.manager.choices = manager_choices 220 221 if request.method == 'GET': 222 form.role.data = user.role_id 223 form.department.data = user.department_id 224 form.manager.data = user.manager_id or 0 225 form.is_onboarding.data = 'yes' if user.is_onboarding else 'no' 226 227 if form.validate_on_submit(): 228 user.first_name = form.first_name.data 229 user.surname = form.surname.data 230 user.username = form.username.data 231 user.role_id = int(form.role.data) 232 user.is_onboarding = (form.is_onboarding.data == 'yes') 233 user.manager_id = int(form.manager.data) if form.manager.data else None 234 user.department_id = int(form.department.data) 235 user.job_title = form.job_title.data 236 237 if form.password.data: 238 user.set_password(form.password.data) 239 240 if form.dateStarted.data: 241 user.dateStarted = form.dateStarted.data 242 243 db.session.commit() 244 245 flash( 246 f'User {user.first_name} {user.surname} ' 247 'user details have been updated') 248 return redirect(url_for('manage_users')) 249 250 return render_template( 251 'admin/editUser.html', 252 title='Edit User', 253 form=form, 254 user=user, 255 )
Edit details of a specific user.
Displays a form pre-populated with the user's current details. Validates the form on submission, updating the user record in the database.
Details:
- If
passwordis left blank, the existing hashed password is unchanged.- The
managerdropdown uses 0 to represent “None” (no manager).
Arguments:
- user_id (int): ID of the user to edit.
Returns:
- Rendered template with the edit user form.
- Redirects to logout if the user is not an admin.
- Redirects to manage_users on successful update with a flash message.
258@app.route('/admin/delete_user/<int:user_id>', methods=['POST']) 259@login_required 260def delete_user(user_id): 261 """Delete a user from the system. 262 263 Details: 264 - Users are prevented from deleting their own account. 265 266 Args: 267 user_id (int): ID of the user to delete. 268 269 Raises: 270 404 error if the user does not exist. 271 272 Returns: 273 - Redirects to manage_users on successful deletion with a flash 274 message. 275 - Redirects to manage_users with a flash message if the user tries 276 to delete their own account. 277 - Redirects to logout if the user is not an admin. 278 """ 279 if current_user.role.role_name != 'admin': 280 return redirect(url_for('logout')) 281 282 user = User.query.get_or_404(user_id) 283 284 if user.id == current_user.id: 285 flash("You cannot delete your own account.") 286 return redirect(url_for('manage_users')) 287 288 db.session.delete(user) 289 db.session.commit() 290 flash(f"User {user.first_name} {user.surname} has been deleted.") 291 292 return redirect(url_for('manage_users'))
Delete a user from the system.
Details:
- Users are prevented from deleting their own account.
Arguments:
- user_id (int): ID of the user to delete.
Raises:
- 404 error if the user does not exist.
Returns:
- Redirects to manage_users on successful deletion with a flash message.
- Redirects to manage_users with a flash message if the user tries to delete their own account.
- Redirects to logout if the user is not an admin.
295@app.route('/admin/create_training_module', methods = ['GET', 'POST']) 296@login_required 297def create_training_module(): 298 """Create a new training module. 299 300 This route allows an admin to create a new training module, including 301 questions and options. It handles form submission, validation, and 302 associates the module with onboarding pathways. 303 304 Details: 305 - A POST with `add_question` in the form will append an extra 306 question entry and re-render the form without saving. 307 308 Args: 309 None (form data submitted via POST). 310 311 Returns: 312 - Redirects to manage_training_modules with a flash message on 313 successful creation. 314 - Re-renders the “create” template if fields are invalid or when 315 dynamically adding a question. 316 - Redirects to logout if the user is not an admin. 317 """ 318 if current_user.role.role_name != "admin": 319 return redirect(url_for('logout')) 320 321 form = CreateTrainingModuleForm() 322 form.pathways.choices = [ 323 (path.id, path.path_name) 324 for path in OnboardingPath.query.all() 325 ] 326 327 if request.method == 'POST' and 'add_question' in request.form: 328 form.questions.append_entry() 329 return render_template( 330 'admin/create_training_module.html', 331 title = 'Create Training Module', 332 form = form 333 ) 334 335 if form.validate_on_submit(): 336 try: 337 training_module = TrainingModule( 338 module_title = form.module_title.data, 339 module_description = form.module_description.data, 340 module_instructions = form.module_instructions.data, 341 video_url = form.video_url.data or None 342 ) 343 db.session.add(training_module) 344 db.session.flush() 345 346 for pathway_id in form.pathways.data: 347 pathway = OnboardingPath.query.get(pathway_id) 348 if pathway: 349 onboarding_step = OnboardingStep( 350 step_name = training_module.module_title, 351 path = pathway, 352 training_module = training_module 353 ) 354 db.session.add(onboarding_step) 355 356 if not form.questions.data: 357 flash("You must add at least one question.") 358 db.session.rollback() 359 return redirect(url_for('create_training_module')) 360 361 for question_form in form.questions: 362 question = Question( 363 question_text = question_form.question_text.data, 364 training_module = training_module 365 ) 366 db.session.add(question) 367 db.session.flush() 368 for option_form in ( 369 question_form.option1, 370 question_form.option2, 371 question_form.option3, 372 question_form.option4, 373 ): 374 option = Option( 375 option_text = option_form.option_text.data, 376 is_correct = option_form.is_correct.data, 377 question = question, 378 ) 379 db.session.add(option) 380 381 db.session.commit() 382 flash( 383 f'Training module "{training_module.module_title}" has been ' 384 'successfully created!') 385 return redirect(url_for('manage_training_modules')) 386 387 except Exception as e: 388 db.session.rollback() 389 print(f'Error: {str(e)}') 390 flash("An error occurred, please contact support.") 391 return redirect('admin/create_training_module.html', title='Create Training Module', form=form) 392 393 return render_template('admin/create_training_module.html', title='Create Training Module', form=form)
Create a new training module.
This route allows an admin to create a new training module, including questions and options. It handles form submission, validation, and associates the module with onboarding pathways.
Details:
- A POST with
add_questionin the form will append an extra question entry and re-render the form without saving.
Arguments:
- None (form data submitted via POST).
Returns:
- Redirects to manage_training_modules with a flash message on successful creation.
- Re-renders the “create” template if fields are invalid or when dynamically adding a question.
- Redirects to logout if the user is not an admin.
397@app.route('/admin/manage_training_modules', methods = ['GET']) 398@login_required 399def manage_training_modules(): 400 """Display all active training modules. 401 402 Returns: 403 - Rendered template with a list of all active training modules. 404 - Redirects to logout if the user is not an admin. 405 """ 406 if current_user.role.role_name != "admin": 407 return redirect(url_for('logout')) 408 409 modules = TrainingModule.query.filter_by(active=True).all() 410 411 return render_template( 412 'admin/manage_training_modules.html', 413 title = 'Manage Training Modules', 414 modules = modules 415 )
Display all active training modules.
Returns:
- Rendered template with a list of all active training modules.
- Redirects to logout if the user is not an admin.
418@app.route('/admin/details_training_module/<int:module_id>', methods = ['GET']) 419@login_required 420def details_training_module(module_id): 421 """Display details of a specific training module. 422 423 Args: 424 module_id (int): ID of the training module to view. 425 426 Raises: 427 404 error if the module does not exist. 428 429 Returns: 430 - Rendered template with training module details and associated 431 questions. 432 - Redirects to logout if the user is not an admin. 433 """ 434 if current_user.role.role_name != "admin": 435 return redirect(url_for('logout')) 436 437 module = TrainingModule.query.get_or_404(module_id) 438 439 questions = Question.query.filter_by(training_module_id=module_id).all() 440 441 return render_template( 442 'admin/details_training_module.html', 443 title=f'{module.module_title} Details', 444 module=module, 445 questions=questions 446 )
Display details of a specific training module.
Arguments:
- module_id (int): ID of the training module to view.
Raises:
- 404 error if the module does not exist.
Returns:
- Rendered template with training module details and associated questions.
- Redirects to logout if the user is not an admin.
449@app.route('/admin/edit_training_module/<int:module_id>', methods = ['GET', 'POST']) 450@login_required 451def edit_training_module(module_id): 452 """Edit an existing training module. 453 454 This route allows an admin to edit the details of a training module, 455 including its title, description, instructions, video URL, and associated 456 questions and options. It also allows the admin to assign the module to 457 specific onboarding pathways. 458 459 Details: 460 - OnboardingStep entries are cleared and re-created based on the form 461 submission. 462 - Questions and options are updated based on the form data. 463 464 Args: 465 module_id (int): ID of the training module to edit. 466 467 Raises: 468 404 error if the module does not exist. 469 470 Returns: 471 - Rendered template with the edit form pre-populated with the 472 module's current details. 473 - Redirects to manage_training_modules on successful update with a 474 flash message. 475 - Re-renders the edit form if validation fails or on GET request. 476 - Redirects to logout if the user is not an admin. 477 """ 478 if current_user.role.role_name != "admin": 479 return redirect(url_for('logout')) 480 481 module = TrainingModule.query.get_or_404(module_id) 482 483 form = CreateTrainingModuleForm(obj=module) 484 form.pathways.choices = [ 485 (path.id, path.path_name) for path in OnboardingPath.query.all() 486 ] 487 488 if request.method == 'GET': 489 form.questions.entries.clear() 490 for question in module.questions: 491 question_form = form.questions.append_entry() 492 question_form.question_text.data = question.question_text 493 for i, option in enumerate(question.options): 494 sub = getattr(question_form, f'option{i+1}') 495 sub.option_text.data = option.option_text 496 sub.is_correct.data = option.is_correct 497 498 form.pathways.data = [ 499 step.onboarding_path_id 500 for step in module.onboarding_steps 501 ] 502 503 if form.validate_on_submit(): 504 module.module_title = form.module_title.data 505 module.module_description = form.module_description.data 506 module.module_instructions = form.module_instructions.data 507 module.video_url = form.video_url.data or None 508 509 OnboardingStep.query.filter_by(training_module_id = module.id).delete() 510 for path_id in form.pathways.data: 511 path = OnboardingPath.query.get(path_id) 512 db.session.add(OnboardingStep( 513 step_name=module.module_title, 514 path=path, 515 training_module=module 516 )) 517 for i in range(len(module.questions)): 518 question_obj = module.questions[i] 519 question_form = form.questions[i] 520 521 question_obj.question_text = question_form.question_text.data 522 523 for j, option_obj in enumerate(question_obj.options): 524 option = getattr(question_form, f'option{j+1}') 525 option_obj.option_text = option.option_text.data 526 option_obj.is_correct = option.is_correct.data 527 528 db.session.commit() 529 flash(f'"{module.module_title}" has been updated.') 530 return redirect(url_for('manage_training_modules')) 531 elif request.method == 'POST': 532 flash('Please check form for errors.') 533 534 return render_template( 535 'admin/edit_training_module.html', 536 title=f'Edit Module: {module.module_title}', 537 form=form, 538 module=module 539 )
Edit an existing training module.
This route allows an admin to edit the details of a training module, including its title, description, instructions, video URL, and associated questions and options. It also allows the admin to assign the module to specific onboarding pathways.
Details:
- OnboardingStep entries are cleared and re-created based on the form submission.
- Questions and options are updated based on the form data.
Arguments:
- module_id (int): ID of the training module to edit.
Raises:
- 404 error if the module does not exist.
Returns:
- Rendered template with the edit form pre-populated with the module's current details.
- Redirects to manage_training_modules on successful update with a flash message.
- Re-renders the edit form if validation fails or on GET request.
- Redirects to logout if the user is not an admin.
542@app.route('/admin/delete_training_module/<int:module_id>', methods=['POST']) 543@login_required 544def delete_training_module(module_id): 545 """Deactivate a training module. 546 547 This marks the module as inactive so it no longer appears in listings. 548 It does not delete the record from the database. 549 550 Args: 551 module_id (int): ID of the training module to be deleted. 552 553 Raises: 554 404 error if the module does not exist. 555 556 Returns: 557 - Redirect to the training module management page with a success message. 558 - Redirects to logout if the user is not an admin. 559 """ 560 if current_user.role.role_name != 'admin': 561 return redirect(url_for('logout')) 562 563 module = TrainingModule.query.get_or_404(module_id) 564 module.active = False 565 566 try: 567 db.session.commit() 568 flash(f'Module "{module.module_title}" has been deleted.') 569 except Exception as e: 570 db.session.rollback() 571 print(f'Failed to deactivate module {module_id}: {e}') 572 flash('An error occurred while deleting the module. Contact support.') 573 574 return redirect(url_for('manage_training_modules'))
Deactivate a training module.
This marks the module as inactive so it no longer appears in listings. It does not delete the record from the database.
Arguments:
- module_id (int): ID of the training module to be deleted.
Raises:
- 404 error if the module does not exist.
Returns:
- Redirect to the training module management page with a success message.
- Redirects to logout if the user is not an admin.