source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py @ 7147

Last change on this file since 7147 was 7145, checked in by Henrik Bettermann, 13 years ago

Change some function names according to the style guide:

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

  • Property svn:keywords set to Id
File size: 30.4 KB
RevLine 
[5273]1##
2## browser.py
3## Login : <uli@pu.smp.net>
[6153]4## Started on  Sun Jun 27 11:03:10 2010 Uli Fouquet & Henrik Bettermann
[5273]5## $Id: browser.py 7145 2011-11-19 17:56:03Z henrik $
[6078]6##
[6063]7## Copyright (C) 2010 Uli Fouquet & Henrik Bettermann
[5273]8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
[6078]12##
[5273]13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
[6078]17##
[5273]18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
[5824]22"""UI components for basic applicants and related components.
[5273]23"""
[7063]24import os
[6082]25import sys
[5273]26import grok
27
[6816]28from datetime import datetime, date
29from zope.authentication.interfaces import ILogout, IAuthentication
30from zope.component import getUtility
[6081]31from zope.formlib.widget import CustomWidgetFactory
[6358]32from zope.formlib.form import setUpEditWidgets
[5937]33from zope.securitypolicy.interfaces import IPrincipalRoleManager
[6153]34from zope.traversing.browser import absoluteURL
[6081]35
[6303]36from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[6357]37from reportlab.pdfgen import canvas
[6364]38from reportlab.lib.units import cm
[6390]39from reportlab.lib.pagesizes import A4
[6364]40from reportlab.lib.styles import getSampleStyleSheet
41from reportlab.platypus import (Frame, Paragraph, Image,
42    Table, Spacer)
43from reportlab.platypus.tables import TableStyle
[6357]44
[6469]45from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
46from waeup.sirp.accesscodes.workflow import USED
[5273]47from waeup.sirp.browser import (
[6321]48    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
[6081]49from waeup.sirp.browser.breadcrumbs import Breadcrumb
[6103]50from waeup.sirp.browser.layout import NullValidator
[6321]51from waeup.sirp.browser.pages import add_local_role, del_local_roles
[6013]52from waeup.sirp.browser.resources import datepicker, tabs, datatable
[6153]53from waeup.sirp.browser.viewlets import (
54    ManageActionButton, PrimaryNavTab, LeftSidebarLink
55    )
[7063]56from waeup.sirp.interfaces import (
57    IWAeUPObject, ILocalRolesAssignable, IExtFileStore, IFileStoreNameChooser)
[6321]58from waeup.sirp.permissions import get_users_with_local_roles
[7094]59from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH
[6254]60from waeup.sirp.university.interfaces import ICertificate
[7081]61from waeup.sirp.utils.helpers import string_from_bytes, file_size
[6054]62from waeup.sirp.widgets.datewidget import (
63    FriendlyDateWidget, FriendlyDateDisplayWidget)
[6084]64from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
[5303]65from waeup.sirp.widgets.objectwidget import (
[5301]66    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
[6153]67from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
[6081]68from waeup.sirp.applicants.interfaces import (
[6321]69    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
[7063]70    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
[7094]71    MAX_UPLOAD_SIZE,
[5686]72    )
[6353]73from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
[5320]74
[5273]75results_widget = CustomWidgetFactory(
[5301]76    WAeUPObjectWidget, ResultEntry)
[5273]77
78results_display_widget = CustomWidgetFactory(
[5301]79    WAeUPObjectDisplayWidget, ResultEntry)
[5273]80
[6067]81class ApplicantsRootPage(WAeUPPage):
[5822]82    grok.context(IApplicantsRoot)
83    grok.name('index')
[6153]84    grok.require('waeup.Public')
[5822]85    title = 'Applicants'
[6069]86    label = 'Application Section'
[5843]87    pnav = 3
[6012]88
89    def update(self):
[6067]90        super(ApplicantsRootPage, self).update()
[6012]91        datatable.need()
92        return
93
[5828]94class ManageApplicantsRootActionButton(ManageActionButton):
95    grok.context(IApplicantsRoot)
[6067]96    grok.view(ApplicantsRootPage)
[7136]97    grok.require('waeup.manageApplication')
[6069]98    text = 'Manage application section'
[5828]99
[6069]100class ApplicantsRootManageFormPage(WAeUPEditFormPage):
[5828]101    grok.context(IApplicantsRoot)
102    grok.name('manage')
[6107]103    grok.template('applicantsrootmanagepage')
[6069]104    title = 'Applicants'
105    label = 'Manage application section'
[5843]106    pnav = 3
[7136]107    grok.require('waeup.manageApplication')
[6069]108    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
[6184]109    tabtwoactions1 = ['Remove selected local roles']
110    tabtwoactions2 = ['Add local role']
[6069]111    subunits = 'Applicants Containers'
[6078]112
[6069]113    def update(self):
114        tabs.need()
[6108]115        datatable.need()
[6069]116        return super(ApplicantsRootManageFormPage, self).update()
[5828]117
[6184]118    def getLocalRoles(self):
119        roles = ILocalRolesAssignable(self.context)
120        return roles()
121
122    def getUsers(self):
123        """Get a list of all users.
124        """
125        for key, val in grok.getSite()['users'].items():
126            url = self.url(val)
127            yield(dict(url=url, name=key, val=val))
128
129    def getUsersWithLocalRoles(self):
130        return get_users_with_local_roles(self.context)
131
[6069]132    # ToDo: Show warning message before deletion
133    @grok.action('Remove selected')
134    def delApplicantsContainers(self, **data):
135        form = self.request.form
136        child_id = form['val_id']
137        if not isinstance(child_id, list):
138            child_id = [child_id]
139        deleted = []
140        for id in child_id:
141            try:
142                del self.context[id]
143                deleted.append(id)
144            except:
145                self.flash('Could not delete %s: %s: %s' % (
146                        id, sys.exc_info()[0], sys.exc_info()[1]))
147        if len(deleted):
148            self.flash('Successfully removed: %s' % ', '.join(deleted))
[6078]149        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
150        return
[5828]151
[6069]152    @grok.action('Add applicants container', validator=NullValidator)
153    def addApplicantsContainer(self, **data):
154        self.redirect(self.url(self.context, '@@add'))
[6078]155        return
156
[6069]157    @grok.action('Cancel', validator=NullValidator)
158    def cancel(self, **data):
159        self.redirect(self.url(self.context))
[6078]160        return
161
[6184]162    @grok.action('Add local role', validator=NullValidator)
163    def addLocalRole(self, **data):
164        return add_local_role(self,2, **data)
165
166    @grok.action('Remove selected local roles')
167    def delLocalRoles(self, **data):
168        return del_local_roles(self,2,**data)
169
[6069]170class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
[5822]171    grok.context(IApplicantsRoot)
[7136]172    grok.require('waeup.manageApplication')
[5822]173    grok.name('add')
[6107]174    grok.template('applicantscontaineraddpage')
[6069]175    title = 'Applicants'
176    label = 'Add applicants container'
[5843]177    pnav = 3
[6078]178
[6103]179    form_fields = grok.AutoFields(
180        IApplicantsContainerAdd).omit('code').omit('title')
[6083]181    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
182    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[6078]183
[6083]184    def update(self):
185        datepicker.need() # Enable jQuery datepicker in date fields.
186        return super(ApplicantsContainerAddFormPage, self).update()
187
[6069]188    @grok.action('Add applicants container')
189    def addApplicantsContainer(self, **data):
[6103]190        year = data['year']
191        code = u'%s%s' % (data['prefix'], year)
192        prefix = application_types_vocab.getTerm(data['prefix'])
193        title = u'%s %s/%s' % (prefix.title, year, year + 1)
[6087]194        if code in self.context.keys():
[6105]195            self.flash(
196                'An applicants container for the same application '
197                'type and entrance year exists already in the database.')
[5822]198            return
199        # Add new applicants container...
[6083]200        provider = data['provider'][1]
[5822]201        container = provider.factory()
[6069]202        self.applyData(container, **data)
[6087]203        container.code = code
204        container.title = title
205        self.context[code] = container
[6105]206        self.flash('Added: "%s".' % code)
[6069]207        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
[5822]208        return
[6078]209
[6103]210    @grok.action('Cancel', validator=NullValidator)
[6069]211    def cancel(self, **data):
[6103]212        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
[6078]213
[5845]214class ApplicantsRootBreadcrumb(Breadcrumb):
215    """A breadcrumb for applicantsroot.
216    """
217    grok.context(IApplicantsRoot)
[6654]218    title = u'Applicants'
[6078]219
[5845]220class ApplicantsContainerBreadcrumb(Breadcrumb):
221    """A breadcrumb for applicantscontainers.
222    """
223    grok.context(IApplicantsContainer)
[6319]224
[6153]225class ApplicantBreadcrumb(Breadcrumb):
226    """A breadcrumb for applicants.
227    """
228    grok.context(IApplicant)
[6319]229
[6153]230    @property
231    def title(self):
232        """Get a title for a context.
233        """
234        return self.context.access_code
[5828]235
236class ApplicantsTab(PrimaryNavTab):
[6153]237    """Applicants tab in primary navigation.
[5828]238    """
[6078]239
[5828]240    grok.context(IWAeUPObject)
241    grok.order(3)
[6336]242    grok.require('waeup.Public')
[5828]243    grok.template('primarynavtab')
244
[5843]245    pnav = 3
[5828]246    tab_title = u'Applicants'
247
248    @property
249    def link_target(self):
250        return self.view.application_url('applicants')
251
[6029]252class ApplicantsContainerPage(WAeUPDisplayFormPage):
[5830]253    """The standard view for regular applicant containers.
254    """
255    grok.context(IApplicantsContainer)
256    grok.name('index')
[6153]257    grok.require('waeup.Public')
[6029]258    grok.template('applicantscontainerpage')
[5850]259    pnav = 3
[6053]260
[6105]261    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
[6054]262    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
263    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
[6084]264    form_fields['description'].custom_widget = ReSTDisplayWidget
[6053]265
[5837]266    @property
267    def title(self):
[6087]268        return "Applicants Container: %s" % self.context.title
[5837]269
270    @property
271    def label(self):
[6087]272        return self.context.title
[5830]273
[6107]274class ApplicantsContainerManageActionButton(ManageActionButton):
[6336]275    grok.order(1)
[5832]276    grok.context(IApplicantsContainer)
277    grok.view(ApplicantsContainerPage)
[7136]278    grok.require('waeup.manageApplication')
[6070]279    text = 'Manage applicants container'
[5832]280
[6336]281class LoginApplicantActionButton(ManageActionButton):
282    grok.order(2)
283    grok.context(IApplicantsContainer)
284    grok.view(ApplicantsContainerPage)
285    grok.require('waeup.Anonymous')
286    icon = 'login.png'
287    text = 'Login for applicants'
288    target = 'login'
[5832]289
[6107]290class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
[5837]291    grok.context(IApplicantsContainer)
[5850]292    grok.name('manage')
[6107]293    grok.template('applicantscontainermanagepage')
[6105]294    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
295    taboneactions = ['Save','Cancel']
296    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
[6184]297    tabthreeactions1 = ['Remove selected local roles']
298    tabthreeactions2 = ['Add local role']
[5844]299    # Use friendlier date widget...
[6054]300    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
301    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[7136]302    grok.require('waeup.manageApplication')
[5850]303
304    @property
305    def title(self):
[6087]306        return "Applicants Container: %s" % self.context.title
[6078]307
[5850]308    @property
309    def label(self):
[6087]310        return 'Manage applicants container'
[5850]311
[5845]312    pnav = 3
[5837]313
314    def update(self):
[5850]315        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]316        tabs.need()
[6015]317        datatable.need()  # Enable jQurey datatables for contents listing
[6107]318        return super(ApplicantsContainerManageFormPage, self).update()
[5837]319
[6184]320    def getLocalRoles(self):
321        roles = ILocalRolesAssignable(self.context)
322        return roles()
323
324    def getUsers(self):
325        """Get a list of all users.
326        """
327        for key, val in grok.getSite()['users'].items():
328            url = self.url(val)
329            yield(dict(url=url, name=key, val=val))
330
331    def getUsersWithLocalRoles(self):
332        return get_users_with_local_roles(self.context)
333
[5850]334    @grok.action('Save')
[5837]335    def apply(self, **data):
336        self.applyData(self.context, **data)
337        self.flash('Data saved.')
338        return
[6078]339
[6105]340    # ToDo: Show warning message before deletion
341    @grok.action('Remove selected')
342    def delApplicant(self, **data):
[6189]343        form = self.request.form
344        if form.has_key('val_id'):
345            child_id = form['val_id']
346        else:
347            self.flash('No applicant selected!')
348            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
349            return
350        if not isinstance(child_id, list):
351            child_id = [child_id]
352        deleted = []
353        for id in child_id:
354            try:
355                del self.context[id]
356                deleted.append(id)
357            except:
358                self.flash('Could not delete %s: %s: %s' % (
359                        id, sys.exc_info()[0], sys.exc_info()[1]))
360        if len(deleted):
361            self.flash('Successfully removed: %s' % ', '.join(deleted))
362        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
363        return
[6105]364
365    @grok.action('Add applicant', validator=NullValidator)
366    def addApplicant(self, **data):
[6327]367        self.redirect(self.url(self.context, 'addapplicant'))
368        return
[6105]369
370    @grok.action('Cancel', validator=NullValidator)
[5837]371    def cancel(self, **data):
372        self.redirect(self.url(self.context))
373        return
[5886]374
[6184]375    @grok.action('Add local role', validator=NullValidator)
376    def addLocalRole(self, **data):
377        return add_local_role(self,3, **data)
[6105]378
[6184]379    @grok.action('Remove selected local roles')
380    def delLocalRoles(self, **data):
381        return del_local_roles(self,3,**data)
382
[5886]383class LoginApplicant(WAeUPPage):
384    grok.context(IApplicantsContainer)
385    grok.name('login')
[6153]386    grok.require('waeup.Public')
[5886]387
[6110]388    @property
389    def title(self):
390        return u"Applicant Login: %s" % self.context.title
[6078]391
[5886]392    @property
393    def label(self):
[6724]394        return u"Login for '%s' applicants only" % self.context.title
[5886]395
396    pnav = 3
[6319]397
[6110]398    @property
399    def ac_prefix(self):
400        return self.context.ac_prefix
[6078]401
[5896]402    def update(self, SUBMIT=None):
403        self.ac_series = self.request.form.get('form.ac_series', None)
404        self.ac_number = self.request.form.get('form.ac_number', None)
[5886]405        if SUBMIT is None:
406            return
[5894]407        if self.request.principal.id == 'zope.anybody':
[6105]408            self.flash('Entered credentials are invalid.')
[5886]409            return
[5894]410        if not IApplicantPrincipal.providedBy(self.request.principal):
411            # Don't care if user is already authenticated as non-applicant
412            return
[6377]413
414        # From here we handle an applicant (not an officer browsing)
[5905]415        pin = self.request.principal.access_code
[6375]416
[6816]417        # If application has not yet started,
418        # logout without marking AC as used
419        if self.context.startdate > date.today():
420            self.flash('Application has not yet started.')
421            auth = getUtility(IAuthentication)
422            ILogout(auth).logout(self.request)
423            self.redirect(self.url(self.context, 'login'))
424            return
425
426        # If application has ended and applicant record doesn't exist,
427        # logout without marking AC as used
[7063]428        if not pin in self.context.keys() and (
429            self.context.enddate < date.today()):
[6816]430            self.flash('Application has ended.')
431            auth = getUtility(IAuthentication)
432            ILogout(auth).logout(self.request)
433            self.redirect(self.url(self.context, 'login'))
434            return
435
436        # Mark AC as used (this also fires a pin related transition)
[7063]437        if get_access_code(pin).state != USED:
[6471]438            comment = u"AC invalidated"
[6469]439            # Here we know that the ac is in state initialized so we do not
440            # expect an exception
[6471]441            invalidate_accesscode(pin,comment)
[6375]442
[6377]443        if not pin in self.context.keys():
444            # Create applicant record
445            applicant = Applicant()
446            applicant.access_code = pin
447            self.context[pin] = applicant
[6359]448
[6377]449        role_manager = IPrincipalRoleManager(self.context[pin])
[6394]450        role_manager.assignRoleToPrincipal(
451            'waeup.local.ApplicationOwner', self.request.principal.id)
[6359]452
[6394]453        # Assign current principal the PortalUser role
[6397]454        role_manager = IPrincipalRoleManager(grok.getSite())
[6394]455        role_manager.assignRoleToPrincipal(
456            'waeup.PortalUser', self.request.principal.id)
[6377]457
458        # Mark application as started
459        if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
460            IWorkflowInfo(self.context[pin]).fireTransition('start')
461
[5937]462        self.redirect(self.url(self.context[pin], 'edit'))
[5886]463        return
[6319]464
[6327]465class ApplicantAddFormPage(WAeUPAddFormPage):
[6622]466    """Add-form to add an applicant.
[6327]467    """
468    grok.context(IApplicantsContainer)
[7136]469    grok.require('waeup.manageApplication')
[6327]470    grok.name('addapplicant')
471    grok.template('applicantaddpage')
472    title = 'Applicants'
473    label = 'Add applicant'
474    pnav = 3
475
476    @property
477    def title(self):
478        return "Applicants Container: %s" % self.context.title
479
480    @property
481    def ac_prefix(self):
482        return self.context.ac_prefix
483
484    @grok.action('Create application record')
485    def addApplicant(self, **data):
486        ac_series = self.request.form.get('form.ac_series', None)
487        ac_number = self.request.form.get('form.ac_number', None)
488        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
[6459]489        if not invalidate_accesscode(pin, comment=u"Invalidated by system"):
[6383]490            self.flash('%s is not a valid access code.' % pin)
491            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
492            return
493        else:
[6327]494            # Create applicant record
495            applicant = Applicant()
496            applicant.access_code = pin
497            self.context[pin] = applicant
498        self.redirect(self.url(self.context[pin], 'edit'))
499        return
500
[6396]501class AccessCodeViewLink(LeftSidebarLink):
[6153]502    grok.order(1)
503    grok.require('waeup.Public')
[6396]504    icon = 'actionicon_view.png'
505    title = 'View Record'
506    target = '/@@index'
[5886]507
[6396]508    @property
509    def url(self):
[6153]510        if not IApplicantPrincipal.providedBy(self.request.principal):
511            return ''
512        access_code = getattr(self.request.principal,'access_code',None)
513        if access_code:
514            applicant_object = get_applicant_data(access_code)
[6396]515            return absoluteURL(applicant_object, self.request) + self.target
[7063]516        return ''
[6153]517
[6396]518class AccessCodeEditLink(AccessCodeViewLink):
519    grok.order(2)
520    grok.require('waeup.Public')
521    icon = 'actionicon_modify.png'
522    title = 'Edit Record'
523    target = '/@@edit'
524
525    @property
526    def url(self):
527        if not IApplicantPrincipal.providedBy(self.request.principal):
528            return ''
529        access_code = getattr(self.request.principal,'access_code',None)
530        if access_code:
531            applicant_object = get_applicant_data(access_code)
532            if applicant_object.locked:
533                return ''
534            return absoluteURL(applicant_object, self.request) + self.target
[7063]535        return ''
[6396]536
537class AccessCodeSlipLink(AccessCodeViewLink):
538    grok.order(3)
539    grok.require('waeup.Public')
540    icon = 'actionicon_pdf.png'
541    title = 'Download Slip'
542    target = '/application_slip.pdf'
543
[5273]544class DisplayApplicant(WAeUPDisplayFormPage):
545    grok.context(IApplicant)
546    grok.name('index')
[7113]547    grok.require('waeup.viewApplication')
[6320]548    form_fields = grok.AutoFields(IApplicant).omit(
549        'locked').omit('course_admitted')
[6054]550    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[5273]551    label = 'Applicant'
[6254]552    grok.template('form_display')
[5843]553    pnav = 3
[5273]554
[7063]555    def update(self):
556        self.passport_url = self.url(self.context, 'passport.jpg')
557        return
558
[6196]559    @property
560    def title(self):
[6465]561        if self.request.principal.title == 'Applicant':
562            return u'Your Application Record'
[6196]563        return '%s' % self.context.access_code
564
565    @property
566    def label(self):
567        container_title = self.context.__parent__.title
568        return '%s Application Record' % container_title
569
[6254]570    def getCourseAdmitted(self):
[6355]571        """Return link, title and code in html format to the certificate
572           admitted.
[6351]573        """
[6254]574        course_admitted = self.context.course_admitted
575        if ICertificate.providedBy(course_admitted):
576            url = self.url(course_admitted)
577            title = course_admitted.title
578            code = course_admitted.code
[6366]579            return '<a href="%s">%s - %s</a>' %(url,code,title)
[6457]580        return ''
[6254]581
[6358]582class PDFActionButton(ManageActionButton):
583    grok.context(IApplicant)
[7136]584    grok.require('waeup.viewApplication')
[6358]585    icon = 'actionicon_pdf.png'
[6367]586    text = 'Download application slip'
[6358]587    target = 'application_slip.pdf'
588
589class ExportPDFPage(grok.View):
590    """Deliver a PDF slip of the context.
591    """
592    grok.context(IApplicant)
593    grok.name('application_slip.pdf')
[7136]594    grok.require('waeup.viewApplication')
[6358]595    form_fields = grok.AutoFields(IApplicant).omit(
596        'locked').omit('course_admitted')
597    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
598    prefix = 'form'
599
[6363]600    @property
601    def label(self):
602        container_title = self.context.__parent__.title
603        return '%s Application Record' % container_title
604
[6358]605    def getCourseAdmitted(self):
606        """Return title and code in html format to the certificate
607           admitted.
608        """
609        course_admitted = self.context.course_admitted
610        if ICertificate.providedBy(course_admitted):
611            title = course_admitted.title
612            code = course_admitted.code
[6365]613            return '%s - %s' %(code,title)
[6462]614        return ''
[6358]615
616    def setUpWidgets(self, ignore_request=False):
617        self.adapters = {}
618        self.widgets = setUpEditWidgets(
619            self.form_fields, self.prefix, self.context, self.request,
620            adapters=self.adapters, for_display=True,
621            ignore_request=ignore_request
622            )
623
624    def render(self):
[6364]625        SLIP_STYLE = TableStyle(
626            [('VALIGN',(0,0),(-1,-1),'TOP')]
627            )
[6358]628
629        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
[6364]630        pdf.setTitle(self.label)
631        pdf.setSubject('Application')
632        pdf.setAuthor('%s (%s)' % (self.request.principal.title,
633            self.request.principal.id))
634        pdf.setCreator('WAeUP SIRP')
[6358]635        width, height = A4
636        style = getSampleStyleSheet()
[6365]637        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
[6363]638
[6358]639        story = []
[6365]640        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
[6363]641        header_title = getattr(grok.getSite(), 'name', u'Sample University')
642        story.append(Paragraph(header_title, style["Heading1"]))
643        frame_header.addFromList(story,pdf)
644
645        story = []
[6365]646        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
[6364]647        story.append(Paragraph(self.label, style["Heading2"]))
648        story.append(Spacer(1, 18))
649        for msg in self.context.history.messages:
650            f_msg = '<font face="Courier" size=10>%s</font>' % msg
651            story.append(Paragraph(f_msg, style["Normal"]))
[6363]652        story.append(Spacer(1, 24))
[7063]653
[7068]654
[7063]655        # insert passport photograph
656        img = getUtility(IExtFileStore).getFileByContext(self.context)
657        if img is None:
[7089]658            img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7063]659        doc_img = Image(img.name, width=4*cm, height=3*cm, kind='bound')
[7068]660        story.append(doc_img)
661        story.append(Spacer(1, 18))
[7063]662
663        # render widget fields
[7068]664        data = []
[6358]665        self.setUpWidgets()
666        for widget in self.widgets:
[6462]667            f_label = '<font size=12>%s</font>:' % widget.label.strip()
[6363]668            f_label = Paragraph(f_label, style["Normal"])
[7063]669            f_text = '<font size=12>%s</font>' % widget()
670            f_text = Paragraph(f_text, style["Normal"])
671            data.append([f_label,f_text])
[6462]672        f_label = '<font size=12>Admitted Course of Study:</font>'
[6363]673        f_text = '<font size=12>%s</font>' % self.getCourseAdmitted()
674        f_label = Paragraph(f_label, style["Normal"])
675        f_text = Paragraph(f_text, style["Normal"])
676        data.append([f_label,f_text])
[6364]677        table = Table(data,style=SLIP_STYLE)
[6363]678        story.append(table)
679        frame_body.addFromList(story,pdf)
680
[6364]681        story = []
[6365]682        frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
[6364]683        timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
684        f_text = '<font size=10>%s</font>' % timestamp
685        story.append(Paragraph(f_text, style["Normal"]))
686        frame_footer.addFromList(story,pdf)
687
[6358]688        self.response.setHeader(
689            'Content-Type', 'application/pdf')
690        return pdf.getpdfdata()
691
[6383]692class ApplicantManageActionButton(ManageActionButton):
[6198]693    grok.context(IApplicant)
694    grok.view(DisplayApplicant)
[7136]695    grok.require('waeup.manageApplication')
[6383]696    text = 'Manage application record'
[6198]697    target = 'edit_full'
698
[7081]699
700def handle_img_upload(upload, context, view):
[7063]701    """Handle upload of applicant image.
[7081]702
703    Returns `True` in case of success or `False`.
704
705    Please note that file pointer passed in (`upload`) most probably
706    points to end of file when leaving this function.
[7063]707    """
[7081]708    size = file_size(upload)
709    if size > MAX_UPLOAD_SIZE:
710        view.flash('Uploaded image is too big!')
711        return False
712    upload.seek(0) # file pointer moved when determining size
[7063]713    store = getUtility(IExtFileStore)
714    file_id = IFileStoreNameChooser(context).chooseName()
715    store.createFile(file_id, upload)
[7081]716    return True
[7063]717
[6196]718class EditApplicantFull(WAeUPEditFormPage):
719    """A full edit view for applicant data.
720    """
721    grok.context(IApplicant)
722    grok.name('edit_full')
[7136]723    grok.require('waeup.manageApplication')
[6476]724    form_fields = grok.AutoFields(IApplicant)
[6196]725    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
726    grok.template('form_edit')
[6322]727    manage_applications = True
[6196]728    pnav = 3
729
730    def update(self):
731        datepicker.need() # Enable jQuery datepicker in date fields.
732        super(EditApplicantFull, self).update()
[6353]733        self.wf_info = IWorkflowInfo(self.context)
[7081]734        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
[7084]735        self.passport_changed = None
[6598]736        upload = self.request.form.get('form.passport', None)
737        if upload:
738            # We got a fresh upload
[7084]739            self.passport_changed = handle_img_upload(
740                upload, self.context, self)
[6196]741        return
742
743    @property
744    def title(self):
745        return self.context.access_code
746
747    @property
748    def label(self):
749        container_title = self.context.__parent__.title
750        return '%s Application Form' % container_title
751
[6303]752    def getTransitions(self):
[6351]753        """Return a list of dicts of allowed transition ids and titles.
[6353]754
755        Each list entry provides keys ``name`` and ``title`` for
756        internal name and (human readable) title of a single
757        transition.
[6349]758        """
[6353]759        allowed_transitions = self.wf_info.getManualTransitions()
[6355]760        return [dict(name='', title='No transition')] +[
761            dict(name=x, title=y) for x, y in allowed_transitions]
[6303]762
[6196]763    @grok.action('Save')
764    def save(self, **data):
[7084]765        if self.passport_changed is False:  # False is not None!
766            return # error during image upload. Ignore other values
[6475]767        changed_fields = self.applyData(self.context, **data)
768        changed_fields = changed_fields.values()
[7063]769        fields_string = '+'.join(
770            ' + '.join(str(i) for i in b) for b in changed_fields)
[7084]771        if self.passport_changed:
772            fields_string += ' + passport'
[7094]773        #self.context._p_changed = True
[6303]774        form = self.request.form
[7085]775        trans_id = form.get('transition', None)
776        if trans_id:
777            self.wf_info.fireTransition(trans_id)
[6196]778        self.flash('Form has been saved.')
[6475]779        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
[6644]780        if fields_string:
781            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
[6196]782        return
783
784class EditApplicantStudent(EditApplicantFull):
[5982]785    """An applicant-centered edit view for applicant data.
786    """
[6196]787    grok.context(IApplicantEdit)
[5273]788    grok.name('edit')
[6198]789    grok.require('waeup.handleApplication')
[6459]790    form_fields = grok.AutoFields(IApplicantEdit).omit(
[6476]791        'locked', 'course_admitted', 'student_id',
[6459]792        'screening_score',
793        )
[6054]794    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[6196]795    grok.template('form_edit')
[6322]796    manage_applications = False
[6465]797    title = u'Your Application Form'
[5484]798
[7145]799    def emit_lock_message(self):
[6105]800        self.flash('The requested form is locked (read-only).')
[5941]801        self.redirect(self.url(self.context))
802        return
[6078]803
[5686]804    def update(self):
[5941]805        if self.context.locked:
[7145]806            self.emit_lock_message()
[5941]807            return
[6040]808        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]809        super(EditApplicantStudent, self).update()
[5686]810        return
[5952]811
[6196]812    def dataNotComplete(self):
[6322]813        if not self.request.form.get('confirm_passport', False):
[6196]814            return 'Passport confirmation box not ticked.'
815        return False
[5952]816
[5273]817    @grok.action('Save')
818    def save(self, **data):
[7084]819        if self.passport_changed is False:  # False is not None!
820            return # error during image upload. Ignore other values
[5273]821        self.applyData(self.context, **data)
[7094]822        #self.context._p_changed = True
[6196]823        self.flash('Form has been saved.')
[5273]824        return
825
[5484]826    @grok.action('Final Submit')
827    def finalsubmit(self, **data):
[7084]828        if self.passport_changed is False:  # False is not None!
829            return # error during image upload. Ignore other values
[5273]830        self.applyData(self.context, **data)
[5484]831        self.context._p_changed = True
[6196]832        if self.dataNotComplete():
833            self.flash(self.dataNotComplete())
[5941]834            return
[6303]835        state = IWorkflowState(self.context).getState()
[6322]836        # This shouldn't happen, but the application officer
837        # might have forgotten to lock the form after changing the state
[6303]838        if state != STARTED:
[6322]839            self.flash('This form cannot be submitted. Wrong state!')
[6303]840            return
841        IWorkflowInfo(self.context).fireTransition('submit')
[6476]842        self.context.application_date = datetime.now()
[5941]843        self.context.locked = True
[6196]844        self.flash('Form has been submitted.')
845        self.redirect(self.url(self.context))
[5273]846        return
[5941]847
[6367]848class ApplicantViewActionButton(ManageActionButton):
849    grok.context(IApplicant)
[6383]850    grok.view(EditApplicantFull)
[7136]851    grok.require('waeup.manageApplication')
[6383]852    icon = 'actionicon_view.png'
[6367]853    text = 'View application record'
[6598]854    target = 'index'
[7063]855
856class PassportImage(grok.View):
857    """Renders the passport image for applicants.
858    """
859    grok.name('passport.jpg')
860    grok.context(IApplicant)
[7113]861    grok.require('waeup.viewApplication')
[7063]862
863    def render(self):
864        # A filename chooser turns a context into a filename suitable
865        # for file storage.
866        image = getUtility(IExtFileStore).getFileByContext(self.context)
867        self.response.setHeader(
868            'Content-Type', 'image/jpeg')
869        if image is None:
870            # show placeholder image
[7089]871            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
[7063]872        return image
Note: See TracBrowser for help on using the repository browser.