source: main/waeup.sirp/branches/ulif-extimgstore/src/waeup/sirp/applicants/browser.py @ 7055

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

Minor clean ups.

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