source: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/applicants/browser.py @ 9819

Last change on this file since 9819 was 9211, checked in by uli, 12 years ago

Rollback r9209. Looks like multiple merges from trunk confuse svn when merging back into trunk.

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