## $Id: viewlets.py 7997 2012-03-28 16:49:18Z henrik $ ## ## 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 ## import os import grok from zope.component import getUtility from zope.interface import Interface from zope.i18n import translate from waeup.kofa.interfaces import ( IKofaObject, IExtFileStore, IFileStoreNameChooser, IKofaUtils) from waeup.kofa.interfaces import MessageFactory as _ from waeup.kofa.utils.helpers import string_from_bytes, file_size from waeup.kofa.browser import DEFAULT_IMAGE_PATH from waeup.kofa.browser.viewlets import ( PrimaryNavTab, ManageActionButton, AddActionButton) from waeup.kofa.browser.layout import default_primary_nav_template from waeup.kofa.students.workflow import (ADMITTED, PAID, CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED) from waeup.kofa.students.browser import ( StudentClearanceManageFormPage, write_log_message, StudentBaseManageFormPage, StudentFilesUploadPage, ExportPDFClearanceSlipPage, StudentsContainerPage, StudentsContainerManagePage, StudentBaseDisplayFormPage, StudentClearanceDisplayFormPage, StudentPersonalDisplayFormPage, StudyCourseDisplayFormPage, StudyLevelDisplayFormPage, CourseTicketDisplayFormPage, OnlinePaymentDisplayFormPage, AccommodationManageFormPage, BedTicketDisplayFormPage,) from waeup.kofa.students.interfaces import ( IStudentsContainer, IStudent, IStudentStudyCourse, IStudentAccommodation, IStudentStudyLevel, ICourseTicket, IStudentOnlinePayment, IBedTicket, ) from waeup.kofa.interfaces import MessageFactory as _ grok.context(IKofaObject) # Make IKofaObject the default context grok.templatedir('browser_templates') ALLOWED_FILE_EXTENSIONS = ('jpg', 'png', 'pdf', 'tif') class StudentManageSidebar(grok.ViewletManager): grok.name('left_studentmanage') class StudentManageLink(grok.Viewlet): """A link displayed in the student box which shows up for StudentNavigation objects. """ grok.baseclass() grok.viewletmanager(StudentManageSidebar) grok.context(IKofaObject) grok.view(Interface) grok.order(5) grok.require('waeup.viewStudent') link = 'index' text = _(u'Base Data') def render(self): url = self.view.url(self.context.getStudent(), self.link) # Here we know that the cookie has been set lang = self.request.cookies.get('kofa.language') text = translate(self.text, 'waeup.kofa', target_language=lang) return u'
  • %s
  • ' % ( url, text) class StudentManageApplicationLink(StudentManageLink): grok.order(1) link = 'application_slip' text = _(u'Application Slip') def render(self): slip = getUtility(IExtFileStore).getFileByContext( self.context.getStudent(), attr=self.link) if slip: url = self.view.url(self.context,self.link) return u'
  • %s
  • ' % ( url, self.text) return '' class StudentManageBaseLink(StudentManageLink): grok.order(2) link = 'index' text = _(u'Base Data') class StudentManageClearanceLink(StudentManageLink): grok.order(3) link = 'view_clearance' text = _(u'Clearance Data') class StudentManagePersonalLink(StudentManageLink): grok.order(4) link = 'view_personal' text = _(u'Personal Data') class StudentManageStudyCourseLink(StudentManageLink): grok.order(5) link = 'studycourse' text = _(u'Study Course') class StudentManagePaymentsLink(StudentManageLink): grok.order(6) grok.require('waeup.payStudent') link = 'payments' text = _(u'Payments') class StudentManageAccommodationLink(StudentManageLink): grok.order(7) grok.require('waeup.handleAccommodation') link = 'accommodation' text = _(u'Accommodation') class StudentManageHistoryLink(StudentManageLink): grok.order(8) link = 'history' text = _(u'History') class StudentsContainerManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudentsContainer) grok.view(StudentsContainerPage) grok.require('waeup.manageStudent') text = _('Manage student section') class StudentsContainerAddActionButton(AddActionButton): grok.order(1) grok.context(IStudentsContainer) grok.view(StudentsContainerManagePage) grok.require('waeup.manageStudent') text = _('Add student') target = 'addstudent' class ContactActionButton(ManageActionButton): grok.order(4) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.manageStudent') icon = 'actionicon_mail.png' text = _('Send email') target = 'contactstudent' class StudentBaseManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.manageStudent') text = _('Manage') target = 'manage_base' class StudentClearanceManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.manageStudent') text = _('Manage') target = 'edit_clearance' class StudentClearActionButton(ManageActionButton): grok.order(2) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.clearStudent') text = _('Clear student') target = 'clear' icon = 'actionicon_accept.png' @property def target_url(self): if self.context.state != REQUESTED: return '' return self.view.url(self.view.context, self.target) class StudentRejectClearanceActionButton(ManageActionButton): grok.order(3) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.clearStudent') text = _('Reject clearance') target = 'reject_clearance' icon = 'actionicon_reject.png' @property def target_url(self): if self.context.state not in (REQUESTED, CLEARED): return '' return self.view.url(self.view.context, self.target) class ClearanceSlipActionButton(ManageActionButton): grok.order(4) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.viewStudent') icon = 'actionicon_pdf.png' text = _('Download clearance slip') target = 'clearance.pdf' class StudentPersonalEditActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentPersonalDisplayFormPage) grok.require('waeup.viewStudent') text = _('Edit') target = 'edit_personal' class StudyCourseManageActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyCourse) grok.view(StudyCourseDisplayFormPage) grok.require('waeup.manageStudent') text = _('Manage') target = 'manage' class CourseRegistrationSlipActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyLevel) grok.view(StudyLevelDisplayFormPage) grok.require('waeup.viewStudent') icon = 'actionicon_pdf.png' text = _('Download course registration slip') target = 'course_registration.pdf' class StudyLevelManageActionButton(ManageActionButton): grok.order(2) grok.context(IStudentStudyLevel) grok.view(StudyLevelDisplayFormPage) grok.require('waeup.manageStudent') text = _('Manage') target = 'manage' class StudentValidateCoursesActionButton(ManageActionButton): grok.order(3) grok.context(IStudentStudyLevel) grok.view(StudyLevelDisplayFormPage) grok.require('waeup.validateStudent') text = _('Validate courses') target = 'validate_courses' icon = 'actionicon_accept.png' @property def target_url(self): if self.context.getStudent().state != REGISTERED or \ str(self.context.__parent__.current_level) != self.context.__name__: return '' return self.view.url(self.view.context, self.target) class StudentRejectCoursesActionButton(ManageActionButton): grok.order(4) grok.context(IStudentStudyLevel) grok.view(StudyLevelDisplayFormPage) grok.require('waeup.validateStudent') text = _('Reject courses') target = 'reject_courses' icon = 'actionicon_reject.png' @property def target_url(self): if self.context.getStudent().state not in (VALIDATED, REGISTERED) or \ str(self.context.__parent__.current_level) != self.context.__name__: return '' return self.view.url(self.view.context, self.target) class CourseTicketManageActionButton(ManageActionButton): grok.order(1) grok.context(ICourseTicket) grok.view(CourseTicketDisplayFormPage) grok.require('waeup.manageStudent') text = _('Manage') target = 'manage' #class OnlinePaymentManageActionButton(ManageActionButton): # grok.order(1) # grok.context(IStudentPaymentsContainer) # grok.view(PaymentsDisplayFormPage) # grok.require('waeup.manageStudent') # text = 'Manage payments' # target = 'manage' class PaymentReceiptActionButton(ManageActionButton): grok.order(1) grok.context(IStudentOnlinePayment) grok.view(OnlinePaymentDisplayFormPage) grok.require('waeup.viewStudent') icon = 'actionicon_pdf.png' text = _('Download payment receipt') target = 'payment_receipt.pdf' @property def target_url(self): if self.context.p_state != 'paid': return '' return self.view.url(self.view.context, self.target) class RequestCallbackActionButton(ManageActionButton): grok.order(2) grok.context(IStudentOnlinePayment) grok.view(OnlinePaymentDisplayFormPage) grok.require('waeup.payStudent') icon = 'actionicon_call.png' text = _('Request callback') target = 'simulate_callback' # This button must be neutralized # in the customization package. @property def target_url(self): if self.context.p_state == 'paid': return '' return self.view.url(self.view.context, self.target) class AddBedTicketActionButton(ManageActionButton): grok.order(1) grok.context(IStudentAccommodation) grok.view(AccommodationManageFormPage) grok.require('waeup.handleAccommodation') icon = 'actionicon_home.png' text = _('Book accommodation') target = 'add' class BedTicketSlipActionButton(ManageActionButton): grok.order(1) grok.context(IBedTicket) grok.view(BedTicketDisplayFormPage) grok.require('waeup.handleAccommodation') icon = 'actionicon_pdf.png' text = _('Download bed allocation slip') target = 'bed_allocation.pdf' class RelocateStudentActionButton(ManageActionButton): grok.order(2) grok.context(IBedTicket) grok.view(BedTicketDisplayFormPage) grok.require('waeup.manageHostels') icon = 'actionicon_reload.png' text = _('Relocate student') target = 'relocate' class StudentBaseActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.handleStudent') text = _('Edit') target = 'edit_base' class StudentPasswordActionButton(ManageActionButton): grok.order(2) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.handleStudent') icon = 'actionicon_key.png' text = _('Change password') target = 'change_password' class StudentPassportActionButton(ManageActionButton): grok.order(3) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.handleStudent') icon = 'actionicon_portrait.png' text = _('Change portrait') target = 'change_portrait' @property def target_url(self): if self.context.state != ADMITTED: return '' return self.view.url(self.view.context, self.target) class StudentClearanceStartActionButton(ManageActionButton): grok.order(1) grok.context(IStudent) grok.view(StudentClearanceDisplayFormPage) grok.require('waeup.handleStudent') icon = 'actionicon_start.gif' 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 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 CourseRegistrationStartActionButton(ManageActionButton): grok.order(1) grok.context(IStudentStudyCourse) grok.view(StudyCourseDisplayFormPage) grok.require('waeup.handleStudent') icon = 'actionicon_start.gif' 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 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 != 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 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 != 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 StudentsTab(PrimaryNavTab): """Students tab in primary navigation. """ grok.context(IKofaObject) grok.order(4) grok.require('waeup.viewStudentsTab') pnav = 4 tab_title = _(u'Students') @property def link_target(self): return self.view.application_url('students') class PrimaryStudentNavManager(grok.ViewletManager): """Viewlet manager for the primary navigation tab. """ grok.name('primary_nav_student') class PrimaryStudentNavTab(grok.Viewlet): """Base for primary student nav tabs. """ grok.baseclass() grok.viewletmanager(PrimaryStudentNavManager) template = default_primary_nav_template grok.order(1) grok.require('waeup.Authenticated') pnav = 0 tab_title = u'Some Text' @property def link_target(self): return self.view.application_url() @property def active(self): view_pnav = getattr(self.view, 'pnav', 0) if view_pnav == self.pnav: return 'active' return '' class MyStudentDataTab(PrimaryStudentNavTab): """MyData dropdown tab in primary navigation. """ grok.order(3) grok.require('waeup.viewMyStudentDataTab') grok.template('mydatadropdowntabs') pnav = 4 tab_title = _(u'My Data') @property def active(self): view_pnav = getattr(self.view, 'pnav', 0) if view_pnav == self.pnav: return 'active dropdown' return 'dropdown' @property def targets(self): student_url = self.view.application_url() + ( '/students/%s' % self.request.principal.id) #app_slip = getUtility(IExtFileStore).getFileByContext( # self.context.getStudent(), 'application_slip') targets = [] #if app_slip: # targets = [{'url':student_url + '/application_slip', 'title':'Application Slip'},] targets += [ {'url':student_url, 'title':'Base Data'}, {'url':student_url + '/view_clearance', 'title':_('Clearance Data')}, {'url':student_url + '/view_personal', 'title':_('Personal Data')}, {'url':student_url + '/studycourse', 'title':_('Study Course')}, {'url':student_url + '/payments', 'title':_('Payments')}, {'url':student_url + '/accommodation', 'title':_('Accommodation Data')}, {'url':student_url + '/history', 'title':_('History')}, ] return targets def handle_file_delete(context, view, download_name): """Handle deletion of student file. """ store = getUtility(IExtFileStore) store.deleteFileByContext(context, attr=download_name) write_log_message(view, 'deleted: %s' % download_name) view.flash(_('${a} deleted.', mapping = {'a':download_name})) return def handle_file_upload(upload, context, view, max_size, download_name=None): """Handle upload of student file. Returns `True` in case of success or `False`. Please note that file pointer passed in (`upload`) most probably points to end of file when leaving this function. """ # Check some file requirements first if upload.filename.count('.') == 0: view.flash(_('File name has no extension.')) return False if upload.filename.count('.') > 1: view.flash(_('File name contains more than one dot.')) return False basename, expected_ext = os.path.splitext(download_name) dummy, ext = os.path.splitext(upload.filename) ext.lower() if expected_ext: if ext != expected_ext: view.flash(_('${a} file extension expected.', mapping = {'a':expected_ext.replace('.','')})) return False else: if not ext.replace('.','') in ALLOWED_FILE_EXTENSIONS: view.flash( _('Only the following extension are allowed: ${a}', mapping = {'a':', '.join(ALLOWED_FILE_EXTENSIONS)})) return False download_name += ext size = file_size(upload) if size > max_size: view.flash(_('Uploaded file is too big.')) return False upload.seek(0) # file pointer moved when determining size store = getUtility(IExtFileStore) file_id = IFileStoreNameChooser(context).chooseName(attr=download_name) store.createFile(file_id, upload) write_log_message(view, 'uploaded: %s (%s)' % (download_name,upload.filename)) view.flash(_('File ${a} uploaded.', mapping = {'a':download_name})) return True class FileManager(grok.ViewletManager): """Viewlet manager for uploading files, preferably scanned images. """ grok.name('files') class FileDisplay(grok.Viewlet): """Base file display viewlet. """ grok.baseclass() grok.context(IStudent) grok.viewletmanager(FileManager) grok.view(StudentClearanceDisplayFormPage) grok.template('filedisplay') grok.order(1) grok.require('waeup.viewStudent') label = _(u'File') title = _(u'Scan') download_name = u'filename.jpg' @property def file_exists(self): image = getUtility(IExtFileStore).getFileByContext( self.context, attr=self.download_name) if image: return True else: return False class FileUpload(FileDisplay): """Base upload viewlet. """ grok.baseclass() grok.context(IStudent) grok.viewletmanager(FileManager) grok.view(StudentClearanceManageFormPage) grok.template('fileupload') grok.require('waeup.uploadStudentFile') tab_redirect = '?tab2' mus = 1024 * 150 upload_button =_('Upload new file') delete_button = _('Delete attachment') @property def input_name(self): return "%s" % self.__name__ def update(self): self.max_upload_size = string_from_bytes(self.mus) delete_button = self.request.form.get( 'delete_%s' % self.input_name, None) upload_button = self.request.form.get( 'upload_%s' % self.input_name, None) if delete_button: handle_file_delete( context=self.context, view=self.view, download_name=self.download_name) self.view.redirect( self.view.url( self.context, self.view.__name__) + self.tab_redirect) return if upload_button: upload = self.request.form.get(self.input_name, None) if upload: # We got a fresh upload handle_file_upload(upload, self.context, self.view, self.mus, self.download_name) self.view.redirect( self.view.url( self.context, self.view.__name__) + self.tab_redirect) else: self.view.flash(_('No local file selected.')) self.view.redirect( self.view.url( self.context, self.view.__name__) + self.tab_redirect) return class PassportDisplay(FileDisplay): """Passport display viewlet. """ grok.order(1) grok.context(IStudent) grok.view(StudentBaseDisplayFormPage) grok.require('waeup.viewStudent') grok.template('imagedisplay') label = _(u'Passport Picture') download_name = u'passport.jpg' class PassportUploadManage(FileUpload): """Passport upload viewlet for officers. """ grok.order(1) grok.context(IStudent) grok.view(StudentBaseManageFormPage) grok.require('waeup.manageStudent') grok.template('imageupload') label = _(u'Passport Picture (jpg only)') mus = 1024 * 50 download_name = u'passport.jpg' tab_redirect = '?tab2' class PassportUploadEdit(PassportUploadManage): """Passport upload viewlet for students. """ grok.view(StudentFilesUploadPage) grok.require('waeup.uploadStudentFile') class BirthCertificateDisplay(FileDisplay): """Birth Certificate display viewlet. """ grok.order(1) label = _(u'Birth Certificate') title = _(u'Birth Certificate Scan') download_name = u'birth_certificate' class BirthCertificateSlip(BirthCertificateDisplay): grok.view(ExportPDFClearanceSlipPage) class BirthCertificateUpload(FileUpload): """Birth Certificate upload viewlet. """ grok.order(1) label = _(u'Birth Certificate') title = _(u'Birth Certificate Scan') mus = 1024 * 150 download_name = u'birth_certificate' tab_redirect = '?tab2' class AcceptanceLetterDisplay(FileDisplay): """Acceptance Letter display viewlet. """ grok.order(1) label = _(u'Acceptance Letter') title = _(u'Acceptance Letter Scan') download_name = u'acceptance_letter' class AcceptanceLetterSlip(AcceptanceLetterDisplay): grok.view(ExportPDFClearanceSlipPage) class AcceptanceLetterUpload(FileUpload): """AcceptanceLetter upload viewlet. """ grok.order(2) label = _(u'Acceptance Letter') title = _(u'Acceptance Letter Scan') mus = 1024 * 150 download_name = u'acceptance_letter' tab_redirect = '?tab2' class Image(grok.View): """Renders images for students. """ grok.baseclass() grok.name('none.jpg') grok.context(IStudent) grok.require('waeup.viewStudent') download_name = u'none.jpg' def render(self): # A filename chooser turns a context into a filename suitable # for file storage. image = getUtility(IExtFileStore).getFileByContext( self.context, attr=self.download_name) if image is None: # show placeholder image self.response.setHeader('Content-Type', 'image/jpeg') return open(DEFAULT_IMAGE_PATH, 'rb').read() dummy,ext = os.path.splitext(image.name) if ext == '.jpg': self.response.setHeader('Content-Type', 'image/jpeg') elif ext == '.png': self.response.setHeader('Content-Type', 'image/png') elif ext == '.pdf': self.response.setHeader('Content-Type', 'application/pdf') elif ext == '.tif': self.response.setHeader('Content-Type', 'image/tiff') return image class Passport(Image): """Renders jpeg passport picture. """ grok.name('passport.jpg') download_name = u'passport.jpg' grok.context(IStudent) class ApplicationSlipImage(Image): """Renders application slip scan. """ grok.name('application_slip') download_name = u'application_slip' class BirthCertificateImage(Image): """Renders birth certificate scan. """ grok.name('birth_certificate') download_name = u'birth_certificate' class AcceptanceLetterImage(Image): """Renders acceptance letter scan. """ grok.name('acceptance_letter') download_name = u'acceptance_letter'