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

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

Much more logic for bed allocation, bed release.

Implement student relocation.

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