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

Last change on this file since 7066 was 7063, checked in by uli, 14 years ago

Merge changes from branch ulif-extimgstore back into trunk.
Beside external image storage also waeupdocs should work again.

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