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

Last change on this file since 7124 was 7113, checked in by uli, 13 years ago

First sketch of PrincipalRoleManager? specifically designed for applicants.

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