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

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

Don't add log message when no attribute has been changed, but log all transitions.

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