source: main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py @ 8635

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

In some custom packages we need to display login credentials on landing pages after registration. Make provisions in base package for that.

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