## 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 datetime import datetime from zope.formlib.widget import CustomWidgetFactory from zope.formlib.form import setUpEditWidgets from zope.securitypolicy.interfaces import IPrincipalRoleManager from zope.traversing.browser import absoluteURL from zope.component import ( createObject,) from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState from reportlab.pdfgen import canvas from reportlab.lib.units import cm from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet from reportlab.platypus import (Frame, Paragraph, Image, Table, Spacer) from reportlab.platypus.tables import TableStyle from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code 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.layout import NullValidator from waeup.sirp.browser.pages import add_local_role, del_local_roles from waeup.sirp.browser.resources import datepicker, tabs, datatable from waeup.sirp.browser.viewlets import ( ManageActionButton, PrimaryNavTab, AddActionButton, ActionButton, PlainActionButton, ) from waeup.sirp.image.browser.widget import ( ThumbnailWidget, EncodingImageFileWidget, ) from waeup.sirp.image.image import createWAeUPImageFile from waeup.sirp.interfaces import ( IWAeUPObject, ILocalRolesAssignable, IUserAccount, ) from waeup.sirp.permissions import get_users_with_local_roles from waeup.sirp.university.interfaces import ICertificate from waeup.sirp.widgets.datewidget import ( FriendlyDateWidget, FriendlyDateDisplayWidget) from waeup.sirp.widgets.restwidget import ReSTDisplayWidget from waeup.sirp.widgets.objectwidget import ( WAeUPObjectWidget, WAeUPObjectDisplayWidget) from waeup.sirp.students.interfaces import ( IStudentsContainer, IStudent, IStudentClearance, IStudentPersonal, IStudentBase, IStudentStudyCourse, IStudentPayments, IStudentAccommodation, IStudentNavigation, IStudentBaseEdit, IStudentClearanceEdit, ) from waeup.sirp.students.student import Student from waeup.sirp.students.catalog import search from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code from waeup.sirp.accesscodes.workflow import USED class StudentsTab(PrimaryNavTab): """Students tab in primary navigation. """ grok.context(IWAeUPObject) grok.order(3) 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 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(IStudentPayments) title = u'Payments' class AccommodationBreadcrumb(Breadcrumb): """A breadcrumb for the student accommodation folder. """ grok.context(IStudentAccommodation) title = u'Accommodation' 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 SetPassword(WAeUPPage): grok.context(IWAeUPObject) grok.name('setpassword') grok.require('waeup.Public') title = '' label = 'Set password for first-time login' acprefix = 'PWD' pnav = 4 def update(self, SUBMIT=None): self.reg_number = self.request.form.get('form.reg_number', None) # We must not use form.ac_series and form.ac_number in forms since these # are interpreted by applicant credentials self.acseries = self.request.form.get('form.acseries', None) self.acnumber = self.request.form.get('form.acnumber', 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 self.student_id = hitlist[0].student_id student_pw = hitlist[0].context.password if student_pw: self.flash('Password has already been set. Please request a password reset.') return pin = '%s-%s-%s' % (self.acprefix,self.acseries,self.acnumber) code = get_access_code(pin) if not code: self.flash('Access code is invalid.') 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" # 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(hitlist[0].context).setPassword(self.acnumber) 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() 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.name @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 ob_class = self.__implemented__.__name__.replace('waeup.sirp.','') if form.has_key('password') and form['password']: if form['password'] != form['control_password']: self.flash('Passwords do not match.') return IUserAccount(self.context).setPassword(form['password']) self.context.loggerInfo(ob_class, 'password changed') changed_fields = self.applyData(self.context, **data) changed_fields = changed_fields.values() fields_string = '+'.join(' + '.join(str(i) for i in b) for b in 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: self.context.loggerInfo(ob_class, '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.name 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): changed_fields = self.applyData(self.context, **data) changed_fields = changed_fields.values() fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields) self.context._p_changed = True form = self.request.form self.flash('Form has been saved.') ob_class = self.__implemented__.__name__.replace('waeup.sirp.','') if fields_string: self.context.loggerInfo(ob_class, 'saved: % s' % fields_string) 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.name 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 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__.name class StudyCourseManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyCourse) grok.view(StudyCourseDisplayFormPage) grok.require('waeup.manageStudents') text = 'Manage' target = 'edit' class StudyCourseManageFormPage(WAeUPEditFormPage): """ Page to edit the student study course data """ grok.context(IStudentStudyCourse) grok.name('edit') grok.require('waeup.manageStudents') form_fields = grok.AutoFields(IStudentStudyCourse) title = 'Study Course' label = 'Manage study course' pnav = 4 class PaymentsDisplayFormPage(WAeUPDisplayFormPage): """ Page to display the student payments """ grok.context(IStudentPayments) grok.name('index') grok.require('waeup.viewStudent') form_fields = grok.AutoFields(IStudentPayments) #grok.template('paymentspage') title = 'Payments' pnav = 4 @property def label(self): return '%s: Payments' % self.context.__parent__.name class AccommodationDisplayFormPage(WAeUPDisplayFormPage): """ Page to display the student accommodation data """ grok.context(IStudentAccommodation) grok.name('index') 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__.name 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.name # 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 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') grok.template('baseeditpage') label = 'Change password' title = 'Base Data' pnav = 4 def update(self): datepicker.need() # Enable jQuery datepicker in date fields. super(StudentBaseEditFormPage, self).update() self.wf_info = IWorkflowInfo(self.context) return @grok.action('Save') def save(self, **data): form = self.request.form ob_class = self.__implemented__.__name__.replace('waeup.sirp.','') if form.has_key('password') and form['password']: if form['password'] != form['control_password']: self.flash('Passwords do not match.') return IUserAccount(self.context).setPassword(form['password']) self.context.loggerInfo(ob_class, 'password changed') changed_fields = self.applyData(self.context, **data) changed_fields = changed_fields.values() fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields) self.context._p_changed = True self.flash('Form has been saved.') if fields_string: self.context.loggerInfo(ob_class, 'saved: % s' % fields_string) return class StudentClearanceEditActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.handleStudent') text = 'Edit and submit' target = 'cedit' 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') #grok.template('clearanceeditpage') label = 'Edit clerance data' title = 'Clearance Data' pnav = 4 form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')