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

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

Use only one primarynavtab.pt pagetemplate and render list item only if link_target is provided.

Provide link_target of EnquiriesTab? and ApplicantsAnonTab? only if principal id is zope.anybody.

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