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
RevLine 
[5273]1## $Id: browser.py 7249 2011-12-01 15:41:31Z henrik $
[6078]2##
[7192]3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
[5273]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.
[6078]8##
[5273]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.
[6078]13##
[5273]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##
[5824]18"""UI components for basic applicants and related components.
[5273]19"""
[7063]20import os
[6082]21import sys
[5273]22import grok
23
[6816]24from datetime import datetime, date
25from zope.authentication.interfaces import ILogout, IAuthentication
[7240]26from zope.component import getUtility, createObject
[6081]27from zope.formlib.widget import CustomWidgetFactory
[6358]28from zope.formlib.form import setUpEditWidgets
[5937]29from zope.securitypolicy.interfaces import IPrincipalRoleManager
[6153]30from zope.traversing.browser import absoluteURL
[6081]31
[6303]32from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
[6357]33from reportlab.pdfgen import canvas
[6364]34from reportlab.lib.units import cm
[6390]35from reportlab.lib.pagesizes import A4
[6364]36from reportlab.lib.styles import getSampleStyleSheet
37from reportlab.platypus import (Frame, Paragraph, Image,
38    Table, Spacer)
39from reportlab.platypus.tables import TableStyle
[6357]40
[6469]41from waeup.sirp.accesscodes import invalidate_accesscode, get_access_code
42from waeup.sirp.accesscodes.workflow import USED
[5273]43from waeup.sirp.browser import (
[6321]44    WAeUPPage, WAeUPEditFormPage, WAeUPAddFormPage, WAeUPDisplayFormPage)
[6081]45from waeup.sirp.browser.breadcrumbs import Breadcrumb
[6103]46from waeup.sirp.browser.layout import NullValidator
[6321]47from waeup.sirp.browser.pages import add_local_role, del_local_roles
[6013]48from waeup.sirp.browser.resources import datepicker, tabs, datatable
[6153]49from waeup.sirp.browser.viewlets import (
50    ManageActionButton, PrimaryNavTab, LeftSidebarLink
51    )
[7063]52from waeup.sirp.interfaces import (
[7240]53    IWAeUPObject, ILocalRolesAssignable, IExtFileStore,
54    IFileStoreNameChooser, IPasswordValidator, IUserAccount)
[6321]55from waeup.sirp.permissions import get_users_with_local_roles
[7094]56from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH
[6254]57from waeup.sirp.university.interfaces import ICertificate
[7081]58from waeup.sirp.utils.helpers import string_from_bytes, file_size
[6054]59from waeup.sirp.widgets.datewidget import (
60    FriendlyDateWidget, FriendlyDateDisplayWidget)
[6084]61from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
[5303]62from waeup.sirp.widgets.objectwidget import (
[5301]63    WAeUPObjectWidget, WAeUPObjectDisplayWidget)
[7240]64from waeup.sirp.applicants import Applicant, get_applicant_data
[6081]65from waeup.sirp.applicants.interfaces import (
[7240]66    IApplicant, IApplicantEdit, IApplicantsRoot,
[7063]67    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
[7094]68    MAX_UPLOAD_SIZE,
[5686]69    )
[6353]70from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
[7240]71from waeup.sirp.students.viewlets import PrimaryStudentNavTab
[5320]72
[7240]73grok.context(IWAeUPObject) # Make IWAeUPObject the default context
[5273]74
[6067]75class ApplicantsRootPage(WAeUPPage):
[5822]76    grok.context(IApplicantsRoot)
77    grok.name('index')
[6153]78    grok.require('waeup.Public')
[5822]79    title = 'Applicants'
[6069]80    label = 'Application Section'
[5843]81    pnav = 3
[6012]82
83    def update(self):
[6067]84        super(ApplicantsRootPage, self).update()
[6012]85        datatable.need()
86        return
87
[5828]88class ManageApplicantsRootActionButton(ManageActionButton):
89    grok.context(IApplicantsRoot)
[6067]90    grok.view(ApplicantsRootPage)
[7136]91    grok.require('waeup.manageApplication')
[6069]92    text = 'Manage application section'
[5828]93
[6069]94class ApplicantsRootManageFormPage(WAeUPEditFormPage):
[5828]95    grok.context(IApplicantsRoot)
96    grok.name('manage')
[6107]97    grok.template('applicantsrootmanagepage')
[6069]98    title = 'Applicants'
99    label = 'Manage application section'
[5843]100    pnav = 3
[7136]101    grok.require('waeup.manageApplication')
[6069]102    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
[6184]103    tabtwoactions1 = ['Remove selected local roles']
104    tabtwoactions2 = ['Add local role']
[6069]105    subunits = 'Applicants Containers'
[6078]106
[6069]107    def update(self):
108        tabs.need()
[6108]109        datatable.need()
[6069]110        return super(ApplicantsRootManageFormPage, self).update()
[5828]111
[6184]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
[6069]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))
[6078]143        self.redirect(self.url(self.context, '@@manage')+'#tab-1')
144        return
[5828]145
[6069]146    @grok.action('Add applicants container', validator=NullValidator)
147    def addApplicantsContainer(self, **data):
148        self.redirect(self.url(self.context, '@@add'))
[6078]149        return
150
[6069]151    @grok.action('Cancel', validator=NullValidator)
152    def cancel(self, **data):
153        self.redirect(self.url(self.context))
[6078]154        return
155
[6184]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
[6069]164class ApplicantsContainerAddFormPage(WAeUPAddFormPage):
[5822]165    grok.context(IApplicantsRoot)
[7136]166    grok.require('waeup.manageApplication')
[5822]167    grok.name('add')
[6107]168    grok.template('applicantscontaineraddpage')
[6069]169    title = 'Applicants'
170    label = 'Add applicants container'
[5843]171    pnav = 3
[6078]172
[6103]173    form_fields = grok.AutoFields(
174        IApplicantsContainerAdd).omit('code').omit('title')
[6083]175    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
176    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[6078]177
[6083]178    def update(self):
179        datepicker.need() # Enable jQuery datepicker in date fields.
180        return super(ApplicantsContainerAddFormPage, self).update()
181
[6069]182    @grok.action('Add applicants container')
183    def addApplicantsContainer(self, **data):
[6103]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)
[6087]188        if code in self.context.keys():
[6105]189            self.flash(
190                'An applicants container for the same application '
191                'type and entrance year exists already in the database.')
[5822]192            return
193        # Add new applicants container...
[6083]194        provider = data['provider'][1]
[5822]195        container = provider.factory()
[6069]196        self.applyData(container, **data)
[6087]197        container.code = code
198        container.title = title
199        self.context[code] = container
[6105]200        self.flash('Added: "%s".' % code)
[6069]201        self.redirect(self.url(self.context, u'@@manage')+'#tab-1')
[5822]202        return
[6078]203
[6103]204    @grok.action('Cancel', validator=NullValidator)
[6069]205    def cancel(self, **data):
[6103]206        self.redirect(self.url(self.context, '@@manage') + '#tab-1')
[6078]207
[5845]208class ApplicantsRootBreadcrumb(Breadcrumb):
209    """A breadcrumb for applicantsroot.
210    """
211    grok.context(IApplicantsRoot)
[6654]212    title = u'Applicants'
[6078]213
[5845]214class ApplicantsContainerBreadcrumb(Breadcrumb):
215    """A breadcrumb for applicantscontainers.
216    """
217    grok.context(IApplicantsContainer)
[6319]218
[6153]219class ApplicantBreadcrumb(Breadcrumb):
220    """A breadcrumb for applicants.
221    """
222    grok.context(IApplicant)
[6319]223
[6153]224    @property
225    def title(self):
226        """Get a title for a context.
227        """
[7240]228        return self.context.application_number
[5828]229
[7184]230class ApplicantsAuthTab(PrimaryNavTab):
[6153]231    """Applicants tab in primary navigation.
[5828]232    """
233    grok.context(IWAeUPObject)
234    grok.order(3)
[7240]235    grok.require('waeup.viewApplicationsTab')
[5843]236    pnav = 3
[5828]237    tab_title = u'Applicants'
238
239    @property
240    def link_target(self):
241        return self.view.application_url('applicants')
242
[7184]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.
[7243]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
[7184]259
[7240]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
[6029]277class ApplicantsContainerPage(WAeUPDisplayFormPage):
[5830]278    """The standard view for regular applicant containers.
279    """
280    grok.context(IApplicantsContainer)
281    grok.name('index')
[6153]282    grok.require('waeup.Public')
[6029]283    grok.template('applicantscontainerpage')
[5850]284    pnav = 3
[6053]285
[6105]286    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
[6054]287    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
288    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
[6084]289    form_fields['description'].custom_widget = ReSTDisplayWidget
[6053]290
[5837]291    @property
292    def title(self):
[6087]293        return "Applicants Container: %s" % self.context.title
[5837]294
295    @property
296    def label(self):
[6087]297        return self.context.title
[5830]298
[6107]299class ApplicantsContainerManageActionButton(ManageActionButton):
[6336]300    grok.order(1)
[5832]301    grok.context(IApplicantsContainer)
302    grok.view(ApplicantsContainerPage)
[7136]303    grok.require('waeup.manageApplication')
[6070]304    text = 'Manage applicants container'
[5832]305
[7240]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'
[5832]314
[6107]315class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
[5837]316    grok.context(IApplicantsContainer)
[5850]317    grok.name('manage')
[6107]318    grok.template('applicantscontainermanagepage')
[6105]319    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
320    taboneactions = ['Save','Cancel']
321    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
[6184]322    tabthreeactions1 = ['Remove selected local roles']
323    tabthreeactions2 = ['Add local role']
[5844]324    # Use friendlier date widget...
[6054]325    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
326    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[7136]327    grok.require('waeup.manageApplication')
[5850]328
329    @property
330    def title(self):
[6087]331        return "Applicants Container: %s" % self.context.title
[6078]332
[5850]333    @property
334    def label(self):
[6087]335        return 'Manage applicants container'
[5850]336
[5845]337    pnav = 3
[5837]338
339    def update(self):
[5850]340        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]341        tabs.need()
[6015]342        datatable.need()  # Enable jQurey datatables for contents listing
[6107]343        return super(ApplicantsContainerManageFormPage, self).update()
[5837]344
[6184]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
[5850]359    @grok.action('Save')
[5837]360    def apply(self, **data):
361        self.applyData(self.context, **data)
362        self.flash('Data saved.')
363        return
[6078]364
[6105]365    # ToDo: Show warning message before deletion
366    @grok.action('Remove selected')
367    def delApplicant(self, **data):
[6189]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
[6105]389
390    @grok.action('Add applicant', validator=NullValidator)
391    def addApplicant(self, **data):
[6327]392        self.redirect(self.url(self.context, 'addapplicant'))
393        return
[6105]394
395    @grok.action('Cancel', validator=NullValidator)
[5837]396    def cancel(self, **data):
397        self.redirect(self.url(self.context))
398        return
[5886]399
[6184]400    @grok.action('Add local role', validator=NullValidator)
401    def addLocalRole(self, **data):
402        return add_local_role(self,3, **data)
[6105]403
[6184]404    @grok.action('Remove selected local roles')
405    def delLocalRoles(self, **data):
406        return del_local_roles(self,3,**data)
407
[6327]408class ApplicantAddFormPage(WAeUPAddFormPage):
[6622]409    """Add-form to add an applicant.
[6327]410    """
411    grok.context(IApplicantsContainer)
[7136]412    grok.require('waeup.manageApplication')
[6327]413    grok.name('addapplicant')
[7240]414    #grok.template('applicantaddpage')
415    form_fields = grok.AutoFields(IApplicant).select(
416        'firstname', 'middlenames', 'lastname',
417        'email', 'phone')
[6327]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):
[7240]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'))
[6327]433        return
434
[7200]435class ApplicantDisplayFormPage(WAeUPDisplayFormPage):
[5273]436    grok.context(IApplicant)
437    grok.name('index')
[7113]438    grok.require('waeup.viewApplication')
[7200]439    grok.template('applicantdisplaypage')
[6320]440    form_fields = grok.AutoFields(IApplicant).omit(
[7240]441        'locked', 'course_admitted', 'password')
[6054]442    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[5273]443    label = 'Applicant'
[5843]444    pnav = 3
[5273]445
[7063]446    def update(self):
447        self.passport_url = self.url(self.context, 'passport.jpg')
[7240]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')
[7063]451        return
452
[6196]453    @property
[7240]454    def hasPassword(self):
455        if self.context.password:
456            return 'set'
457        return 'unset'
458
459    @property
[6196]460    def title(self):
[7240]461        return 'Application Record %s' % self.context.application_number
[6196]462
463    @property
464    def label(self):
465        container_title = self.context.__parent__.title
[7240]466        return '%s Application Record %s' % (
467            container_title, self.context.application_number)
[6196]468
[6254]469    def getCourseAdmitted(self):
[6355]470        """Return link, title and code in html format to the certificate
471           admitted.
[6351]472        """
[6254]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
[6366]478            return '<a href="%s">%s - %s</a>' %(url,code,title)
[6457]479        return ''
[6254]480
[6358]481class PDFActionButton(ManageActionButton):
482    grok.context(IApplicant)
[7136]483    grok.require('waeup.viewApplication')
[6358]484    icon = 'actionicon_pdf.png'
[6367]485    text = 'Download application slip'
[6358]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')
[7136]493    grok.require('waeup.viewApplication')
[6358]494    form_fields = grok.AutoFields(IApplicant).omit(
[7240]495        'locked', 'course_admitted')
[6358]496    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
497    prefix = 'form'
498
[6363]499    @property
500    def label(self):
501        container_title = self.context.__parent__.title
[7240]502        return '%s Application Record %s' % (
503            container_title, self.context.application_number)
[6363]504
[6358]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
[6365]513            return '%s - %s' %(code,title)
[6462]514        return ''
[6358]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):
[6364]525        SLIP_STYLE = TableStyle(
526            [('VALIGN',(0,0),(-1,-1),'TOP')]
527            )
[6358]528
529        pdf = canvas.Canvas('application_slip.pdf',pagesize=A4)
[6364]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')
[6358]535        width, height = A4
536        style = getSampleStyleSheet()
[6365]537        pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
[6363]538
[6358]539        story = []
[6365]540        frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
[6363]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 = []
[6365]546        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
[6364]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"]))
[6363]552        story.append(Spacer(1, 24))
[7063]553
[7068]554
[7063]555        # insert passport photograph
556        img = getUtility(IExtFileStore).getFileByContext(self.context)
557        if img is None:
[7089]558            img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb')
[7063]559        doc_img = Image(img.name, width=4*cm, height=3*cm, kind='bound')
[7068]560        story.append(doc_img)
561        story.append(Spacer(1, 18))
[7063]562
563        # render widget fields
[7068]564        data = []
[6358]565        self.setUpWidgets()
566        for widget in self.widgets:
[6462]567            f_label = '<font size=12>%s</font>:' % widget.label.strip()
[6363]568            f_label = Paragraph(f_label, style["Normal"])
[7063]569            f_text = '<font size=12>%s</font>' % widget()
570            f_text = Paragraph(f_text, style["Normal"])
571            data.append([f_label,f_text])
[6462]572        f_label = '<font size=12>Admitted Course of Study:</font>'
[6363]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])
[6364]577        table = Table(data,style=SLIP_STYLE)
[6363]578        story.append(table)
579        frame_body.addFromList(story,pdf)
580
[6364]581        story = []
[6365]582        frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
[6364]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
[6358]588        self.response.setHeader(
589            'Content-Type', 'application/pdf')
590        return pdf.getpdfdata()
591
[6383]592class ApplicantManageActionButton(ManageActionButton):
[6198]593    grok.context(IApplicant)
[7200]594    grok.view(ApplicantDisplayFormPage)
[7136]595    grok.require('waeup.manageApplication')
[6383]596    text = 'Manage application record'
[7200]597    target = 'manage'
[6198]598
[7240]599class ApplicantEditActionButton(ManageActionButton):
600    grok.context(IApplicant)
601    grok.view(ApplicantDisplayFormPage)
602    grok.require('waeup.handleApplication')
603    text = 'Edit application record'
604    target ='edit'
[7081]605
[7240]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
[7081]614def handle_img_upload(upload, context, view):
[7063]615    """Handle upload of applicant image.
[7081]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.
[7063]621    """
[7081]622    size = file_size(upload)
623    if size > MAX_UPLOAD_SIZE:
624        view.flash('Uploaded image is too big!')
625        return False
[7247]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
[7081]631    upload.seek(0) # file pointer moved when determining size
[7063]632    store = getUtility(IExtFileStore)
633    file_id = IFileStoreNameChooser(context).chooseName()
634    store.createFile(file_id, upload)
[7081]635    return True
[7063]636
[7200]637class ApplicantManageFormPage(WAeUPEditFormPage):
[6196]638    """A full edit view for applicant data.
639    """
640    grok.context(IApplicant)
[7200]641    grok.name('manage')
[7136]642    grok.require('waeup.manageApplication')
[6476]643    form_fields = grok.AutoFields(IApplicant)
[6196]644    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[7200]645    grok.template('applicanteditpage')
[6322]646    manage_applications = True
[6196]647    pnav = 3
648
649    def update(self):
650        datepicker.need() # Enable jQuery datepicker in date fields.
[7200]651        super(ApplicantManageFormPage, self).update()
[6353]652        self.wf_info = IWorkflowInfo(self.context)
[7081]653        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
[7084]654        self.passport_changed = None
[6598]655        upload = self.request.form.get('form.passport', None)
656        if upload:
657            # We got a fresh upload
[7084]658            self.passport_changed = handle_img_upload(
659                upload, self.context, self)
[6196]660        return
661
662    @property
663    def title(self):
[7240]664        return 'Application Record %s' % self.context.application_number
[6196]665
666    @property
667    def label(self):
668        container_title = self.context.__parent__.title
[7240]669        return '%s Application Form %s' % (
670            container_title, self.context.application_number)
[6196]671
[6303]672    def getTransitions(self):
[6351]673        """Return a list of dicts of allowed transition ids and titles.
[6353]674
675        Each list entry provides keys ``name`` and ``title`` for
676        internal name and (human readable) title of a single
677        transition.
[6349]678        """
[6353]679        allowed_transitions = self.wf_info.getManualTransitions()
[6355]680        return [dict(name='', title='No transition')] +[
681            dict(name=x, title=y) for x, y in allowed_transitions]
[6303]682
[6196]683    @grok.action('Save')
684    def save(self, **data):
[7240]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
[7084]694        if self.passport_changed is False:  # False is not None!
695            return # error during image upload. Ignore other values
[6475]696        changed_fields = self.applyData(self.context, **data)
[7199]697        # Turn list of lists into single list
698        if changed_fields:
699            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
[7240]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')
[7199]708        fields_string = ' + '.join(changed_fields)
[7085]709        trans_id = form.get('transition', None)
710        if trans_id:
711            self.wf_info.fireTransition(trans_id)
[6196]712        self.flash('Form has been saved.')
[6475]713        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
[6644]714        if fields_string:
715            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
[6196]716        return
717
[7200]718class ApplicantEditFormPage(ApplicantManageFormPage):
[5982]719    """An applicant-centered edit view for applicant data.
720    """
[6196]721    grok.context(IApplicantEdit)
[5273]722    grok.name('edit')
[6198]723    grok.require('waeup.handleApplication')
[6459]724    form_fields = grok.AutoFields(IApplicantEdit).omit(
[6476]725        'locked', 'course_admitted', 'student_id',
[7240]726        'screening_score', 'applicant_id'
[6459]727        )
[6054]728    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[7200]729    grok.template('applicanteditpage')
[6322]730    manage_applications = False
[6465]731    title = u'Your Application Form'
[5484]732
[7145]733    def emit_lock_message(self):
[6105]734        self.flash('The requested form is locked (read-only).')
[5941]735        self.redirect(self.url(self.context))
736        return
[6078]737
[5686]738    def update(self):
[5941]739        if self.context.locked:
[7145]740            self.emit_lock_message()
[5941]741            return
[6040]742        datepicker.need() # Enable jQuery datepicker in date fields.
[7200]743        super(ApplicantEditFormPage, self).update()
[5686]744        return
[5952]745
[6196]746    def dataNotComplete(self):
[6322]747        if not self.request.form.get('confirm_passport', False):
[6196]748            return 'Passport confirmation box not ticked.'
749        return False
[5952]750
[5273]751    @grok.action('Save')
752    def save(self, **data):
[7084]753        if self.passport_changed is False:  # False is not None!
754            return # error during image upload. Ignore other values
[5273]755        self.applyData(self.context, **data)
[7094]756        #self.context._p_changed = True
[6196]757        self.flash('Form has been saved.')
[5273]758        return
759
[5484]760    @grok.action('Final Submit')
761    def finalsubmit(self, **data):
[7084]762        if self.passport_changed is False:  # False is not None!
763            return # error during image upload. Ignore other values
[5273]764        self.applyData(self.context, **data)
[5484]765        self.context._p_changed = True
[6196]766        if self.dataNotComplete():
767            self.flash(self.dataNotComplete())
[5941]768            return
[6303]769        state = IWorkflowState(self.context).getState()
[6322]770        # This shouldn't happen, but the application officer
771        # might have forgotten to lock the form after changing the state
[6303]772        if state != STARTED:
[6322]773            self.flash('This form cannot be submitted. Wrong state!')
[6303]774            return
775        IWorkflowInfo(self.context).fireTransition('submit')
[6476]776        self.context.application_date = datetime.now()
[5941]777        self.context.locked = True
[6196]778        self.flash('Form has been submitted.')
779        self.redirect(self.url(self.context))
[5273]780        return
[5941]781
[6367]782class ApplicantViewActionButton(ManageActionButton):
783    grok.context(IApplicant)
[7200]784    grok.view(ApplicantManageFormPage)
[7240]785    grok.require('waeup.viewApplication')
[6383]786    icon = 'actionicon_view.png'
[6367]787    text = 'View application record'
[6598]788    target = 'index'
[7063]789
790class PassportImage(grok.View):
791    """Renders the passport image for applicants.
792    """
793    grok.name('passport.jpg')
794    grok.context(IApplicant)
[7113]795    grok.require('waeup.viewApplication')
[7063]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
[7089]805            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
[7063]806        return image
Note: See TracBrowser for help on using the repository browser.