source: main/waeup.kofa/branches/uli-diazo-themed/src/waeup/kofa/applicants/browser.py @ 11011

Last change on this file since 11011 was 11011, checked in by Henrik Bettermann, 11 years ago

More adjustments in applicants module.

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