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

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

Reorganise permissions:

The navigation viewlets now manage the permission requirements themselves. No need to do this in pagetemplates.

Rename permission waeup.View to waeup.viewAcademics because it only refers to the academic section.

Add permission waeup.Authenticated (which is used in students). The StudentRecordOwner? explicitly needs this permission. Otherwise the MyData? tab disappears when changing the password.

Roles do not need to get the waeup.Public permission. This is already guaranteed in site.zcml.

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