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

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

PIN stuff.

File size: 24.4 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
35
36from waeup.sirp.accesscodes import invalidate_accesscode
37from waeup.sirp.browser import (
38    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
39from waeup.sirp.browser.breadcrumbs import Breadcrumb
40from waeup.sirp.browser.layout import NullValidator
41from waeup.sirp.browser.pages import add_local_role, del_local_roles
42from waeup.sirp.browser.resources import datepicker, tabs, datatable
43from waeup.sirp.browser.viewlets import (
44    ManageActionButton, PrimaryNavTab, LeftSidebarLink
45    )
46from waeup.sirp.image.browser.widget import (
47    ThumbnailWidget, EncodingImageFileWidget,
48    )
49from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
50from waeup.sirp.permissions import get_users_with_local_roles
51from waeup.sirp.university.interfaces import ICertificate
52from waeup.sirp.widgets.datewidget import (
53    FriendlyDateWidget, FriendlyDateDisplayWidget)
54from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
55from waeup.sirp.widgets.objectwidget import (
56    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
57from waeup.sirp.widgets.multilistwidget import (
58    MultiListWidget, MultiListDisplayWidget)
59from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
60from waeup.sirp.applicants.interfaces import (
61    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
62    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab
63    )
64from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
65
66results_widget = CustomWidgetFactory(
67    WAeUPObjectWidget, ResultEntry)
68
69results_display_widget = CustomWidgetFactory(
70    WAeUPObjectDisplayWidget, ResultEntry)
71
72list_results_widget = CustomWidgetFactory(
73    MultiListWidget, subwidget=results_widget)
74
75list_results_display_widget = CustomWidgetFactory(
76    MultiListDisplayWidget, subwidget=results_display_widget)
77
78#TRANSITION_OBJECTS = create_workflow()
79
80#TRANSITION_DICT = dict([
81#    (transition_object.transition_id,transition_object.title)
82#    for transition_object in TRANSITION_OBJECTS])
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'Application Section'
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 applicants only'
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        pin = self.request.principal.access_code
417        if pin in self.context.keys():
418            self.redirect(self.url(self.context[pin], 'edit'))
419            return
420
421        # Mark pin as used
422        invalidate_accesscode(pin)
423
424        # Create applicant record
425        applicant = Applicant()
426        applicant.access_code = pin
427        self.context[pin] = applicant
428
429        # Assign current principal the owner role on created applicant
430        # record
431        role_manager = IPrincipalRoleManager(self.context[pin])
432        role_manager.assignRoleToPrincipal(
433            'waeup.local.ApplicationOwner', self.request.principal.id)
434        # Assign current principal the PortalUser role
435        role_manager = IPrincipalRoleManager(grok.getSite()['faculties'])
436        role_manager.assignRoleToPrincipal(
437            'waeup.PortalUser', self.request.principal.id)
438        # XXX: disable for now. Pins will get a different workflow.
439        #state = IWorkflowState(self.context[pin]).getState()
440        #if state == INITIALIZED:
441        #    IWorkflowInfo(self.context[pin]).fireTransition('start')
442        self.redirect(self.url(self.context[pin], 'edit'))
443        return
444
445class ApplicantAddFormPage(WAeUPAddFormPage):
446    """Add-form to add certificate to a department.
447    """
448    grok.context(IApplicantsContainer)
449    grok.require('waeup.manageApplications')
450    grok.name('addapplicant')
451    grok.template('applicantaddpage')
452    title = 'Applicants'
453    label = 'Add applicant'
454    pnav = 3
455
456    @property
457    def title(self):
458        return "Applicants Container: %s" % self.context.title
459
460    @property
461    def ac_prefix(self):
462        return self.context.ac_prefix
463
464    @grok.action('Create application record')
465    def addApplicant(self, **data):
466        ac_series = self.request.form.get('form.ac_series', None)
467        ac_number = self.request.form.get('form.ac_number', None)
468        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
469        if pin not in self.context.keys():
470            # Create applicant record
471            applicant = Applicant()
472            applicant.access_code = pin
473            self.context[pin] = applicant
474        self.redirect(self.url(self.context[pin], 'edit'))
475        return
476
477class AccessCodeLink(LeftSidebarLink):
478    grok.order(1)
479    grok.require('waeup.Public')
480
481    def render(self):
482        if not IApplicantPrincipal.providedBy(self.request.principal):
483            return ''
484        access_code = getattr(self.request.principal,'access_code',None)
485        if access_code:
486            applicant_object = get_applicant_data(access_code)
487            url = absoluteURL(applicant_object, self.request)
488            return u'<div class="portlet"><a href="%s/edit">%s</a></div>' % (
489                url,access_code)
490        return ''
491
492class DisplayApplicant(WAeUPDisplayFormPage):
493    grok.context(IApplicant)
494    grok.name('index')
495    grok.require('waeup.handleApplication')
496    form_fields = grok.AutoFields(IApplicant).omit(
497        'locked').omit('course_admitted')
498    #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
499    form_fields['passport'].custom_widget = ThumbnailWidget
500    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
501    label = 'Applicant'
502    grok.template('form_display')
503    pnav = 3
504
505    @property
506    def title(self):
507        return '%s' % self.context.access_code
508
509    @property
510    def label(self):
511        container_title = self.context.__parent__.title
512        return '%s Application Record' % container_title
513
514    def getCourseAdmitted(self):
515        """Return link, title and code in html format to the certificate
516           admitted.
517        """
518        course_admitted = self.context.course_admitted
519        if ICertificate.providedBy(course_admitted):
520            url = self.url(course_admitted)
521            title = course_admitted.title
522            code = course_admitted.code
523            return '<a href="%s">%s (%s)</a>' %(url,title,code)
524        return 'not yet admitted'
525
526class PDFActionButton(ManageActionButton):
527    grok.context(IApplicant)
528    grok.view(DisplayApplicant)
529    grok.require('waeup.handleApplication')
530    icon = 'actionicon_pdf.png'
531    text = 'Download pdf slip'
532    target = 'application_slip.pdf'
533
534class ExportPDFPage(grok.View):
535    """Deliver a PDF slip of the context.
536    """
537    grok.context(IApplicant)
538    grok.name('application_slip.pdf')
539    grok.require('waeup.handleApplication')
540    form_fields = grok.AutoFields(IApplicant).omit(
541        'locked').omit('course_admitted')
542    form_fields['passport'].custom_widget = ThumbnailWidget
543    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
544
545    prefix = 'form'
546
547    def getCourseAdmitted(self):
548        """Return title and code in html format to the certificate
549           admitted.
550        """
551        course_admitted = self.context.course_admitted
552        if ICertificate.providedBy(course_admitted):
553            title = course_admitted.title
554            code = course_admitted.code
555            return '%s (%s)' %(title,code)
556        return 'not yet admitted'
557
558    def setUpWidgets(self, ignore_request=False):
559        self.adapters = {}
560        self.widgets = setUpEditWidgets(
561            self.form_fields, self.prefix, self.context, self.request,
562            adapters=self.adapters, for_display=True,
563            ignore_request=ignore_request
564            )
565
566    # Render a demo pdf page
567    def render(self):
568        from reportlab.pdfgen import canvas
569        from reportlab.lib.units import cm
570        from reportlab.lib.pagesizes import A4, landscape
571        from reportlab.lib.styles import getSampleStyleSheet
572        from reportlab.platypus import Frame, Paragraph
573
574        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
575        width, height = A4
576        style = getSampleStyleSheet()
577        story = []
578        frame = Frame(1*cm,1*cm,width-(2*cm),height-(2*cm))
579        self.setUpWidgets()
580        #import pdb; pdb.set_trace()
581        for widget in self.widgets:
582            if widget.name != 'form.passport':
583                ptext = widget()
584                story.append(Paragraph(ptext, style["Normal"]))
585        frame.addFromList(story,pdf)
586        self.response.setHeader(
587            'Content-Type', 'application/pdf')
588        return pdf.getpdfdata()
589
590class ApplicantsManageActionButton(ManageActionButton):
591    grok.context(IApplicant)
592    grok.view(DisplayApplicant)
593    grok.require('waeup.manageApplications')
594    text = 'Edit application record'
595    target = 'edit_full'
596
597class EditApplicantFull(WAeUPEditFormPage):
598    """A full edit view for applicant data.
599    """
600    grok.context(IApplicant)
601    grok.name('edit_full')
602    grok.require('waeup.manageApplications')
603    form_fields = grok.AutoFields(IApplicant)   #.omit('locked')
604    form_fields['passport'].custom_widget = EncodingImageFileWidget
605    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
606    grok.template('form_edit')
607    manage_applications = True
608    pnav = 3
609
610    def update(self):
611        datepicker.need() # Enable jQuery datepicker in date fields.
612        super(EditApplicantFull, self).update()
613        self.wf_info = IWorkflowInfo(self.context)
614        return
615
616    @property
617    def title(self):
618        return self.context.access_code
619
620    @property
621    def label(self):
622        container_title = self.context.__parent__.title
623        return '%s Application Form' % container_title
624
625    def getTransitions(self):
626        """Return a list of dicts of allowed transition ids and titles.
627
628        Each list entry provides keys ``name`` and ``title`` for
629        internal name and (human readable) title of a single
630        transition.
631        """
632        allowed_transitions = self.wf_info.getManualTransitions()
633        return [dict(name='', title='No transition')] +[
634            dict(name=x, title=y) for x, y in allowed_transitions]
635
636    @grok.action('Save')
637    def save(self, **data):
638        self.applyData(self.context, **data)
639        self.context._p_changed = True
640        form = self.request.form
641        if form.has_key('transition') and form['transition']:
642            transition_id = form['transition']
643            self.wf_info.fireTransition(transition_id)
644        self.flash('Form has been saved.')
645        self.context.getApplicantsRootLogger().info('Saved')
646        return
647
648class EditApplicantStudent(EditApplicantFull):
649    """An applicant-centered edit view for applicant data.
650    """
651    grok.context(IApplicantEdit)
652    grok.name('edit')
653    grok.require('waeup.handleApplication')
654    form_fields = grok.AutoFields(IApplicantEdit).omit('locked')
655    form_fields['passport'].custom_widget = EncodingImageFileWidget
656    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
657    grok.template('form_edit')
658    manage_applications = False
659
660
661    def emitLockMessage(self):
662        self.flash('The requested form is locked (read-only).')
663        self.redirect(self.url(self.context))
664        return
665
666    def update(self):
667        if self.context.locked:
668            self.redirect(self.url(self.context))
669            return
670        datepicker.need() # Enable jQuery datepicker in date fields.
671        super(EditApplicantStudent, self).update()
672        return
673
674    def dataNotComplete(self):
675        if not self.request.form.get('confirm_passport', False):
676            return 'Passport confirmation box not ticked.'
677        if len(self.errors) > 0:
678            return 'Form has errors.'
679        return False
680
681    @grok.action('Save')
682    def save(self, **data):
683        if self.context.locked:
684            self.emitLockMessage()
685            return
686        self.applyData(self.context, **data)
687        self.context._p_changed = True
688        self.flash('Form has been saved.')
689        return
690
691    @grok.action('Final Submit')
692    def finalsubmit(self, **data):
693        if self.context.locked:
694            self.emitLockMessage()
695            return
696        self.applyData(self.context, **data)
697        self.context._p_changed = True
698        if self.dataNotComplete():
699            self.flash(self.dataNotComplete())
700            return
701        state = IWorkflowState(self.context).getState()
702        # This shouldn't happen, but the application officer
703        # might have forgotten to lock the form after changing the state
704        if state != STARTED:
705            self.flash('This form cannot be submitted. Wrong state!')
706            return
707        IWorkflowInfo(self.context).fireTransition('submit')
708        self.context.locked = True
709        self.flash('Form has been submitted.')
710        self.redirect(self.url(self.context))
711        return
712
Note: See TracBrowser for help on using the repository browser.