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

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

Redirect to specified tabs using the request's QUERY_STRING value. This suboptimal solution will be replaced later by a pure Javascript solution.

  • Property svn:keywords set to Id
File size: 32.9 KB
RevLine 
[5273]1## $Id: browser.py 7484 2012-01-16 07:06:21Z 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
[7250]23from time import time
[7370]24from datetime import datetime, date
[7392]25from zope.component import getUtility, createObject, getAdapter
[6358]26from zope.formlib.form import setUpEditWidgets
[7322]27from hurry.workflow.interfaces import (
28    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
[7363]29from waeup.sirp.applicants.interfaces import (
30    IApplicant, IApplicantEdit, IApplicantsRoot,
31    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
32    MAX_UPLOAD_SIZE, IApplicantOnlinePayment,
33    )
34from waeup.sirp.applicants.workflow import INITIALIZED, STARTED, PAID, SUBMITTED
[5273]35from waeup.sirp.browser import (
[7363]36    SIRPPage, SIRPEditFormPage, SIRPAddFormPage, SIRPDisplayFormPage,
37    DEFAULT_PASSPORT_IMAGE_PATH)
38from waeup.sirp.browser.interfaces import ICaptchaManager
[6081]39from waeup.sirp.browser.breadcrumbs import Breadcrumb
[7459]40from waeup.sirp.browser.layout import (
41    NullValidator, jsaction, action, UtilityView)
[6321]42from waeup.sirp.browser.pages import add_local_role, del_local_roles
[7330]43from waeup.sirp.browser.resources import datepicker, tabs, datatable, warning
[7063]44from waeup.sirp.interfaces import (
[7392]45    ISIRPObject, ILocalRolesAssignable, IExtFileStore, IPDF,
[7365]46    IFileStoreNameChooser, IPasswordValidator, IUserAccount, ISIRPUtils)
[6321]47from waeup.sirp.permissions import get_users_with_local_roles
[7363]48from waeup.sirp.students.interfaces import IStudentsUtils
[7081]49from waeup.sirp.utils.helpers import string_from_bytes, file_size
[6054]50from waeup.sirp.widgets.datewidget import (
[7250]51    FriendlyDateWidget, FriendlyDateDisplayWidget,
52    FriendlyDatetimeDisplayWidget)
[7363]53from waeup.sirp.widgets.phonewidget import PhoneWidget
[6084]54from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
[5320]55
[7321]56grok.context(ISIRPObject) # Make ISIRPObject the default context
[5273]57
[7321]58class ApplicantsRootPage(SIRPPage):
[5822]59    grok.context(IApplicantsRoot)
60    grok.name('index')
[6153]61    grok.require('waeup.Public')
[6069]62    label = 'Application Section'
[5843]63    pnav = 3
[6012]64
65    def update(self):
[6067]66        super(ApplicantsRootPage, self).update()
[6012]67        datatable.need()
68        return
69
[7321]70class ApplicantsRootManageFormPage(SIRPEditFormPage):
[5828]71    grok.context(IApplicantsRoot)
72    grok.name('manage')
[6107]73    grok.template('applicantsrootmanagepage')
[6069]74    label = 'Manage application section'
[5843]75    pnav = 3
[7136]76    grok.require('waeup.manageApplication')
[6069]77    taboneactions = ['Add applicants container', 'Remove selected','Cancel']
[6184]78    tabtwoactions1 = ['Remove selected local roles']
79    tabtwoactions2 = ['Add local role']
[6069]80    subunits = 'Applicants Containers'
[6078]81
[6069]82    def update(self):
83        tabs.need()
[6108]84        datatable.need()
[7330]85        warning.need()
[6069]86        return super(ApplicantsRootManageFormPage, self).update()
[5828]87
[6184]88    def getLocalRoles(self):
89        roles = ILocalRolesAssignable(self.context)
90        return roles()
91
92    def getUsers(self):
93        """Get a list of all users.
94        """
95        for key, val in grok.getSite()['users'].items():
96            url = self.url(val)
97            yield(dict(url=url, name=key, val=val))
98
99    def getUsersWithLocalRoles(self):
100        return get_users_with_local_roles(self.context)
101
[7330]102    @jsaction('Remove selected')
[6069]103    def delApplicantsContainers(self, **data):
104        form = self.request.form
105        child_id = form['val_id']
106        if not isinstance(child_id, list):
107            child_id = [child_id]
108        deleted = []
109        for id in child_id:
110            try:
111                del self.context[id]
112                deleted.append(id)
113            except:
114                self.flash('Could not delete %s: %s: %s' % (
115                        id, sys.exc_info()[0], sys.exc_info()[1]))
116        if len(deleted):
117            self.flash('Successfully removed: %s' % ', '.join(deleted))
[7484]118        self.redirect(self.url(self.context, '@@manage'))
[6078]119        return
[5828]120
[7459]121    @action('Add applicants container', validator=NullValidator)
[6069]122    def addApplicantsContainer(self, **data):
123        self.redirect(self.url(self.context, '@@add'))
[6078]124        return
125
[7459]126    @action('Cancel', validator=NullValidator)
[6069]127    def cancel(self, **data):
128        self.redirect(self.url(self.context))
[6078]129        return
130
[7459]131    @action('Add local role', validator=NullValidator)
[6184]132    def addLocalRole(self, **data):
[7484]133        return add_local_role(self,3, **data)
[6184]134
[7459]135    @action('Remove selected local roles')
[6184]136    def delLocalRoles(self, **data):
[7484]137        return del_local_roles(self,3,**data)
[6184]138
[7321]139class ApplicantsContainerAddFormPage(SIRPAddFormPage):
[5822]140    grok.context(IApplicantsRoot)
[7136]141    grok.require('waeup.manageApplication')
[5822]142    grok.name('add')
[6107]143    grok.template('applicantscontaineraddpage')
[6069]144    label = 'Add applicants container'
[5843]145    pnav = 3
[6078]146
[6103]147    form_fields = grok.AutoFields(
148        IApplicantsContainerAdd).omit('code').omit('title')
[6083]149    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
150    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[6078]151
[6083]152    def update(self):
153        datepicker.need() # Enable jQuery datepicker in date fields.
154        return super(ApplicantsContainerAddFormPage, self).update()
155
[7459]156    @action('Add applicants container')
[6069]157    def addApplicantsContainer(self, **data):
[6103]158        year = data['year']
159        code = u'%s%s' % (data['prefix'], year)
160        prefix = application_types_vocab.getTerm(data['prefix'])
161        title = u'%s %s/%s' % (prefix.title, year, year + 1)
[6087]162        if code in self.context.keys():
[6105]163            self.flash(
164                'An applicants container for the same application '
165                'type and entrance year exists already in the database.')
[5822]166            return
167        # Add new applicants container...
[6083]168        provider = data['provider'][1]
[5822]169        container = provider.factory()
[6069]170        self.applyData(container, **data)
[6087]171        container.code = code
172        container.title = title
173        self.context[code] = container
[6105]174        self.flash('Added: "%s".' % code)
[7484]175        self.redirect(self.url(self.context, u'@@manage'))
[5822]176        return
[6078]177
[7459]178    @action('Cancel', validator=NullValidator)
[6069]179    def cancel(self, **data):
[7484]180        self.redirect(self.url(self.context, '@@manage'))
[6078]181
[5845]182class ApplicantsRootBreadcrumb(Breadcrumb):
183    """A breadcrumb for applicantsroot.
184    """
185    grok.context(IApplicantsRoot)
[6654]186    title = u'Applicants'
[6078]187
[5845]188class ApplicantsContainerBreadcrumb(Breadcrumb):
189    """A breadcrumb for applicantscontainers.
190    """
191    grok.context(IApplicantsContainer)
[6319]192
[6153]193class ApplicantBreadcrumb(Breadcrumb):
194    """A breadcrumb for applicants.
195    """
196    grok.context(IApplicant)
[6319]197
[6153]198    @property
199    def title(self):
200        """Get a title for a context.
201        """
[7240]202        return self.context.application_number
[5828]203
[7250]204class OnlinePaymentBreadcrumb(Breadcrumb):
205    """A breadcrumb for payments.
206    """
207    grok.context(IApplicantOnlinePayment)
208
209    @property
210    def title(self):
211        return self.context.p_id
212
[7321]213class ApplicantsContainerPage(SIRPDisplayFormPage):
[5830]214    """The standard view for regular applicant containers.
215    """
216    grok.context(IApplicantsContainer)
217    grok.name('index')
[6153]218    grok.require('waeup.Public')
[6029]219    grok.template('applicantscontainerpage')
[5850]220    pnav = 3
[6053]221
[6105]222    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
[6054]223    form_fields['startdate'].custom_widget = FriendlyDateDisplayWidget('le')
224    form_fields['enddate'].custom_widget = FriendlyDateDisplayWidget('le')
[6084]225    form_fields['description'].custom_widget = ReSTDisplayWidget
[6053]226
[5837]227    @property
[7467]228    def label(self):
[6087]229        return "Applicants Container: %s" % self.context.title
[5837]230
[7321]231class ApplicantsContainerManageFormPage(SIRPEditFormPage):
[5837]232    grok.context(IApplicantsContainer)
[5850]233    grok.name('manage')
[6107]234    grok.template('applicantscontainermanagepage')
[6105]235    form_fields = grok.AutoFields(IApplicantsContainer).omit('title')
236    taboneactions = ['Save','Cancel']
237    tabtwoactions = ['Add applicant', 'Remove selected','Cancel']
[6184]238    tabthreeactions1 = ['Remove selected local roles']
239    tabthreeactions2 = ['Add local role']
[5844]240    # Use friendlier date widget...
[6054]241    form_fields['startdate'].custom_widget = FriendlyDateWidget('le')
242    form_fields['enddate'].custom_widget = FriendlyDateWidget('le')
[7136]243    grok.require('waeup.manageApplication')
[5850]244
245    @property
246    def label(self):
[6087]247        return 'Manage applicants container'
[5850]248
[5845]249    pnav = 3
[5837]250
251    def update(self):
[5850]252        datepicker.need() # Enable jQuery datepicker in date fields.
[5982]253        tabs.need()
[7484]254        self.tab1 = self.tab2 = self.tab3 = ''
255        qs = self.request.get('QUERY_STRING', '')
256        if not qs:
257            qs = 'tab1'
258        setattr(self, qs, 'active')
[7330]259        warning.need()
[6015]260        datatable.need()  # Enable jQurey datatables for contents listing
[6107]261        return super(ApplicantsContainerManageFormPage, self).update()
[5837]262
[6184]263    def getLocalRoles(self):
264        roles = ILocalRolesAssignable(self.context)
265        return roles()
266
267    def getUsers(self):
268        """Get a list of all users.
269        """
270        for key, val in grok.getSite()['users'].items():
271            url = self.url(val)
272            yield(dict(url=url, name=key, val=val))
273
274    def getUsersWithLocalRoles(self):
275        return get_users_with_local_roles(self.context)
276
[7459]277    @action('Save')
[5837]278    def apply(self, **data):
279        self.applyData(self.context, **data)
280        self.flash('Data saved.')
281        return
[6078]282
[7330]283    @jsaction('Remove selected')
[6105]284    def delApplicant(self, **data):
[6189]285        form = self.request.form
286        if form.has_key('val_id'):
287            child_id = form['val_id']
288        else:
289            self.flash('No applicant selected!')
[7484]290            self.redirect(self.url(self.context, '@@manage')+'?tab2')
[6189]291            return
292        if not isinstance(child_id, list):
293            child_id = [child_id]
294        deleted = []
295        for id in child_id:
296            try:
297                del self.context[id]
298                deleted.append(id)
299            except:
300                self.flash('Could not delete %s: %s: %s' % (
301                        id, sys.exc_info()[0], sys.exc_info()[1]))
302        if len(deleted):
303            self.flash('Successfully removed: %s' % ', '.join(deleted))
[7484]304        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
[6189]305        return
[6105]306
[7459]307    @action('Add applicant', validator=NullValidator)
[6105]308    def addApplicant(self, **data):
[6327]309        self.redirect(self.url(self.context, 'addapplicant'))
310        return
[6105]311
[7459]312    @action('Cancel', validator=NullValidator)
[5837]313    def cancel(self, **data):
314        self.redirect(self.url(self.context))
315        return
[5886]316
[7459]317    @action('Add local role', validator=NullValidator)
[6184]318    def addLocalRole(self, **data):
319        return add_local_role(self,3, **data)
[6105]320
[7459]321    @action('Remove selected local roles')
[6184]322    def delLocalRoles(self, **data):
323        return del_local_roles(self,3,**data)
324
[7321]325class ApplicantAddFormPage(SIRPAddFormPage):
[6622]326    """Add-form to add an applicant.
[6327]327    """
328    grok.context(IApplicantsContainer)
[7136]329    grok.require('waeup.manageApplication')
[6327]330    grok.name('addapplicant')
[7240]331    #grok.template('applicantaddpage')
332    form_fields = grok.AutoFields(IApplicant).select(
[7356]333        'firstname', 'middlename', 'lastname',
[7240]334        'email', 'phone')
[6327]335    label = 'Add applicant'
336    pnav = 3
337
[7459]338    @action('Create application record')
[6327]339    def addApplicant(self, **data):
[7260]340        applicant = createObject(u'waeup.Applicant')
[7240]341        self.applyData(applicant, **data)
342        self.context.addApplicant(applicant)
343        self.flash('Applicant record created.')
[7363]344        self.redirect(
345            self.url(self.context[applicant.application_number], 'index'))
[6327]346        return
347
[7321]348class ApplicantDisplayFormPage(SIRPDisplayFormPage):
[5273]349    grok.context(IApplicant)
350    grok.name('index')
[7113]351    grok.require('waeup.viewApplication')
[7200]352    grok.template('applicantdisplaypage')
[6320]353    form_fields = grok.AutoFields(IApplicant).omit(
[7347]354        'locked', 'course_admitted', 'password')
[6054]355    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
[5273]356    label = 'Applicant'
[5843]357    pnav = 3
[5273]358
[7063]359    def update(self):
360        self.passport_url = self.url(self.context, 'passport.jpg')
[7240]361        # Mark application as started if applicant logs in for the first time
[7272]362        usertype = getattr(self.request.principal, 'user_type', None)
363        if usertype == 'applicant' and \
364            IWorkflowState(self.context).getState() == INITIALIZED:
[7240]365            IWorkflowInfo(self.context).fireTransition('start')
[7063]366        return
367
[6196]368    @property
[7240]369    def hasPassword(self):
370        if self.context.password:
371            return 'set'
372        return 'unset'
373
374    @property
[6196]375    def label(self):
376        container_title = self.context.__parent__.title
[7240]377        return '%s Application Record %s' % (
378            container_title, self.context.application_number)
[6196]379
[7347]380    def getCourseAdmitted(self):
381        """Return link, title and code in html format to the certificate
382           admitted.
383        """
384        course_admitted = self.context.course_admitted
[7351]385        if getattr(course_admitted, '__parent__',None):
[7347]386            url = self.url(course_admitted)
387            title = course_admitted.title
388            code = course_admitted.code
389            return '<a href="%s">%s - %s</a>' %(url,code,title)
390        return ''
[6254]391
[7259]392class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
393    grok.context(IApplicant)
394    grok.name('base')
395    form_fields = grok.AutoFields(IApplicant).select(
396        'applicant_id', 'firstname', 'lastname','email', 'course1')
397
[7459]398class CreateStudentPage(UtilityView, grok.View):
[7341]399    """Create a student object from applicatnt data
400    and copy applicant object.
401    """
402    grok.context(IApplicant)
403    grok.name('createstudent')
404    grok.require('waeup.manageStudent')
405
406    def update(self):
407        msg = self.context.createStudent()[1]
408        self.flash(msg)
409        self.redirect(self.url(self.context))
410        return
411
412    def render(self):
413        return
414
[7459]415class AcceptanceFeePaymentAddPage(UtilityView, grok.View):
[7250]416    """ Page to add an online payment ticket
417    """
418    grok.context(IApplicant)
419    grok.name('addafp')
420    grok.require('waeup.payApplicant')
421
422    def update(self):
423        p_category = 'acceptance'
424        session = str(self.context.__parent__.year)
425        try:
426            academic_session = grok.getSite()['configuration'][session]
427        except KeyError:
428            self.flash('Session configuration object is not available.')
429            return
430        timestamp = "%d" % int(time()*1000)
431        for key in self.context.keys():
432            ticket = self.context[key]
433            if ticket.p_state == 'paid':
434                  self.flash(
435                      'This type of payment has already been made.')
436                  self.redirect(self.url(self.context))
437                  return
438        payment = createObject(u'waeup.ApplicantOnlinePayment')
439        payment.p_id = "p%s" % timestamp
440        payment.p_item = self.context.__parent__.title
441        payment.p_year = self.context.__parent__.year
442        payment.p_category = p_category
443        payment.amount_auth = academic_session.acceptance_fee
444        payment.surcharge_1 = academic_session.surcharge_1
445        payment.surcharge_2 = academic_session.surcharge_2
446        payment.surcharge_3 = academic_session.surcharge_3
447        self.context[payment.p_id] = payment
448        self.flash('Payment ticket created.')
449        return
450
451    def render(self):
452        usertype = getattr(self.request.principal, 'user_type', None)
453        if usertype == 'applicant':
454            self.redirect(self.url(self.context, '@@edit'))
455            return
456        self.redirect(self.url(self.context, '@@manage'))
457        return
458
459
[7321]460class OnlinePaymentDisplayFormPage(SIRPDisplayFormPage):
[7250]461    """ Page to view an online payment ticket
462    """
463    grok.context(IApplicantOnlinePayment)
464    grok.name('index')
465    grok.require('waeup.viewApplication')
466    form_fields = grok.AutoFields(IApplicantOnlinePayment)
[7363]467    form_fields[
468        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
469    form_fields[
470        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[7250]471    pnav = 3
472
473    @property
474    def label(self):
475        return '%s: Online Payment Ticket %s' % (
[7364]476            self.context.__parent__.display_fullname,self.context.p_id)
[7250]477
[7459]478class OnlinePaymentCallbackPage(UtilityView, grok.View):
[7250]479    """ Callback view
480    """
481    grok.context(IApplicantOnlinePayment)
482    grok.name('callback')
483    grok.require('waeup.payApplicant')
484
485    # This update method simulates a valid callback und must be
486    # specified in the customization package. The parameters must be taken
487    # from the incoming request.
488    def update(self):
[7322]489        self.wf_info = IWorkflowInfo(self.context.__parent__)
490        try:
491            self.wf_info.fireTransition('pay')
492        except InvalidTransitionError:
493            self.flash('Error: %s' % sys.exc_info()[1])
[7250]494            return
495        self.context.r_amount_approved = self.context.amount_auth
496        self.context.r_card_num = u'0000'
497        self.context.r_code = u'00'
498        self.context.p_state = 'paid'
499        self.context.payment_date = datetime.now()
500        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
501        self.context.__parent__.loggerInfo(
502            ob_class, 'valid callback: %s' % self.context.p_id)
503        self.flash('Valid callback received.')
504        return
505
506    def render(self):
507        self.redirect(self.url(self.context, '@@index'))
508        return
509
[7459]510class ExportPDFPaymentSlipPage(UtilityView, grok.View):
[7250]511    """Deliver a PDF slip of the context.
512    """
513    grok.context(IApplicantOnlinePayment)
514    grok.name('payment_receipt.pdf')
515    grok.require('waeup.viewApplication')
516    form_fields = grok.AutoFields(IApplicantOnlinePayment)
517    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
518    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
519    prefix = 'form'
[7318]520    title = 'Payment Data'
[7250]521
522    @property
523    def label(self):
524        return 'Online Payment Receipt %s' % self.context.p_id
525
526    def render(self):
527        if self.context.p_state != 'paid':
528            self.flash('Ticket not yet paid.')
529            self.redirect(self.url(self.context))
530            return
[7259]531        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
[7250]532            self.request)
533        students_utils = getUtility(IStudentsUtils)
[7318]534        return students_utils.renderPDF(self,'payment_receipt.pdf',
[7250]535            self.context.__parent__, applicantview)
536
[7459]537class ExportPDFPage(UtilityView, grok.View):
[6358]538    """Deliver a PDF slip of the context.
539    """
540    grok.context(IApplicant)
541    grok.name('application_slip.pdf')
[7136]542    grok.require('waeup.viewApplication')
[6358]543    form_fields = grok.AutoFields(IApplicant).omit(
[7347]544        'locked', 'course_admitted')
[6358]545    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
546    prefix = 'form'
547
[6363]548    @property
549    def label(self):
550        container_title = self.context.__parent__.title
[7240]551        return '%s Application Record %s' % (
552            container_title, self.context.application_number)
[6363]553
[7347]554    def getCourseAdmitted(self):
555        """Return title and code in html format to the certificate
556           admitted.
557        """
558        course_admitted = self.context.course_admitted
[7351]559        #if ICertificate.providedBy(course_admitted):
560        if getattr(course_admitted, '__parent__',None):
[7347]561            title = course_admitted.title
562            code = course_admitted.code
563            return '%s - %s' %(code,title)
564        return ''
[6358]565
566    def setUpWidgets(self, ignore_request=False):
567        self.adapters = {}
568        self.widgets = setUpEditWidgets(
569            self.form_fields, self.prefix, self.context, self.request,
570            adapters=self.adapters, for_display=True,
571            ignore_request=ignore_request
572            )
573
574    def render(self):
[7392]575        pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
576            view=self)
[6358]577        self.response.setHeader(
578            'Content-Type', 'application/pdf')
[7392]579        return pdfstream
[6358]580
[7081]581def handle_img_upload(upload, context, view):
[7063]582    """Handle upload of applicant image.
[7081]583
584    Returns `True` in case of success or `False`.
585
586    Please note that file pointer passed in (`upload`) most probably
587    points to end of file when leaving this function.
[7063]588    """
[7081]589    size = file_size(upload)
590    if size > MAX_UPLOAD_SIZE:
591        view.flash('Uploaded image is too big!')
592        return False
[7247]593    dummy, ext = os.path.splitext(upload.filename)
594    ext.lower()
595    if ext != '.jpg':
596        view.flash('jpg file extension expected.')
597        return False
[7081]598    upload.seek(0) # file pointer moved when determining size
[7063]599    store = getUtility(IExtFileStore)
600    file_id = IFileStoreNameChooser(context).chooseName()
601    store.createFile(file_id, upload)
[7081]602    return True
[7063]603
[7321]604class ApplicantManageFormPage(SIRPEditFormPage):
[6196]605    """A full edit view for applicant data.
606    """
607    grok.context(IApplicant)
[7200]608    grok.name('manage')
[7136]609    grok.require('waeup.manageApplication')
[6476]610    form_fields = grok.AutoFields(IApplicant)
[6196]611    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[7351]612    form_fields['student_id'].for_display = True
[7378]613    form_fields['applicant_id'].for_display = True
[7423]614    form_fields['phone'].custom_widget = PhoneWidget
[7200]615    grok.template('applicanteditpage')
[6322]616    manage_applications = True
[6196]617    pnav = 3
[7250]618    display_actions = [['Save', 'Final Submit'],
619                       ['Add online payment ticket','Remove selected tickets']]
[6196]620
621    def update(self):
622        datepicker.need() # Enable jQuery datepicker in date fields.
[7330]623        warning.need()
[7200]624        super(ApplicantManageFormPage, self).update()
[6353]625        self.wf_info = IWorkflowInfo(self.context)
[7081]626        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
[7084]627        self.passport_changed = None
[6598]628        upload = self.request.form.get('form.passport', None)
629        if upload:
630            # We got a fresh upload
[7084]631            self.passport_changed = handle_img_upload(
632                upload, self.context, self)
[6196]633        return
634
635    @property
636    def label(self):
637        container_title = self.context.__parent__.title
[7240]638        return '%s Application Form %s' % (
639            container_title, self.context.application_number)
[6196]640
[6303]641    def getTransitions(self):
[6351]642        """Return a list of dicts of allowed transition ids and titles.
[6353]643
644        Each list entry provides keys ``name`` and ``title`` for
645        internal name and (human readable) title of a single
646        transition.
[6349]647        """
[6353]648        allowed_transitions = self.wf_info.getManualTransitions()
[6355]649        return [dict(name='', title='No transition')] +[
650            dict(name=x, title=y) for x, y in allowed_transitions]
[6303]651
[7459]652    @action('Save')
[6196]653    def save(self, **data):
[7240]654        form = self.request.form
655        password = form.get('password', None)
656        password_ctl = form.get('control_password', None)
657        if password:
658            validator = getUtility(IPasswordValidator)
659            errors = validator.validate_password(password, password_ctl)
660            if errors:
661                self.flash( ' '.join(errors))
662                return
[7084]663        if self.passport_changed is False:  # False is not None!
664            return # error during image upload. Ignore other values
[6475]665        changed_fields = self.applyData(self.context, **data)
[7199]666        # Turn list of lists into single list
667        if changed_fields:
668            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
[7240]669        else:
670            changed_fields = []
671        if self.passport_changed:
672            changed_fields.append('passport')
673        if password:
674            # Now we know that the form has no errors and can set password ...
675            IUserAccount(self.context).setPassword(password)
676            changed_fields.append('password')
[7199]677        fields_string = ' + '.join(changed_fields)
[7085]678        trans_id = form.get('transition', None)
679        if trans_id:
680            self.wf_info.fireTransition(trans_id)
[6196]681        self.flash('Form has been saved.')
[6475]682        ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
[6644]683        if fields_string:
684            self.context.loggerInfo(ob_class, 'saved: % s' % fields_string)
[6196]685        return
686
[7250]687    def unremovable(self, ticket):
[7330]688        return False
[7250]689
690    # This method is also used by the ApplicantEditFormPage
691    def delPaymentTickets(self, **data):
692        form = self.request.form
693        if form.has_key('val_id'):
694            child_id = form['val_id']
695        else:
696            self.flash('No payment selected.')
697            self.redirect(self.url(self.context))
698            return
699        if not isinstance(child_id, list):
700            child_id = [child_id]
701        deleted = []
702        for id in child_id:
703            # Applicants are not allowed to remove used payment tickets
704            if not self.unremovable(self.context[id]):
705                try:
706                    del self.context[id]
707                    deleted.append(id)
708                except:
709                    self.flash('Could not delete %s: %s: %s' % (
710                            id, sys.exc_info()[0], sys.exc_info()[1]))
711        if len(deleted):
712            self.flash('Successfully removed: %s' % ', '.join(deleted))
713            ob_class = self.__implemented__.__name__.replace('waeup.sirp.','')
[7363]714            self.context.loggerInfo(
715                ob_class, 'removed: % s' % ', '.join(deleted))
[7250]716        return
717
[7252]718    # We explicitely want the forms to be validated before payment tickets
719    # can be created. If no validation is requested, use
[7459]720    # 'validator=NullValidator' in the action directive
721    @action('Add online payment ticket')
[7250]722    def addPaymentTicket(self, **data):
723        self.redirect(self.url(self.context, '@@addafp'))
[7252]724        return
[7250]725
[7330]726    @jsaction('Remove selected tickets')
[7250]727    def removePaymentTickets(self, **data):
728        self.delPaymentTickets(**data)
729        self.redirect(self.url(self.context) + '/@@manage')
730        return
731
[7200]732class ApplicantEditFormPage(ApplicantManageFormPage):
[5982]733    """An applicant-centered edit view for applicant data.
734    """
[6196]735    grok.context(IApplicantEdit)
[5273]736    grok.name('edit')
[6198]737    grok.require('waeup.handleApplication')
[6459]738    form_fields = grok.AutoFields(IApplicantEdit).omit(
[6476]739        'locked', 'course_admitted', 'student_id',
[7378]740        'screening_score', 'reg_number'
[6459]741        )
[6054]742    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
[7433]743    form_fields['phone'].custom_widget = PhoneWidget
[7459]744    form_fields['applicant_id'].for_display = True
[7200]745    grok.template('applicanteditpage')
[6322]746    manage_applications = False
[5484]747
[7250]748    @property
749    def display_actions(self):
750        state = IWorkflowState(self.context).getState()
751        if state == INITIALIZED:
752            actions = [[],[]]
753        elif state == STARTED:
754            actions = [['Save'],
755                       ['Add online payment ticket','Remove selected tickets']]
756        elif state == PAID:
757            actions = [['Save', 'Final Submit'],
758                       ['Remove selected tickets']]
[7351]759        else:
[7250]760            actions = [[],[]]
761        return actions
762
[7330]763    def unremovable(self, ticket):
764        state = IWorkflowState(self.context).getState()
765        return ticket.r_code or state in (INITIALIZED, SUBMITTED)
766
[7145]767    def emit_lock_message(self):
[6105]768        self.flash('The requested form is locked (read-only).')
[5941]769        self.redirect(self.url(self.context))
770        return
[6078]771
[5686]772    def update(self):
[5941]773        if self.context.locked:
[7145]774            self.emit_lock_message()
[5941]775            return
[7200]776        super(ApplicantEditFormPage, self).update()
[5686]777        return
[5952]778
[6196]779    def dataNotComplete(self):
[7252]780        store = getUtility(IExtFileStore)
781        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
782            return 'No passport picture uploaded.'
[6322]783        if not self.request.form.get('confirm_passport', False):
[7252]784            return 'Passport picture confirmation box not ticked.'
[6196]785        return False
[5952]786
[7252]787    # We explicitely want the forms to be validated before payment tickets
788    # can be created. If no validation is requested, use
[7459]789    # 'validator=NullValidator' in the action directive
790    @action('Add online payment ticket')
[7250]791    def addPaymentTicket(self, **data):
792        self.redirect(self.url(self.context, '@@addafp'))
[7252]793        return
[7250]794
[7330]795    @jsaction('Remove selected tickets')
[7250]796    def removePaymentTickets(self, **data):
797        self.delPaymentTickets(**data)
798        self.redirect(self.url(self.context) + '/@@edit')
799        return
800
[7459]801    @action('Save')
[5273]802    def save(self, **data):
[7084]803        if self.passport_changed is False:  # False is not None!
804            return # error during image upload. Ignore other values
[5273]805        self.applyData(self.context, **data)
[6196]806        self.flash('Form has been saved.')
[5273]807        return
808
[7459]809    @action('Final Submit')
[5484]810    def finalsubmit(self, **data):
[7084]811        if self.passport_changed is False:  # False is not None!
812            return # error during image upload. Ignore other values
[6196]813        if self.dataNotComplete():
814            self.flash(self.dataNotComplete())
[5941]815            return
[7252]816        self.applyData(self.context, **data)
[6303]817        state = IWorkflowState(self.context).getState()
[6322]818        # This shouldn't happen, but the application officer
819        # might have forgotten to lock the form after changing the state
[7250]820        if state != PAID:
[6322]821            self.flash('This form cannot be submitted. Wrong state!')
[6303]822            return
823        IWorkflowInfo(self.context).fireTransition('submit')
[6476]824        self.context.application_date = datetime.now()
[5941]825        self.context.locked = True
[6196]826        self.flash('Form has been submitted.')
827        self.redirect(self.url(self.context))
[5273]828        return
[5941]829
[7063]830class PassportImage(grok.View):
831    """Renders the passport image for applicants.
832    """
833    grok.name('passport.jpg')
834    grok.context(IApplicant)
[7113]835    grok.require('waeup.viewApplication')
[7063]836
837    def render(self):
838        # A filename chooser turns a context into a filename suitable
839        # for file storage.
840        image = getUtility(IExtFileStore).getFileByContext(self.context)
841        self.response.setHeader(
842            'Content-Type', 'image/jpeg')
843        if image is None:
844            # show placeholder image
[7089]845            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
[7063]846        return image
[7363]847
848class ApplicantRegistrationPage(SIRPAddFormPage):
849    """Captcha'd registration page for applicants.
850    """
851    grok.context(IApplicantsContainer)
852    grok.name('register')
[7373]853    grok.require('waeup.Anonymous')
[7363]854    grok.template('applicantregister')
855    form_fields = grok.AutoFields(IApplicantEdit).select(
856        'firstname', 'middlename', 'lastname', 'email', 'phone')
857    form_fields['phone'].custom_widget = PhoneWidget
858
[7368]859    @property
860    def label(self):
861        return "Register for %s Application" % self.context.title
862
[7363]863    def update(self):
[7368]864        # Check if application has started ...
865        if not self.context.startdate or self.context.startdate > date.today():
866            self.flash('Application has not yet started.')
867            self.redirect(self.url(self.context))
868            return
869        # ... or ended
870        if not self.context.enddate or self.context.enddate < date.today():
871            self.flash('Application has ended.')
872            self.redirect(self.url(self.context))
873            return
874        # Handle captcha
[7363]875        self.captcha = getUtility(ICaptchaManager).getCaptcha()
876        self.captcha_result = self.captcha.verify(self.request)
877        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
878        return
879
[7459]880    @action('Get login credentials')
[7363]881    def register(self, **data):
882        if not self.captcha_result.is_valid:
883            # captcha will display error messages automatically.
884            # No need to flash something.
885            return
[7365]886        # Add applicant and create password
[7363]887        applicant = createObject('waeup.Applicant')
888        self.applyData(applicant, **data)
889        self.context.addApplicant(applicant)
[7399]890        sirp_utils = getUtility(ISIRPUtils)
891        password = sirp_utils.genPassword()
[7380]892        IUserAccount(applicant).setPassword(password)
[7365]893        # Send email with credentials
[7399]894        login_url = self.url(grok.getSite(), 'login')
[7407]895        msg = 'You have successfully been registered for the'
896        if sirp_utils.sendCredentials(IUserAccount(applicant),
897            password, login_url, msg):
[7380]898            self.redirect(self.url(self.context, 'registration_complete',
899                                   data = dict(email=applicant.email)))
900            return
901        else:
902            self.flash('Email could not been sent. Please retry later.')
903        return
904
905class ApplicantRegistrationEmailSent(SIRPPage):
906    """Landing page after successful registration.
907    """
908    grok.name('registration_complete')
909    grok.require('waeup.Public')
910    grok.template('applicantregemailsent')
911    label = 'Your registration was successful'
912
913    def update(self, email=None):
914        self.email = email
915        return
Note: See TracBrowser for help on using the repository browser.