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

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

Put viewlets into their own module.

Remove unneeded imports.

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