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

Last change on this file since 7093 was 7089, checked in by uli, 13 years ago

Use path to default passport image instead of open file descriptor.
If we would use a file descriptor, we had to make sure to reset it after
each read (seek(0), which did not happen) and we could run into trouble
when multiple requests/threads access the file at the same time.

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