## ## browser.py ## Login : ## Started on Sun Jun 27 11:03:10 2010 Uli Fouquet & Henrik Bettermann ## $Id$ ## ## Copyright (C) 2010 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 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 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, LeftSidebarLink ) from waeup.sirp.image.browser.widget import ( ThumbnailWidget, EncodingImageFileWidget, ) from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable 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.widgets.multilistwidget import ( MultiListWidget, MultiListDisplayWidget) from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data from waeup.sirp.applicants.interfaces import ( IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot, IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab ) from waeup.sirp.applicants.workflow import INITIALIZED, STARTED results_widget = CustomWidgetFactory( WAeUPObjectWidget, ResultEntry) results_display_widget = CustomWidgetFactory( WAeUPObjectDisplayWidget, ResultEntry) list_results_widget = CustomWidgetFactory( MultiListWidget, subwidget=results_widget) list_results_display_widget = CustomWidgetFactory( MultiListDisplayWidget, subwidget=results_display_widget) #TRANSITION_OBJECTS = create_workflow() #TRANSITION_DICT = dict([ # (transition_object.transition_id,transition_object.title) # for transition_object in TRANSITION_OBJECTS]) 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.manageApplications') 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.manageApplications') 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.manageApplications') 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'Application Section' 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.access_code class ApplicantsTab(PrimaryNavTab): """Applicants tab in primary navigation. """ grok.context(IWAeUPObject) grok.order(3) grok.require('waeup.Public') grok.template('primarynavtab') pnav = 3 tab_title = u'Applicants' @property def link_target(self): return self.view.application_url('applicants') 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.manageApplications') text = 'Manage applicants container' class LoginApplicantActionButton(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.manageApplications') @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 LoginApplicant(WAeUPPage): grok.context(IApplicantsContainer) grok.name('login') grok.require('waeup.Public') @property def title(self): return u"Applicant Login: %s" % self.context.title @property def label(self): return u'Login for applicants only' pnav = 3 @property def ac_prefix(self): return self.context.ac_prefix def update(self, SUBMIT=None): self.ac_series = self.request.form.get('form.ac_series', None) self.ac_number = self.request.form.get('form.ac_number', None) if SUBMIT is None: return if self.request.principal.id == 'zope.anybody': self.flash('Entered credentials are invalid.') return if not IApplicantPrincipal.providedBy(self.request.principal): # Don't care if user is already authenticated as non-applicant return # From here we handle an applicant (not an officer browsing) pin = self.request.principal.access_code # Mark pin as used (this also fires a pin related transition) if get_access_code(pin).state == USED: pass else: comment = u"AC invalidated" # Here we know that the ac is in state initialized so we do not # expect an exception invalidate_accesscode(pin,comment) if not pin in self.context.keys(): # Create applicant record applicant = Applicant() applicant.access_code = pin self.context[pin] = applicant role_manager = IPrincipalRoleManager(self.context[pin]) role_manager.assignRoleToPrincipal( 'waeup.local.ApplicationOwner', self.request.principal.id) # Assign current principal the PortalUser role role_manager = IPrincipalRoleManager(grok.getSite()) role_manager.assignRoleToPrincipal( 'waeup.PortalUser', self.request.principal.id) # Mark application as started if IWorkflowState(self.context[pin]).getState() is INITIALIZED: IWorkflowInfo(self.context[pin]).fireTransition('start') self.redirect(self.url(self.context[pin], 'edit')) return class ApplicantAddFormPage(WAeUPAddFormPage): """Add-form to add certificate to a department. """ grok.context(IApplicantsContainer) grok.require('waeup.manageApplications') grok.name('addapplicant') grok.template('applicantaddpage') title = 'Applicants' label = 'Add applicant' pnav = 3 @property def title(self): return "Applicants Container: %s" % self.context.title @property def ac_prefix(self): return self.context.ac_prefix @grok.action('Create application record') def addApplicant(self, **data): ac_series = self.request.form.get('form.ac_series', None) ac_number = self.request.form.get('form.ac_number', None) pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number) if not invalidate_accesscode(pin, comment=u"Invalidated by system"): self.flash('%s is not a valid access code.' % pin) self.redirect(self.url(self.context, '@@manage')+'#tab-2') return else: # Create applicant record applicant = Applicant() applicant.access_code = pin self.context[pin] = applicant self.redirect(self.url(self.context[pin], 'edit')) return class AccessCodeViewLink(LeftSidebarLink): grok.order(1) grok.require('waeup.Public') icon = 'actionicon_view.png' title = 'View Record' target = '/@@index' @property def url(self): if not IApplicantPrincipal.providedBy(self.request.principal): return '' access_code = getattr(self.request.principal,'access_code',None) if access_code: applicant_object = get_applicant_data(access_code) return absoluteURL(applicant_object, self.request) + self.target else: return '' class AccessCodeEditLink(AccessCodeViewLink): grok.order(2) grok.require('waeup.Public') icon = 'actionicon_modify.png' title = 'Edit Record' target = '/@@edit' @property def url(self): if not IApplicantPrincipal.providedBy(self.request.principal): return '' access_code = getattr(self.request.principal,'access_code',None) if access_code: applicant_object = get_applicant_data(access_code) if applicant_object.locked: return '' return absoluteURL(applicant_object, self.request) + self.target else: return '' class AccessCodeSlipLink(AccessCodeViewLink): grok.order(3) grok.require('waeup.Public') icon = 'actionicon_pdf.png' title = 'Download Slip' target = '/application_slip.pdf' class DisplayApplicant(WAeUPDisplayFormPage): grok.context(IApplicant) grok.name('index') grok.require('waeup.handleApplication') form_fields = grok.AutoFields(IApplicant).omit( 'locked').omit('course_admitted') #form_fields['fst_sit_results'].custom_widget = list_results_display_widget form_fields['passport'].custom_widget = ThumbnailWidget form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le') label = 'Applicant' grok.template('form_display') pnav = 3 @property def title(self): if self.request.principal.title == 'Applicant': return u'Your Application Record' return '%s' % self.context.access_code @property def label(self): container_title = self.context.__parent__.title return '%s Application Record' % container_title 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 PDFActionButton(ManageActionButton): grok.context(IApplicant) #grok.view(DisplayApplicant) grok.require('waeup.manageApplications') 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.handleApplication') form_fields = grok.AutoFields(IApplicant).omit( 'locked').omit('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' % container_title 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): # (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(HRFlowable()) 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)) self.setUpWidgets() data = [] for widget in self.widgets: f_label = '%s:' % widget.label.strip() f_label = Paragraph(f_label, style["Normal"]) if widget.name != 'form.passport': f_text = '%s' % widget() f_text = Paragraph(f_text, style["Normal"]) data.append([f_label,f_text]) else: filename = widget._data.file.name im = Image(filename,width=4*cm, height=3*cm,kind='bound') data.append([f_label,im]) 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]) 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(DisplayApplicant) grok.require('waeup.manageApplications') text = 'Manage application record' target = 'edit_full' class EditApplicantFull(WAeUPEditFormPage): """A full edit view for applicant data. """ grok.context(IApplicant) grok.name('edit_full') grok.require('waeup.manageApplications') form_fields = grok.AutoFields(IApplicant) form_fields['passport'].custom_widget = EncodingImageFileWidget form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year') grok.template('form_edit') manage_applications = True pnav = 3 def update(self): datepicker.need() # Enable jQuery datepicker in date fields. super(EditApplicantFull, self).update() self.wf_info = IWorkflowInfo(self.context) return @property def title(self): return self.context.access_code @property def label(self): container_title = self.context.__parent__.title return '%s Application Form' % container_title 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): 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 if form.has_key('transition') and form['transition']: transition_id = form['transition'] self.wf_info.fireTransition(transition_id) self.flash('Form has been saved.') ob_class = self.__implemented__.__name__.replace('waeup.sirp.','') self.context.loggerInfo(ob_class, 'saved: % s' % fields_string) return class EditApplicantStudent(EditApplicantFull): """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', ) form_fields['passport'].custom_widget = EncodingImageFileWidget form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year') grok.template('form_edit') manage_applications = False title = u'Your Application Form' 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.locked: self.emitLockMessage() return datepicker.need() # Enable jQuery datepicker in date fields. super(EditApplicantStudent, self).update() return def dataNotComplete(self): if not self.request.form.get('confirm_passport', False): return 'Passport confirmation box not ticked.' if len(self.errors) > 0: return 'Form has errors.' return False @grok.action('Save') def save(self, **data): if self.context.locked: self.emitLockMessage() return self.applyData(self.context, **data) self.context._p_changed = True self.flash('Form has been saved.') return @grok.action('Final Submit') def finalsubmit(self, **data): if self.context.locked: self.emitLockMessage() return self.applyData(self.context, **data) self.context._p_changed = True if self.dataNotComplete(): self.flash(self.dataNotComplete()) 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 != STARTED: 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(EditApplicantFull) grok.require('waeup.manageApplications') icon = 'actionicon_view.png' text = 'View application record' target = 'index'