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

Last change on this file since 6381 was 6377, checked in by uli, 14 years ago

Reenable application start workflow on login.

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