Coverage for app/logic/minor.py: 97%

88 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-24 15:19 +0000

1from collections import defaultdict 

2from playhouse.shortcuts import model_to_dict 

3from peewee import JOIN, fn, Case, DoesNotExist 

4 

5from app.models.user import User 

6from app.models.term import Term 

7from app.models.event import Event 

8from app.models.course import Course 

9from app.models.program import Program 

10from app.models.certification import Certification 

11from app.models.courseInstructor import CourseInstructor 

12from app.models.eventParticipant import EventParticipant 

13from app.models.courseParticipant import CourseParticipant 

14from app.models.individualRequirement import IndividualRequirement 

15from app.models.certificationRequirement import CertificationRequirement 

16from app.models.communityEngagementRequest import CommunityEngagementRequest 

17 

18def getEngagementTotal(engagementData): 

19 """  

20 Count the number of engagements (from all terms) that have matched with a requirement  

21 """ 

22 

23 # map the flattened list of engagements to their matched values, and sum them 

24 return sum(map(lambda e: e['matched'], sum(engagementData.values(),[]))) 

25 

26 

27def getMinorInterest(): 

28 """ 

29 Get all students that have indicated interest in the CCE minor and return a list of dicts of all interested students 

30 """ 

31 interestedStudents = (User.select(User.firstName, User.lastName, User.username) 

32 .join(IndividualRequirement, JOIN.LEFT_OUTER, on=(User.username == IndividualRequirement.username)) 

33 .where(User.isStudent & User.minorInterest & IndividualRequirement.username.is_null(True))) 

34 

35 interestedStudentList = [{'firstName': student.firstName, 'lastName': student.lastName, 'username': student.username} for student in interestedStudents] 

36 

37 return interestedStudentList 

38 

39def getMinorProgress(): 

40 """ 

41 Get all the users who have an IndividualRequirement record under the CCE certification which  

42 and returns a list of dicts containing the student, how many engagements they have completed,  

43 and if they have completed the summer experience.  

44 """ 

45 summerCase = Case(None, [(CertificationRequirement.name == "Summer Program", 1)], 0) 

46 

47 engagedStudentsWithCount = ( 

48 User.select(User, fn.COUNT(IndividualRequirement.id).alias('engagementCount'), 

49 fn.SUM(summerCase).alias('hasSummer'), 

50 fn.IF(fn.COUNT(CommunityEngagementRequest.id) > 0, True, False).alias('hasCommunityEngagementRequest')) 

51 .join(IndividualRequirement, on=(User.username == IndividualRequirement.username)) 

52 .join(CertificationRequirement, on=(IndividualRequirement.requirement_id == CertificationRequirement.id)) 

53 .switch(User).join(CommunityEngagementRequest, JOIN.LEFT_OUTER, on= (User.username == CommunityEngagementRequest.user,)) 

54 .where(CertificationRequirement.certification_id == Certification.CCE) 

55 .group_by(User.firstName, User.lastName, User.username) 

56 .order_by(fn.COUNT(IndividualRequirement.id).desc()) 

57 ) 

58 engagedStudentsList = [{'username': student.username, 

59 'firstName': student.firstName, 

60 'lastName': student.lastName, 

61 'engagementCount': student.engagementCount - student.hasSummer, 

62 'hasCommunityEngagementRequest': student.hasCommunityEngagementRequest, 

63 'hasSummer': "Completed" if student.hasSummer else "Incomplete"} for student in engagedStudentsWithCount] 

64 return engagedStudentsList 

65 

66def toggleMinorInterest(username): 

67 """ 

68 Given a username, update their minor interest and minor status. 

69 """ 

70 user = User.get(username=username) 

71 user.minorInterest = not user.minorInterest 

72 

73 user.save() 

74 

75def getCourseInformation(id): 

76 """ 

77 Given a course ID, return an object containing the course information and  

78 its instructors full names. 

79 """ 

80 # retrieve the course and the course instructors 

81 course = model_to_dict(Course.get_by_id(id)) 

82 

83 courseInstructors = (CourseInstructor.select(CourseInstructor, User) 

84 .join(Course).switch() 

85 .join(User) 

86 .where(Course.id == id)) 

87 

88 courseInformation = {"instructors": [(instructor.user.firstName + " " + instructor.user.lastName) for instructor in courseInstructors], "course": course} 

89 

90 return courseInformation 

91 

92def getProgramEngagementHistory(program_id, username, term_id): 

