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

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

Increase MAX_UPLOAD_SIZE, 20 kB is more realistic.

We can import DEFAULT_PASSPORT_IMAGE_MALE directly instead of IMAGE_PATH. That means configuration is solely done in interfaces.py not in browser.py.

DEFAULT_PASSPORT_IMAGE_FEMALE is no longer used.

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