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

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

Application is only allowed between start and end time. Show dates on login page and logout applicants if current time exceeds these limits.

File size: 31.1 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 sys
25import grok
26
27from datetime import datetime, date
28from zope.authentication.interfaces import ILogout, IAuthentication
29from zope.component import getUtility
30from zope.formlib.widget import CustomWidgetFactory
31from zope.formlib.form import setUpEditWidgets
32from zope.securitypolicy.interfaces import IPrincipalRoleManager
33from zope.traversing.browser import absoluteURL
34
35from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
36from reportlab.pdfgen import canvas
37from reportlab.lib.units import cm
38from reportlab.lib.pagesizes import A4
39from reportlab.lib.styles import getSampleStyleSheet
40from reportlab.platypus import (Frame, Paragraph, Image,
41    Table, Spacer)
42from reportlab.platypus.tables import TableStyle
43
44from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
45from waeup.sirp.accesscodes.workflow import USED
46from waeup.sirp.browser import (
47    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
48from waeup.sirp.browser.breadcrumbs import Breadcrumb
49from waeup.sirp.browser.layout import NullValidator
50from waeup.sirp.browser.pages import add_local_role, del_local_roles
51from waeup.sirp.browser.resources import datepicker, tabs, datatable
52from waeup.sirp.browser.viewlets import (
53    ManageActionButton, PrimaryNavTab, LeftSidebarLink
54    )
55from waeup.sirp.image.browser.widget import (
56    ThumbnailWidget, EncodingImageFileWidget,
57    )
58from waeup.sirp.image.image import createWAeUPImageFile
59from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
60from waeup.sirp.permissions import get_users_with_local_roles
61from waeup.sirp.university.interfaces import ICertificate
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    )
72from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
73
74results_widget = CustomWidgetFactory(
75    WAeUPObjectWidget, ResultEntry)
76
77results_display_widget = CustomWidgetFactory(
78    WAeUPObjectDisplayWidget, ResultEntry)
79
80class ApplicantsRootPage(WAeUPPage):
81    grok.context(IApplicantsRoot)
82    grok.name('index')
83    grok.require('waeup.Public')
84    title = 'Applicants'
85    label = 'Application Section'
86    pnav = 3
87
88    def update(self):
89        super(ApplicantsRootPage, self).update()
90        datatable.need()
91        return
92
93class ManageApplicantsRootActionButton(ManageActionButton):
94    grok.context(IApplicantsRoot)
95    grok.view(ApplicantsRootPage)
96    grok.require('waeup.manageApplications')
97    text = 'Manage application section'
98
99class ApplicantsRootManageFormPage(WAeUPEditFormPage):
100    grok.context(IApplicantsRoot)
101    grok.name('manage')
102    grok.template('applicantsrootmanagepage')
103    title = 'Applicants'
104    label = 'Manage application section'
105    pnav = 3
106    grok.require('waeup.manageApplications')
107    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
108    tabtwoactions1 = ['Remove selected local roles']
109    tabtwoactions2 = ['Add local role']
110    subunits = 'Applicants Containers'
111
112    def update(self):
113        tabs.need()
114        datatable.need()
115        return super(ApplicantsRootManageFormPage, self).update()
116
117    def getLocalRoles(self):
118        roles = ILocalRolesAssignable(self.context)
119        return roles()
120
121    def getUsers(self):
122        """Get a list of all users.
123        """
124        for key, val in grok.getSite()['users'].items():
125            url = self.url(val)
126            yield(dict(url=url, name=key, val=val))
127
128    def getUsersWithLocalRoles(self):
129        return get_users_with_local_roles(self.context)
130
131    # ToDo: Show warning message before deletion
132    @grok.action('Remove selected')
133    def delApplicantsContainers(self, **data):
134        form = self.request.form
135        child_id = form['val_id']
136        if not isinstance(child_id, list):
137            child_id = [child_id]
138        deleted = []
139        for id in child_id:
140            try:
141                del self.context[id]
142                deleted.append(id)
143            except:
144                self.flash('Could not delete %s: %s: %s' % (
145                        id, sys.exc_info()[0], sys.exc_info()[1]))
146        if len(deleted):
147            self.flash('Successfully removed: %s' % ', '.join(deleted))
148        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
149        return
150
151    @grok.action('Add applicants container', validator=NullValidator)
152    def addApplicantsContainer(self, **data):
153        self.redirect(self.url(self.context, '@@add'))
154        return
155
156    @grok.action('Cancel', validator=NullValidator)
157    def cancel(self, **data):
158        self.redirect(self.url(self.context))
159        return
160
161    @grok.action('Add local role', validator=NullValidator)
162    def addLocalRole(self, **data):
163        return add_local_role(self,2, **data)
164
165    @grok.action('Remove selected local roles')
166    def delLocalRoles(self, **data):
167        return del_local_roles(self,2,**data)
168
169class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
170    grok.context(IApplicantsRoot)
171    grok.require('waeup.manageApplications')
172    grok.name('add')
173    grok.template('applicantscontaineraddpage')
174    title = 'Applicants'
175    label = 'Add applicants container'
176    pnav = 3
177
178    form_fields = grok.AutoFields(
179        IApplicantsContainerAdd).omit('code').omit('title')
180    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
181    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
182
183    def update(self):
184        datepicker.need() # Enable jQuery datepicker in date fields.
185        return super(ApplicantsContainerAddFormPage, self).update()
186
187    @grok.action('Add applicants container')
188    def addApplicantsContainer(self, **data):
189        year = data['year']
190        code = u'%s%s' % (data['prefix'], year)
191        prefix = application_types_vocab.getTerm(data['prefix'])
192        title = u'%s %s/%s' % (prefix.title, year, year + 1)
193        if code in self.context.keys():
194            self.flash(
195                'An applicants container for the same application '
196                'type and entrance year exists already in the database.')
197            return
198        # Add new applicants container...
199        provider = data['provider'][1]
200        container = provider.factory()
201        self.applyData(container, **data)
202        container.code = code
203        container.title = title
204        self.context[code] = container
205        self.flash('Added: "%s".' % code)
206        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
207        return
208
209    @grok.action('Cancel', validator=NullValidator)
210    def cancel(self, **data):
211        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
212
213class ApplicantsRootBreadcrumb(Breadcrumb):
214    """A breadcrumb for applicantsroot.
215    """
216    grok.context(IApplicantsRoot)
217    title = u'Applicants'
218
219class ApplicantsContainerBreadcrumb(Breadcrumb):
220    """A breadcrumb for applicantscontainers.
221    """
222    grok.context(IApplicantsContainer)
223
224class ApplicantBreadcrumb(Breadcrumb):
225    """A breadcrumb for applicants.
226    """
227    grok.context(IApplicant)
228
229    @property
230    def title(self):
231        """Get a title for a context.
232        """
233        return self.context.access_code
234
235class ApplicantsTab(PrimaryNavTab):
236    """Applicants tab in primary navigation.
237    """
238
239    grok.context(IWAeUPObject)
240    grok.order(3)
241    grok.require('waeup.Public')
242    grok.template('primarynavtab')
243
244    pnav = 3
245    tab_title = u'Applicants'
246
247    @property
248    def link_target(self):
249        return self.view.application_url('applicants')
250
251class ApplicantsContainerPage(WAeUPDisplayFormPage):
252    """The standard view for regular applicant containers.
253    """
254    grok.context(IApplicantsContainer)
255    grok.name('index')
256    grok.require('waeup.Public')
257    grok.template('applicantscontainerpage')
258    pnav = 3
259
260    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
261    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
262    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
263    form_fields['description'].custom_widget = ReSTDisplayWidget
264
265    @property
266    def title(self):
267        return "Applicants Container: %s" % self.context.title
268
269    @property
270    def label(self):
271        return self.context.title
272
273class ApplicantsContainerManageActionButton(ManageActionButton):
274    grok.order(1)
275    grok.context(IApplicantsContainer)
276    grok.view(ApplicantsContainerPage)
277    grok.require('waeup.manageApplications')
278    text = 'Manage applicants container'
279
280class LoginApplicantActionButton(ManageActionButton):
281    grok.order(2)
282    grok.context(IApplicantsContainer)
283    grok.view(ApplicantsContainerPage)
284    grok.require('waeup.Anonymous')
285    icon = 'login.png'
286    text = 'Login for applicants'
287    target = 'login'
288
289class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
290    grok.context(IApplicantsContainer)
291    grok.name('manage')
292    grok.template('applicantscontainermanagepage')
293    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
294    taboneactions = ['Save','Cancel']
295    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
296    tabthreeactions1 = ['Remove selected local roles']
297    tabthreeactions2 = ['Add local role']
298    # Use friendlier date widget...
299    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
300    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
301    grok.require('waeup.manageApplications')
302
303    @property
304    def title(self):
305        return "Applicants Container: %s" % self.context.title
306
307    @property
308    def label(self):
309        return 'Manage applicants container'
310
311    pnav = 3
312
313    def update(self):
314        datepicker.need() # Enable jQuery datepicker in date fields.
315        tabs.need()
316        datatable.need()  # Enable jQurey datatables for contents listing
317        return super(ApplicantsContainerManageFormPage, self).update()
318
319    def getLocalRoles(self):
320        roles = ILocalRolesAssignable(self.context)
321        return roles()
322
323    def getUsers(self):
324        """Get a list of all users.
325        """
326        for key, val in grok.getSite()['users'].items():
327            url = self.url(val)
328            yield(dict(url=url, name=key, val=val))
329
330    def getUsersWithLocalRoles(self):
331        return get_users_with_local_roles(self.context)
332
333    @grok.action('Save')
334    def apply(self, **data):
335        self.applyData(self.context, **data)
336        self.flash('Data saved.')
337        return
338
339    # ToDo: Show warning message before deletion
340    @grok.action('Remove selected')
341    def delApplicant(self, **data):
342        form = self.request.form
343        if form.has_key('val_id'):
344            child_id = form['val_id']
345        else:
346            self.flash('No applicant selected!')
347            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
348            return
349        if not isinstance(child_id, list):
350            child_id = [child_id]
351        deleted = []
352        for id in child_id:
353            try:
354                del self.context[id]
355                deleted.append(id)
356            except:
357                self.flash('Could not delete %s: %s: %s' % (
358                        id, sys.exc_info()[0], sys.exc_info()[1]))
359        if len(deleted):
360            self.flash('Successfully removed: %s' % ', '.join(deleted))
361        self.redirect(self.url(self.context, u'@@manage')+'#tab-2')
362        return
363
364    @grok.action('Add applicant', validator=NullValidator)
365    def addApplicant(self, **data):
366        self.redirect(self.url(self.context, 'addapplicant'))
367        return
368
369    @grok.action('Cancel', validator=NullValidator)
370    def cancel(self, **data):
371        self.redirect(self.url(self.context))
372        return
373
374    @grok.action('Add local role', validator=NullValidator)
375    def addLocalRole(self, **data):
376        return add_local_role(self,3, **data)
377
378    @grok.action('Remove selected local roles')
379    def delLocalRoles(self, **data):
380        return del_local_roles(self,3,**data)
381
382class LoginApplicant(WAeUPPage):
383    grok.context(IApplicantsContainer)
384    grok.name('login')
385    grok.require('waeup.Public')
386
387    @property
388    def title(self):
389        return u"Applicant Login: %s" % self.context.title
390
391    @property
392    def label(self):
393        return u"Login for '%s' applicants only" % self.context.title
394
395    pnav = 3
396
397    @property
398    def ac_prefix(self):
399        return self.context.ac_prefix
400
401    def update(self, SUBMIT=None):
402        self.ac_series = self.request.form.get('form.ac_series', None)
403        self.ac_number = self.request.form.get('form.ac_number', None)
404        if SUBMIT is None:
405            return
406        if self.request.principal.id == 'zope.anybody':
407            self.flash('Entered credentials are invalid.')
408            return
409        if not IApplicantPrincipal.providedBy(self.request.principal):
410            # Don't care if user is already authenticated as non-applicant
411            return
412
413        # From here we handle an applicant (not an officer browsing)
414        pin = self.request.principal.access_code
415
416        # If application has not yet started,
417        # logout without marking AC as used
418        if self.context.startdate > date.today():
419            self.flash('Application has not yet started.')
420            auth = getUtility(IAuthentication)
421            ILogout(auth).logout(self.request)
422            self.redirect(self.url(self.context, 'login'))
423            return
424
425        # If application has ended and applicant record doesn't exist,
426        # logout without marking AC as used
427        if not pin in self.context.keys() and 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            pass
437        else:
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        #else:
517        #    return ''
518
519class AccessCodeEditLink(AccessCodeViewLink):
520    grok.order(2)
521    grok.require('waeup.Public')
522    icon = 'actionicon_modify.png'
523    title = 'Edit Record'
524    target = '/@@edit'
525
526    @property
527    def url(self):
528        if not IApplicantPrincipal.providedBy(self.request.principal):
529            return ''
530        access_code = getattr(self.request.principal,'access_code',None)
531        if access_code:
532            applicant_object = get_applicant_data(access_code)
533            if applicant_object.locked:
534                return ''
535            return absoluteURL(applicant_object, self.request) + self.target
536        #else:
537        #    return ''
538
539class AccessCodeSlipLink(AccessCodeViewLink):
540    grok.order(3)
541    grok.require('waeup.Public')
542    icon = 'actionicon_pdf.png'
543    title = 'Download Slip'
544    target = '/application_slip.pdf'
545
546class DisplayApplicant(WAeUPDisplayFormPage):
547    grok.context(IApplicant)
548    grok.name('index')
549    grok.require('waeup.handleApplication')
550    form_fields = grok.AutoFields(IApplicant).omit(
551        'locked').omit('course_admitted')
552    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
553    form_fields['passport'].custom_widget = ThumbnailWidget
554    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
555    label = 'Applicant'
556    grok.template('form_display')
557    pnav = 3
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.view(DisplayApplicant)
585    grok.require('waeup.manageApplications')
586    icon = 'actionicon_pdf.png'
587    text = 'Download application slip'
588    target = 'application_slip.pdf'
589
590class ExportPDFPage(grok.View):
591    """Deliver a PDF slip of the context.
592    """
593    grok.context(IApplicant)
594    grok.name('application_slip.pdf')
595    grok.require('waeup.handleApplication')
596    form_fields = grok.AutoFields(IApplicant).omit(
597        'locked').omit('course_admitted')
598    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
599    prefix = 'form'
600
601    @property
602    def label(self):
603        container_title = self.context.__parent__.title
604        return '%s Application Record' % container_title
605
606    def getCourseAdmitted(self):
607        """Return title and code in html format to the certificate
608           admitted.
609        """
610        course_admitted = self.context.course_admitted
611        if ICertificate.providedBy(course_admitted):
612            title = course_admitted.title
613            code = course_admitted.code
614            return '%s - %s' %(code,title)
615        return ''
616
617    def setUpWidgets(self, ignore_request=False):
618        self.adapters = {}
619        self.widgets = setUpEditWidgets(
620            self.form_fields, self.prefix, self.context, self.request,
621            adapters=self.adapters, for_display=True,
622            ignore_request=ignore_request
623            )
624
625    def render(self):
626        # (0,0),(-1,-1) = whole table
627        # (0,0),(0,-1) = first column
628        # (-1,0),(-1,-1) = last column
629        # (0,0),(-1,0) = first row
630        # (0,-1),(-1,-1) = last row
631        SLIP_STYLE = TableStyle(
632            [('VALIGN',(0,0),(-1,-1),'TOP')]
633            )
634
635        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
636        pdf.setTitle(self.label)
637        pdf.setSubject('Application')
638        pdf.setAuthor('%s (%s)' % (self.request.principal.title,
639            self.request.principal.id))
640        pdf.setCreator('WAeUP SIRP')
641        width, height = A4
642        style = getSampleStyleSheet()
643        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
644
645        story = []
646        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
647        header_title = getattr(grok.getSite(), 'name', u'Sample University')
648        story.append(Paragraph(header_title, style["Heading1"]))
649        frame_header.addFromList(story,pdf)
650
651        story = []
652        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
653        story.append(Paragraph(self.label, style["Heading2"]))
654        #story.append(HRFlowable())
655        story.append(Spacer(1, 18))
656        for msg in self.context.history.messages:
657            f_msg = '<font face="Courier" size=10>%s</font>' % msg
658            story.append(Paragraph(f_msg, style["Normal"]))
659        story.append(Spacer(1, 24))
660        self.setUpWidgets()
661        data = []
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            if widget.name != 'form.passport':
666                f_text = '<font size=12>%s</font>' % widget()
667                f_text = Paragraph(f_text, style["Normal"])
668                data.append([f_label,f_text])
669            else:
670                filename = widget._data.file.name
671                im = Image(filename,width=4*cm, height=3*cm,kind='bound')
672                data.append([f_label,im])
673        f_label = '<font size=12>Admitted Course of Study:</font>'
674        f_text = '<font size=12>%s</font>' % self.getCourseAdmitted()
675        f_label = Paragraph(f_label, style["Normal"])
676        f_text = Paragraph(f_text, style["Normal"])
677        data.append([f_label,f_text])
678        table = Table(data,style=SLIP_STYLE)
679        story.append(table)
680        frame_body.addFromList(story,pdf)
681
682        story = []
683        frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
684        timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
685        f_text = '<font size=10>%s</font>' % timestamp
686        story.append(Paragraph(f_text, style["Normal"]))
687        frame_footer.addFromList(story,pdf)
688
689        self.response.setHeader(
690            'Content-Type', 'application/pdf')
691        return pdf.getpdfdata()
692
693class ApplicantManageActionButton(ManageActionButton):
694    grok.context(IApplicant)
695    grok.view(DisplayApplicant)
696    grok.require('waeup.manageApplications')
697    text = 'Manage application record'
698    target = 'edit_full'
699
700class EditApplicantFull(WAeUPEditFormPage):
701    """A full edit view for applicant data.
702    """
703    grok.context(IApplicant)
704    grok.name('edit_full')
705    grok.require('waeup.manageApplications')
706    form_fields = grok.AutoFields(IApplicant)
707    form_fields['passport'].custom_widget = EncodingImageFileWidget
708    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
709    grok.template('form_edit')
710    manage_applications = True
711    pnav = 3
712
713    def update(self):
714        datepicker.need() # Enable jQuery datepicker in date fields.
715        super(EditApplicantFull, self).update()
716        self.wf_info = IWorkflowInfo(self.context)
717        upload = self.request.form.get('form.passport', None)
718        if upload:
719            # We got a fresh upload
720            image = createWAeUPImageFile(upload.filename, upload)
721            # This would normally be totally illegal. We set context
722            # data without the complete form data being
723            # verified. Normally data is set in methods like `save`
724            # only, which is only called when all form data is correct
725            # (and if not, the context is not touched). With images
726            # and their uploads the problem then is, that the uploaded
727            # image won't be shown in a partially erranous form
728            # because the new uploaded image is still not part of the
729            # context obeject and the image widget cannot work around
730            # that (it has no access to the new data). For this reason
731            # we set the image here and not in 'save()' or other
732            # methods.
733            self.context.passport = image
734            upload.seek(0)
735            self.passport_changed = True
736        return
737
738    @property
739    def title(self):
740        return self.context.access_code
741
742    @property
743    def label(self):
744        container_title = self.context.__parent__.title
745        return '%s Application Form' % container_title
746
747    def getTransitions(self):
748        """Return a list of dicts of allowed transition ids and titles.
749
750        Each list entry provides keys ``name`` and ``title`` for
751        internal name and (human readable) title of a single
752        transition.
753        """
754        allowed_transitions = self.wf_info.getManualTransitions()
755        return [dict(name='', title='No transition')] +[
756            dict(name=x, title=y) for x, y in allowed_transitions]
757
758    @grok.action('Save')
759    def save(self, **data):
760        changed_fields = self.applyData(self.context, **data)
761        changed_fields = changed_fields.values()
762        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
763        self.context._p_changed = True
764        form = self.request.form
765        if form.has_key('transition') and form['transition']:
766            transition_id = form['transition']
767            self.wf_info.fireTransition(transition_id)
768        self.flash('Form has been saved.')
769        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
770        if fields_string:
771            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
772        return
773
774class EditApplicantStudent(EditApplicantFull):
775    """An applicant-centered edit view for applicant data.
776    """
777    grok.context(IApplicantEdit)
778    grok.name('edit')
779    grok.require('waeup.handleApplication')
780    form_fields = grok.AutoFields(IApplicantEdit).omit(
781        'locked', 'course_admitted', 'student_id',
782        'screening_score',
783        )
784    form_fields['passport'].custom_widget = EncodingImageFileWidget
785    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
786    grok.template('form_edit')
787    manage_applications = False
788    title = u'Your Application Form'
789
790
791    def emitLockMessage(self):
792        self.flash('The requested form is locked (read-only).')
793        self.redirect(self.url(self.context))
794        return
795
796    def update(self):
797        if self.context.locked:
798            self.emitLockMessage()
799            return
800        datepicker.need() # Enable jQuery datepicker in date fields.
801        upload = self.request.form.get('form.passport', None)
802        if upload:
803            # We got a fresh upload
804            image = createWAeUPImageFile(upload.filename, upload)
805            # This would normally be totally illegal. We set context
806            # data without the complete form data being
807            # verified. Normally data is set in methods like `save`
808            # only, which is only called when all form data is correct
809            # (and if not, the context is not touched). With images
810            # and their uploads the problem then is, that the uploaded
811            # image won't be shown in a partially erranous form
812            # because the new uploaded image is still not part of the
813            # context obeject and the image widget cannot work around
814            # that (it has no access to the new data). For this reason
815            # we set the image here and not in 'save()' or other
816            # methods.
817            self.context.passport = image
818            upload.seek(0)
819            self.passport_changed = True
820        super(EditApplicantStudent, self).update()
821        return
822
823    def dataNotComplete(self):
824        if not self.request.form.get('confirm_passport', False):
825            return 'Passport confirmation box not ticked.'
826        #if len(self.errors) > 0:
827        #    return 'Form has errors.'
828        return False
829
830    @grok.action('Save')
831    def save(self, **data):
832        #if self.context.locked:
833        #    self.emitLockMessage()
834        #    return
835        self.applyData(self.context, **data)
836        self.context._p_changed = True
837        self.flash('Form has been saved.')
838        return
839
840    @grok.action('Final Submit')
841    def finalsubmit(self, **data):
842        #if self.context.locked:
843        #    self.emitLockMessage()
844        #    return
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(EditApplicantFull)
866    grok.require('waeup.manageApplications')
867    icon = 'actionicon_view.png'
868    text = 'View application record'
869    target = 'index'
Note: See TracBrowser for help on using the repository browser.