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

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

Some repairs.

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