Coverage for app/controllers/minor/routes.py: 28%
103 statements
« prev ^ index » next coverage.py v7.10.2, created at 2026-07-27 15:23 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2026-07-27 15:23 +0000
1from flask import g, render_template, request, abort, flash, redirect, url_for
2from peewee import DoesNotExist
4from app.controllers.minor import minor_bp
5from app.models.user import User
6from app.models.cceMinorProposal import CCEMinorProposal
7from app.models.term import Term
8from app.models.attachmentUpload import AttachmentUpload
9from app.logic.fileHandler import FileHandler
10from app.logic.utils import selectSurroundingTerms, getFilesFromRequest
11from app.logic.minor import (
12 changeProposalStatus,
13 createOtherEngagement,
14 updateOtherEngagementRequest,
15 setCommunityEngagementForUser,
16 getEngagementTotal,
17 createSummerExperience,
18 updateSummerExperience,
19 getProgramEngagementHistory,
20 getCourseInformation,
21 getCommunityEngagementByTerm,
22 getMinorSpreadsheet,
23 getCCEMinorProposals,
24 removeProposal
25)
27@minor_bp.route('/profile/<username>/cceMinor', methods=['GET'])
28def viewCceMinor(username):
29 """
30 Load minor management page with community engagements and summer experience
31 """
32 if not (g.current_user.isAdmin or g.current_user.username == username or g.current_user.isCeltsStudentStaff):
33 return abort(403)
35 sustainedEngagementByTerm = getCommunityEngagementByTerm(username)
37 activeTab = request.args.get("tab", "sustainedCommunityEngagements")
38 return render_template("minor/profile.html",
39 user = User.get_by_id(username),
40 proposalList = getCCEMinorProposals(username),
41 sustainedEngagementByTerm = sustainedEngagementByTerm,
42 totalSustainedEngagements = getEngagementTotal(sustainedEngagementByTerm),
43 activeTab=activeTab)
45@minor_bp.route('/cceMinor/<username>/otherEngagement', methods=['GET', 'POST'])
46def createOtherEngagementRequest(username):
47 if not (g.current_user.isAdmin or g.current_user.username == username):
48 return abort(403)
50 # once we submit the form for creation
51 if request.method == "POST":
52 createOtherEngagement(username, request)
53 flash("Proposal successfully created.", "success")
54 return redirect(url_for('minor.viewCceMinor', username=username, tab="manageProposals"))
56 return render_template("minor/requestOtherEngagement.html",
57 editable = True,
58 user = User.get_by_id(username),
59 selectableTerms = selectSurroundingTerms(g.current_term),
60 postRoute = f"/cceMinor/{username}/otherEngagement", # when form is submitted, what POST route is it being submitted to.
61 attachmentFilePath = "",
62 attachmentFileName = "",
63 proposal = None)
65@minor_bp.route('/cceMinor/viewOtherEngagement/<proposalID>', methods=['GET'])
66@minor_bp.route('/cceMinor/viewSummerExperience/<proposalID>', methods=['GET'])
67@minor_bp.route('/cceMinor/editOtherEngagement/<proposalID>', methods=['GET', 'POST'])
68@minor_bp.route('/cceMinor/editSummerExperience/<proposalID>', methods=['GET', 'POST'])
69def editOrViewProposal(proposalID: int):
70 proposal = CCEMinorProposal.get_by_id(int(proposalID))
71 if not (g.current_user.isAdmin or g.current_user.username == proposal.student.username):
72 return abort(403)
74 editProposal = 'view' not in request.path
76 # if proposal is approved or completed, only admins can edit, but not if the admin is the student
77 if proposal.isApproved and editProposal:
78 if g.current_user.username == proposal.student.username or not g.current_user.isAdmin:
79 return abort(403)
81 attachmentObject = AttachmentUpload.get_or_none(proposal=proposalID)
82 attachmentFilePath = ""
83 attachmentFileName = ""
85 if attachmentObject:
86 fileHandler = FileHandler(proposalId=proposalID)
87 attachmentFilePath = fileHandler.getFileFullPath(attachmentObject.fileName).lstrip("app/") # we need to remove app/ from the url because it prevents it from displaying
88 attachmentFileName = attachmentObject.fileName
90 if request.method == "GET":
91 selectedTerm = Term.get_by_id(proposal.term)
92 flash("Once approved, a proposal can only be edited by an admin.", 'warning')
93 return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/summerExperience.html",
94 editable = editProposal,
95 selectedTerm = selectedTerm,
96 contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [],
97 selectableTerms = selectSurroundingTerms(g.current_term, summerOnly=False if 'OtherEngagement' not in request.path else True),
98 user = User.get_by_id(proposal.student),
99 postRoute = f"/cceMinor/editSummerExperience/{proposal.id}" if "SummerExperience" in request.path else f"/cceMinor/editOtherEngagement/{proposal.id}",
100 proposal = proposal,
101 attachmentFilePath = attachmentFilePath,
102 attachmentFileName = attachmentFileName
103 )
105 if "OtherEngagement" in request.path:
106 updateOtherEngagementRequest(proposalID, request)
107 else:
108 updateSummerExperience(proposalID, request.form)
110 flash("Proposal updated", "success")
111 return redirect(url_for('minor.viewCceMinor', username=proposal.student, tab='manageProposals'))
113@minor_bp.route('/cceMinor/<username>/summerExperience', methods=['GET', 'POST'])
114def createSummerExperienceRequest(username):
115 if not (g.current_user.isAdmin or g.current_user.username == username):
116 return abort(403)
118 # once we submit the form for creation
119 if request.method == "POST":
120 createSummerExperience(username, request.form)
121 flash("Proposal successfully created.", "success")
122 return redirect(url_for('minor.viewCceMinor', username=username, tab="manageProposals"))
124 summerTerms = selectSurroundingTerms(g.current_term, summerOnly=True)
126 return render_template("minor/summerExperience.html",
127 selectableTerms = summerTerms,
128 contentAreas = [],
129 user = User.get_by_id(username),
130 )
132@minor_bp.route('/cceMinor/<username>/getEngagementInformation/<type>/<term>/<id>', methods=['GET'])
133def getEngagementInformation(username, type, id, term):
134 if type == "program":
135 information = getProgramEngagementHistory(id, username, term)
136 else:
137 information = getCourseInformation(id)
139 return information
142@minor_bp.route('/cceMinor/<action>/<username>/<proposalId>', methods=['POST'])
143def updateProposal(action, username, proposalId):
144 try:
145 if not (g.current_user.isAdmin or g.current_user.isFaculty or g.current_user.username == username):
146 flash("Unauthorized to perform this action", "warning")
147 return ""
149 actionMap = {
150 "withdraw": ("Withdrawn", "Proposal successfully withdrawn"),
151 "complete": ("Completed", "Proposal successfully completed"),
152 "approve": ("Approved", "Proposal approved"),
153 "unapprove": ("Submitted", "Proposal unapproved"),
154 }
156 if action not in actionMap:
157 flash("Invalid action", "warning")
158 return ""
160 newStatus, message = actionMap[action]
162 if action == "withdraw":
163 removeProposal(proposalId)
164 else:
165 changeProposalStatus(proposalId, newStatus)
166 flash(message, "success")
168 except Exception as e:
169 print(e)
170 flash("Proposal status could not be changed", "warning")
172 return ""
175@minor_bp.route('/cceMinor/getMinorSpreadsheet', methods=['GET'])
176def returnMinorSpreadsheet():
177 """
178 Returns a spreadsheet containing users and related spreadsheet information.
179 """
180 minorSpreadsheet = getMinorSpreadsheet() # can we get for any term or only curr term?
182 return minorSpreadsheet
184@minor_bp.route('/cceMinor/<username>/modifyCommunityEngagement', methods=['PUT','DELETE'])
185def modifyCommunityEngagement(username):
186 """
187 Saving a term participation/activities for sustained community engagement
188 """
189 if not g.current_user.isCeltsAdmin:
190 abort(403)
192 action = 'add' if request.method == 'PUT' else 'remove'
193 try:
194 setCommunityEngagementForUser(action, request.form, g.current_user)
195 except DoesNotExist:
196 return "There are already 4 Sustained Community Engagement records."
198 return ""