## 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 grok from time import time from datetime import date, datetime 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 from waeup.sirp.students.utils import getAccommodationDetails from waeup.sirp.browser.resources import toggleall from waeup.sirp.authentication import get_principal_role_manager 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' 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 (payment.amount_auth, payment.p_item, payment.p_session, payment.surcharge_1, payment.surcharge_2, payment.surcharge_3, error) = self.getPaymentDetails( data['p_category'],self.context.__parent__) if error: self.flash(error) self.redirect(self.url(self.context)) return 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.__parent__.__parent__.fullname,self.context.p_id) 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 self.flash('Valid callback received.') return def render(self): self.redirect(self.url(self.context, '@@index')) return # 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 the bed tickets """ grok.context(IStudentAccommodation) grok.name('index') grok.require('waeup.handleStudent') form_fields = grok.AutoFields(IStudentAccommodation) grok.template('accommodationmanagepage') title = 'Accommodation' pnav = 4 def unremovable(self): prm = get_principal_role_manager() roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)] return ('waeup.Student' in roles) 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 # We need an event handler which releases the bed space too. @grok.action('Remove selected tickets') def delBedTicket(self, **data): 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: # Students are not allowed to remove bed tickets if not self.unremovable(): 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 bed ticket') def addBedTicket(self, **data): self.redirect(self.url(self.context, '@@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) def update(self, SUBMIT=None): (booking_session, booking_fee, maint_fee, allowed_states, error) = self.getAccommodationDetails(self.context.__parent__) if not self.context.getStudent().state in allowed_states: 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) 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 bedticket = createObject(u'waeup.BedTicket') bedticket.booking_code = pin bedticket.bed = u'Test Bed' self.context['test'] = bedticket self.flash('Bed ticket created.') self.redirect(self.url(self.context)) 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