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

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

Adjust copyright statement and svn keyword in applicants.

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