## $Id: browser.py 7276 2011-12-06 07:34:26Z 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 ## """UI components for basic applicants and related components. """ import os import sys import grok from time import time from datetime import datetime from zope.component import getUtility, createObject from zope.formlib.form import setUpEditWidgets 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.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 from waeup.sirp.interfaces import ( IWAeUPObject, ILocalRolesAssignable, IExtFileStore, IFileStoreNameChooser, IPasswordValidator, IUserAccount) from waeup.sirp.permissions import get_users_with_local_roles from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH from waeup.sirp.university.interfaces import ICertificate from waeup.sirp.utils.helpers import string_from_bytes, file_size from waeup.sirp.widgets.datewidget import ( FriendlyDateWidget, FriendlyDateDisplayWidget, FriendlyDatetimeDisplayWidget) from waeup.sirp.widgets.restwidget import ReSTDisplayWidget from waeup.sirp.applicants.interfaces import ( IApplicant, IApplicantEdit, IApplicantsRoot, IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab, MAX_UPLOAD_SIZE, IApplicantOnlinePayment, ) from waeup.sirp.applicants.workflow import INITIALIZED, STARTED, PAID from waeup.sirp.students.viewlets import PrimaryStudentNavTab from waeup.sirp.students.interfaces import IStudentsUtils grok.context(IWAeUPObject) # Make IWAeUPObject the default context class ApplicantsRootPage(WAeUPPage): grok.context(IApplicantsRoot) grok.name('index') grok.require('waeup.Public') title = 'Applicants' label = 'Application Section' pnav = 3 def update(self): super(ApplicantsRootPage, self).update() datatable.need() return class ManageApplicantsRootActionButton(ManageActionButton): grok.context(IApplicantsRoot) grok.view(ApplicantsRootPage) grok.require('waeup.manageApplication') text = 'Manage application section' class ApplicantsRootManageFormPage(WAeUPEditFormPage): grok.context(IApplicantsRoot) grok.name('manage') grok.template('applicantsrootmanagepage') title = 'Applicants' label = 'Manage application section' pnav = 3 grok.require('waeup.manageApplication') taboneactions = ['Add applicants container', 'Remove selected','Cancel'] tabtwoactions1 = ['Remove selected local roles'] tabtwoactions2 = ['Add local role'] subunits = 'Applicants Containers' def update(self): tabs.need() datatable.need() return super(ApplicantsRootManageFormPage, self).update() def getLocalRoles(self): roles = ILocalRolesAssignable(self.context) return roles() def getUsers(self): """Get a list of all users. """ for key, val in grok.getSite()['users'].items(): url = self.url(val) yield(dict(url=url, name=key, val=val)) def getUsersWithLocalRoles(self): return get_users_with_local_roles(self.context) # ToDo: Show warning message before deletion @grok.action('Remove selected') def delApplicantsContainers(self, **data): form = self.request.form child_id = form['val_id'] 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, '@@manage')+'#tab-1') return @grok.action('Add applicants container', validator=NullValidator) def addApplicantsContainer(self, **data): self.redirect(self.url(self.context, '@@add')) return @grok.action('Cancel', validator=NullValidator) def cancel(self, **data): self.redirect(self.url(self.context)) return @grok.action('Add local role', validator=NullValidator) def addLocalRole(self, **data): return add_local_role(self,2, **data) @grok.action('Remove selected local roles') def delLocalRoles(self, **data): return del_local_roles(self,2,**data) class ApplicantsContainerAddFormPage(WAeUPAddFormPage): grok.context(IApplicantsRoot) grok.require('waeup.manageApplication') grok.name('add') grok.template('applicantscontaineraddpage') title = 'Applicants' label = 'Add applicants container' pnav = 3 form_fields = grok.AutoFields( IApplicantsContainerAdd).omit('code').omit('title') form_fields['startdate'].custom_widget = FriendlyDateWidget('le') form_fields['enddate'].custom_widget = FriendlyDateWidget('le') def update(self): datepicker.need() # Enable jQuery datepicker in date fields. return super(ApplicantsContainerAddFormPage, self).update() @grok.action('Add applicants container') def addApplicantsContainer(self, **data): year = data['year'] code = u'%s%s' % (data['prefix'], year) prefix = application_types_vocab.getTerm(data['prefix']) title = u'%s %s/%s' % (prefix.title, year, year + 1) if code in self.context.keys(): self.flash( 'An applicants container for the same application ' 'type and entrance year exists already in the database.') return # Add new applicants container... provider = data['provider'][1] container = provider.factory() self.applyData(container, **data) container.code = code container.title = title self.context[code] = container self.flash('Added: "%s".' % code) self.redirect(self.url(self.context, u'@@manage')+'#tab-1') return @grok.action('Cancel', validator=NullValidator) def cancel(self, **data): self.redirect(self.url(self.context, '@@manage') + '#tab-1') class ApplicantsRootBreadcrumb(Breadcrumb): """A breadcrumb for applicantsroot. """ grok.context(IApplicantsRoot) title = u'Applicants' class ApplicantsContainerBreadcrumb(Breadcrumb): """A breadcrumb for applicantscontainers. """ grok.context(IApplicantsContainer) class ApplicantBreadcrumb(Breadcrumb): """A breadcrumb for applicants. """ grok.context(IApplicant) @property def title(self): """Get a title for a context. """ return self.context.application_number class OnlinePaymentBreadcrumb(Breadcrumb): """A breadcrumb for payments. """ grok.context(IApplicantOnlinePayment) @property def title(self): return self.context.p_id class ApplicantsAuthTab(PrimaryNavTab): """Applicants tab in primary navigation. """ grok.context(IWAeUPObject) grok.order(3) grok.require('waeup.viewApplicantsTab') pnav = 3 tab_title = u'Applicants' @property def link_target(self): return self.view.application_url('applicants') class ApplicantsAnonTab(ApplicantsAuthTab): """Applicants tab in primary navigation. Display tab only for anonymous. Authenticated users can call the form from the user navigation bar. """ grok.require('waeup.Anonymous') tab_title = u'Application' # Also zope.manager has role Anonymous. # To avoid displaying this tab, we have to check the principal id too. @property def link_target(self): if self.request.principal.id == 'zope.anybody': return self.view.application_url('applicants') return class MyApplicationDataTab(PrimaryStudentNavTab): """MyData-tab in primary navigation. """ grok.order(3) grok.require('waeup.viewMyApplicationDataTab') pnav = 3 tab_title = u'My Data' @property def link_target(self): try: container, application_number = self.request.principal.id.split('_') except ValueError: return rel_link = '/applicants/%s/%s' % (container, application_number) return self.view.application_url() + rel_link class ApplicantsContainerPage(WAeUPDisplayFormPage): """The standard view for regular applicant containers. """ grok.context(IApplicantsContainer) grok.name('index') grok.require('waeup.Public') grok.template('applicantscontainerpage') pnav = 3 form_fields = grok.AutoFields(IApplicantsContainer).omit('title') form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le') form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le') form_fields['description'].custom_widget = ReSTDisplayWidget @property def title(self): return "Applicants Container: %s" % self.context.title @property def label(self): return self.context.title class ApplicantsContainerManageActionButton(ManageActionButton): grok.order(1) grok.context(IApplicantsContainer) grok.view(ApplicantsContainerPage) grok.require('waeup.manageApplication') text = 'Manage applicants container' #class ApplicantLoginActionButton(ManageActionButton): # grok.order(2) # grok.context(IApplicantsContainer) # grok.view(ApplicantsContainerPage) # grok.require('waeup.Anonymous') # icon = 'login.png' # text = 'Login for applicants' # target = 'login' class ApplicantsContainerManageFormPage(WAeUPEditFormPage): grok.context(IApplicantsContainer) grok.name('manage') grok.template('applicantscontainermanagepage') form_fields = grok.AutoFields(IApplicantsContainer).omit('title') taboneactions = ['Save','Cancel'] tabtwoactions = ['Add applicant', 'Remove selected','Cancel'] tabthreeactions1 = ['Remove selected local roles'] tabthreeactions2 = ['Add local role'] # Use friendlier date widget... form_fields['startdate'].custom_widget = FriendlyDateWidget('le') form_fields['enddate'].custom_widget = FriendlyDateWidget('le') grok.require('waeup.manageApplication') @property def title(self): return "Applicants Container: %s" % self.context.title @property def label(self): return 'Manage applicants container' pnav = 3 def update(self): datepicker.need() # Enable jQuery datepicker in date fields. tabs.need() datatable.need() # Enable jQurey datatables for contents listing return super(ApplicantsContainerManageFormPage, self).update() def getLocalRoles(self): roles = ILocalRolesAssignable(self.context) return roles() def getUsers(self): """Get a list of all users. """ for key, val in grok.getSite()['users'].items(): url = self.url(val) yield(dict(url=url, name=key, val=val)) def getUsersWithLocalRoles(self): return get_users_with_local_roles(self.context) @grok.action('Save') def apply(self, **data): self.applyData(self.context, **data) self.flash('Data saved.') return # ToDo: Show warning message before deletion @grok.action('Remove selected') def delApplicant(self, **data): form = self.request.form if form.has_key('val_id'): child_id = form['val_id'] else: self.flash('No applicant 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 @grok.action('Add applicant', validator=NullValidator) def addApplicant(self, **data): self.redirect(self.url(self.context, 'addapplicant')) return @grok.action('Cancel', validator=NullValidator) def cancel(self, **data): self.redirect(self.url(self.context)) return @grok.action('Add local role', validator=NullValidator) def addLocalRole(self, **data): return add_local_role(self,3, **data) @grok.action('Remove selected local roles') def delLocalRoles(self, **data): return del_local_roles(self,3,**data) class ApplicantAddFormPage(WAeUPAddFormPage): """Add-form to add an applicant. """ grok.context(IApplicantsContainer) grok.require('waeup.manageApplication') grok.name('addapplicant') #grok.template('applicantaddpage') form_fields = grok.AutoFields(IApplicant).select( 'firstname', 'middlenames', 'lastname', 'email', 'phone') title = 'Applicants' label = 'Add applicant' pnav = 3 @property def title(self): return "Applicants Container: %s" % self.context.title @grok.action('Create application record') def addApplicant(self, **data): applicant = createObject(u'waeup.Applicant') self.applyData(applicant, **data) self.context.addApplicant(applicant) self.flash('Applicant record created.') self.redirect(self.url(self.context[applicant.application_number], 'index')) return class ApplicantDisplayFormPage(WAeUPDisplayFormPage): grok.context(IApplicant) grok.name('index') grok.require('waeup.viewApplication') grok.template('applicantdisplaypage') form_fields = grok.AutoFields(IApplicant).omit( 'locked', 'course_admitted', 'password') form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le') label = 'Applicant' pnav = 3 def update(self): self.passport_url = self.url(self.context, 'passport.jpg') # Mark application as started if applicant logs in for the first time usertype = getattr(self.request.principal, 'user_type', None) if usertype == 'applicant' and \ IWorkflowState(self.context).getState() == INITIALIZED: IWorkflowInfo(self.context).fireTransition('start') return @property def hasPassword(self): if self.context.password: return 'set' return 'unset' @property def title(self): return 'Application Record %s' % self.context.application_number @property def label(self): container_title = self.context.__parent__.title return '%s Application Record %s' % ( container_title, self.context.application_number) def getCourseAdmitted(self): """Return link, title and code in html format to the certificate admitted. """ course_admitted = self.context.course_admitted if ICertificate.providedBy(course_admitted): url = self.url(course_admitted) title = course_admitted.title code = course_admitted.code return '%s - %s' %(url,code,title) return '' class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage): grok.context(IApplicant) grok.name('base') form_fields = grok.AutoFields(IApplicant).select( 'applicant_id', 'firstname', 'lastname','email', 'course1') class AcceptanceFeePaymentAddPage(grok.View): """ Page to add an online payment ticket """ grok.context(IApplicant) grok.name('addafp') grok.require('waeup.payApplicant') def update(self): p_category = 'acceptance' d = {} session = str(self.context.__parent__.year) try: academic_session = grok.getSite()['configuration'][session] except KeyError: self.flash('Session configuration object is not available.') return timestamp = "%d" % int(time()*1000) #order_id = "%s%s" % (student_id[1:],timestamp) for key in self.context.keys(): ticket = self.context[key] if ticket.p_state == 'paid': self.flash( 'This type of payment has already been made.') self.redirect(self.url(self.context)) return payment = createObject(u'waeup.ApplicantOnlinePayment') payment.p_id = "p%s" % timestamp payment.p_item = self.context.__parent__.title payment.p_year = self.context.__parent__.year payment.p_category = p_category payment.amount_auth = academic_session.acceptance_fee payment.surcharge_1 = academic_session.surcharge_1 payment.surcharge_2 = academic_session.surcharge_2 payment.surcharge_3 = academic_session.surcharge_3 self.context[payment.p_id] = payment self.flash('Payment ticket created.') return def render(self): usertype = getattr(self.request.principal, 'user_type', None) if usertype == 'applicant': self.redirect(self.url(self.context, '@@edit')) return self.redirect(self.url(self.context, '@@manage')) return class OnlinePaymentDisplayFormPage(WAeUPDisplayFormPage): """ Page to view an online payment ticket """ grok.context(IApplicantOnlinePayment) grok.name('index') grok.require('waeup.viewApplication') form_fields = grok.AutoFields(IApplicantOnlinePayment) form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le') pnav = 3 @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__.fullname,self.context.p_id) class PaymentReceiptActionButton(ManageActionButton): grok.order(1) grok.context(IApplicantOnlinePayment) grok.view(OnlinePaymentDisplayFormPage) grok.require('waeup.viewApplication') 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(IApplicantOnlinePayment) grok.view(OnlinePaymentDisplayFormPage) grok.require('waeup.payApplicant') icon = 'actionicon_call.png' text = 'Request callback' target = 'callback' @property def target_url(self): if self.context.p_state != 'unpaid': return '' return self.view.url(self.view.context, self.target) class OnlinePaymentCallbackPage(grok.View): """ Callback view """ grok.context(IApplicantOnlinePayment) grok.name('callback') grok.require('waeup.payApplicant') # 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): if self.context.p_state == 'paid': self.flash('This ticket has already been paid.') return 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() ob_class = self.__implemented__.__name__.replace('waeup.sirp.','') self.context.__parent__.loggerInfo( ob_class, 'valid callback: %s' % self.context.p_id) self.wf_info = IWorkflowInfo(self.context.__parent__) self.wf_info.fireTransition('pay') 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(IApplicantOnlinePayment) grok.name('payment_receipt.pdf') grok.require('waeup.viewApplication') form_fields = grok.AutoFields(IApplicantOnlinePayment) 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): if self.context.p_state != 'paid': self.flash('Ticket not yet paid.') self.redirect(self.url(self.context)) return applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__, self.request) students_utils = getUtility(IStudentsUtils) return students_utils.renderPDF(self,'Payment', 'payment_receipt.pdf', self.context.__parent__, applicantview) class PDFActionButton(ManageActionButton): grok.context(IApplicant) grok.require('waeup.viewApplication') icon = 'actionicon_pdf.png' text = 'Download application slip' target = 'application_slip.pdf' class ExportPDFPage(grok.View): """Deliver a PDF slip of the context. """ grok.context(IApplicant) grok.name('application_slip.pdf') grok.require('waeup.viewApplication') form_fields = grok.AutoFields(IApplicant).omit( 'locked', 'course_admitted') form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le') prefix = 'form' @property def label(self): container_title = self.context.__parent__.title return '%s Application Record %s' % ( container_title, self.context.application_number) def getCourseAdmitted(self): """Return title and code in html format to the certificate admitted. """ course_admitted = self.context.course_admitted if ICertificate.providedBy(course_admitted): title = course_admitted.title code = course_admitted.code return '%s - %s' %(code,title) return '' def setUpWidgets(self, ignore_request=False): self.adapters = {} self.widgets = setUpEditWidgets( self.form_fields, self.prefix, self.context, self.request, adapters=self.adapters, for_display=True, ignore_request=ignore_request ) def render(self): # To recall the table coordinate system: # (0,0),(-1,-1) = whole table # (0,0),(0,-1) = first column # (-1,0),(-1,-1) = last column # (0,0),(-1,0) = first row # (0,-1),(-1,-1) = last row SLIP_STYLE = TableStyle( [('VALIGN',(0,0),(-1,-1),'TOP')] ) pdf = canvas.Canvas('application_slip.pdf',pagesize=A4) pdf.setTitle(self.label) pdf.setSubject('Application') pdf.setAuthor('%s (%s)' % (self.request.principal.title, self.request.principal.id)) pdf.setCreator('WAeUP SIRP') width, height = A4 style = getSampleStyleSheet() pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm)) story = [] frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm)) header_title = getattr(grok.getSite(), 'name', u'Sample University') story.append(Paragraph(header_title, style["Heading1"])) frame_header.addFromList(story,pdf) story = [] frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm)) story.append(Paragraph(self.label, style["Heading2"])) story.append(Spacer(1, 18)) for msg in self.context.history.messages: f_msg = '%s' % msg story.append(Paragraph(f_msg, style["Normal"])) story.append(Spacer(1, 24)) # Setup table data data = [] # Insert passport photograph img = getUtility(IExtFileStore).getFileByContext(self.context) if img is None: img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb') doc_img = Image(img.name, width=4*cm, height=3*cm, kind='bound') data.append([doc_img]) data.append([Spacer(1, 18)]) # Render widget fields self.setUpWidgets() for widget in self.widgets: f_label = '%s:' % widget.label.strip() f_label = Paragraph(f_label, style["Normal"]) f_text = '%s' % widget() f_text = Paragraph(f_text, style["Normal"]) data.append([f_label,f_text]) f_label = 'Admitted Course of Study:' f_text = '%s' % self.getCourseAdmitted() f_label = Paragraph(f_label, style["Normal"]) f_text = Paragraph(f_text, style["Normal"]) data.append([f_label,f_text]) # Create table table = Table(data,style=SLIP_STYLE) story.append(table) frame_body.addFromList(story,pdf) story = [] frame_footer = Frame(1*cm,0,width-(2*cm),1*cm) timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S") f_text = '%s' % timestamp story.append(Paragraph(f_text, style["Normal"])) frame_footer.addFromList(story,pdf) self.response.setHeader( 'Content-Type', 'application/pdf') return pdf.getpdfdata() class ApplicantManageActionButton(ManageActionButton): grok.context(IApplicant) grok.view(ApplicantDisplayFormPage) grok.require('waeup.manageApplication') text = 'Manage application record' target = 'manage' class ApplicantEditActionButton(ManageActionButton): grok.context(IApplicant) grok.view(ApplicantDisplayFormPage) grok.require('waeup.handleApplication') text = 'Edit application record' target ='edit' @property def target_url(self): """Get a URL to the target... """ if self.context.locked: return return self.view.url(self.view.context, self.target) def handle_img_upload(upload, context, view): """Handle upload of applicant image. 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. """ size = file_size(upload) if size > MAX_UPLOAD_SIZE: view.flash('Uploaded image is too big!') return False dummy, ext = os.path.splitext(upload.filename) ext.lower() if ext != '.jpg': view.flash('jpg file extension expected.') return False upload.seek(0) # file pointer moved when determining size store = getUtility(IExtFileStore) file_id = IFileStoreNameChooser(context).chooseName() store.createFile(file_id, upload) return True class ApplicantManageFormPage(WAeUPEditFormPage): """A full edit view for applicant data. """ grok.context(IApplicant) grok.name('manage') grok.require('waeup.manageApplication') form_fields = grok.AutoFields(IApplicant) form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year') grok.template('applicanteditpage') manage_applications = True pnav = 3 display_actions = [['Save', 'Final Submit'], ['Add online payment ticket','Remove selected tickets']] def update(self): datepicker.need() # Enable jQuery datepicker in date fields. super(ApplicantManageFormPage, self).update() self.wf_info = IWorkflowInfo(self.context) self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE) self.passport_changed = None upload = self.request.form.get('form.passport', None) if upload: # We got a fresh upload self.passport_changed = handle_img_upload( upload, self.context, self) return @property def title(self): return 'Application Record %s' % self.context.application_number @property def label(self): container_title = self.context.__parent__.title return '%s Application Form %s' % ( container_title, self.context.application_number) 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: validator = getUtility(IPasswordValidator) errors = validator.validate_password(password, password_ctl) if errors: self.flash( ' '.join(errors)) return if self.passport_changed is False: # False is not None! return # error during image upload. Ignore other values changed_fields = self.applyData(self.context, **data) # Turn list of lists into single list if changed_fields: changed_fields = reduce(lambda x,y: x+y, changed_fields.values()) else: changed_fields = [] if self.passport_changed: changed_fields.append('passport') if password: # Now we know that the form has no errors and can set password ... IUserAccount(self.context).setPassword(password) changed_fields.append('password') fields_string = ' + '.join(changed_fields) trans_id = form.get('transition', None) if trans_id: self.wf_info.fireTransition(trans_id) 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 def unremovable(self, ticket): usertype = getattr(self.request.principal, 'user_type', None) if not usertype: return False return self.request.principal.user_type == 'applicant' and ticket.r_code # This method is also used by the ApplicantEditFormPage def delPaymentTickets(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: # Applicants 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)) ob_class = self.__implemented__.__name__.replace('waeup.sirp.','') self.context.loggerInfo(ob_class, 'removed: % s' % ', '.join(deleted)) return # We explicitely want the forms to be validated before payment tickets # can be created. If no validation is requested, use # 'validator=NullValidator' in the grok.action directive @grok.action('Add online payment ticket') def addPaymentTicket(self, **data): self.redirect(self.url(self.context, '@@addafp')) return @grok.action('Remove selected tickets') def removePaymentTickets(self, **data): self.delPaymentTickets(**data) self.redirect(self.url(self.context) + '/@@manage') return class ApplicantEditFormPage(ApplicantManageFormPage): """An applicant-centered edit view for applicant data. """ grok.context(IApplicantEdit) grok.name('edit') grok.require('waeup.handleApplication') form_fields = grok.AutoFields(IApplicantEdit).omit( 'locked', 'course_admitted', 'student_id', 'screening_score', 'applicant_id', 'reg_number' ) form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year') grok.template('applicanteditpage') manage_applications = False title = u'Your Application Form' @property def display_actions(self): state = IWorkflowState(self.context).getState() if state == INITIALIZED: actions = [[],[]] elif state == STARTED: actions = [['Save'], ['Add online payment ticket','Remove selected tickets']] elif state == PAID: actions = [['Save', 'Final Submit'], ['Remove selected tickets']] elif state == SUBMITTED: actions = [[],[]] return actions def emit_lock_message(self): self.flash('The requested form is locked (read-only).') self.redirect(self.url(self.context)) return def update(self): if self.context.locked: self.emit_lock_message() return datepicker.need() # Enable jQuery datepicker in date fields. super(ApplicantEditFormPage, self).update() return def dataNotComplete(self): store = getUtility(IExtFileStore) if not store.getFileByContext(self.context, attr=u'passport.jpg'): return 'No passport picture uploaded.' if not self.request.form.get('confirm_passport', False): return 'Passport picture confirmation box not ticked.' return False # We explicitely want the forms to be validated before payment tickets # can be created. If no validation is requested, use # 'validator=NullValidator' in the grok.action directive @grok.action('Add online payment ticket') def addPaymentTicket(self, **data): self.redirect(self.url(self.context, '@@addafp')) return @grok.action('Remove selected tickets') def removePaymentTickets(self, **data): self.delPaymentTickets(**data) self.redirect(self.url(self.context) + '/@@edit') return @grok.action('Save') def save(self, **data): #import pdb; pdb.set_trace() if self.passport_changed is False: # False is not None! return # error during image upload. Ignore other values self.applyData(self.context, **data) self.flash('Form has been saved.') return @grok.action('Final Submit') def finalsubmit(self, **data): if self.passport_changed is False: # False is not None! return # error during image upload. Ignore other values if self.dataNotComplete(): self.flash(self.dataNotComplete()) return self.applyData(self.context, **data) 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 != PAID: self.flash('This form cannot be submitted. Wrong state!') return IWorkflowInfo(self.context).fireTransition('submit') self.context.application_date = datetime.now() self.context.locked = True self.flash('Form has been submitted.') self.redirect(self.url(self.context)) return class ApplicantViewActionButton(ManageActionButton): grok.context(IApplicant) grok.view(ApplicantManageFormPage) grok.require('waeup.viewApplication') icon = 'actionicon_view.png' text = 'View application record' target = 'index' class PassportImage(grok.View): """Renders the passport image for applicants. """ grok.name('passport.jpg') grok.context(IApplicant) grok.require('waeup.viewApplication') def render(self): # A filename chooser turns a context into a filename suitable # for file storage. image = getUtility(IExtFileStore).getFileByContext(self.context) self.response.setHeader( 'Content-Type', 'image/jpeg') if image is None: # show placeholder image return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read() return image