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

1from flask import g, render_template, request, abort, flash, redirect, url_for 

2from peewee import DoesNotExist 

3 

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) 

26 

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) 

34 

35 sustainedEngagementByTerm = getCommunityEngagementByTerm(username) 

36 

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) 

44 

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) 

49 

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")) 

55 

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) 

64 

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) 

73 

74 editProposal = 'view' not in request.path 

75 

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) 

80 

81 attachmentObject = AttachmentUpload.get_or_none(proposal=proposalID) 

82 attachmentFilePath = "" 

83 attachmentFileName = "" 

84 

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 

89 

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 ) 

104 

105 if "OtherEngagement" in request.path: 

106 updateOtherEngagementRequest(proposalID, request) 

107 else: 

108 updateSummerExperience(proposalID, request.form) 

109 

110 flash("Proposal updated", "success") 

111 return redirect(url_for('minor.viewCceMinor', username=proposal.student, tab='manageProposals')) 

112 

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) 

117 

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")) 

123 

124 summerTerms = selectSurroundingTerms(g.current_term, summerOnly=True) 

125 

126 return render_template("minor/summerExperience.html", 

127 selectableTerms = summerTerms, 

128 contentAreas = [], 

129 user = User.get_by_id(username), 

130 ) 

131 

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) 

138 

139 return information 

140 

141 

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 "" 

148 

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 } 

155 

156 if action not in actionMap: 

157 flash("Invalid action", "warning") 

158 return "" 

159 

160 newStatus, message = actionMap[action] 

161 

162 if action == "withdraw": 

163 removeProposal(proposalId) 

164 else: 

165 changeProposalStatus(proposalId, newStatus) 

166 flash(message, "success") 

167 

168 except Exception as e: 

169 print(e) 

170 flash("Proposal status could not be changed", "warning") 

171 

172 return "" 

173 

174 

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? 

181 

182 return minorSpreadsheet 

183 

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) 

191 

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." 

197 

198 return "" 

199