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

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

ApplicantLoginPage? not used anymore.

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