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

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

Flash message if student record is created.

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