Changeset 15163 for main/waeup.kofa/trunk/src/waeup/kofa
- Timestamp:
- 23 Sep 2018, 05:05:04 (6 years ago)
- Location:
- main/waeup.kofa/trunk
- Files:
-
- 1 deleted
- 29 edited
- 2 copied
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.kofa/trunk
- Property svn:mergeinfo changed
/main/waeup.kofa/branches/henrik-transcript-workflow (added) merged: 15127-15128,15133-15134,15140-15143,15145-15150,15152-15155,15157-15160,15162
- Property svn:mergeinfo changed
-
main/waeup.kofa/trunk/src/waeup/kofa
- Property svn:mergeinfo changed
/main/waeup.kofa/branches/henrik-transcript-workflow/src/waeup/kofa (added) merged: 15128,15134,15140-15143,15145-15150,15152-15155,15157-15160,15162
- Property svn:mergeinfo changed
-
main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py
r15116 r15163 60 60 ISessionConfiguration, ISessionConfigurationAdd, IJobManager, 61 61 IPasswordValidator, IContactForm, IKofaUtils, ICSVExporter, 62 academic_sessions_vocab, DOCLINK, registration_states_vocab) 62 academic_sessions_vocab, DOCLINK, registration_states_vocab, 63 TRANSREQ, TRANSVAL) 64 from waeup.kofa.university.faculty import Faculty 63 65 from waeup.kofa.university.department import Department 64 66 from waeup.kofa.university.course import Course … … 375 377 self.redirect(self.application_url() + rel_link) 376 378 return 377 # Redirect lecturerafter login379 # Redirect some officers after login 378 380 local_roles = get_user_account(self.request).getLocalRoles() 379 381 if local_roles.has_key('waeup.local.Lecturer'): 380 382 self.redirect(self.application_url() + '/my_courses') 383 return 384 if local_roles.has_key('waeup.local.TranscriptOfficer'): 385 self.redirect(self.application_url() + '/my_transcript_requests') 386 return 387 if local_roles.has_key('waeup.local.TranscriptSignee'): 388 self.redirect(self.application_url() + '/my_transcript_signing_requests') 381 389 return 382 390 if not self.camefrom: … … 1023 1031 return courses 1024 1032 1033 class TranscriptOfficerLandingPage(KofaPage): 1034 """Display students, who requested a transcript, when 1035 transcript officer logs in. 1036 """ 1037 grok.name('my_transcript_requests') 1038 grok.require('waeup.Authenticated') 1039 grok.context(IUniversity) 1040 grok.template('mytranscriptrequests') 1041 label =_('My ToDoList') 1042 1043 @property 1044 def getStudents(self): 1045 students = ([],[]) 1046 cat = queryUtility(ICatalog, name='students_catalog') 1047 local_roles = get_user_account(self.request).getLocalRoles() 1048 for item in local_roles.get('waeup.local.TranscriptOfficer', []): 1049 if isinstance(item, Faculty): 1050 faccode = item.code 1051 results1 = cat.searchResults( 1052 state=(TRANSREQ, TRANSREQ), faccode=(faccode, faccode)) 1053 for result in results1: 1054 students[0].append(StudentQueryResultItem(result, self)) 1055 results2 = cat.searchResults( 1056 state=(TRANSVAL, TRANSVAL), faccode=(faccode, faccode)) 1057 for result in results2: 1058 students[1].append(StudentQueryResultItem(result, self)) 1059 return students 1060 1061 class TranscriptSigneeLandingPage(KofaPage): 1062 """Display students with validated transcript requests, when 1063 transcript signee logs in. 1064 """ 1065 grok.name('my_transcript_signing_requests') 1066 grok.require('waeup.Authenticated') 1067 grok.context(IUniversity) 1068 grok.template('mytranscriptrequests') 1069 label =_('My ToDoList') 1070 1071 @property 1072 def getStudents(self): 1073 students = ([],[]) 1074 cat = queryUtility(ICatalog, name='students_catalog') 1075 local_roles = get_user_account(self.request).getLocalRoles() 1076 for item in local_roles.get('waeup.local.TranscriptSignee', []): 1077 if isinstance(item, Faculty): 1078 faccode = item.code 1079 results = cat.searchResults( 1080 state=(TRANSVAL, TRANSVAL), faccode=(faccode, faccode)) 1081 for result in results: 1082 students[1].append(StudentQueryResultItem(result, self)) 1083 return students 1084 1025 1085 # 1026 1086 # Search pages... -
main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py
r15060 r15163 439 439 'nationality,officer_comment,perm_address,personal_updated,' 440 440 'phone,reg_number,sex,student_id,suspended,suspended_comment,' 441 'transcript_comment,'442 441 'password,state,history,certcode,is_postgrad,current_level,' 443 442 'current_session\r\n') -
main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py
r14286 r15163 365 365 return '' 366 366 367 class TranscriptOfficerStudents(ManageLink): 368 """A link to transcript requests. 369 """ 370 grok.order(6) 371 grok.require('waeup.Authenticated') 372 373 link = u'my_transcript_requests' 374 text = _(u'My ToDoList') 375 376 def render(self): 377 user_account = get_user_account(self.request) 378 if user_account: 379 local_roles = user_account.getLocalRoles() 380 if local_roles.has_key('waeup.local.TranscriptOfficer'): 381 url = self.view.url(grok.getSite(), self.link) 382 text = translate(self.text, context=self.request) 383 return u'<li><a href="%s">%s</a></li>' % (url, text) 384 return '' 385 386 class TranscriptSigneeStudents(ManageLink): 387 """A link to transcript signing requests. 388 """ 389 grok.order(6) 390 grok.require('waeup.Authenticated') 391 392 link = u'my_transcript_signing_requests' 393 text = _(u'My ToDoList') 394 395 def render(self): 396 user_account = get_user_account(self.request) 397 if user_account: 398 local_roles = user_account.getLocalRoles() 399 if local_roles.has_key('waeup.local.TranscriptSignee'): 400 url = self.view.url(grok.getSite(), self.link) 401 text = translate(self.text, context=self.request) 402 return u'<li><a href="%s">%s</a></li>' % (url, text) 403 return '' 404 367 405 class MyPreferences(ManageLink): 368 406 """A link to personal preferences, placed in upper left box. -
main/waeup.kofa/trunk/src/waeup/kofa/doctests/permissions.txt
r14948 r15163 39 39 >>> from waeup.kofa.permissions import get_waeup_roles 40 40 >>> len(list(get_waeup_roles())) 41 2 841 29 42 42 43 43 >>> len(list(get_waeup_roles(also_local=True))) 44 4944 52 45 45 46 46 … … 78 78 u'waeup.StudentsOfficer', 79 79 u'waeup.TranscriptOfficer', 80 u'waeup.TranscriptSignee', 80 81 u'waeup.UsersManager', 81 82 u'waeup.WorkflowManager', -
main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py
r14699 r15163 56 56 VALIDATED = 'courses validated' 57 57 GRADUATED = 'graduated' 58 TRANSCRIPT = 'transcript requested' 58 TRANSREQ = 'transcript requested' 59 TRANSVAL = 'transcript validated' 60 TRANSREL = 'transcript released' 59 61 60 62 … … 103 105 (_('returning'), RETURNING), 104 106 (_('graduated'), GRADUATED), 105 (_('transcript requested'), TRANSCRIPT), 107 (_('transcript requested'), TRANSREQ), 108 (_('transcript validated'), TRANSVAL), 109 (_('transcript released'), TRANSREL), 106 110 ) 107 111 -
main/waeup.kofa/trunk/src/waeup/kofa/permissions.py
r14992 r15163 155 155 156 156 157 class ViewTranscript(grok.Permission):158 grok.name('waeup.viewTranscript')159 160 161 157 class ManagePortalConfiguration(grok.Permission): 162 158 """The ManagePortalConfiguration permission allows to … … 392 388 'waeup.viewAcademics', 393 389 'waeup.exportData') 390 391 392 class LocalTranscriptOfficer(grok.Role): 393 """The LocalTranscriptOfficer role can be assigned at faculty 394 level. The role allows to view, to validate and to 395 release student transcripts at faculty level. 396 Local Transcript Officers get the TranscriptOfficer role 397 for this subset of students. 398 """ 399 grok.name('waeup.local.TranscriptOfficer') 400 grok.title(u'Transcript Officer') 401 grok.permissions('waeup.viewAcademics',) 402 403 404 class LocalTranscriptSignee(grok.Role): 405 """The LocalTranscriptSignee role can be assigned at faculty 406 level. The role allows to view and to sign student transcripts 407 at faculty level. Local Transcript Signees get the TranscriptSignee role 408 for this subset of students. 409 """ 410 grok.name('waeup.local.TranscriptSignee') 411 grok.title(u'Transcript Signee') 412 grok.permissions('waeup.viewAcademics',) 394 413 395 414 … … 552 571 'waeup.exportData', 553 572 'waeup.viewTranscript', 573 'waeup.processTranscript', 554 574 'waeup.viewDocuments', 'waeup.manageDocuments', 555 575 'waeup.managePortalConfiguration', … … 603 623 'waeup.exportData', 604 624 'waeup.viewTranscript', 625 'waeup.processTranscript', 605 626 'waeup.viewDocuments', 'waeup.manageDocuments', 606 627 'waeup.managePortalConfiguration', 'waeup.viewApplication', -
main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py
r15066 r15163 528 528 problems. 529 529 """ 530 if obj.student.studycourse_locked: 531 return 'Studycourse is locked.' 530 532 certificate = getattr(obj, 'certificate', None) 531 533 entry_session = getattr(obj, 'entry_session', None) … … 619 621 return 620 622 623 def checkUpdateRequirements(self, obj, row, site): 624 """ 625 """ 626 if obj.student.studycourse_locked: 627 return 'Studylevel is locked.' 628 return None 629 621 630 class CourseTicketProcessor(StudentProcessorBase): 622 631 """The Course Ticket Processor imports course tickets, the subobjects … … 714 723 del parent[ticket.code] 715 724 return 725 726 def checkUpdateRequirements(self, obj, row, site): 727 """ 728 """ 729 if obj.student.studycourse_locked: 730 return 'Studycourse is locked.' 731 return None 716 732 717 733 def checkConversion(self, row, mode='ignore'): -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
r15116 r15163 68 68 IStudentsContainer, IStudent, IUGStudentClearance, IPGStudentClearance, 69 69 IStudentPersonal, IStudentPersonalEdit, IStudentBase, IStudentStudyCourse, 70 IStudentStudyCourseTransfer, IStudentStudyCourseTranscript,70 IStudentStudyCourseTransfer, 71 71 IStudentAccommodation, IStudentStudyLevel, ICourseTicket, ICourseTicketAdd, 72 72 IStudentPaymentsContainer, IStudentOnlinePayment, IStudentPreviousPayment, … … 78 78 from waeup.kofa.students.workflow import ( 79 79 ADMITTED, PAID, CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, 80 VALIDATED, GRADUATED, TRANS CRIPT, FORBIDDEN_POSTGRAD_TRANS80 VALIDATED, GRADUATED, TRANSREQ, TRANSVAL, TRANSREL, FORBIDDEN_POSTGRAD_TRANS 81 81 ) 82 82 … … 264 264 form = self.request.form 265 265 self.hitlist = [] 266 if form.get('searchtype', None) == 'suspended': 267 self.searchtype = form['searchtype'] 268 self.searchterm = None 269 elif form.get('searchtype', None) == 'transcript': 266 if form.get('searchtype', None) in ('suspended', TRANSREQ, TRANSVAL): 270 267 self.searchtype = form['searchtype'] 271 268 self.searchterm = None … … 309 306 form = self.request.form 310 307 self.hitlist = [] 311 if form.get('searchtype', None) == 'suspended':308 if form.get('searchtype', None) in ('suspended', TRANSREQ, TRANSVAL): 312 309 self.searchtype = form['searchtype'] 313 310 self.searchterm = None … … 996 993 997 994 def update(self): 998 if not self.context.is_current: 995 if not self.context.is_current \ 996 or self.context.student.studycourse_locked: 999 997 emit_lock_message(self) 1000 998 return … … 1096 1094 self.ac_series = self.request.form.get('ac_series', None) 1097 1095 self.ac_number = self.request.form.get('ac_number', None) 1098 if self.context.transcript_comment is not None: 1099 self.correspondence = self.context.transcript_comment.replace( 1100 '\n', '<br>') 1096 if getattr( 1097 self.context['studycourse'], 'transcript_comment', None) is not None: 1098 self.correspondence = self.context[ 1099 'studycourse'].transcript_comment.replace( 1100 '\n', '<br>') 1101 1101 else: 1102 1102 self.correspondence = '' … … 1128 1128 tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc) 1129 1129 today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z') 1130 old_transcript_comment = self.context.transcript_comment 1130 old_transcript_comment = getattr( 1131 self.context['studycourse'], 'transcript_comment', None) 1131 1132 if old_transcript_comment == None: 1132 1133 old_transcript_comment = '' 1133 self.context .transcript_comment = '''On %s %s wrote:1134 self.context['studycourse'].transcript_comment = '''On %s %s wrote: 1134 1135 1135 1136 %s … … 1146 1147 return 1147 1148 1148 class StudentTranscriptRequestProcessFormPage(KofaEditFormPage): 1149 """ Page to process transcript requests 1150 """ 1151 grok.context(IStudent) 1152 grok.name('process_transcript_request') 1153 grok.require('waeup.viewTranscript') 1154 grok.template('transcriptprocess') 1155 form_fields = grok.AutoFields(IStudentTranscript) 1156 label = _('Process transcript request') 1157 buttonname = _('Save comment and mark as processed') 1158 pnav = 4 1149 class StudentTranscriptValidateView(UtilityView, grok.View): 1150 """ View to validate transcript 1151 """ 1152 grok.context(IStudentStudyCourse) 1153 grok.name('validate_transcript') 1154 grok.require('waeup.processTranscript') 1159 1155 1160 1156 def update(self, SUBMIT=None): 1161 super(StudentTranscriptRequestProcessFormPage, self).update() 1162 if self.context.state != TRANSCRIPT: 1157 if self.context.student.state != TRANSREQ: 1163 1158 self.flash(_('Student is in wrong state.'), type="warning") 1164 1159 self.redirect(self.url(self.context)) 1165 1160 return 1166 if self.context.transcript_comment is not None: 1161 # Fire transition 1162 IWorkflowInfo(self.context.student).fireTransition( 1163 'validate_transcript') 1164 self.flash(_('Transcript validated.')) 1165 self.redirect(self.url(self.context) + '/transcript') 1166 return 1167 1168 def render(self): 1169 return 1170 1171 class StudentTranscriptSignView(UtilityView, grok.View): 1172 """ View to sign transcript 1173 """ 1174 grok.context(IStudentStudyCourse) 1175 grok.name('sign_transcript') 1176 grok.require('waeup.signTranscript') 1177 1178 def update(self, SUBMIT=None): 1179 if self.context.student.state != TRANSVAL: 1180 self.flash(_('Student is in wrong state.'), type="warning") 1181 self.redirect(self.url(self.context)) 1182 return 1183 prev_transcript_signees = getattr( 1184 self.context, 'transcript_signees', None) 1185 if prev_transcript_signees \ 1186 and '(%s)' % self.request.principal.id in prev_transcript_signees: 1187 self.flash(_('You have already signed this transcript.'), 1188 type="warning") 1189 self.redirect(self.url(self.context) + '/transcript') 1190 return 1191 self.flash(_('Transcript signed.')) 1192 ob_class = self.__implemented__.__name__.replace('waeup.kofa.','') 1193 self.context.student.__parent__.logger.info( 1194 '%s - %s - Transcript signed' 1195 % (ob_class, self.context.student.student_id)) 1196 self.context.student.history.addMessage('Transcript signed') 1197 tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc) 1198 today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z') 1199 if prev_transcript_signees == None: 1200 prev_transcript_signees = '' 1201 self.context.transcript_signees = ( 1202 u"Electronically signed by %s (%s) on %s\n%s" 1203 % (self.request.principal.title, self.request.principal.id, today, 1204 prev_transcript_signees)) 1205 self.redirect(self.url(self.context) + '/transcript') 1206 return 1207 1208 def render(self): 1209 return 1210 1211 class StudentTranscriptReleaseFormPage(KofaEditFormPage): 1212 """ Page to release transcript 1213 """ 1214 grok.context(IStudentStudyCourse) 1215 grok.name('release_transcript') 1216 grok.require('waeup.processTranscript') 1217 grok.template('transcriptrelease') 1218 label = _('Release transcript') 1219 buttonname = _('Save comment and release transcript') 1220 pnav = 4 1221 1222 def update(self, SUBMIT=None): 1223 super(StudentTranscriptReleaseFormPage, self).update() 1224 if self.context.student.state != TRANSVAL: 1225 self.flash(_('Student is in wrong state.'), type="warning") 1226 self.redirect(self.url(self.context)) 1227 return 1228 if getattr(self.context, 'transcript_comment', None) is not None: 1167 1229 self.correspondence = self.context.transcript_comment.replace( 1168 1230 '\n', '<br>') 1169 1231 else: 1170 1232 self.correspondence = '' 1233 if getattr(self.context, 'transcript_signees', None) is not None: 1234 self.signees = self.context.transcript_signees.replace( 1235 '\n', '<br><br>') 1236 else: 1237 self.signees = '' 1171 1238 if SUBMIT is None: 1172 1239 return 1173 IWorkflowInfo(self.context).fireTransition('process_transcript') 1174 self.flash(_('Transcript request processed.')) 1240 # Fire transition 1241 IWorkflowInfo(self.context.student).fireTransition('release_transcript') 1242 self.flash(_('Transcript released and final transcript file saved.')) 1175 1243 comment = self.request.form.get('comment', '').replace('\r', '') 1176 1244 tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc) 1177 1245 today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z') 1178 old_transcript_comment = self.context.transcript_comment 1246 old_transcript_comment = getattr( 1247 self.context, 'transcript_comment', None) 1179 1248 if old_transcript_comment == None: 1180 1249 old_transcript_comment = '' … … 1187 1256 self.context.writeLogMessage( 1188 1257 self, 'comment: %s' % comment.replace('\n', '<br>')) 1189 subject = _('Transcript processed') 1190 args = {'subject':subject, 'body':comment} 1191 self.redirect(self.url(self.context) + 1192 '/contactstudent?%s' % urlencode(args)) 1258 # Produce transcript file 1259 self.redirect(self.url(self.context) + '/transcript.pdf') 1193 1260 return 1194 1261 1195 1262 class StudentTranscriptRequestManageFormPage(KofaEditFormPage): 1196 """ Page to manage transcript data 1197 """ 1198 grok.context(IStudent) 1263 """ Page to manage transcript data. There is no link button 1264 for this page. It's an emergency page. 1265 """ 1266 grok.context(IStudentStudyCourse) 1199 1267 grok.name('manage_transcript_request') 1200 grok.require('waeup. manageStudent')1268 grok.require('waeup.processTranscript') 1201 1269 form_fields = grok.AutoFields(IStudentTranscript) 1202 1270 label = _('Manage transcript request') … … 1211 1279 """ Page to display the student's transcript. 1212 1280 """ 1213 grok.context(IStudentStudyCourse Transcript)1281 grok.context(IStudentStudyCourse) 1214 1282 grok.name('transcript') 1215 1283 grok.require('waeup.viewTranscript') … … 1218 1286 1219 1287 def update(self): 1220 if not self.context.student.transcript_enabled: 1221 self.flash(_('You are not allowed to view the transcript.'), 1222 type="warning") 1288 final_slip = getUtility(IExtFileStore).getFileByContext( 1289 self.context.student, attr='final_transcript') 1290 if not self.context.student.transcript_enabled or final_slip: 1291 self.flash(_('Forbidden!'), type="warning") 1223 1292 self.redirect(self.url(self.context)) 1224 1293 return … … 1243 1312 grok.name('transcript.pdf') 1244 1313 grok.require('waeup.viewTranscript') 1245 form_fields = grok.AutoFields(IStudentStudyCourseTranscript)1246 1314 prefix = 'form' 1247 1315 omit_fields = ( … … 1251 1319 1252 1320 def update(self): 1253 if not self.context.student.transcript_enabled: 1254 self.flash(_('You are not allowed to download the transcript.'), 1255 type="warning") 1321 final_slip = getUtility(IExtFileStore).getFileByContext( 1322 self.context.student, attr='final_transcript') 1323 if not self.context.student.transcript_enabled \ 1324 or final_slip: 1325 self.flash(_('Forbidden!'), type="warning") 1256 1326 self.redirect(self.url(self.context)) 1257 1327 return … … 1272 1342 1273 1343 def _sigsInFooter(self): 1344 if getattr( 1345 self.context.student['studycourse'], 'transcript_signees', None): 1346 return () 1274 1347 return (_('CERTIFIED TRUE COPY'),) 1275 1348 1276 1349 def _signatures(self): 1277 return None 1350 return () 1351 1352 def _digital_sigs(self): 1353 if getattr( 1354 self.context.student['studycourse'], 'transcript_signees', None): 1355 return self.context.student['studycourse'].transcript_signees 1356 return () 1357 1358 def _save_file(self): 1359 if self.context.student.state == TRANSREL: 1360 return True 1361 return False 1278 1362 1279 1363 def render(self): … … 1297 1381 ] 1298 1382 1299 returnstudents_utils.renderPDFTranscript(1383 pdfstream = students_utils.renderPDFTranscript( 1300 1384 self, 'transcript.pdf', 1301 1385 self.context.student, studentview, … … 1304 1388 signatures=self._signatures(), 1305 1389 sigs_in_footer=self._sigsInFooter(), 1390 digital_sigs=self._digital_sigs(), 1391 save_file=self._save_file(), 1306 1392 ) 1393 if not pdfstream: 1394 self.redirect(self.url(self.context.student)) 1395 return 1396 return pdfstream 1307 1397 1308 1398 class StudentTransferFormPage(KofaAddFormPage): … … 1482 1572 1483 1573 def update(self, ADD=None, course=None): 1484 if not self.context.__parent__.is_current: 1574 if not self.context.__parent__.is_current \ 1575 or self.context.student.studycourse_locked: 1485 1576 emit_lock_message(self) 1486 1577 return … … 1554 1645 emit_lock_message(self) 1555 1646 return 1556 if str(self.context. __parent__.current_level) != self.context.__name__:1647 if str(self.context.student.current_level) != self.context.__name__: 1557 1648 self.flash(_('This is not the student\'s current level.'), 1558 1649 type="danger") … … 1650 1741 1651 1742 def update(self): 1652 if not self.context.__parent__.is_current: 1743 if not self.context.__parent__.is_current \ 1744 or self.context.student.studycourse_locked: 1653 1745 emit_lock_message(self) 1654 1746 return … … 1702 1794 pnav = 4 1703 1795 grok.template('courseticketmanagepage') 1796 1797 def update(self): 1798 if not self.context.__parent__.__parent__.is_current \ 1799 or self.context.student.studycourse_locked: 1800 emit_lock_message(self) 1801 return 1802 super(CourseTicketManageFormPage, self).update() 1803 return 1704 1804 1705 1805 @property … … 2630 2730 2631 2731 def update(self): 2632 if not self.context.is_current: 2732 if not self.context.is_current \ 2733 or self.context.student.studycourse_locked: 2633 2734 emit_lock_message(self) 2634 2735 return -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/containermanagepage.pt
r13347 r15163 17 17 with deactivated account (search term omitted) 18 18 </option> 19 <option i18n:translate="" value="transcript"> 20 who requested transcript (search term omitted) 19 <option i18n:translate="" value="transcript requested"> 20 in state 'transcript requested' (search term omitted) 21 </option> 22 <option i18n:translate="" value="transcript validated"> 23 in state 'transcript validated' (search term omitted) 21 24 </option> 22 25 </select> -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/containerpage.pt
r11254 r15163 17 17 with deactivated account (search term omitted) 18 18 </option> 19 <option i18n:translate="" value="transcript"> 20 who requested transcript (search term omitted) 19 <option i18n:translate="" value="transcript requested"> 20 in state 'transcript requested' (search term omitted) 21 </option> 22 <option i18n:translate="" value="transcript validated"> 23 in state 'transcript validated' (search term omitted) 21 24 </option> 22 25 </select> -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/transcript.pt
r14382 r15163 58 58 </tbody> 59 59 </table> 60 <br /> 61 <div tal:content="python:getattr(context, 'transcript_signees', '')">SIGNEES</div> 60 62 </tal:def> -
main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/transcriptrequest.pt
r11551 r15163 73 73 74 74 <div id="tab2" class="tab-pane"> 75 < p tal:content="structure view/correspondence"></p>75 <br /><p tal:content="structure view/correspondence"></p> 76 76 </div> 77 77 </div> -
main/waeup.kofa/trunk/src/waeup/kofa/students/catalog.py
r10465 r15163 29 29 from waeup.kofa.students.interfaces import IStudent, ICourseTicket 30 30 from waeup.kofa.university.vocabularies import course_levels 31 from waeup.kofa.students.workflow import (TRANSREQ, TRANSVAL) 31 32 32 33 class StudentsCatalog(grok.Indexes): … … 88 89 hitlist.append(StudentQueryResultItem(student, view=view)) 89 90 return hitlist 90 elif searchtype == 'transcript':91 elif searchtype in (TRANSREQ, TRANSVAL): 91 92 cat = queryUtility(ICatalog, name='students_catalog') 92 results = cat.searchResults( 93 state=('transcript requested', 'transcript requested')) 93 results = cat.searchResults(state=(searchtype, searchtype)) 94 94 else: 95 95 # Temporary solution to display all students added -
main/waeup.kofa/trunk/src/waeup/kofa/students/dynamicroles.py
r11254 r15163 46 46 'waeup.local.LocalStudentsManager': 'waeup.StudentsManager', 47 47 'waeup.local.LocalWorkflowManager': 'waeup.WorkflowManager', 48 'waeup.local.TranscriptOfficer': 'waeup.TranscriptOfficer', 49 'waeup.local.TranscriptSignee': 'waeup.TranscriptSignee', 48 50 } 49 51 -
main/waeup.kofa/trunk/src/waeup/kofa/students/fileviewlets.py
r14880 r15163 153 153 154 154 155 class FinalTranscriptImage(StudentImage): 156 """Renders final transcript. 157 """ 158 grok.name('final_transcript') 159 download_name = u'final_transcript' 160 161 155 162 class BirthCertificateImage(StudentImage): 156 163 """Renders birth certificate scan. -
main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
r14702 r15163 219 219 transcript_enabled = Attribute('True if transcript processing is enabled') 220 220 clearance_locked = Attribute('True if clearance form is locked') 221 studycourse_locked = Attribute( 222 'True if nobody is allowed to change studycourse, studylecel or ' 223 'course ticket data, neither through the UI nor via import') 221 224 222 225 password = Attribute('Encrypted password') … … 371 374 372 375 class IStudentTranscript(IKofaObject): 373 """Representation of student transcript data. 376 """Representation of student transcript data. Only used for 377 StudentTranscriptRequestManageFormPage. 374 378 """ 375 379 … … 379 383 ) 380 384 385 transcript_signees = schema.Text( 386 title = _(u'Signees'), 387 required = False, 388 ) 389 381 390 382 391 class IStudent(IStudentBase,IUGStudentClearance,IPGStudentClearance, 383 IStudentPersonal , IStudentTranscript):392 IStudentPersonal): 384 393 """Representation of a student. 385 394 """ … … 528 537 IStudentStudyCourseTransfer['current_level'].order = IStudentStudyCourse[ 529 538 'current_level'].order 530 531 class IStudentStudyCourseTranscript(IKofaObject):532 """An interface for student transcripts.533 """534 entry_mode = schema.Choice(535 title = _(u'Entry Mode'),536 source = StudyModeSource(),537 required = True,538 readonly = False,539 )540 541 entry_session = schema.Choice(542 title = _(u'Entry Session'),543 source = academic_sessions_vocab,544 #default = datetime.now().year,545 required = True,546 readonly = False,547 )548 539 549 540 class IStudentVerdictUpdate(IKofaObject): -
main/waeup.kofa/trunk/src/waeup/kofa/students/permissions.py
r13762 r15163 92 92 """ 93 93 grok.name('waeup.loginAsStudent') 94 95 class ViewTranscript(grok.Permission): 96 """The ViewTranscript role is needed to view transcript pages and slips. 97 """ 98 grok.name('waeup.viewTranscript') 99 100 class ProcessTranscript(grok.Permission): 101 grok.name('waeup.processTranscript') 102 """The ProcessTranscript role is needed to validate and relase transcripts. 103 """ 104 105 class SignTranscript(grok.Permission): 106 grok.name('waeup.signTranscript') 107 """The ViewTranscript role is needed to sign transcripts. 108 """ 94 109 95 110 # Local role … … 142 157 143 158 class TranscriptOfficer(grok.Role): 159 """The Transcript Officer is allowed to view, to validate and to 160 release student transcripts. The officer is not allowed to 161 manage student data. 162 """ 144 163 grok.name('waeup.TranscriptOfficer') 145 164 grok.title(u'Transcript Officer') 146 165 grok.permissions('waeup.viewAcademics', 147 166 'waeup.viewTranscript', 167 'waeup.processTranscript', 148 168 'waeup.viewStudent', 149 169 'waeup.viewStudentsContainer', 170 ) 171 172 class TranscriptSignee(grok.Role): 173 """The Transcript Signee is allowed to view and to sign student 174 transcripts. 175 """ 176 grok.name('waeup.TranscriptSignee') 177 grok.title(u'Transcript Signee') 178 grok.permissions('waeup.viewAcademics', 179 'waeup.viewTranscript', 180 'waeup.signTranscript', 181 'waeup.viewStudent', 150 182 ) 151 183 -
main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py
r14514 r15163 21 21 'clearance requested', 'cleared', 'school fee paid', 22 22 'courses registered', 'courses validated', 'returning', 23 'graduated', 'transcript requested', 'Total') 23 'graduated', 'transcript requested', 'transcript validated', 24 'transcript released', 'Total') 24 25 25 26 def test_iface(self): … … 41 42 ((u'fac1', u'Total',), 42 43 self.states, 43 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),44 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))44 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), 45 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),))) 45 46 self.assertEqual( 46 47 result2, 47 48 ((u'fac1', u'Total'), 48 49 self.states, 49 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),50 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),)))50 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 51 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),))) 51 52 return 52 53 … … 62 63 ((u'fac1', u'fac2', u'Total'), 63 64 self.states, 64 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),65 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),66 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))65 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), 66 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 67 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),))) 67 68 self.assertEqual( 68 69 result2, 69 70 ((u'fac1', u'fac2', u'Total'), 70 71 self.states, 71 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),72 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),73 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),)))72 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 73 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 74 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),))) 74 75 return 75 76 … … 83 84 ((u'fac1/dep1', u'fac2/dep2', u'Total'), 84 85 self.states, 85 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),86 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),87 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))88 self.assertEqual( 89 result2, 90 ((u'fac1/dep1', u'fac2/dep2', u'Total'), 91 self.states, 92 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),93 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),94 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),)))86 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), 87 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 88 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),))) 89 self.assertEqual( 90 result2, 91 ((u'fac1/dep1', u'fac2/dep2', u'Total'), 92 self.states, 93 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 94 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 95 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),))) 95 96 return 96 97 … … 104 105 ((u'fac1/dep1', u'fac2/dep2', u'Total'), 105 106 self.states, 106 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),107 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),108 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))109 self.assertEqual( 110 result2, 111 ((u'fac1/dep1', u'fac2/dep2', u'Total'), 112 self.states, 113 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),114 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),115 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),)))107 ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), 108 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 109 (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),))) 110 self.assertEqual( 111 result2, 112 ((u'fac1/dep1', u'fac2/dep2', u'Total'), 113 self.states, 114 ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 115 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), 116 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),))) 116 117 return 117 118 -
main/waeup.kofa/trunk/src/waeup/kofa/students/student.py
r14169 r15163 38 38 IKofaUtils, registration_states_vocab, IExtFileStore, 39 39 CREATED, ADMITTED, CLEARANCE, PAID, REGISTERED, VALIDATED, RETURNING, 40 GRADUATED )40 GRADUATED, TRANSVAL, TRANSREL) 41 41 from waeup.kofa.students.accommodation import StudentAccommodation 42 42 from waeup.kofa.students.interfaces import ( … … 239 239 def transcript_enabled(self): 240 240 return True 241 242 @property 243 def studycourse_locked(self): 244 return self.state in (GRADUATED, TRANSREL, TRANSVAL) 241 245 242 246 @property -
main/waeup.kofa/trunk/src/waeup/kofa/students/studycourse.py
r14382 r15163 26 26 from waeup.kofa.students.interfaces import ( 27 27 IStudentStudyCourse, IStudentNavigation, IStudentStudyLevel, 28 IStudent StudyCourseTranscript)28 IStudentTranscript) 29 29 from waeup.kofa.students.studylevel import CourseTicket 30 30 from waeup.kofa.students.workflow import CLEARED, RETURNING, PAID … … 34 34 """This is a container for study levels. 35 35 """ 36 grok.implements(IStudentStudyCourse, IStudentNavigation, 37 IStudentStudyCourseTranscript) 36 grok.implements(IStudentStudyCourse, IStudentNavigation) 38 37 grok.provides(IStudentStudyCourse) 39 38 -
main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py
r15066 r15163 665 665 entry_mode='transfer'), self.app) 666 666 self.assertTrue(err is None) 667 # In state 'transcript validated' studycourses are locked. 668 IWorkflowState(self.student).setState('transcript validated') 669 err = self.processor.checkUpdateRequirements( 670 self.student['studycourse'], 671 dict(reg_number='1', current_level=999), self.app) 672 self.assertEqual(err, 'Studycourse is locked.') 673 # In state 'graduated' studycourses are also locked. 674 IWorkflowState(self.student).setState('graduated') 675 err = self.processor.checkUpdateRequirements( 676 self.student['studycourse'], 677 dict(reg_number='1', current_level=999), self.app) 678 self.assertEqual(err, 'Studycourse is locked.') 679 # In state 'transcript released' studycourses are also locked ... 680 IWorkflowState(self.student).setState('transcript released') 681 err = self.processor.checkUpdateRequirements( 682 self.student['studycourse'], 683 dict(reg_number='1', current_level=999), self.app) 684 self.assertEqual(err, 'Studycourse is locked.') 685 # ... but not in state 'transcript requested'. 686 IWorkflowState(self.student).setState('transcript requested') 687 err = self.processor.checkUpdateRequirements( 688 self.student['studycourse'], 689 dict(reg_number='1', current_level=999), self.app) 690 self.assertTrue(err is None) 667 691 668 692 def test_import(self): … … 826 850 shutil.rmtree(os.path.dirname(fin_file)) 827 851 852 def test_import_update_locked(self): 853 num, num_warns, fin_file, fail_file = self.processor.doImport( 854 self.csv_file, STUDYLEVEL_HEADER_FIELDS,'create') 855 shutil.rmtree(os.path.dirname(fin_file)) 856 # In state 'transcript validated' studylevels can't be edited 857 student = self.app['students']['X666666'] 858 IWorkflowState(student).setState('transcript validated') 859 # Two more records could't be imported because course tickets 860 # of X666666 are locked 861 num, num_warns, fin_file, fail_file = self.processor.doImport( 862 self.csv_file, STUDYLEVEL_HEADER_FIELDS,'update') 863 self.assertEqual(num_warns,5) 864 content = open(fail_file).read() 865 self.assertEqual( 866 content, 867 'reg_number,level_verdict,level_session,matric_number,level,--ERRORS--\r\n' 868 '1,<IGNORE>,2008,<IGNORE>,100,Studylevel is locked.\r\n' 869 '1,<IGNORE>,2008,<IGNORE>,200,Studylevel is locked.\r\n' 870 '1,A,2008,<IGNORE>,111,level: Invalid value\r\n' 871 '1,A,2008,<IGNORE>,nonsense,level: Invalid value\r\n' 872 '1,A,2008,<IGNORE>,<IGNORE>,Cannot update: no such entry\r\n' 873 ) 874 shutil.rmtree(os.path.dirname(fin_file)) 875 828 876 def test_import_remove(self): 829 877 # We perform the same import twice, … … 989 1037 'INFO - system - K1000000 - Course ticket in 100 removed: COURSE1' 990 1038 in logcontent) 1039 1040 def test_import_update_locked(self): 1041 num, num_warns, fin_file, fail_file = self.processor.doImport( 1042 self.csv_file, COURSETICKET_HEADER_FIELDS,'create') 1043 shutil.rmtree(os.path.dirname(fin_file)) 1044 # In state 'transcript validated' course tickets can't edited 1045 student = self.app['students']['X666666'] 1046 IWorkflowState(student).setState('transcript validated') 1047 num, num_warns, fin_file, fail_file = self.processor.doImport( 1048 self.csv_file, COURSETICKET_HEADER_FIELDS,'update') 1049 fail_file = open(fail_file).read() 1050 # Two more records could't be imported because course tickets 1051 # of X666666 are locked 1052 self.assertEqual(num_warns,7) 1053 self.assertEqual(fail_file, 1054 'reg_number,code,mandatory,level,level_session,score,matric_number,--ERRORS--\r\n' 1055 '1,COURSE1,True,100,<IGNORE>,1,<IGNORE>,Studycourse is locked.\r\n' 1056 '1,COURSE1,True,200,2008,1,<IGNORE>,Studycourse is locked.\r\n' 1057 '1,COURSE1,<IGNORE>,nonsense,<IGNORE>,5,<IGNORE>,Cannot update: no such entry\r\n' 1058 '1,NONSENSE,<IGNORE>,100,<IGNORE>,5,<IGNORE>,code: non-existent\r\n' 1059 '1,COURSE1,<IGNORE>,200,2004,6,<IGNORE>,level_session: does not match 2008\r\n' 1060 '1,COURSE1,<IGNORE>,300,2008,6,<IGNORE>,level object: does not exist\r\n' 1061 '1,COURSE1,<IGNORE>,300,2008X,6,<IGNORE>,level_session: Invalid value\r\n') 1062 shutil.rmtree(os.path.dirname(fin_file)) 991 1063 992 1064 class PaymentProcessorTest(StudentImportExportSetup): -
main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py
r15055 r15163 41 41 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase 42 42 from waeup.kofa.app import University 43 from waeup.kofa.interfaces import IFileStoreNameChooser, IExtFileStore 43 44 from waeup.kofa.payments.interfaces import IPayer 44 45 from waeup.kofa.students.payments import StudentOnlinePayment … … 2119 2120 print "Sample PDF transcript.pdf written to %s" % path 2120 2121 2121 def test_process_transcript _request(self):2122 def test_process_transcript(self): 2122 2123 IWorkflowState(self.student).setState('transcript requested') 2123 2124 notify(grok.ObjectModifiedEvent(self.student)) 2124 self.student .transcript_comment = (2125 self.student['studycourse'].transcript_comment = ( 2125 2126 u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n' 2126 2127 'Comment line2\n\nDispatch Address:\nAddress line 1 \n' 2127 2128 'Address line2\n\n') 2128 # Create transcript officer2129 # Create officer with both roles 2129 2130 self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret') 2130 2131 self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng' … … 2132 2133 prmglobal = IPrincipalRoleManager(self.app) 2133 2134 prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript') 2134 # Login as transcript officer 2135 prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrtranscript') 2136 prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript') 2137 # Login 2135 2138 self.browser.open(self.login_path) 2136 2139 self.browser.getControl(name="form.login").value = 'mrtranscript' … … 2143 2146 '...<div>Transcript Officer</div>...', 2144 2147 self.browser.contents) 2145 # Officer can search for students in state 'transcrip rrequested'2148 # Officer can search for students in state 'transcript requested' 2146 2149 self.browser.open(self.container_path) 2147 self.browser.getControl(name="searchtype").value = ['transcript'] 2150 self.browser.getControl(name="searchtype").value = [ 2151 'transcript requested'] 2148 2152 self.browser.getControl("Find student(s)").click() 2149 2153 self.assertTrue('Anna Tester' in self.browser.contents) 2150 2154 self.browser.getLink("K1000000").click() 2151 self.browser.getLink("Manage transcript request").click() 2155 self.assertFalse('Release transcript request' in self.browser.contents) 2156 # Officers can still edit studycourse, studylevel and course tickets. 2157 self.browser.open(self.studycourse_path + '/manage') 2158 self.assertTrue('Undergraduate Full-Time</option>' 2159 in self.browser.contents) 2160 self.browser.getControl(name="form.certificate").value = ['CERT1'] 2161 self.browser.getControl(name="form.current_session").value = ['2004'] 2162 self.browser.getControl(name="form.current_verdict").value = ['A'] 2163 self.browser.getControl(name="form.entry_mode").value = ['ug_ft'] 2164 self.browser.getControl("Save").click() 2165 self.browser.getControl(name="form.current_level").value = ['100'] 2166 self.browser.getControl("Save").click() 2167 self.browser.getControl(name="addlevel").value = ['100'] 2168 self.browser.getControl(name="level_session").value = ['2004'] 2169 self.browser.getControl("Add study level").click() 2170 self.browser.getLink("100").click() 2171 self.browser.getLink("Manage").click() 2172 self.browser.getControl(name="form.level_session").value = ['2002'] 2173 self.browser.getControl("Save").click() 2174 self.browser.getLink("COURSE1").click() 2175 self.browser.getLink("Manage").click() 2176 self.browser.getControl("Save").click() 2177 self.assertTrue('Form has been saved' in self.browser.contents) 2178 # Officer can validate the transcript 2179 self.browser.open(self.studycourse_path + '/transcript') 2180 self.browser.getLink("Validate transcript").click() 2181 # After validation all manage forms are locked. 2182 self.browser.open(self.studycourse_path + '/manage') 2183 self.assertTrue('The requested form is locked' in self.browser.contents) 2184 self.assertFalse('Undergraduate Full-Time</option>' 2185 in self.browser.contents) 2186 self.browser.open(self.studycourse_path + '/100/manage') 2187 self.assertTrue('The requested form is locked' in self.browser.contents) 2188 self.browser.open(self.studycourse_path + '/100/COURSE1/manage') 2189 self.assertTrue('The requested form is locked' in self.browser.contents) 2190 2191 # Transcript can be signed if officer has the permission to sign 2192 #self.browser.open(self.studycourse_path + '/transcript') 2193 #self.assertFalse('Sign transcript' in self.browser.contents) 2194 #prmglobal = IPrincipalRoleManager(self.app) 2195 #prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript') 2196 2197 self.browser.open(self.studycourse_path + '/transcript') 2198 self.browser.getLink("Sign transcript electronically").click() 2199 # Transcript signing has been logged ... 2200 logfile = os.path.join( 2201 self.app['datacenter'].storage, 'logs', 'students.log') 2202 logcontent = open(logfile).read() 2203 self.assertTrue( 2204 'mrtranscript - students.browser.StudentTranscriptSignView - ' 2205 'K1000000 - Transcript signed' in logcontent) 2206 # ... appears in the student's history ... 2207 self.browser.open(self.history_path) 2208 self.assertTrue('Transcript signed by Ruth Gordon' 2209 in self.browser.contents) 2210 # ... and is also stored in the transcript_signee attribute. 2211 self.assertTrue( 2212 u'Electronically signed by Ruth Gordon (mrtranscript) on ' 2213 in self.student['studycourse'].transcript_signees) 2214 # Officer can release the transcript 2215 self.browser.open(self.studycourse_path + '/transcript') 2216 self.browser.getLink("Release transcript").click() 2152 2217 self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>' 2153 2218 'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>' … … 2155 2220 self.browser.getControl(name="comment").value = ( 2156 2221 'Hello,\nYour transcript has been sent to the address provided.') 2157 self.browser.getControl("Save comment and mark as processed").click()2222 self.browser.getControl("Save comment and release transcript").click() 2158 2223 self.assertTrue( 2159 2224 'UTC mrtranscript wrote:\n\nHello,\nYour transcript has ' 2160 2225 'been sent to the address provided.\n\n' 2161 in self.student .transcript_comment)2226 in self.student['studycourse'].transcript_comment) 2162 2227 # The comment has been logged 2163 2228 logfile = os.path.join( … … 2165 2230 logcontent = open(logfile).read() 2166 2231 self.assertTrue( 2167 'mrtranscript - students.browser.StudentTranscriptRe questProcessFormPage - '2232 'mrtranscript - students.browser.StudentTranscriptReleaseFormPage - ' 2168 2233 'K1000000 - comment: Hello,<br>' 2169 2234 'Your transcript has been sent to the address provided' 2170 2235 in logcontent) 2236 # File has been stored in the file system 2237 # Check if transcript exists in the file system and is a PDF file 2238 storage = getUtility(IExtFileStore) 2239 file_id = IFileStoreNameChooser( 2240 self.student).chooseName(attr='final_transcript.pdf') 2241 pdf = storage.getFile(file_id).read() 2242 self.assertTrue(len(pdf) > 0) 2243 self.assertEqual(pdf[:8], '%PDF-1.4') 2244 # Copy the file to samples_dir 2245 path = os.path.join(samples_dir(), 'final_transcript.pdf') 2246 open(path, 'wb').write(pdf) 2247 print "Sample PDF final_transcript.pdf written to %s" % path 2248 # Check if there is an transcript pdf link in UI 2249 self.browser.open(self.student_path) 2250 self.assertTrue('Final Transcript' in self.browser.contents) 2251 self.browser.getLink("Final Transcript").click() 2252 self.assertEqual(self.browser.headers['Status'], '200 Ok') 2253 self.assertEqual(self.browser.headers['Content-Type'], 2254 'application/pdf') 2255 # Transcript views are no longer accessible 2256 self.browser.open(self.studycourse_path) 2257 self.assertFalse('studycourse/transcript' in self.browser.contents) 2258 self.browser.open(self.studycourse_path + '/transcript') 2259 self.assertTrue('Forbidden!' in self.browser.contents) 2260 self.browser.open(self.studycourse_path + '/transcript.pdf') 2261 self.assertTrue('Forbidden!' in self.browser.contents) 2262 # If we reset the transcript process 2263 # (can't be done by transcript officer), the file will be deleted 2264 IWorkflowInfo(self.student).fireTransition('reset11') 2265 self.browser.open(self.student_path) 2266 self.assertFalse('Final Transcript' in self.browser.contents) 2267 # ... and transcript process information has been removed 2268 self.assertEqual(self.student['studycourse'].transcript_comment, None) 2269 self.assertEqual(self.student['studycourse'].transcript_signees, None) 2270 2271 def test_landingpage_transcript_officer(self): 2272 IWorkflowState(self.student).setState('transcript requested') 2273 notify(grok.ObjectModifiedEvent(self.student)) 2274 # Create transcript officer 2275 self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret') 2276 self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng' 2277 self.app['users']['mrtranscript'].title = 'Ruth Gordon' 2278 # We assign transcript officer role at faculty level 2279 fac = self.app['faculties']['fac1'] 2280 prmlocal = IPrincipalRoleManager(fac) 2281 prmlocal.assignRoleToPrincipal( 2282 'waeup.local.TranscriptOfficer', 'mrtranscript') 2283 notify(LocalRoleSetEvent( 2284 fac, 'waeup.local.TranscriptOfficer', 'mrtranscript', granted=True)) 2285 # Login as transcript officer 2286 self.browser.open(self.login_path) 2287 self.browser.getControl(name="form.login").value = 'mrtranscript' 2288 self.browser.getControl(name="form.password").value = 'mrtranscriptsecret' 2289 self.browser.getControl("Login").click() 2290 self.assertMatches('...You logged in...', self.browser.contents) 2291 # Officer is on landing page and does see the transcript link 2292 self.assertTrue( 2293 'http://localhost/app/students/K1000000/studycourse/transcript' 2294 in self.browser.contents) 2295 self.browser.getLink("K1000000").click() 2296 self.assertTrue( 2297 'Anna Tester: Transcript Data' in self.browser.contents) 2298 # Officer is on transcript page and can validate the transcript 2299 self.browser.getLink("Validate transcript").click() 2300 self.assertTrue( 2301 '<div class="alert alert-success">Transcript validated.</div>' 2302 in self.browser.contents) 2303 # Officer is still on transcript page and can release the transcript 2304 self.browser.getLink("Release transcript").click() 2305 self.browser.getControl("Save comment and release transcript").click() 2306 self.assertTrue( 2307 '<div class="alert alert-success">' 2308 'Transcript released and final transcript file saved.</div>' 2309 in self.browser.contents) 2310 2311 def test_landingpage_transcript_signee(self): 2312 IWorkflowState(self.student).setState('transcript validated') 2313 notify(grok.ObjectModifiedEvent(self.student)) 2314 # Create transcript signee 2315 self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret') 2316 self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng' 2317 self.app['users']['mrtranscript'].title = 'Ruth Gordon' 2318 # We assign transcript officer role at faculty level 2319 fac = self.app['faculties']['fac1'] 2320 prmlocal = IPrincipalRoleManager(fac) 2321 prmlocal.assignRoleToPrincipal( 2322 'waeup.local.TranscriptSignee', 'mrtranscript') 2323 notify(LocalRoleSetEvent( 2324 fac, 'waeup.local.TranscriptSignee', 'mrtranscript', granted=True)) 2325 # Login as transcript officer 2326 self.browser.open(self.login_path) 2327 self.browser.getControl(name="form.login").value = 'mrtranscript' 2328 self.browser.getControl(name="form.password").value = 'mrtranscriptsecret' 2329 self.browser.getControl("Login").click() 2330 self.assertMatches('...You logged in...', self.browser.contents) 2331 # Officer is on landing page and does see the transcript link 2332 self.assertTrue( 2333 'http://localhost/app/students/K1000000/studycourse/transcript' 2334 in self.browser.contents) 2335 self.browser.getLink("K1000000").click() 2336 self.assertTrue( 2337 'Anna Tester: Transcript Data' in self.browser.contents) 2338 # Officer is on transcript page and can sign the transcript 2339 self.browser.getLink("Sign transcript").click() 2340 self.assertTrue( 2341 '<div class="alert alert-success">Transcript signed.</div>' 2342 in self.browser.contents) 2343 # Officer is still on transcript page 2344 self.assertTrue( 2345 'Anna Tester: Transcript Data' in self.browser.contents) 2346 # Officer can sign the transcript only once 2347 self.browser.getLink("Sign transcript").click() 2348 self.assertTrue( 2349 '<div class="alert alert-warning">' 2350 'You have already signed this transcript.</div>' 2351 in self.browser.contents) 2352 # Signature can be seen on transcript page 2353 self.assertTrue( 2354 'Electronically signed by Ruth Gordon (mrtranscript) on' 2355 in self.browser.contents) 2356 2171 2357 2172 2358 class StudentUITests(StudentsFullSetup): … … 3303 3489 '... UTC K1000000 wrote:\n\nComment line 1 \n' 3304 3490 'Comment line2\n\nDispatch Address:\nAddress line 1 \n' 3305 'Address line2\n\n', self.student .transcript_comment)3491 'Address line2\n\n', self.student['studycourse'].transcript_comment) 3306 3492 # The comment has been logged 3307 3493 logfile = os.path.join( … … 3572 3758 'firstname,flash_notice,lastname,matric_number,middlename,nationality,' 3573 3759 'officer_comment,perm_address,personal_updated,phone,reg_number,' 3574 'sex,student_id,suspended,suspended_comment, transcript_comment,'3575 'password,state,history,certcode,is_postgrad, current_level,'3576 'current_ session\r\n'3760 'sex,student_id,suspended,suspended_comment,' 3761 'password,state,history,certcode,is_postgrad,' 3762 'current_level,current_session\r\n' 3577 3763 ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,' 3578 '1234#,123,m,K1000000,0,, ,{SSHA}' in self.browser.contents)3764 '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents) 3579 3765 3580 3766 def test_payment_dates(self): … … 3678 3864 'firstname,flash_notice,lastname,matric_number,middlename,nationality,' 3679 3865 'officer_comment,perm_address,personal_updated,phone,reg_number,' 3680 'sex,student_id,suspended,suspended_comment, transcript_comment,'3681 'password,state,history,certcode,is_postgrad, current_level,'3682 'current_ session\r\n'3866 'sex,student_id,suspended,suspended_comment,' 3867 'password,state,history,certcode,is_postgrad,' 3868 'current_level,current_session\r\n' 3683 3869 ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,' 3684 '1234#,123,m,K1000000,0,, ,{SSHA}' in self.browser.contents)3870 '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents) 3685 3871 self.assertEqual(len(self.app['datacenter'].running_exports), 1) 3686 3872 job_id = self.app['datacenter'].running_exports[0][0] -
main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py
r15060 r15163 127 127 'my adm code,my clr code,1981-02-04#,anna@sample.com,,' 128 128 'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n",,' 129 '+234-123-12345#,123,f,A111111,0,,, ,created'129 '+234-123-12345#,123,f,A111111,0,,,created' 130 130 ) 131 131 … … 160 160 'nationality,officer_comment,perm_address,personal_updated,' 161 161 'phone,reg_number,sex,student_id,suspended,suspended_comment,' 162 ' transcript_comment,password,state,history,certcode,is_postgrad,'163 'c urrent_level,current_session\r\n'162 'password,state,history,' 163 'certcode,is_postgrad,current_level,current_session\r\n' 164 164 'my adm code,my clr code,' 165 165 '1981-02-04#,anna@sample.com,,Anna,,Tester,234,M.,NG,,' 166 166 '"Studentroad 21\nLagos 123456\n",,+234-123-12345#,123,f,' 167 'A111111,0,,, ,created'167 'A111111,0,,,created' 168 168 in result 169 169 ) … … 182 182 'nationality,officer_comment,perm_address,personal_updated,' 183 183 'phone,reg_number,sex,student_id,suspended,suspended_comment,' 184 ' transcript_comment,password,state,history,certcode,'184 'password,state,history,certcode,' 185 185 'is_postgrad,current_level,current_session\r\n' 186 186 'my adm code,my clr code,1981-02-04#,anna@sample.com,,' 187 187 'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n"' 188 ',,+234-123-12345#,123,f,A111111,0,,, ,created'188 ',,+234-123-12345#,123,f,A111111,0,,,created' 189 189 in result 190 190 ) … … 202 202 'nationality,officer_comment,perm_address,personal_updated,' 203 203 'phone,reg_number,sex,student_id,suspended,suspended_comment,' 204 ' transcript_comment,password,state,history,certcode,'204 'password,state,history,certcode,' 205 205 'is_postgrad,current_level,current_session\r\n' 206 206 'my adm code,my clr code,1981-02-04#,anna@sample.com,,' 207 207 'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n"' 208 ',,+234-123-12345#,123,f,A111111,0,,, ,created'208 ',,+234-123-12345#,123,f,A111111,0,,,created' 209 209 in result 210 210 ) -
main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py
r15102 r15163 20 20 import grok 21 21 from time import time 22 from cStringIO import StringIO 22 23 from reportlab.lib import colors 23 24 from reportlab.lib.units import cm … … 33 34 from waeup.kofa.interfaces import ( 34 35 IExtFileStore, IKofaUtils, RETURNING, PAID, CLEARED, 35 academic_sessions_vocab )36 academic_sessions_vocab, IFileStoreNameChooser) 36 37 from waeup.kofa.interfaces import MessageFactory as _ 37 38 from waeup.kofa.students.interfaces import IStudentsUtils … … 854 855 level_obj.cumulative_params[0], level_obj.student)[0] 855 856 857 def _saveTranscriptPDF(self, student, transcript): 858 """Create a transcript PDF file and store it in student folder. 859 """ 860 file_store = getUtility(IExtFileStore) 861 file_id = IFileStoreNameChooser(student).chooseName( 862 attr="final_transcript.pdf") 863 file_store.createFile(file_id, StringIO(transcript)) 864 return 865 856 866 def renderPDFTranscript(self, view, filename='transcript.pdf', 857 867 student=None, 858 868 studentview=None, 859 note=None, signatures=None, sigs_in_footer=(), 869 note=None, 870 signatures=(), 871 sigs_in_footer=(), 872 digital_sigs=(), 860 873 show_scans=True, topMargin=1.5, 861 874 omit_fields=(), 862 875 tableheader=None, 863 no_passport=False): 876 no_passport=False, 877 save_file=False): 864 878 """Render pdf slip of a transcripts. 865 879 """ … … 957 971 data.append(signaturetables[0]) 958 972 973 # Insert digital signatures 974 if digital_sigs: 975 data.append(Spacer(1, 20)) 976 sigs = digital_sigs.split('\n') 977 for sig in sigs: 978 data.append(Paragraph(sig, NOTE_STYLE)) 979 959 980 view.response.setHeader( 960 981 'Content-Type', 'application/pdf') … … 966 987 view.flash(_('Error in image file.')) 967 988 return view.redirect(view.url(view.context)) 989 if save_file: 990 self._saveTranscriptPDF(student, pdf_stream) 991 return 968 992 return pdf_stream 969 993 -
main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py
r14158 r15163 36 36 IStudentsContainer, IStudent, IStudentStudyCourse, IStudentStudyLevel, 37 37 ICourseTicket, IStudentOnlinePayment, IBedTicket, 38 IStudentPaymentsContainer, IStudentsUtils 38 IStudentPaymentsContainer, IStudentsUtils, 39 39 ) 40 40 from waeup.kofa.students.workflow import ( 41 41 ADMITTED, PAID, REQUESTED, CLEARED, REGISTERED, VALIDATED, GRADUATED, 42 TRANS CRIPT)42 TRANSREQ, TRANSVAL, TRANSREL) 43 43 from waeup.kofa.university.interfaces import ICourse 44 44 … … 96 96 return '' 97 97 98 99 98 class StudentManageBaseLink(StudentManageLink): 100 99 grok.order(2) … … 144 143 145 144 145 class StudentManageTranscriptLink(StudentManageLink): 146 grok.order(9) 147 link = 'final_transcript' 148 text = _(u'Final Transcript') 149 150 def render(self): 151 slip = getUtility(IExtFileStore).getFileByContext( 152 self.context.student, attr=self.link) 153 if slip: 154 lang = self.request.cookies.get('kofa.language') 155 text = translate( 156 self.text, 'waeup.kofa', target_language=lang) 157 url = self.view.url(self.context.student, self.link) 158 return u'<li><a href="%s">%s</a></li>' % ( 159 url, text) 160 return '' 161 162 146 163 class StudentsContainerManageActionButton(ManageActionButton): 147 164 grok.order(1) … … 370 387 def target_url(self): 371 388 if self.context.is_current: 372 return self.view.url(self.view.context, self.target)373 return False374 375 376 class StudyCourseTranscriptActionButton(ManageActionButton):377 grok.order(2)378 grok.name('transcript')379 grok.context(IStudentStudyCourse)380 grok.view(StudyCourseDisplayFormPage)381 grok.require('waeup.viewTranscript')382 text = _('Transcript')383 target = 'transcript'384 icon = 'actionicon_transcript.png'385 386 @property387 def target_url(self):388 if self.context.student.transcript_enabled:389 return self.view.url(self.view.context, self.target)390 return False391 392 393 class TranscriptSlipActionButton(ManageActionButton):394 grok.order(1)395 grok.name('transcript_slip')396 grok.context(IStudentStudyCourse)397 grok.view(StudyCourseTranscriptPage)398 grok.require('waeup.viewTranscript')399 text = _('Academic Transcript')400 target = 'transcript.pdf'401 icon = 'actionicon_pdf.png'402 403 @property404 def target_url(self):405 if self.context.student.transcript_enabled:406 389 return self.view.url(self.view.context, self.target) 407 390 return False … … 743 726 744 727 728 class StudyCourseTranscriptActionButton(ManageActionButton): 729 grok.order(2) 730 grok.name('transcript') 731 grok.context(IStudentStudyCourse) 732 grok.view(StudyCourseDisplayFormPage) 733 grok.require('waeup.viewTranscript') 734 text = _('Transcript') 735 target = 'transcript' 736 icon = 'actionicon_transcript.png' 737 738 @property 739 def target_url(self): 740 final_slip = getUtility(IExtFileStore).getFileByContext( 741 self.context.student, attr='final_transcript') 742 if self.context.student.transcript_enabled and not final_slip: 743 return self.view.url(self.view.context, self.target) 744 return False 745 746 745 747 class RequestTranscriptActionButton(ManageActionButton): 746 748 grok.order(8) … … 759 761 760 762 761 class ProcessTranscriptRequestActionButton(ManageActionButton): 762 grok.order(9) 763 grok.context(IStudent) 764 grok.view(StudentBaseDisplayFormPage) 763 class ValidateTranscriptActionButton(ManageActionButton): 764 grok.order(8) 765 grok.context(IStudentStudyCourse) 766 grok.view(StudyCourseTranscriptPage) 767 grok.require('waeup.processTranscript') 768 text = _('Validate transcript') 769 target = 'validate_transcript' 770 icon = 'actionicon_transcript.png' 771 772 @property 773 def target_url(self): 774 if self.context.student.state != TRANSREQ: 775 return '' 776 return self.view.url(self.view.context, self.target) 777 778 @property 779 def onclick(self): 780 return "return window.confirm(%s);" % _( 781 "'Course results cannot be changed after transcript " 782 "validation. \\n\\n" 783 "You really want to validate the transcript?'") 784 785 786 class ReleaseTranscriptActionButton(ManageActionButton): 787 grok.order(8) 788 grok.context(IStudentStudyCourse) 789 grok.view(StudyCourseTranscriptPage) 790 grok.require('waeup.processTranscript') 791 text = _('Release transcript') 792 target = 'release_transcript' 793 icon = 'actionicon_transcript.png' 794 795 @property 796 def target_url(self): 797 if self.context.student.state != TRANSVAL: 798 return '' 799 return self.view.url(self.view.context, self.target) 800 801 802 class TranscriptSlipActionButton(ManageActionButton): 803 grok.order(1) 804 grok.name('transcript_slip') 805 grok.context(IStudentStudyCourse) 806 grok.view(StudyCourseTranscriptPage) 765 807 grok.require('waeup.viewTranscript') 766 text = _('Manage transcript request') 767 target = 'process_transcript_request' 808 text = _('Academic Transcript') 809 target = 'transcript.pdf' 810 icon = 'actionicon_pdf.png' 811 812 @property 813 def target_url(self): 814 final_slip = getUtility(IExtFileStore).getFileByContext( 815 self.context.student, attr='final_transcript') 816 if self.context.student.transcript_enabled \ 817 and not final_slip: 818 return self.view.url(self.view.context, self.target) 819 return False 820 821 822 class SignTranscriptActionButton(ManageActionButton): 823 grok.order(2) 824 grok.context(IStudentStudyCourse) 825 grok.view(StudyCourseTranscriptPage) 826 grok.require('waeup.signTranscript') 827 text = _('Sign transcript electronically') 828 target = 'sign_transcript' 768 829 icon = 'actionicon_transcript.png' 769 830 770 831 @property 771 832 def target_url(self): 772 if self.context.state != TRANSCRIPT: 773 return '' 774 return self.view.url(self.view.context, self.target) 833 if self.context.student.state != TRANSVAL: 834 return '' 835 return self.view.url(self.view.context, self.target) 836 837 @property 838 def onclick(self): 839 return "return window.confirm(%s);" % _( 840 "'Signing a transcript electronically cannot be revoked. " 841 "The electronic signature replaces your handwritten signature.\\n\\n" 842 "You really want to sign the transcript?'") 775 843 776 844 -
main/waeup.kofa/trunk/src/waeup/kofa/students/workflow.py
r13610 r15163 9 9 IObjectHistory, IKofaWorkflowInfo, IKofaUtils, 10 10 CREATED, ADMITTED, CLEARANCE, REQUESTED, CLEARED, PAID, RETURNING, 11 REGISTERED, VALIDATED, GRADUATED, TRANSCRIPT) 11 REGISTERED, VALIDATED, GRADUATED, TRANSREQ, TRANSVAL, TRANSREL, 12 IExtFileStore) 12 13 from waeup.kofa.interfaces import MessageFactory as _ 13 14 from waeup.kofa.workflow import KofaWorkflow, KofaWorkflowInfo … … 198 199 msg = _("Transcript requested"), 199 200 source = GRADUATED, 200 destination = TRANSCRIPT), 201 202 Transition( 203 transition_id = 'process_transcript', 204 title = _('Transcript processed'), 205 msg = _("Transcript processed"), 206 source = TRANSCRIPT, 201 destination = TRANSREQ), 202 203 Transition( 204 transition_id = 'reset10', 205 title = _('Reject transcript request'), 206 msg = _("Transcript request rejected"), 207 source = TRANSREQ, 208 destination = GRADUATED), 209 210 Transition( 211 transition_id = 'validate_transcript', 212 title = _('Validate transcript'), 213 msg = _("Transcript validated"), 214 source = TRANSREQ, 215 destination = TRANSVAL), 216 217 Transition( 218 transition_id = 'release_transcript', 219 title = _('Release transcript'), 220 msg = _("Transcript released"), 221 source = TRANSVAL, 222 destination = TRANSREL), 223 224 Transition( 225 transition_id = 'reset11', 226 title = _('Reset to graduated'), 227 msg = _("Transcript process reset"), 228 source = TRANSREL, 207 229 destination = GRADUATED), 208 230 ) … … 275 297 level_object.validated_by = None 276 298 level_object.validation_date = None 299 elif event.transition.transition_id == 'reset11': 300 transcript_file = getUtility(IExtFileStore).getFileByContext( 301 obj, attr='final_transcript') 302 if transcript_file: 303 getUtility(IExtFileStore).deleteFileByContext( 304 obj, attr='final_transcript') 305 obj['studycourse'].transcript_comment = None 306 obj['studycourse'].transcript_signees = None 277 307 # In some tests we don't have a students container 278 308 try: -
main/waeup.kofa/trunk/src/waeup/kofa/university/faculty.py
r14511 r15163 63 63 'waeup.local.LocalStudentsManager', 64 64 'waeup.local.LocalWorkflowManager', 65 'waeup.local.TranscriptOfficer', 66 'waeup.local.TranscriptSignee', 65 67 ] 66 68
Note: See TracChangeset for help on using the changeset viewer.