## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## """UI components for students and related components. """ import sys import grok from time import time from datetime import date, datetime from zope.event import notify from zope.catalog.interfaces import ICatalog from zope.component import queryUtility from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState from zope.component import createObject from waeup.sirp.accesscodes import ( invalidate_accesscode, get_access_code, create_accesscode) from waeup.sirp.accesscodes.workflow import USED from waeup.sirp.browser import ( WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage) from waeup.sirp.browser.breadcrumbs import Breadcrumb from waeup.sirp.browser.resources import datepicker, datatable, tabs from waeup.sirp.browser.viewlets import ( ManageActionButton, PrimaryNavTab, AddActionButton) from waeup.sirp.interfaces import IWAeUPObject, IUserAccount from waeup.sirp.widgets.datewidget import ( FriendlyDateWidget, FriendlyDateDisplayWidget, FriendlyDatetimeDisplayWidget) from waeup.sirp.university.vocabularies import study_modes from waeup.sirp.students.interfaces import ( IStudentsContainer, IStudent, IStudentClearance, IStudentPasswordSetting, IStudentPersonal, IStudentBase, IStudentStudyCourse, IStudentAccommodation, IStudentClearanceEdit, IStudentStudyLevel, ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer, IStudentOnlinePayment, IBedTicket ) from waeup.sirp.students.catalog import search from waeup.sirp.students.workflow import ( CLEARANCE, RETURNING, CLEARED, REGISTERED, VALIDATED) from waeup.sirp.students.studylevel import StudentStudyLevel, CourseTicket from waeup.sirp.students.vocabularies import StudyLevelSource from waeup.sirp.students.utils import ( getPaymentDetails, getAccommodationDetails, selectBed, renderPDF) from waeup.sirp.browser.resources import toggleall from waeup.sirp.authentication import get_principal_role_manager from waeup.sirp.hostels.hostel import NOT_OCCUPIED def write_log_message(view, message): ob_class = view.__implemented__.__name__.replace('waeup.sirp.','') view.context.getStudent().loggerInfo(ob_class, message) return # Save function used for save methods in manager pages def msave(view, **data): form = view.request.form changed_fields = view.applyData(view.context, **data) # Turn list of lists into single list if changed_fields: changed_fields = reduce(lambda x,y: x+y, changed_fields.values()) fields_string = ' + '.join(changed_fields) view.context._p_changed = True view.flash('Form has been saved.') if fields_string: write_log_message(view, 'saved: % s' % fields_string) return class StudentsTab(PrimaryNavTab): """Students tab in primary navigation. """ grok.context(IWAeUPObject) grok.order(4) grok.require('waeup.viewStudent') grok.template('primarynavtab') pnav = 4 tab_title = u'Students' @property def link_target(self): return self.view.application_url('students') class StudentsBreadcrumb(Breadcrumb): """A breadcrumb for the students container. """ grok.context(IStudentsContainer) title = u'Students' class StudentBreadcrumb(Breadcrumb): """A breadcrumb for the student container. """ grok.context(IStudent) def title(self): return self.context.fullname class SudyCourseBreadcrumb(Breadcrumb): """A breadcrumb for the student study course. """ grok.context(IStudentStudyCourse) title = u'Study Course' class PaymentsBreadcrumb(Breadcrumb): """A breadcrumb for the student payments folder. """ grok.context(IStudentPaymentsContainer) title = u'Payments' class OnlinePaymentBreadcrumb(Breadcrumb): """A breadcrumb for course lists. """ grok.context(IStudentOnlinePayment) @property def title(self): return self.context.p_id class AccommodationBreadcrumb(Breadcrumb): """A breadcrumb for the student accommodation folder. """ grok.context(IStudentAccommodation) title = u'Accommodation' #@property #def target(self): # prm = get_principal_role_manager() # principal = get_current_principal() # roles = [x[0] for x in prm.getRolesForPrincipal(principal.id)] # if 'waeup.Student' in roles: # return 'index' # else: # return 'manage' class BedTicketBreadcrumb(Breadcrumb): """A breadcrumb for bed tickets. """ grok.context(IBedTicket) @property def title(self): return 'Bed Ticket %s' % self.context.getSessionString() class StudyLevelBreadcrumb(Breadcrumb): """A breadcrumb for course lists. """ grok.context(IStudentStudyLevel) @property def title(self): return self.context.level_title class StudentsContainerPage(WAeUPPage): """The standard view for student containers. """ grok.context(IStudentsContainer) grok.name('index') grok.require('waeup.viewStudent') grok.template('containerpage') label = 'Student Section' title = 'Students' pnav = 4 def update(self, *args, **kw): datatable.need() form = self.request.form self.hitlist = [] if 'searchterm' in form and form['searchterm']: self.searchterm = form['searchterm'] self.searchtype = form['searchtype'] elif 'old_searchterm' in form: self.searchterm = form['old_searchterm'] self.searchtype = form['old_searchtype'] else: if 'search' in form: self.flash('Empty search string.') return self.hitlist = search(query=self.searchterm, searchtype=self.searchtype, view=self) if not self.hitlist: self.flash('No student found.') return class SetPasswordPage(WAeUPPage): grok.context(IWAeUPObject) grok.name('setpassword') grok.require('waeup.Public') grok.template('setpassword') title = '' label = 'Set password for first-time login' ac_prefix = 'PWD' pnav = 0 def update(self, SUBMIT=None): self.reg_number = self.request.form.get('reg_number', None) self.ac_series = self.request.form.get('ac_series', None) self.ac_number = self.request.form.get('ac_number', None) if SUBMIT is None: return hitlist = search(query=self.reg_number, searchtype='reg_number', view=self) if not hitlist: self.flash('No student found.') return if len(hitlist) != 1: # Cannot happen but anyway self.flash('More than one student found.') return student = hitlist[0].context self.student_id = student.student_id student_pw = student.password pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number) code = get_access_code(pin) if not code: self.flash('Access code is invalid.') return if student_pw and pin == student.adm_code: self.flash('Password has already been set. Your Student Id is %s' % self.student_id) return elif student_pw: self.flash('Password has already been set. You are using the wrong Access Code.') return # Mark pin as used (this also fires a pin related transition) # and set student password if code.state == USED: self.flash('Access code has already been used.') return else: comment = u"AC invalidated for %s" % self.student_id # Here we know that the ac is in state initialized so we do not # expect an exception #import pdb; pdb.set_trace() invalidate_accesscode(pin,comment) IUserAccount(student).setPassword(self.ac_number) student.adm_code = pin self.flash('Password has been set. Your Student Id is %s' % self.student_id) return class StudentsContainerManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudentsContainer) grok.view(StudentsContainerPage) grok.require('waeup.manageStudents') text = 'Manage student section' class StudentsContainerManagePage(WAeUPPage): """The manage page for student containers. """ grok.context(IStudentsContainer) grok.name('manage') grok.require('waeup.manageStudents') grok.template('containermanagepage') pnav = 4 title = 'Manage student section' @property def label(self): return self.title def update(self, *args, **kw): datatable.need() toggleall.need() form = self.request.form self.hitlist = [] if 'searchterm' in form and form['searchterm']: self.searchterm = form['searchterm'] self.searchtype = form['searchtype'] elif 'old_searchterm' in form: self.searchterm = form['old_searchterm'] self.searchtype = form['old_searchtype'] else: if 'search' in form: self.flash('Empty search string.') return if not 'entries' in form: self.hitlist = search(query=self.searchterm, searchtype=self.searchtype, view=self) if not self.hitlist: self.flash('No student found.') return entries = form['entries'] if isinstance(entries, basestring): entries = [entries] deleted = [] for entry in entries: if 'remove' in form: del self.context[entry] deleted.append(entry) self.hitlist = search(query=self.searchterm, searchtype=self.searchtype, view=self) if len(deleted): self.flash('Successfully removed: %s' % ', '.join(deleted)) return class StudentsContainerAddActionButton(AddActionButton): grok.order(1) grok.context(IStudentsContainer) grok.view(StudentsContainerManagePage) grok.require('waeup.manageStudents') text = 'Add student' target = 'addstudent' class StudentAddFormPage(WAeUPAddFormPage): """Add-form to add a student. """ grok.context(IStudentsContainer) grok.require('waeup.manageStudents') grok.name('addstudent') grok.template('studentaddpage') form_fields = grok.AutoFields(IStudent) title = 'Students' label = 'Add student' pnav = 4 @grok.action('Create student record') def addStudent(self, **data): student = createObject(u'waeup.Student') self.applyData(student, **data) self.context.addStudent(student) self.flash('Student record created.') self.redirect(self.url(self.context[student.student_id], 'index')) return class StudentBaseDisplayFormPage(WAeUPDisplayFormPage): """ Page to display student base data """ grok.context(IStudent) grok.name('index') grok.require('waeup.viewStudent') grok.template('basepage') form_fields = grok.AutoFields(IStudentBase).omit('password') pnav = 4 title = 'Base Data' @property def label(self): return '%s: Base Data' % self.context.fullname @property def hasPassword(self): if self.context.password: return 'set' return 'unset' class StudentBaseManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.manageStudents') text = 'Manage' target = 'edit_base' class StudentBaseManageFormPage(WAeUPEditFormPage): """ View to edit student base data """ grok.context(IStudent) grok.name('edit_base') grok.require('waeup.manageStudents') form_fields = grok.AutoFields(IStudentBase).omit('student_id') grok.template('basemanagepage') label = 'Manage base data' title = 'Base Data' pnav = 4 def update(self): datepicker.need() # Enable jQuery datepicker in date fields. super(StudentBaseManageFormPage, self).update() self.wf_info = IWorkflowInfo(self.context) return def getTransitions(self): """Return a list of dicts of allowed transition ids and titles. Each list entry provides keys ``name`` and ``title`` for internal name and (human readable) title of a single transition. """ allowed_transitions = self.wf_info.getManualTransitions() return [dict(name='', title='No transition')] +[ dict(name=x, title=y) for x, y in allowed_transitions] @grok.action('Save') def save(self, **data): form = self.request.form password = form.get('password', None) password_ctl = form.get('control_password', None) if password: if (password != password_ctl): self.flash('Passwords do not match.') else: # XXX: This is too early. PW should only be saved if there # are no (other) errors left in form. IUserAccount(self.context).setPassword(password) write_log_message(self, 'password changed') #self.reg_number = form.get('form.reg_number', None) #if self.reg_number: # hitlist = search(query=self.reg_number,searchtype='reg_number', view=self) # if hitlist and hitlist[0].student_id != self.context.student_id: # self.flash('Registration number exists.') # return #self.matric_number = form.get('form.matric_number', None) #if self.matric_number: # hitlist = search(query=self.matric_number, # searchtype='matric_number', view=self) # if hitlist and hitlist[0].student_id != self.context.student_id: # self.flash('Matriculation number exists.') # return # Turn list of lists into single list changed_fields = self.applyData(self.context, **data) if changed_fields: changed_fields = reduce(lambda x,y: x+y, changed_fields.values()) fields_string = ' + '.join(changed_fields) self.context._p_changed = True if form.has_key('transition') and form['transition']: transition_id = form['transition'] self.wf_info.fireTransition(transition_id) self.flash('Form has been saved.') if fields_string: write_log_message(self, 'saved: % s' % fields_string) return class StudentClearanceDisplayFormPage(WAeUPDisplayFormPage): """ Page to display student clearance data """ grok.context(IStudent) grok.name('view_clearance') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentClearance).omit('clearance_locked') form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le') title = 'Clearance Data' pnav = 4 @property def label(self): return '%s: Clearance Data' % self.context.fullname class StudentClearanceManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.manageStudents') text = 'Manage' target = 'edit_clearance' class StudentClearanceManageFormPage(WAeUPEditFormPage): """ Page to edit student clearance data """ grok.context(IStudent) grok.name('edit_clearance') grok.require('waeup.manageStudents') form_fields = grok.AutoFields(IStudentClearance) label = 'Manage clearance data' title = 'Clearance Data' pnav = 4 form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year') def update(self): datepicker.need() # Enable jQuery datepicker in date fields. return super(StudentClearanceManageFormPage, self).update() @grok.action('Save') def save(self, **data): msave(self, **data) return class StudentPersonalDisplayFormPage(WAeUPDisplayFormPage): """ Page to display student personal data """ grok.context(IStudent) grok.name('view_personal') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentPersonal) title = 'Personal Data' pnav = 4 @property def label(self): return '%s: Personal Data' % self.context.fullname class StudentPersonalManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentPersonalDisplayFormPage) grok.require('waeup.manageStudents') text = 'Manage' target = 'edit_personal' class StudentPersonalManageFormPage(WAeUPEditFormPage): """ Page to edit student clearance data """ grok.context(IStudent) grok.name('edit_personal') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentPersonal) label = 'Manage personal data' title = 'Personal Data' pnav = 4 @grok.action('Save') def save(self, **data): msave(self, **data) return class StudyCourseDisplayFormPage(WAeUPDisplayFormPage): """ Page to display the student study course data """ grok.context(IStudentStudyCourse) grok.name('index') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentStudyCourse) grok.template('studycoursepage') title = 'Study Course' pnav = 4 @property def label(self): return '%s: Study Course' % self.context.__parent__.fullname @property def current_mode(self): return study_modes.getTermByToken( self.context.certificate.study_mode).title class StudyCourseManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyCourse) grok.view(StudyCourseDisplayFormPage) grok.require('waeup.manageStudents') text = 'Manage' target = 'manage' class StudyCourseManageFormPage(WAeUPEditFormPage): """ Page to edit the student study course data """ grok.context(IStudentStudyCourse) grok.name('manage') grok.require('waeup.manageStudents') grok.template('studycoursemanagepage') form_fields = grok.AutoFields(IStudentStudyCourse) title = 'Study Course' label = 'Manage study course' pnav = 4 taboneactions = ['Save','Cancel'] tabtwoactions = ['Remove selected levels','Cancel'] tabthreeactions = ['Add study level'] def update(self): super(StudyCourseManageFormPage, self).update() tabs.need() datatable.need() return @grok.action('Save') def save(self, **data): msave(self, **data) return @property def level_dict(self): studylevelsource = StudyLevelSource().factory for code in studylevelsource.getValues(self.context): title = studylevelsource.getTitle(self.context, code) yield(dict(code=code, title=title)) @grok.action('Add study level') def addStudyLevel(self, **data): level_code = self.request.form.get('addlevel', None) studylevel = StudentStudyLevel() studylevel.level = int(level_code) try: self.context.addStudentStudyLevel( self.context.certificate,studylevel) except KeyError: self.flash('This level exists.') self.redirect(self.url(self.context, u'@@manage')+'#tab-2') return @grok.action('Remove selected levels') def delStudyLevels(self, **data): form = self.request.form if form.has_key('val_id'): child_id = form['val_id'] else: self.flash('No study level selected.') self.redirect(self.url(self.context, '@@manage')+'#tab-2') return if not isinstance(child_id, list): child_id = [child_id] deleted = [] for id in child_id: try: del self.context[id] deleted.append(id) except: self.flash('Could not delete %s: %s: %s' % ( id, sys.exc_info()[0], sys.exc_info()[1])) if len(deleted): self.flash('Successfully removed: %s' % ', '.join(deleted)) self.redirect(self.url(self.context, u'@@manage')+'#tab-2') return class StudyLevelDisplayFormPage(WAeUPDisplayFormPage): """ Page to display student study levels """ grok.context(IStudentStudyLevel) grok.name('index') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentStudyLevel) grok.template('studylevelpage') pnav = 4 @property def title(self): return 'Study Level %s' % self.context.level_title @property def label(self): return '%s: Study Level %s' % ( self.context.getStudent().fullname,self.context.level_title) @property def total_credits(self): total_credits = 0 for key, val in self.context.items(): total_credits += val.credits return total_credits class StudyLevelManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyLevel) grok.view(StudyLevelDisplayFormPage) grok.require('waeup.manageStudents') text = 'Manage' target = 'manage' class StudyLevelManageFormPage(WAeUPEditFormPage): """ Page to edit the student study level data """ grok.context(IStudentStudyLevel) grok.name('manage') grok.require('waeup.manageStudents') grok.template('studylevelmanagepage') form_fields = grok.AutoFields(IStudentStudyLevel) pnav = 4 taboneactions = ['Save','Cancel'] tabtwoactions = ['Add course ticket','Remove selected tickets','Cancel'] def update(self): super(StudyLevelManageFormPage, self).update() tabs.need() datatable.need() return @property def title(self): return 'Study Level %s' % self.context.level_title @property def label(self): return 'Manage study level %s' % self.context.level_title @grok.action('Save') def save(self, **data): msave(self, **data) return @grok.action('Add course ticket') def addCourseTicket(self, **data): self.redirect(self.url(self.context, '@@add')) @grok.action('Remove selected tickets') def delCourseTicket(self, **data): form = self.request.form if form.has_key('val_id'): child_id = form['val_id'] else: self.flash('No ticket selected.') self.redirect(self.url(self.context, '@@manage')+'#tab-2') return if not isinstance(child_id, list): child_id = [child_id] deleted = [] for id in child_id: try: del self.context[id] deleted.append(id) except: self.flash('Could not delete %s: %s: %s' % ( id, sys.exc_info()[0], sys.exc_info()[1])) if len(deleted): self.flash('Successfully removed: %s' % ', '.join(deleted)) self.redirect(self.url(self.context, u'@@manage')+'#tab-2') return class CourseTicketAddFormPage(WAeUPAddFormPage): """Add a course ticket. """ grok.context(IStudentStudyLevel) grok.name('add') grok.require('waeup.manageStudents') label = 'Add course ticket' form_fields = grok.AutoFields(ICourseTicketAdd).omit( 'grade', 'score', 'automatic') pnav = 4 @property def title(self): return 'Study Level %s' % self.context.level_title @grok.action('Add course ticket') def addCourseTicket(self, **data): ticket = CourseTicket() course = data['course'] ticket.core_or_elective = data['core_or_elective'] ticket.automatic = False ticket.code = course.code ticket.title = course.title ticket.faculty = course.__parent__.__parent__.__parent__.title ticket.department = course.__parent__.__parent__.title ticket.credits = course.credits ticket.passmark = course.passmark ticket.semester = course.semester try: self.context.addCourseTicket(ticket) except KeyError: self.flash('The ticket exists.') return self.flash('Successfully added %s.' % ticket.code) self.redirect(self.url(self.context, u'@@manage')+'#tab-2') return @grok.action('Cancel') def cancel(self, **data): self.redirect(self.url(self.context)) class CourseTicketDisplayFormPage(WAeUPDisplayFormPage): """ Page to display course tickets """ grok.context(ICourseTicket) grok.name('index') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(ICourseTicket) grok.template('courseticketpage') pnav = 4 @property def title(self): return 'Course Ticket %s' % self.context.code @property def label(self): return '%s: Course Ticket %s' % ( self.context.getStudent().fullname,self.context.code) class CourseTicketManageActionButton(ManageActionButton): grok.order(1) grok.context(ICourseTicket) grok.view(CourseTicketDisplayFormPage) grok.require('waeup.manageStudents') text = 'Manage' target = 'manage' class CourseTicketManageFormPage(WAeUPEditFormPage): """ Page to manage course tickets """ grok.context(ICourseTicket) grok.name('manage') grok.require('waeup.manageStudents') form_fields = grok.AutoFields(ICourseTicket) grok.template('courseticketmanagepage') pnav = 4 @property def title(self): return 'Course Ticket %s' % self.context.code @property def label(self): return 'Manage course ticket %s' % self.context.code @grok.action('Save') def save(self, **data): msave(self, **data) return # We don't need the display form page yet #class PaymentsDisplayFormPage(WAeUPDisplayFormPage): # """ Page to display the student payments # """ # grok.context(IStudentPaymentsContainer) # grok.name('view') # grok.require('waeup.viewStudent') # form_fields = grok.AutoFields(IStudentPaymentsContainer) # grok.template('paymentspage') # title = 'Payments' # pnav = 4 # def formatDatetime(self,datetimeobj): # if isinstance(datetimeobj, datetime): # return datetimeobj.strftime("%Y-%m-%d %H:%M:%S") # else: # return None # @property # def label(self): # return '%s: Payments' % self.context.__parent__.fullname # def update(self): # super(PaymentsDisplayFormPage, self).update() # datatable.need() # return # This manage form page is for both students and students officers. class PaymentsManageFormPage(WAeUPEditFormPage): """ Page to manage the student payments """ grok.context(IStudentPaymentsContainer) grok.name('index') grok.require('waeup.handleStudent') form_fields = grok.AutoFields(IStudentPaymentsContainer) grok.template('paymentsmanagepage') title = 'Payments' pnav = 4 def unremovable(self, ticket): prm = get_principal_role_manager() roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)] return ('waeup.Student' in roles and ticket.r_code) def formatDatetime(self,datetimeobj): if isinstance(datetimeobj, datetime): return datetimeobj.strftime("%Y-%m-%d %H:%M:%S") else: return None @property def label(self): return '%s: Payments' % self.context.__parent__.fullname def update(self): super(PaymentsManageFormPage, self).update() datatable.need() return @grok.action('Remove selected tickets') def delPaymentTicket(self, **data): form = self.request.form if form.has_key('val_id'): child_id = form['val_id'] else: self.flash('No payment selected.') self.redirect(self.url(self.context)) return if not isinstance(child_id, list): child_id = [child_id] deleted = [] for id in child_id: # Students are not allowed to remove used payment tickets if not self.unremovable(self.context[id]): try: del self.context[id] deleted.append(id) except: self.flash('Could not delete %s: %s: %s' % ( id, sys.exc_info()[0], sys.exc_info()[1])) if len(deleted): self.flash('Successfully removed: %s' % ', '.join(deleted)) write_log_message(self,'removed: % s' % ', '.join(deleted)) self.redirect(self.url(self.context)) return @grok.action('Add online payment ticket') def addPaymentTicket(self, **data): self.redirect(self.url(self.context, '@@addop')) #class OnlinePaymentManageActionButton(ManageActionButton): # grok.order(1) # grok.context(IStudentPaymentsContainer) # grok.view(PaymentsDisplayFormPage) # grok.require('waeup.manageStudents') # text = 'Manage payments' # target = 'manage' class OnlinePaymentAddFormPage(WAeUPAddFormPage): """ Page to add an online payment ticket """ grok.context(IStudentPaymentsContainer) grok.name('addop') grok.require('waeup.handleStudent') form_fields = grok.AutoFields(IStudentOnlinePayment).select( 'p_category') #zzgrok.template('addpaymentpage') label = 'Add online payment' title = 'Payments' pnav = 4 # To be sepezified in customization packages def getPaymentDetails(self, category, student): return getPaymentDetails(category, student) @grok.action('Create ticket') def createTicket(self, **data): payment = createObject(u'waeup.StudentOnlinePayment') self.applyData(payment, **data) timestamp = "%d" % int(time()*1000) #order_id = "%s%s" % (student_id[1:],timestamp) payment.p_id = "p%s" % timestamp pay_details = self.getPaymentDetails( data['p_category'],self.context.__parent__) if pay_details['error']: self.flash(pay_details['error']) self.redirect(self.url(self.context)) return payment.amount_auth = pay_details['amount'] payment.p_item = pay_details['p_item'] payment.p_session = pay_details['p_session'] payment.surcharge_1 = pay_details['surcharge_1'] payment.surcharge_2 = pay_details['surcharge_2'] payment.surcharge_3 = pay_details['surcharge_3'] self.context[payment.p_id] = payment self.flash('Payment ticket created.') self.redirect(self.url(self.context)) return class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage): """ Page to view an online payment ticket """ grok.context(IStudentOnlinePayment) grok.name('index') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentOnlinePayment) form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') pnav = 4 @property def title(self): return 'Online Payment Ticket %s' % self.context.p_id @property def label(self): return '%s: Online Payment Ticket %s' % ( self.context.getStudent().fullname,self.context.p_id) class PaymentReceiptActionButton(ManageActionButton): grok.context(IStudentOnlinePayment) grok.require('waeup.viewStudent') icon = 'actionicon_pdf.png' text = 'Download payment receipt' target = 'payment_receipt.pdf' class OnlinePaymentCallbackPage(grok.View): """ Callback view """ grok.context(IStudentOnlinePayment) grok.name('callback') grok.require('waeup.payStudent') # This update method simulates a valid callback und must be # specified in the customization package. The parameters must be taken # from the incoming request. def update(self): student = self.context.getStudent() write_log_message(self,'valid callback: %s' % self.context.p_id) self.context.r_amount_approved = self.context.amount_auth self.context.r_card_num = u'0000' self.context.r_code = u'00' self.context.p_state = 'paid' self.context.payment_date = datetime.now() if self.context.p_category == 'clearance': # Create CLR access code pin, error = create_accesscode('CLR',0,student.student_id) if error: self.flash('Valid callback received. ' + error) return self.context.ac = pin elif self.context.p_category == 'schoolfee': # Create SFE access code pin, error = create_accesscode('SFE',0,student.student_id) if error: self.flash('Valid callback received. ' + error) return self.context.ac = pin elif self.context.p_category == 'bed_allocation': # Create HOS access code pin, error = create_accesscode('HOS',0,student.student_id) if error: self.flash('Valid callback received. ' + error) return self.context.ac = pin self.flash('Valid callback received.') return def render(self): self.redirect(self.url(self.context, '@@index')) return class ExportPDFPaymentSlipPage(grok.View): """Deliver a PDF slip of the context. """ grok.context(IStudentOnlinePayment) grok.name('payment_receipt.pdf') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentOnlinePayment) form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le') form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le') prefix = 'form' @property def label(self): return 'Online Payment Receipt %s' % self.context.p_id def render(self): studentview = StudentBaseDisplayFormPage(self.context.getStudent(), self.request) return renderPDF(self,'Payment', 'payment_receipt.pdf', self.context.getStudent, studentview) # We don't need the display form page yet #class AccommodationDisplayFormPage(WAeUPDisplayFormPage): # """ Page to display the student accommodation data # """ # grok.context(IStudentAccommodation) # grok.name('xxx') # grok.require('waeup.viewStudent') # form_fields = grok.AutoFields(IStudentAccommodation) # #grok.template('accommodationpage') # title = 'Accommodation' # pnav = 4 # @property # def label(self): # return '%s: Accommodation Data' % self.context.__parent__.fullname # This manage form page is for both students and students officers. class AccommodationManageFormPage(WAeUPEditFormPage): """ Page to manage bed tickets. """ grok.context(IStudentAccommodation) grok.name('index') grok.require('waeup.handleStudent') form_fields = grok.AutoFields(IStudentAccommodation) grok.template('accommodationmanagepage') title = 'Accommodation' pnav = 4 officers_only_actions = ['Remove selected'] def formatDatetime(self,datetimeobj): if isinstance(datetimeobj, datetime): return datetimeobj.strftime("%Y-%m-%d %H:%M:%S") else: return None @property def label(self): return '%s: Accommodation' % self.context.__parent__.fullname def update(self): super(AccommodationManageFormPage, self).update() datatable.need() return @property def is_student(self): prm = get_principal_role_manager() roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)] return 'waeup.Student' in roles @grok.action('Remove selected') def delBedTickets(self, **data): if self.is_student: self.flash('You are not allowed to remove bed tickets.') self.redirect(self.url(self.context)) return form = self.request.form if form.has_key('val_id'): child_id = form['val_id'] else: self.flash('No bed ticket selected.') self.redirect(self.url(self.context)) return if not isinstance(child_id, list): child_id = [child_id] deleted = [] for id in child_id: try: del self.context[id] deleted.append(id) except: self.flash('Could not delete %s: %s: %s' % ( id, sys.exc_info()[0], sys.exc_info()[1])) if len(deleted): self.flash('Successfully removed: %s' % ', '.join(deleted)) write_log_message(self,'removed: % s' % ', '.join(deleted)) self.redirect(self.url(self.context)) return @property def selected_actions(self): sa = self.actions if self.is_student: sa = [action for action in self.actions if not action.label in self.officers_only_actions] return sa class AddBedTicketActionButton(ManageActionButton): grok.order(1) grok.context(IStudentAccommodation) grok.view(AccommodationManageFormPage) grok.require('waeup.handleStudent') icon = 'actionicon_home.png' text = 'Book accommodation' target = 'add' class BedTicketAddPage(WAeUPPage): """ Page to add an online payment ticket """ grok.context(IStudentAccommodation) grok.name('add') grok.require('waeup.handleStudent') grok.template('enterpin') ac_prefix = 'HOS' label = 'Add bed ticket' title = 'Add bed ticket' pnav = 4 buttonname = 'Create bed ticket' notice = '' # To be sepezified in customization packages def getAccommodationDetails(self, student): return getAccommodationDetails(student) # To be sepezified in customization packages def selectBed(self, available_beds): return selectBed(available_beds) def update(self, SUBMIT=None): student = self.context.getStudent() acc_details = self.getAccommodationDetails(student) if not student.state in acc_details['allowed_states']: self.flash("You are in the wrong registration state.") self.redirect(self.url(self.context)) return if str(student['studycourse'].entry_session) in self.context.keys(): self.flash('You already booked a bed space for the current accommodation session.') self.redirect(self.url(self.context)) return self.ac_series = self.request.form.get('ac_series', None) self.ac_number = self.request.form.get('ac_number', None) if SUBMIT is None: return pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number) code = get_access_code(pin) if not code: self.flash('Activation code is invalid.') return # Search a bed and exit if no bed is found or if the student # has already booked a bed cat = queryUtility(ICatalog, name='beds_catalog', default=None) entries = cat.searchResults( bed_type=(acc_details['bt'],acc_details['bt'])) available_beds = [ entry for entry in entries if entry.owner == NOT_OCCUPIED] if not available_beds: self.flash('There is no free bed in your category %s.' % acc_details['bt']) return entries = cat.searchResults( owner=(student.student_id,student.student_id)) # Cannot happen but anyway ... if len(entries): bed = [entry for entry in entries][0] self.flash('You already booked bed %s.' % bed.bed_id) return # Mark pin as used (this also fires a pin related transition) if code.state == USED: self.flash('Activation code has already been used.') return else: comment = u"AC invalidated for %s" % self.context.getStudent().student_id # Here we know that the ac is in state initialized so we do not # expect an exception, but the owner might be different if not invalidate_accesscode( pin,comment,self.context.getStudent().student_id): self.flash('You are not the owner of this access code.') return # Create bed ticket and book bed bedticket = createObject(u'waeup.BedTicket') bedticket.booking_code = pin bedticket.booking_session = acc_details['booking_session'] bedticket.bed_type = acc_details['bt'] bed = self.selectBed(available_beds) bed.bookBed(student.student_id) bedticket.bed = bed hall_title = bed.__parent__.hostel_name coordinates = bed.getBedCoordinates()[1:] block, room_nr, bed_nr = coordinates bedticket.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % ( hall_title, block, room_nr, bed_nr) key = str(acc_details['booking_session']) self.context[key] = bedticket self.flash('Bed ticket created and bed booked: %s' % bedticket.bed_coordinates) self.redirect(self.url(self.context)) return class BedTicketDisplayFormPage(WAeUPDisplayFormPage): """ Page to display bed tickets """ grok.context(IBedTicket) grok.name('index') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IBedTicket) form_fields[ 'booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') pnav = 4 @property def label(self): return 'Bed Ticket for Session %s' % self.context.getSessionString() @property def title(self): return 'Bed Ticket %s' % self.context.getSessionString() class RelocateStudentActionButton(ManageActionButton): grok.order(1) grok.context(IBedTicket) grok.view(BedTicketDisplayFormPage) grok.require('waeup.manageHostels') icon = 'actionicon_reload.png' text = 'Relocate student' target = 'relocate' class BedTicketRelocationPage(grok.View): """ Callback view """ grok.context(IBedTicket) grok.name('relocate') grok.require('waeup.manageHostels') # To be sepezified in customization packages def getAccommodationDetails(self, student): return getAccommodationDetails(student) # To be sepezified in customization packages def selectBed(self, available_beds): return selectBed(available_beds) # This update method simulates a valid callback und must be # specified in the customization package. The parameters must be taken # from the incoming request. def update(self): student = self.context.getStudent() acc_details = self.getAccommodationDetails(student) if acc_details['bt'] == self.context.bed_type: self.flash("Bed category hasn't changed. Student can't be relocated.") return # Search a bed and exit if no bed is found or if the student # has already booked a bed cat = queryUtility(ICatalog, name='beds_catalog', default=None) entries = cat.searchResults( bed_type=(acc_details['bt'],acc_details['bt'])) available_beds = [ entry for entry in entries if entry.owner == NOT_OCCUPIED] if not available_beds: self.flash('There is no free bed in your category %s.' % acc_details['bt']) return entries = cat.searchResults( owner=(student.student_id,student.student_id)) # Rlease old bed allocated_bed = self.context.bed allocated_bed.owner = NOT_OCCUPIED notify(grok.ObjectModifiedEvent(allocated_bed)) # Alocate new bed self.context.bed_type = acc_details['bt'] bed = self.selectBed(available_beds) bed.bookBed(student.student_id) self.context.bed = bed hall_title = bed.__parent__.hostel_name coordinates = bed.getBedCoordinates()[1:] block, room_nr, bed_nr = coordinates self.context.bed_coordinates = '%s, Block %s, Room %s, Bed %s' % ( hall_title, block, room_nr, bed_nr) self.flash('Student relocated and new bed booked: %s' % self.context.bed_coordinates) self.redirect(self.url(self.context)) return def render(self): self.redirect(self.url(self.context, '@@index')) return class StudentHistoryPage(WAeUPPage): """ Page to display student clearance data """ grok.context(IStudent) grok.name('history') grok.require('waeup.viewStudent') grok.template('studenthistory') title = 'History' pnav = 4 @property def label(self): return '%s: History' % self.context.fullname # Pages for students only class StudentBaseEditActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.handleStudent') text = 'Change password' target = 'bedit' class StudentPasswordSetting(grok.Adapter): """Adapt IStudent to data needed for password settings. We provide password getters/setters for the attached context (an IStudent object) that cooperate seamless with the usual formlib/form techniques. """ grok.context(IStudent) grok.provides(IStudentPasswordSetting) def __init__(self, context): self.name = context.fullname self.password_repeat = context.password self.context = context return def getPassword(self): return self.context.password def setPassword(self, password): IUserAccount(self.context).setPassword(password) return password = property(getPassword, setPassword) class StudentBaseEditFormPage(WAeUPEditFormPage): """ View to edit student base data by student """ grok.context(IStudent) grok.name('bedit') grok.require('waeup.handleStudent') #form_fields = grok.AutoFields(IStudentBaseEdit).omit( # 'student_id', 'reg_number', 'matric_number') form_fields = grok.AutoFields(IStudentPasswordSetting) grok.template('baseeditpage') label = 'Change password' title = 'Base Data' pnav = 4 def update(self): super(StudentBaseEditFormPage, self).update() self.wf_info = IWorkflowInfo(self.context) return def onFailure(self, action, data, errors): new_status = [] other_errors = False for error in errors: msg = getattr(error, 'message', '') if isinstance(msg, basestring) and msg != '': new_status.append(msg) else: other_errors = True if other_errors: if new_status: new_status.append('see below for further errors') else: new_status.append('See below for details.') if new_status: self.status = u'There were errors: %s' % ', '.join(new_status) return @grok.action('Save', failure=onFailure) def save(self, **data): self.applyData(self.context, **data) self.flash('Form has been saved.') return class StudentClearanceStartActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.handleStudent') icon = 'actionicon_start.png' text = 'Start clearance' target = 'start_clearance' @property def target_url(self): if self.context.state != 'admitted': return '' return self.view.url(self.view.context, self.target) class StartClearancePage(WAeUPPage): grok.context(IStudent) grok.name('start_clearance') grok.require('waeup.handleStudent') grok.template('enterpin') title = 'Start clearance' label = 'Start clearance' ac_prefix = 'CLR' notice = '' pnav = 4 buttonname = 'Start clearance now' def update(self, SUBMIT=None): if not self.context.state == 'admitted': self.flash("Wrong state.") self.redirect(self.url(self.context)) return self.ac_series = self.request.form.get('ac_series', None) self.ac_number = self.request.form.get('ac_number', None) if SUBMIT is None: return pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number) code = get_access_code(pin) if not code: self.flash('Activation code is invalid.') return # Mark pin as used (this also fires a pin related transition) # and fire transition start_clearance if code.state == USED: self.flash('Activation code has already been used.') return else: comment = u"AC invalidated for %s" % self.context.student_id # Here we know that the ac is in state initialized so we do not # expect an exception, but the owner might be different if not invalidate_accesscode(pin,comment,self.context.student_id): self.flash('You are not the owner of this access code.') return self.context.clr_code = pin IWorkflowInfo(self.context).fireTransition('start_clearance') self.flash('Clearance process has been started.') self.redirect(self.url(self.context,'cedit')) return class StudentClearanceEditActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.handleStudent') text = 'Edit' target = 'cedit' @property def target_url(self): if self.context.clearance_locked: return '' return self.view.url(self.view.context, self.target) class StudentClearanceEditFormPage(StudentClearanceManageFormPage): """ View to edit student clearance data by student """ grok.context(IStudent) grok.name('cedit') grok.require('waeup.handleStudent') form_fields = grok.AutoFields( IStudentClearanceEdit).omit('clearance_locked') label = 'Edit clearance data' title = 'Clearance Data' pnav = 4 form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year') def emitLockMessage(self): self.flash('The requested form is locked (read-only).') self.redirect(self.url(self.context)) return def update(self): if self.context.clearance_locked: self.emitLockMessage() return datepicker.need() return super(StudentClearanceEditFormPage, self).update() @grok.action('Save') def save(self, **data): self.applyData(self.context, **data) self.flash('Clearance form has been saved.') return @grok.action('Save and request clearance') def requestclearance(self, **data): self.applyData(self.context, **data) self.context._p_changed = True #if self.dataNotComplete(): # self.flash(self.dataNotComplete()) # return self.flash('Clearance form has been saved.') self.redirect(self.url(self.context,'request_clearance')) return class RequestClearancePage(WAeUPPage): grok.context(IStudent) grok.name('request_clearance') grok.require('waeup.handleStudent') grok.template('enterpin') title = 'Request clearance' label = 'Request clearance' notice = 'Enter the CLR access code used for starting clearance.' ac_prefix = 'CLR' pnav = 4 buttonname = 'Request clearance now' def update(self, SUBMIT=None): self.ac_series = self.request.form.get('ac_series', None) self.ac_number = self.request.form.get('ac_number', None) if SUBMIT is None: return pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number) if self.context.clr_code != pin: self.flash("This isn't your CLR access code.") return state = IWorkflowState(self.context).getState() # This shouldn't happen, but the application officer # might have forgotten to lock the form after changing the state if state != CLEARANCE: self.flash('This form cannot be submitted. Wrong state!') return IWorkflowInfo(self.context).fireTransition('request_clearance') self.flash('Clearance has been requested.') self.redirect(self.url(self.context)) return class CourseRegistrationStartActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyCourse) grok.view(StudyCourseDisplayFormPage) grok.require('waeup.handleStudent') icon = 'actionicon_start.png' text = 'Start course registration' target = 'start_course_registration' @property def target_url(self): if not self.context.getStudent().state in (CLEARED,RETURNING): return '' return self.view.url(self.view.context, self.target) class StartCourseRegistrationPage(WAeUPPage): grok.context(IStudentStudyCourse) grok.name('start_course_registration') grok.require('waeup.handleStudent') grok.template('enterpin') title = 'Start course registration' label = 'Start course registration' ac_prefix = 'SFE' notice = '' pnav = 4 buttonname = 'Start course registration now' def update(self, SUBMIT=None): if not self.context.getStudent().state in (CLEARED,RETURNING): self.flash("Wrong state.") self.redirect(self.url(self.context)) return self.ac_series = self.request.form.get('ac_series', None) self.ac_number = self.request.form.get('ac_number', None) if SUBMIT is None: return pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number) code = get_access_code(pin) if not code: self.flash('Activation code is invalid.') return # Mark pin as used (this also fires a pin related transition) # and fire transition start_clearance if code.state == USED: self.flash('Activation code has already been used.') return else: comment = u"AC invalidated for %s" % self.context.getStudent().student_id # Here we know that the ac is in state initialized so we do not # expect an exception, but the owner might be different if not invalidate_accesscode( pin,comment,self.context.getStudent().student_id): self.flash('You are not the owner of this access code.') return if self.context.getStudent().state == CLEARED: IWorkflowInfo(self.context.getStudent()).fireTransition( 'pay_first_school_fee') elif self.context.getStudent().state == RETURNING: IWorkflowInfo(self.context.getStudent()).fireTransition( 'pay_school_fee') self.flash('Course registration has been started.') self.redirect(self.url(self.context)) return class AddStudyLevelActionButton(AddActionButton): grok.order(1) grok.context(IStudentStudyCourse) grok.view(StudyCourseDisplayFormPage) grok.require('waeup.handleStudent') text = 'Add course list' target = 'add' @property def target_url(self): student = self.view.context.getStudent() condition1 = student.state != 'school fee paid' condition2 = str(student['studycourse'].current_level) in \ self.view.context.keys() if condition1 or condition2: return '' return self.view.url(self.view.context, self.target) class AddStudyLevelFormPage(WAeUPEditFormPage): """ Page for students to add current study levels """ grok.context(IStudentStudyCourse) grok.name('add') grok.require('waeup.handleStudent') grok.template('studyleveladdpage') form_fields = grok.AutoFields(IStudentStudyCourse) title = 'Study Course' pnav = 4 @property def label(self): studylevelsource = StudyLevelSource().factory code = self.context.current_level title = studylevelsource.getTitle(self.context, code) return 'Add current level %s' % title def emitLockMessage(self): self.flash('The requested form is locked (read-only).') self.redirect(self.url(self.context)) return def update(self): if self.context.getStudent().state != 'school fee paid': self.emitLockMessage() return super(AddStudyLevelFormPage, self).update() return @grok.action('Create course list now') def addStudyLevel(self, **data): studylevel = StudentStudyLevel() studylevel.level = self.context.current_level studylevel.level_session = self.context.current_session try: self.context.addStudentStudyLevel( self.context.certificate,studylevel) except KeyError: self.flash('This level exists.') self.redirect(self.url(self.context)) return class StudyLevelEditActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyLevel) grok.view(StudyLevelDisplayFormPage) grok.require('waeup.handleStudent') text = 'Add and remove courses' target = 'edit' @property def target_url(self): student = self.view.context.getStudent() condition1 = student.state != 'school fee paid' condition2 = student[ 'studycourse'].current_level != self.view.context.level if condition1 or condition2: return '' return self.view.url(self.view.context, self.target) class StudyLevelEditFormPage(WAeUPEditFormPage): """ Page to edit the student study level data by students """ grok.context(IStudentStudyLevel) grok.name('edit') grok.require('waeup.handleStudent') grok.template('studyleveleditpage') form_fields = grok.AutoFields(IStudentStudyLevel).omit( 'level_session', 'level_verdict') pnav = 4 def update(self): super(StudyLevelEditFormPage, self).update() # tabs.need() datatable.need() return @property def title(self): return 'Study Level %s' % self.context.level_title @property def label(self): return 'Add and remove course tickets of study level %s' % self.context.level_title @property def total_credits(self): total_credits = 0 for key, val in self.context.items(): total_credits += val.credits return total_credits @grok.action('Add course ticket') def addCourseTicket(self, **data): self.redirect(self.url(self.context, 'ctadd')) @grok.action('Remove selected tickets') def delCourseTicket(self, **data): form = self.request.form if form.has_key('val_id'): child_id = form['val_id'] else: self.flash('No ticket selected.') self.redirect(self.url(self.context, '@@edit')) return if not isinstance(child_id, list): child_id = [child_id] deleted = [] for id in child_id: # Students are not allowed to remove core tickets if not self.context[id].core_or_elective: try: del self.context[id] deleted.append(id) except: self.flash('Could not delete %s: %s: %s' % ( id, sys.exc_info()[0], sys.exc_info()[1])) if len(deleted): self.flash('Successfully removed: %s' % ', '.join(deleted)) self.redirect(self.url(self.context, u'@@edit')) return @grok.action('Register course list') def register_courses(self, **data): state = IWorkflowState(self.context.getStudent()).getState() IWorkflowInfo(self.context.getStudent()).fireTransition('register_courses') self.flash('Course list has been registered.') self.redirect(self.url(self.context)) return class CourseTicketAddFormPage2(CourseTicketAddFormPage): """Add a course ticket by student. """ grok.name('ctadd') grok.require('waeup.handleStudent') form_fields = grok.AutoFields(ICourseTicketAdd).omit( 'grade', 'score', 'core_or_elective', 'automatic') @grok.action('Add course ticket') def addCourseTicket(self, **data): ticket = CourseTicket() course = data['course'] ticket.automatic = False ticket.code = course.code ticket.title = course.title ticket.faculty = course.__parent__.__parent__.__parent__.title ticket.department = course.__parent__.__parent__.title ticket.credits = course.credits ticket.passmark = course.passmark ticket.semester = course.semester try: self.context.addCourseTicket(ticket) except KeyError: self.flash('The ticket exists.') return self.flash('Successfully added %s.' % ticket.code) self.redirect(self.url(self.context, u'@@edit')) return