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

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

Remove more obsolete imports.

File size: 32.5 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.image.image import createWAeUPImageFile
57from waeup.sirp.interfaces import (
58    IWAeUPObject, ILocalRolesAssignable, IExtFileStore, IFileStoreNameChooser)
59from waeup.sirp.permissions import get_users_with_local_roles
60from waeup.sirp.university.interfaces import ICertificate
61from waeup.sirp.widgets.datewidget import (
62    FriendlyDateWidget, FriendlyDateDisplayWidget)
63from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
64from waeup.sirp.widgets.objectwidget import (
65    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
66from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
67from waeup.sirp.applicants.interfaces import (
68    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
69    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
70    IMAGE_PATH,
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    def update(self):
560        self.passport_url = self.url(self.context, 'passport.jpg')
561        return
562
563    @property
564    def title(self):
565        if self.request.principal.title == 'Applicant':
566            return u'Your Application Record'
567        return '%s' % self.context.access_code
568
569    @property
570    def label(self):
571        container_title = self.context.__parent__.title
572        return '%s Application Record' % container_title
573
574    def getCourseAdmitted(self):
575        """Return link, title and code in html format to the certificate
576           admitted.
577        """
578        course_admitted = self.context.course_admitted
579        if ICertificate.providedBy(course_admitted):
580            url = self.url(course_admitted)
581            title = course_admitted.title
582            code = course_admitted.code
583            return '<a href="%s">%s - %s</a>' %(url,code,title)
584        return ''
585
586class PDFActionButton(ManageActionButton):
587    grok.context(IApplicant)
588    #grok.view(DisplayApplicant)
589    grok.require('waeup.manageApplications')
590    icon = 'actionicon_pdf.png'
591    text = 'Download application slip'
592    target = 'application_slip.pdf'
593
594class ExportPDFPage(grok.View):
595    """Deliver a PDF slip of the context.
596    """
597    grok.context(IApplicant)
598    grok.name('application_slip.pdf')
599    grok.require('waeup.handleApplication')
600    form_fields = grok.AutoFields(IApplicant).omit(
601        'locked').omit('course_admitted')
602    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
603    prefix = 'form'
604
605    @property
606    def label(self):
607        container_title = self.context.__parent__.title
608        return '%s Application Record' % container_title
609
610    def getCourseAdmitted(self):
611        """Return title and code in html format to the certificate
612           admitted.
613        """
614        course_admitted = self.context.course_admitted
615        if ICertificate.providedBy(course_admitted):
616            title = course_admitted.title
617            code = course_admitted.code
618            return '%s - %s' %(code,title)
619        return ''
620
621    def setUpWidgets(self, ignore_request=False):
622        self.adapters = {}
623        self.widgets = setUpEditWidgets(
624            self.form_fields, self.prefix, self.context, self.request,
625            adapters=self.adapters, for_display=True,
626            ignore_request=ignore_request
627            )
628
629    def render(self):
630        # (0,0),(-1,-1) = whole table
631        # (0,0),(0,-1) = first column
632        # (-1,0),(-1,-1) = last column
633        # (0,0),(-1,0) = first row
634        # (0,-1),(-1,-1) = last row
635        SLIP_STYLE = TableStyle(
636            [('VALIGN',(0,0),(-1,-1),'TOP')]
637            )
638
639        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
640        pdf.setTitle(self.label)
641        pdf.setSubject('Application')
642        pdf.setAuthor('%s (%s)' % (self.request.principal.title,
643            self.request.principal.id))
644        pdf.setCreator('WAeUP SIRP')
645        width, height = A4
646        style = getSampleStyleSheet()
647        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
648
649        story = []
650        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
651        header_title = getattr(grok.getSite(), 'name', u'Sample University')
652        story.append(Paragraph(header_title, style["Heading1"]))
653        frame_header.addFromList(story,pdf)
654
655        story = []
656        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
657        story.append(Paragraph(self.label, style["Heading2"]))
658        #story.append(HRFlowable())
659        story.append(Spacer(1, 18))
660        for msg in self.context.history.messages:
661            f_msg = '<font face="Courier" size=10>%s</font>' % msg
662            story.append(Paragraph(f_msg, style["Normal"]))
663        story.append(Spacer(1, 24))
664        self.setUpWidgets()
665        data = []
666        for widget in self.widgets:
667            f_label = '<font size=12>%s</font>:' % widget.label.strip()
668            f_label = Paragraph(f_label, style["Normal"])
669            if widget.name != 'form.passport':
670                f_text = '<font size=12>%s</font>' % widget()
671                f_text = Paragraph(f_text, style["Normal"])
672                data.append([f_label,f_text])
673            else:
674                filename = widget._data.file.name
675                im = Image(filename,width=4*cm, height=3*cm,kind='bound')
676                data.append([f_label,im])
677        f_label = '<font size=12>Admitted Course of Study:</font>'
678        f_text = '<font size=12>%s</font>' % self.getCourseAdmitted()
679        f_label = Paragraph(f_label, style["Normal"])
680        f_text = Paragraph(f_text, style["Normal"])
681        data.append([f_label,f_text])
682        table = Table(data,style=SLIP_STYLE)
683        story.append(table)
684        frame_body.addFromList(story,pdf)
685
686        story = []
687        frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
688        timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
689        f_text = '<font size=10>%s</font>' % timestamp
690        story.append(Paragraph(f_text, style["Normal"]))
691        frame_footer.addFromList(story,pdf)
692
693        self.response.setHeader(
694            'Content-Type', 'application/pdf')
695        return pdf.getpdfdata()
696
697class ApplicantManageActionButton(ManageActionButton):
698    grok.context(IApplicant)
699    grok.view(DisplayApplicant)
700    grok.require('waeup.manageApplications')
701    text = 'Manage application record'
702    target = 'edit_full'
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['passport'].custom_widget = EncodingImageFileWidget
712    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
713    grok.template('form_edit')
714    manage_applications = True
715    pnav = 3
716
717    def update(self):
718        datepicker.need() # Enable jQuery datepicker in date fields.
719        super(EditApplicantFull, self).update()
720        self.wf_info = IWorkflowInfo(self.context)
721        upload = self.request.form.get('form.passport', None)
722        if upload:
723            # We got a fresh upload
724            #from waeup.sirp.interfaces import IExtFileStorage
725            #from zope.component import queryUtility
726            #storage = queryUtility(IExtFileStorage)
727            # filename = '__img_appl__' + str(self.context.reg_no) #upload.filename
728            store = getUtility(IExtFileStore)
729            file_id = IFileStoreNameChooser(self.context).chooseName()
730            store.createFile(file_id, upload)
731            #filename = '__img_appl__' + str(
732            #    self.context.access_code) + '.jpg' #upload.filename
733            print "FILENAME: ", file_id
734            # image = createWAeUPImageFile(upload.filename, upload)
735            #image = createWAeUPImageFile(filename, upload)
736            # This would normally be totally illegal. We set context
737            # data without the complete form data being
738            # verified. Normally data is set in methods like `save`
739            # only, which is only called when all form data is correct
740            # (and if not, the context is not touched). With images
741            # and their uploads the problem then is, that the uploaded
742            # image won't be shown in a partially erranous form
743            # because the new uploaded image is still not part of the
744            # context obeject and the image widget cannot work around
745            # that (it has no access to the new data). For this reason
746            # we set the image here and not in 'save()' or other
747            # methods.
748            #self.context.passport = image
749            upload.seek(0)
750            self.passport_changed = True
751        return
752
753    @property
754    def title(self):
755        return self.context.access_code
756
757    @property
758    def label(self):
759        container_title = self.context.__parent__.title
760        return '%s Application Form' % container_title
761
762    def getTransitions(self):
763        """Return a list of dicts of allowed transition ids and titles.
764
765        Each list entry provides keys ``name`` and ``title`` for
766        internal name and (human readable) title of a single
767        transition.
768        """
769        allowed_transitions = self.wf_info.getManualTransitions()
770        return [dict(name='', title='No transition')] +[
771            dict(name=x, title=y) for x, y in allowed_transitions]
772
773    @grok.action('Save')
774    def save(self, **data):
775        changed_fields = self.applyData(self.context, **data)
776        changed_fields = changed_fields.values()
777        fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
778        self.context._p_changed = True
779        form = self.request.form
780        if form.has_key('transition') and form['transition']:
781            transition_id = form['transition']
782            self.wf_info.fireTransition(transition_id)
783        self.flash('Form has been saved.')
784        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
785        if fields_string:
786            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
787        return
788
789class EditApplicantStudent(EditApplicantFull):
790    """An applicant-centered edit view for applicant data.
791    """
792    grok.context(IApplicantEdit)
793    grok.name('edit')
794    grok.require('waeup.handleApplication')
795    form_fields = grok.AutoFields(IApplicantEdit).omit(
796        'locked', 'course_admitted', 'student_id',
797        'screening_score',
798        )
799    #form_fields['passport'].custom_widget = EncodingImageFileWidget
800    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
801    grok.template('form_edit')
802    manage_applications = False
803    title = u'Your Application Form'
804
805
806    def emitLockMessage(self):
807        self.flash('The requested form is locked (read-only).')
808        self.redirect(self.url(self.context))
809        return
810
811    def update(self):
812        if self.context.locked:
813            self.emitLockMessage()
814            return
815        datepicker.need() # Enable jQuery datepicker in date fields.
816        upload = self.request.form.get('form.passport', None)
817        if upload:
818            # We got a fresh upload
819            image = createWAeUPImageFile(upload.filename, upload)
820            # This would normally be totally illegal. We set context
821            # data without the complete form data being
822            # verified. Normally data is set in methods like `save`
823            # only, which is only called when all form data is correct
824            # (and if not, the context is not touched). With images
825            # and their uploads the problem then is, that the uploaded
826            # image won't be shown in a partially erranous form
827            # because the new uploaded image is still not part of the
828            # context obeject and the image widget cannot work around
829            # that (it has no access to the new data). For this reason
830            # we set the image here and not in 'save()' or other
831            # methods.
832            self.context.passport = image
833            upload.seek(0)
834            self.passport_changed = True
835        super(EditApplicantStudent, self).update()
836        return
837
838    def dataNotComplete(self):
839        if not self.request.form.get('confirm_passport', False):
840            return 'Passport confirmation box not ticked.'
841        #if len(self.errors) > 0:
842        #    return 'Form has errors.'
843        return False
844
845    @grok.action('Save')
846    def save(self, **data):
847        #if self.context.locked:
848        #    self.emitLockMessage()
849        #    return
850        self.applyData(self.context, **data)
851        self.context._p_changed = True
852        self.flash('Form has been saved.')
853        return
854
855    @grok.action('Final Submit')
856    def finalsubmit(self, **data):
857        #if self.context.locked:
858        #    self.emitLockMessage()
859        #    return
860        self.applyData(self.context, **data)
861        self.context._p_changed = True
862        if self.dataNotComplete():
863            self.flash(self.dataNotComplete())
864            return
865        state = IWorkflowState(self.context).getState()
866        # This shouldn't happen, but the application officer
867        # might have forgotten to lock the form after changing the state
868        if state != STARTED:
869            self.flash('This form cannot be submitted. Wrong state!')
870            return
871        IWorkflowInfo(self.context).fireTransition('submit')
872        self.context.application_date = datetime.now()
873        self.context.locked = True
874        self.flash('Form has been submitted.')
875        self.redirect(self.url(self.context))
876        return
877
878class ApplicantViewActionButton(ManageActionButton):
879    grok.context(IApplicant)
880    grok.view(EditApplicantFull)
881    grok.require('waeup.manageApplications')
882    icon = 'actionicon_view.png'
883    text = 'View application record'
884    target = 'index'
885
886class PassportImage(grok.View):
887    """Renders the passport image for applicants.
888    """
889    grok.name('passport.jpg')
890    grok.context(IApplicant)
891    grok.require('waeup.handleApplication')
892
893    def render(self):
894        # A filename chooser turns a context into a filename suitable
895        # for file storage.
896        chooser = IFileStoreNameChooser(self.context)
897        file_id = chooser.chooseName()
898        image = getUtility(IExtFileStore).getFile(file_id)
899        self.response.setHeader(
900            'Content-Type', 'image/jpeg')
901        if image is None:
902            # show placeholder image
903            return open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg'), 'rb')
904        return image
Note: See TracBrowser for help on using the repository browser.