93 """ 

94 Given a program_id, username, and term_id, return an object containing all events in the provided program  

95 and in the given term along with the program name. 

96 """ 

97 # execute a query that will retrieve all events in which the user has participated 

98 # that fall under the provided term and programs. 

99 eventsInProgramAndTerm = (Event.select(Event.id, Event.name, fn.SUM(EventParticipant.hoursEarned).alias("hoursEarned")) 

100 .join(Program).switch() 

101 .join(EventParticipant) 

102 .where(EventParticipant.user == username, 

103 Event.term == term_id, 

104 Program.id == program_id) 

105 ) 

106 

107 program = Program.get_by_id(program_id) 

108 

109 # calculate total amount of hours for the whole program that term 

110 totalHours = 0 

111 for event in eventsInProgramAndTerm: 

112 if event.hoursEarned: 

113 totalHours += event.hoursEarned 

114 

115 participatedEvents = {"program":program.programName, "events": [event for event in eventsInProgramAndTerm.dicts()], "totalHours": totalHours} 

116 

117 return participatedEvents 

118 

119def setCommunityEngagementForUser(action, engagementData, currentUser): 

120 """ 

121 Either add or remove an IndividualRequirement record for a student's Sustained Community Engagement 

122 

123 :param action: The behavior of the function. Can be 'add' or 'remove' 

124 :param engagementData: 

125 type: program or course 

126 id: program or course id 

127 username: the username of the student that is having a community engagement added or removed 

128 term: The term the engagement is recorded in 

129 :param currentuser: The user who is performing the add/remove action  

130 

131 :raises DoesNotExist: if there are no available CertificationRequirement slots remaining for the engagement 

132 """ 

133 if engagementData['type'] not in ['program','course']: 

134 raise Exception("Invalid engagement type!") 

135 

136 requirement = (CertificationRequirement.select() 

137 .join(IndividualRequirement, JOIN.LEFT_OUTER, on=( 

138 (IndividualRequirement.requirement == CertificationRequirement.id) & 

139 (IndividualRequirement.username == engagementData['username']))) 

140 .where(IndividualRequirement.username.is_null(True), 

141 CertificationRequirement.certification == Certification.CCE, 

142 CertificationRequirement.name.not_in(['Summer Program']))) 

143 if action == 'add': 

144 try: 

145 IndividualRequirement.create(**{engagementData['type']: engagementData['id'], 

146 "username": engagementData['username'], 

147 "term": engagementData['term'], 

148 "requirement": requirement.get(), 

149 "addedBy": currentUser, 

150 }) 

151 # Thrown if there are no available engagement requirements left. Handled elsewhere. 

152 except DoesNotExist as e: 

153 raise e 

154 

155 elif action == 'remove': 

156 IndividualRequirement.delete().where( 

157 getattr(IndividualRequirement, engagementData['type']) == engagementData['id'], 

158 IndividualRequirement.username == engagementData['username'], 

159 IndividualRequirement.term == engagementData['term'] 

160 ).execute() 

161 else: 

162 raise Exception(f"Invalid action '{action}' sent to setCommunityEngagementForUser") 

163 

164def getCommunityEngagementByTerm(username): 

165 """ 

166 Given a username, return all of their community engagements (service learning courses and event participations.) 

167 """ 

168 courseMatchCase = Case(None, [(IndividualRequirement.course.is_null(True) , 0)], 1) 

169 

170 courses = (Course.select(Course, courseMatchCase.alias("matchedReq")) 

171 .join(CourseParticipant, on=(Course.id == CourseParticipant.course)) 

172 .join(IndividualRequirement, JOIN.LEFT_OUTER, on=( 

173 (IndividualRequirement.course == Course.id) & 

174 (IndividualRequirement.username == CourseParticipant.user) & 

175 (IndividualRequirement.term == Course.term))) 

176 .where(CourseParticipant.user == username) 

177 .group_by(Course.courseName, Course.term)) 

178 

179 # initialize default dict to store term descriptions as keys mapping to each 

180 # engagement's respective type, name, id, and term. 

181 communityEngagementByTermDict = defaultdict(list) 

182 for course in courses: 

183 communityEngagementByTermDict[(course.term.description, course.term.id)].append( 

184 {"name":course.courseName, 

185 "id":course.id, 

186 "type":"course", 

187 "matched": course.matchedReq, 

188 "term":course.term.id}) 

189 

190 programMatchCase = Case(None, [(IndividualRequirement.program.is_null(True) , 0)], 1) 

