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

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

Use only one primarynavtab.pt pagetemplate and render list item only if link_target is provided.

Provide link_target of EnquiriesTab? and ApplicantsAnonTab? only if principal id is zope.anybody.

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