Coverage for app/controllers/minor/routes.py: 29%
101 statements
« prev ^ index » next coverage.py v7.10.2, created at 2026-03-08 07:10 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2026-03-08 07:10 +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 sustainedEngagementByTerm = getCommunityEngagementByTerm(username)
34 activeTab = request.args.get("tab", "sustainedCommunityEngagements")
35 return render_template("minor/profile.html",
36 user = User.get_by_id(username),
37 proposalList = getCCEMinorProposals(username),
38 sustainedEngagementByTerm = sustainedEngagementByTerm,
39 totalSustainedEngagements = getEngagementTotal(sustainedEngagementByTerm),
40 activeTab=activeTab)
42@minor_bp.route('/cceMinor/<username>/otherEngagement', methods=['GET', 'POST'])
43def createOtherEngagementRequest(username):
44 if not (g.current_user.isAdmin or g.current_user.username == username):
45 return abort(403)
47 # once we submit the form for creation
48 if request.method == "POST":
49 createOtherEngagement(username, request)
50 flash("Proposal successfully created.", "success")
51 return redirect(url_for('minor.viewCceMinor', username=username, tab="manageProposals"))
53 return render_template("minor/requestOtherEngagement.html",
54 editable = True,
55 user = User.get_by_id(username),
56 selectableTerms = selectSurroundingTerms(g.current_term),
57 postRoute = f"/cceMinor/{username}/otherEngagement", # when form is submitted, what POST route is it being submitted to.
58 attachmentFilePath = "",
59 attachmentFileName = "",
60 proposal = None)
62@minor_bp.route('/cceMinor/viewOtherEngagement/<proposalID>', methods=['GET'])
63@minor_bp.route('/cceMinor/viewSummerExperience/<proposalID>', methods=['GET'])
64@minor_bp.route('/cceMinor/editOtherEngagement/<proposalID>', methods=['GET', 'POST'])
65@minor_bp.route('/cceMinor/editSummerExperience/<proposalID>', methods=['GET', 'POST'])
66def editOrViewProposal(proposalID: int):
67 proposal = CCEMinorProposal.get_by_id(int(proposalID))
68 if not (g.current_user.isAdmin or g.current_user.username == proposal.student.username):
69 return abort(403)
71 editProposal = 'view' not in request.path
73 # if proposal is approved, only admins can edit, but not if the admin is the student
74 if proposal.isApproved and editProposal:
75 if g.current_user.username == proposal.student or not g.current_user.isAdmin:
76 return abort(403)
78 attachmentObject = AttachmentUpload.get_or_none(proposal=proposalID)
79 attachmentFilePath = ""
80 attachmentFileName = ""
82 if attachmentObject:
83 fileHandler = FileHandler(proposalId=proposalID)
84 attachmentFilePath = fileHandler.getFileFullPath(attachmentObject.fileName).lstrip("app/") # we need to remove app/ from the url because it prevents it from displaying
85 attachmentFileName = attachmentObject.fileName
87 if request.method == "GET":
88 selectedTerm = Term.get_by_id(proposal.term)
89 flash("Once approved, a proposal can only be edited by an admin.", 'warning')
90 return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/summerExperience.html",
91 editable = editProposal,
92 selectedTerm = selectedTerm,
93 contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [],
94 selectableTerms = selectSurroundingTerms(g.current_term, summerOnly=False if 'OtherEngagement' else True),
95 user = User.get_by_id(proposal.student),
96 postRoute = f"/cceMinor/editSummerExperience/{proposal.id}" if "SummerExperience" in request.path else f"/cceMinor/editOtherEngagement/{proposal.id}",
97 proposal = proposal,
98 attachmentFilePath = attachmentFilePath,
99 attachmentFileName = attachmentFileName
100 )
102 if "OtherEngagement" in request.path:
103 updateOtherEngagementRequest(proposalID, request)
104 else:
105 updateSummerExperience(proposalID, request.form)
107 flash("Proposal updated", "success")
108 return redirect(url_for('minor.viewCceMinor', username=proposal.student, tab='manageProposals'))
110@minor_bp.route('/cceMinor/<username>/summerExperience', methods=['GET', 'POST'])
111def createSummerExperienceRequest(username):
112 if not (g.current_user.isAdmin or g.current_user.username == username):
113 return abort(403)
115 # once we submit the form for creation
116 if request.method == "POST":
117 createSummerExperience(username, request.form)
118 flash("Proposal successfully created.", "success")
119 return redirect(url_for('minor.viewCceMinor', username=username, tab="manageProposals"))
121 summerTerms = selectSurroundingTerms(g.current_term, summerOnly=True)
123 return render_template("minor/summerExperience.html",
124 selectableTerms = summerTerms,
125 contentAreas = [],
126 user = User.get_by_id(username),
127 )
129@minor_bp.route('/cceMinor/<username>/getEngagementInformation/<type>/<term>/<id>', methods=['GET'])
130def getEngagementInformation(username, type, id, term):
131 if type == "program":
132 information = getProgramEngagementHistory(id, username, term)
133 else:
134 information = getCourseInformation(id)
136 return information
139@minor_bp.route('/cceMinor/<action>/<username>/<proposalId>', methods=['POST'])
140def updateProposal(action, username, proposalId):
141 try:
142 if not (g.current_user.isAdmin or g.current_user.isFaculty or g.current_user.username == username):
143 flash("Unauthorized to perform this action", "warning")
144 return ""
146 actionMap = {
147 "withdraw": ("Withdrawn", "Proposal successfully withdrawn"),
148 "complete": ("Completed", "Proposal successfully completed"),
149 "approve": ("Approved", "Proposal approved"),
150 "unapprove": ("Submitted", "Proposal unapproved"),
151 }
153 if action not in actionMap:
154 flash("Invalid action", "warning")
155 return ""
157 newStatus, message = actionMap[action]
159 if action == "withdraw":
160 removeProposal(proposalId)
161 else:
162 changeProposalStatus(proposalId, newStatus)
163 flash(message, "success")
165 except Exception as e:
166 print(e)
167 flash("Proposal status could not be changed", "warning")
169 return ""
172@minor_bp.route('/cceMinor/getMinorSpreadsheet', methods=['GET'])
173def returnMinorSpreadsheet():
174 """
175 Returns a spreadsheet containing users and related spreadsheet information.
176 """
177 minorSpreadsheet = getMinorSpreadsheet() # can we get for any term or only curr term?
179 return minorSpreadsheet
181@minor_bp.route('/cceMinor/<username>/modifyCommunityEngagement', methods=['PUT','DELETE'])
182def modifyCommunityEngagement(username):
183 """
184 Saving a term participation/activities for sustained community engagement
185 """
186 if not g.current_user.isCeltsAdmin:
187 abort(403)
189 action = 'add' if request.method == 'PUT' else 'remove'
190 try:
191 setCommunityEngagementForUser(action, request.form, g.current_user)
192 except DoesNotExist:
193 return "There are already 4 Sustained Community Engagement records."
195 return ""