191 

192 events = (Event.select(Event, Program, programMatchCase.alias('matchedReq')) 

193 .join(EventParticipant, on=(Event.id == EventParticipant.event)).switch() 

194 .join(Program) 

195 .join(IndividualRequirement, JOIN.LEFT_OUTER, on=((IndividualRequirement.program == Program.id) & 

196 (IndividualRequirement.username == EventParticipant.user) & 

197 (IndividualRequirement.term == Event.term))) 

198 .where(EventParticipant.user == username) 

199 .group_by(Event.program, Event.term)) 

200 

201 for event in events: 

202 communityEngagementByTermDict[(event.term.description, event.term.id)].append({"name":event.program.programName, 

203 "id":event.program.id, 

204 "type":"program", 

205 "matched": event.matchedReq, 

206 "term":event.term.id 

207 }) 

208 

209 # sorting the communityEngagementByTermDict by the term id 

210 return dict(sorted(communityEngagementByTermDict.items(), key=lambda engagement: engagement[0][1])) 

211 

212def saveOtherEngagementRequest(engagementRequest): 

213 """ 

214 Create a CommunityEngagementRequest entry based off of the form data 

215 """ 

216 engagementRequest['status'] = "Pending" 

217 CommunityEngagementRequest.create(**engagementRequest) 

218 

219 

220def saveSummerExperience(username, summerExperience, currentUser): 

221 """ 

222 :param username: username of the student that the summer experience is for 

223 :param summerExperience: dict  

224 summerExperience: string of what the summer experience was (will be written as the 'description' in the IndividualRequirement table) 

225 selectedSummerTerm: the term description that the summer experience took place in 

226 :param currentUser: the username of the user who added the summer experience record 

227 

228 Delete any existing IndividualRequirement entry for 'username' if it is for 'Summer Program' and create a new IndividualRequirement entry for  

229 'Summer Program' with the contents of summerExperience.  

230 """ 

231 requirementDeleteSubSelect = CertificationRequirement.select().where(CertificationRequirement.certification == Certification.CCE, CertificationRequirement.name << ['Summer Program']) 

232 IndividualRequirement.delete().where(IndividualRequirement.username == username, IndividualRequirement.requirement == requirementDeleteSubSelect).execute() 

233 

234 requirement = (CertificationRequirement.select() 

235 .join(IndividualRequirement, JOIN.LEFT_OUTER, on=((IndividualRequirement.requirement == CertificationRequirement.id) & 

236 (IndividualRequirement.username == username))) 

237 .where(IndividualRequirement.username.is_null(True), 

238 CertificationRequirement.certification == Certification.CCE, 

239 CertificationRequirement.name << ['Summer Program'])) 

240 

241 summerTerm = (Term.select().where(Term.description == summerExperience['selectedSummerTerm'])) 

242 

243 IndividualRequirement.create(**{"description": summerExperience['summerExperience'], 

244 "username": username, 

245 "term": summerTerm.get(), 

246 "requirement": requirement.get(), 

247 "addedBy": currentUser, 

248 }) 

249 return "" 

250 

251def getSummerExperience(username): 

252 """ 

253 Get a students summer experience to populate text box if the student has one 

254 """ 

255 summerExperience = (IndividualRequirement.select() 

256 .join(CertificationRequirement, JOIN.LEFT_OUTER, on=(CertificationRequirement.id == IndividualRequirement.requirement)).switch() 

257 .join(Term, on=(IndividualRequirement.term == Term.id)) 

258 .where(IndividualRequirement.username == username, 

259 CertificationRequirement.certification == Certification.CCE, 

260 CertificationRequirement.name << ['Summer Program'])) 

261 if len(list(summerExperience)) == 1: 

262 return (summerExperience.get().term.description, summerExperience.get().description) 

263 

264 return (None, None) 

265 

266def removeSummerExperience(username): 

267 """ 

268 Delete IndividualRequirement table entry for 'username' 

269 """ 

270 term, summerExperienceToDelete = getSummerExperience(username) 

271 IndividualRequirement.delete().where(IndividualRequirement.username == username, IndividualRequirement.description == summerExperienceToDelete).execute() 

272 

273 

274def getSummerTerms(): 

275 """ 

276 Return a list of all terms with the isSummer flag that is marked True. Used to populate term dropdown for summer experience 

277 """ 

278 summerTerms = list(Term.select().where(Term.isSummer).order_by(Term.termOrder)) 

279 

280 return summerTerms