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

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

Throw in the complete mess of last 2 weeks. External file storage now works basically (tests pass), although there are lots of things still to remove, finetune, document, etc.

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