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

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

Implement a special application procedure. This application is meant for supplementary payments by alumni and other persons who are not students of the portal.

Attention: All custom packages have to be adjusted.

  • Property svn:keywords set to Id
File size: 45.2 KB
RevLine 
[5273]1## $Id: browser.py 10831 2013-12-10 06:24:03Z 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,
[8037]34    IApplicantRegisterUpdate
[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:
531            form_fields = grok.AutoFields(IApplicant).select(
532                'applicant_id', 'firstname', 'middlename', 'lastname',
533                'reg_number', 'date_of_birth', 'email', 'special_application')
534        else:
535            form_fields = grok.AutoFields(IApplicant).omit(
536                'locked', 'course_admitted', 'password', 'suspended',
537                'special_application')
538        return form_fields
539
540    @property
[10534]541    def target(self):
542        return getattr(self.context.__parent__, 'prefix', None)
543
544    @property
[8046]545    def separators(self):
546        return getUtility(IApplicantsUtils).SEPARATORS_DICT
547
[7063]548    def update(self):
549        self.passport_url = self.url(self.context, 'passport.jpg')
[7240]550        # Mark application as started if applicant logs in for the first time
[7272]551        usertype = getattr(self.request.principal, 'user_type', None)
552        if usertype == 'applicant' and \
553            IWorkflowState(self.context).getState() == INITIALIZED:
[7240]554            IWorkflowInfo(self.context).fireTransition('start')
[7063]555        return
556
[6196]557    @property
[7240]558    def hasPassword(self):
559        if self.context.password:
[7714]560            return _('set')
561        return _('unset')
[7240]562
563    @property
[6196]564    def label(self):
565        container_title = self.context.__parent__.title
[8096]566        return _('${a} <br /> Application Record ${b}', mapping = {
[7714]567            'a':container_title, 'b':self.context.application_number})
[6196]568
[7347]569    def getCourseAdmitted(self):
570        """Return link, title and code in html format to the certificate
571           admitted.
572        """
573        course_admitted = self.context.course_admitted
[7351]574        if getattr(course_admitted, '__parent__',None):
[7347]575            url = self.url(course_admitted)
576            title = course_admitted.title
577            code = course_admitted.code
578            return '<a href="%s">%s - %s</a>' %(url,code,title)
579        return ''
[6254]580
[7259]581class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
582    grok.context(IApplicant)
583    grok.name('base')
584    form_fields = grok.AutoFields(IApplicant).select(
[9141]585        'applicant_id','email', 'course1')
[7259]586
[7459]587class CreateStudentPage(UtilityView, grok.View):
[8636]588    """Create a student object from applicant data.
[7341]589    """
590    grok.context(IApplicant)
591    grok.name('createstudent')
592    grok.require('waeup.manageStudent')
593
594    def update(self):
[8314]595        msg = self.context.createStudent(view=self)[1]
[7341]596        self.flash(msg)
597        self.redirect(self.url(self.context))
598        return
599
600    def render(self):
601        return
602
[8636]603class CreateAllStudentsPage(UtilityView, grok.View):
604    """Create all student objects from applicant data
605    in a container.
606
607    This is a hidden page, no link or button will
608    be provided and only PortalManagers can do this.
609    """
[9900]610    #grok.context(IApplicantsContainer)
[8636]611    grok.name('createallstudents')
612    grok.require('waeup.managePortal')
613
614    def update(self):
615        cat = getUtility(ICatalog, name='applicants_catalog')
616        results = list(cat.searchResults(state=(ADMITTED, ADMITTED)))
617        created = []
[9900]618        container_only = False
619        applicants_root = grok.getSite()['applicants']
620        if isinstance(self.context, ApplicantsContainer):
621            container_only = True
[8636]622        for result in results:
[9900]623            if container_only and result.__parent__ is not self.context:
[8636]624                continue
625            success, msg = result.createStudent(view=self)
626            if success:
627                created.append(result.applicant_id)
628            else:
629                ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
[9900]630                applicants_root.logger.info(
[8742]631                    '%s - %s - %s' % (ob_class, result.applicant_id, msg))
[8636]632        if len(created):
633            self.flash(_('${a} students successfully created.',
634                mapping = {'a': len(created)}))
635        else:
636            self.flash(_('No student could be created.'))
[9900]637        self.redirect(self.url(self.context))
[8636]638        return
639
640    def render(self):
641        return
642
[8260]643class ApplicationFeePaymentAddPage(UtilityView, grok.View):
[7250]644    """ Page to add an online payment ticket
645    """
646    grok.context(IApplicant)
647    grok.name('addafp')
648    grok.require('waeup.payApplicant')
[8243]649    factory = u'waeup.ApplicantOnlinePayment'
[7250]650
651    def update(self):
652        for key in self.context.keys():
653            ticket = self.context[key]
654            if ticket.p_state == 'paid':
655                  self.flash(
[7714]656                      _('This type of payment has already been made.'))
[7250]657                  self.redirect(self.url(self.context))
658                  return
[8524]659        applicants_utils = getUtility(IApplicantsUtils)
660        container = self.context.__parent__
[8243]661        payment = createObject(self.factory)
[10831]662        error = applicants_utils.setPaymentDetails(
663            container, payment, self.context)
[8524]664        if error is not None:
665            self.flash(error)
666            self.redirect(self.url(self.context))
667            return
[7250]668        self.context[payment.p_id] = payment
[7714]669        self.flash(_('Payment ticket created.'))
[8280]670        self.redirect(self.url(payment))
[7250]671        return
672
673    def render(self):
674        return
675
676
[7819]677class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
[7250]678    """ Page to view an online payment ticket
679    """
680    grok.context(IApplicantOnlinePayment)
681    grok.name('index')
682    grok.require('waeup.viewApplication')
[9984]683    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
[8170]684    form_fields[
685        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
686    form_fields[
687        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[7250]688    pnav = 3
689
690    @property
691    def label(self):
[7714]692        return _('${a}: Online Payment Ticket ${b}', mapping = {
[8170]693            'a':self.context.__parent__.display_fullname,
694            'b':self.context.p_id})
[7250]695
[8420]696class OnlinePaymentApprovePage(UtilityView, grok.View):
697    """ Approval view
[7250]698    """
699    grok.context(IApplicantOnlinePayment)
[8420]700    grok.name('approve')
701    grok.require('waeup.managePortal')
[7250]702
703    def update(self):
[8428]704        success, msg, log = self.context.approveApplicantPayment()
705        if log is not None:
[9771]706            applicant = self.context.__parent__
707            # Add log message to applicants.log
708            applicant.writeLogMessage(self, log)
709            # Add log message to payments.log
710            self.context.logger.info(
[9795]711                '%s,%s,%s,%s,%s,,,,,,' % (
[9771]712                applicant.applicant_id,
713                self.context.p_id, self.context.p_category,
714                self.context.amount_auth, self.context.r_code))
[8422]715        self.flash(msg)
[7250]716        return
717
718    def render(self):
719        self.redirect(self.url(self.context, '@@index'))
720        return
721
[7459]722class ExportPDFPaymentSlipPage(UtilityView, grok.View):
[7250]723    """Deliver a PDF slip of the context.
724    """
725    grok.context(IApplicantOnlinePayment)
[8262]726    grok.name('payment_slip.pdf')
[7250]727    grok.require('waeup.viewApplication')
[9984]728    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
[8173]729    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
730    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[7250]731    prefix = 'form'
[8258]732    note = None
[7250]733
734    @property
[7714]735    def title(self):
[7819]736        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7811]737        return translate(_('Payment Data'), 'waeup.kofa',
[7714]738            target_language=portal_language)
739
740    @property
[7250]741    def label(self):
[7819]742        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[8262]743        return translate(_('Online Payment Slip'),
[7811]744            'waeup.kofa', target_language=portal_language) \
[7714]745            + ' %s' % self.context.p_id
[7250]746
747    def render(self):
[8262]748        #if self.context.p_state != 'paid':
749        #    self.flash(_('Ticket not yet paid.'))
750        #    self.redirect(self.url(self.context))
751        #    return
[7259]752        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
[7250]753            self.request)
754        students_utils = getUtility(IStudentsUtils)
[8262]755        return students_utils.renderPDF(self,'payment_slip.pdf',
[8258]756            self.context.__parent__, applicantview, note=self.note)
[7250]757
[10571]758class ExportPDFPageApplicationSlip(UtilityView, grok.View):
[6358]759    """Deliver a PDF slip of the context.
760    """
761    grok.context(IApplicant)
762    grok.name('application_slip.pdf')
[7136]763    grok.require('waeup.viewApplication')
[6358]764    prefix = 'form'
765
[8666]766    def update(self):
[9051]767        if self.context.state in ('initialized', 'started', 'paid'):
[8666]768            self.flash(
[9051]769                _('Please pay and submit before trying to download the application slip.'))
[8666]770            return self.redirect(self.url(self.context))
771        return
772
[6358]773    def render(self):
[7392]774        pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
775            view=self)
[6358]776        self.response.setHeader(
777            'Content-Type', 'application/pdf')
[7392]778        return pdfstream
[6358]779
[7081]780def handle_img_upload(upload, context, view):
[7063]781    """Handle upload of applicant image.
[7081]782
783    Returns `True` in case of success or `False`.
784
785    Please note that file pointer passed in (`upload`) most probably
786    points to end of file when leaving this function.
[7063]787    """
[7081]788    size = file_size(upload)
789    if size > MAX_UPLOAD_SIZE:
[7714]790        view.flash(_('Uploaded image is too big!'))
[7081]791        return False
[7247]792    dummy, ext = os.path.splitext(upload.filename)
793    ext.lower()
794    if ext != '.jpg':
[7714]795        view.flash(_('jpg file extension expected.'))
[7247]796        return False
[7081]797    upload.seek(0) # file pointer moved when determining size
[7063]798    store = getUtility(IExtFileStore)
799    file_id = IFileStoreNameChooser(context).chooseName()
800    store.createFile(file_id, upload)
[7081]801    return True
[7063]802
[7819]803class ApplicantManageFormPage(KofaEditFormPage):
[6196]804    """A full edit view for applicant data.
805    """
806    grok.context(IApplicant)
[7200]807    grok.name('manage')
[7136]808    grok.require('waeup.manageApplication')
[7200]809    grok.template('applicanteditpage')
[6322]810    manage_applications = True
[6196]811    pnav = 3
[7714]812    display_actions = [[_('Save'), _('Final Submit')],
813        [_('Add online payment ticket'),_('Remove selected tickets')]]
[6196]814
[8046]815    @property
[10831]816    def form_fields(self):
817        if self.context.special:
818            form_fields = grok.AutoFields(IApplicant).select(
819                'applicant_id', 'firstname', 'middlename', 'lastname',
820                'reg_number', 'date_of_birth', 'email', 'special_application',
821                'locked')
822        else:
823            form_fields = grok.AutoFields(IApplicant)
824            form_fields['student_id'].for_display = True
825            form_fields['applicant_id'].for_display = True
826        return form_fields
827
828    @property
[10534]829    def target(self):
830        return getattr(self.context.__parent__, 'prefix', None)
831
832    @property
[8046]833    def separators(self):
834        return getUtility(IApplicantsUtils).SEPARATORS_DICT
835
[6196]836    def update(self):
837        datepicker.need() # Enable jQuery datepicker in date fields.
[7330]838        warning.need()
[7200]839        super(ApplicantManageFormPage, self).update()
[6353]840        self.wf_info = IWorkflowInfo(self.context)
[7081]841        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
[10090]842        self.upload_success = None
[6598]843        upload = self.request.form.get('form.passport', None)
844        if upload:
[10090]845            # We got a fresh upload, upload_success is
846            # either True or False
847            self.upload_success = handle_img_upload(
[7084]848                upload, self.context, self)
[10090]849            if self.upload_success:
[10095]850                self.context.writeLogMessage(self, 'saved: passport')
[6196]851        return
852
853    @property
854    def label(self):
855        container_title = self.context.__parent__.title
[8096]856        return _('${a} <br /> Application Form ${b}', mapping = {
[7714]857            'a':container_title, 'b':self.context.application_number})
[6196]858
[6303]859    def getTransitions(self):
[6351]860        """Return a list of dicts of allowed transition ids and titles.
[6353]861
862        Each list entry provides keys ``name`` and ``title`` for
863        internal name and (human readable) title of a single
864        transition.
[6349]865        """
[8434]866        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
867            if not t[0] == 'pay']
[7687]868        return [dict(name='', title=_('No transition'))] +[
[6355]869            dict(name=x, title=y) for x, y in allowed_transitions]
[6303]870
[7714]871    @action(_('Save'), style='primary')
[6196]872    def save(self, **data):
[7240]873        form = self.request.form
874        password = form.get('password', None)
875        password_ctl = form.get('control_password', None)
876        if password:
877            validator = getUtility(IPasswordValidator)
878            errors = validator.validate_password(password, password_ctl)
879            if errors:
880                self.flash( ' '.join(errors))
881                return
[10090]882        if self.upload_success is False:  # False is not None!
883            # Error during image upload. Ignore other values.
884            return
[6475]885        changed_fields = self.applyData(self.context, **data)
[7199]886        # Turn list of lists into single list
887        if changed_fields:
888            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
[7240]889        else:
890            changed_fields = []
891        if password:
892            # Now we know that the form has no errors and can set password ...
893            IUserAccount(self.context).setPassword(password)
894            changed_fields.append('password')
[7199]895        fields_string = ' + '.join(changed_fields)
[7085]896        trans_id = form.get('transition', None)
897        if trans_id:
898            self.wf_info.fireTransition(trans_id)
[7714]899        self.flash(_('Form has been saved.'))
[6644]900        if fields_string:
[8742]901            self.context.writeLogMessage(self, 'saved: % s' % fields_string)
[6196]902        return
903
[7250]904    def unremovable(self, ticket):
[7330]905        return False
[7250]906
907    # This method is also used by the ApplicantEditFormPage
908    def delPaymentTickets(self, **data):
909        form = self.request.form
[9701]910        if 'val_id' in form:
[7250]911            child_id = form['val_id']
912        else:
[7714]913            self.flash(_('No payment selected.'))
[7250]914            self.redirect(self.url(self.context))
915            return
916        if not isinstance(child_id, list):
917            child_id = [child_id]
918        deleted = []
919        for id in child_id:
920            # Applicants are not allowed to remove used payment tickets
921            if not self.unremovable(self.context[id]):
922                try:
923                    del self.context[id]
924                    deleted.append(id)
925                except:
[7714]926                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
[7250]927                            id, sys.exc_info()[0], sys.exc_info()[1]))
928        if len(deleted):
[7741]929            self.flash(_('Successfully removed: ${a}',
[7738]930                mapping = {'a':', '.join(deleted)}))
[8742]931            self.context.writeLogMessage(
932                self, 'removed: % s' % ', '.join(deleted))
[7250]933        return
934
[7252]935    # We explicitely want the forms to be validated before payment tickets
936    # can be created. If no validation is requested, use
[7459]937    # 'validator=NullValidator' in the action directive
[7714]938    @action(_('Add online payment ticket'))
[7250]939    def addPaymentTicket(self, **data):
940        self.redirect(self.url(self.context, '@@addafp'))
[7252]941        return
[7250]942
[7714]943    @jsaction(_('Remove selected tickets'))
[7250]944    def removePaymentTickets(self, **data):
945        self.delPaymentTickets(**data)
946        self.redirect(self.url(self.context) + '/@@manage')
947        return
948
[10094]949    # Not used in base package
950    def file_exists(self, attr):
951        file = getUtility(IExtFileStore).getFileByContext(
952            self.context, attr=attr)
953        if file:
954            return True
955        else:
956            return False
957
[7200]958class ApplicantEditFormPage(ApplicantManageFormPage):
[5982]959    """An applicant-centered edit view for applicant data.
960    """
[6196]961    grok.context(IApplicantEdit)
[5273]962    grok.name('edit')
[6198]963    grok.require('waeup.handleApplication')
[7200]964    grok.template('applicanteditpage')
[6322]965    manage_applications = False
[10358]966    submit_state = PAID
[5484]967
[7250]968    @property
[10831]969    def form_fields(self):
970        if self.context.special:
971            form_fields = grok.AutoFields(IApplicantEdit).select(
972                'applicant_id', 'firstname', 'middlename', 'lastname',
973                'reg_number', 'date_of_birth', 'email', 'special_application')
974        else:
975            form_fields = grok.AutoFields(IApplicantEdit).omit(
976                'locked', 'course_admitted', 'student_id',
977                'suspended', 'special_application'
978                )
979            form_fields['applicant_id'].for_display = True
980            form_fields['reg_number'].for_display = True
981        return form_fields
982
983    @property
[7250]984    def display_actions(self):
[8286]985        state = IWorkflowState(self.context).getState()
[10358]986        actions = [[],[]]
987        if state == STARTED:
[7714]988            actions = [[_('Save')],
989                [_('Add online payment ticket'),_('Remove selected tickets')]]
[8286]990        elif state == PAID:
[7714]991            actions = [[_('Save'), _('Final Submit')],
992                [_('Remove selected tickets')]]
[7250]993        return actions
994
[7330]995    def unremovable(self, ticket):
[8286]996        state = IWorkflowState(self.context).getState()
997        return ticket.r_code or state in (INITIALIZED, SUBMITTED)
[7330]998
[7145]999    def emit_lock_message(self):
[7714]1000        self.flash(_('The requested form is locked (read-only).'))
[5941]1001        self.redirect(self.url(self.context))
1002        return
[6078]1003
[5686]1004    def update(self):
[8665]1005        if self.context.locked or (
1006            self.context.__parent__.expired and
1007            self.context.__parent__.strict_deadline):
[7145]1008            self.emit_lock_message()
[5941]1009            return
[7200]1010        super(ApplicantEditFormPage, self).update()
[5686]1011        return
[5952]1012
[6196]1013    def dataNotComplete(self):
[7252]1014        store = getUtility(IExtFileStore)
1015        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
[7714]1016            return _('No passport picture uploaded.')
[6322]1017        if not self.request.form.get('confirm_passport', False):
[7714]1018            return _('Passport picture confirmation box not ticked.')
[6196]1019        return False
[5952]1020
[7252]1021    # We explicitely want the forms to be validated before payment tickets
1022    # can be created. If no validation is requested, use
[7459]1023    # 'validator=NullValidator' in the action directive
[7714]1024    @action(_('Add online payment ticket'))
[7250]1025    def addPaymentTicket(self, **data):
1026        self.redirect(self.url(self.context, '@@addafp'))
[7252]1027        return
[7250]1028
[7714]1029    @jsaction(_('Remove selected tickets'))
[7250]1030    def removePaymentTickets(self, **data):
1031        self.delPaymentTickets(**data)
1032        self.redirect(self.url(self.context) + '/@@edit')
1033        return
1034
[7996]1035    @action(_('Save'), style='primary')
[5273]1036    def save(self, **data):
[10090]1037        if self.upload_success is False:  # False is not None!
1038            # Error during image upload. Ignore other values.
1039            return
[10219]1040        if data.get('course1', 1) == data.get('course2', 2):
[10210]1041            self.flash(_('1st and 2nd choice must be different.'))
1042            return
[5273]1043        self.applyData(self.context, **data)
[10210]1044        self.flash(_('Form has been saved.'))
[5273]1045        return
1046
[8550]1047    @submitaction(_('Final Submit'))
[5484]1048    def finalsubmit(self, **data):
[10090]1049        if self.upload_success is False:  # False is not None!
[7084]1050            return # error during image upload. Ignore other values
[6196]1051        if self.dataNotComplete():
1052            self.flash(self.dataNotComplete())
[5941]1053            return
[7252]1054        self.applyData(self.context, **data)
[8286]1055        state = IWorkflowState(self.context).getState()
[6322]1056        # This shouldn't happen, but the application officer
1057        # might have forgotten to lock the form after changing the state
[10358]1058        if state != self.submit_state:
[8589]1059            self.flash(_('The form cannot be submitted. Wrong state!'))
[6303]1060            return
1061        IWorkflowInfo(self.context).fireTransition('submit')
[8589]1062        # application_date is used in export files for sorting.
1063        # We can thus store utc.
[8194]1064        self.context.application_date = datetime.utcnow()
[7714]1065        self.flash(_('Form has been submitted.'))
[6196]1066        self.redirect(self.url(self.context))
[5273]1067        return
[5941]1068
[7063]1069class PassportImage(grok.View):
1070    """Renders the passport image for applicants.
1071    """
1072    grok.name('passport.jpg')
1073    grok.context(IApplicant)
[7113]1074    grok.require('waeup.viewApplication')
[7063]1075
1076    def render(self):
1077        # A filename chooser turns a context into a filename suitable
1078        # for file storage.
1079        image = getUtility(IExtFileStore).getFileByContext(self.context)
1080        self.response.setHeader(
1081            'Content-Type', 'image/jpeg')
1082        if image is None:
1083            # show placeholder image
[7089]1084            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
[7063]1085        return image
[7363]1086
[7819]1087class ApplicantRegistrationPage(KofaAddFormPage):
[7363]1088    """Captcha'd registration page for applicants.
1089    """
1090    grok.context(IApplicantsContainer)
1091    grok.name('register')
[7373]1092    grok.require('waeup.Anonymous')
[7363]1093    grok.template('applicantregister')
1094
[7368]1095    @property
[8033]1096    def form_fields(self):
1097        form_fields = None
[8128]1098        if self.context.mode == 'update':
1099            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1100                'firstname','reg_number','email')
1101        else: #if self.context.mode == 'create':
[8033]1102            form_fields = grok.AutoFields(IApplicantEdit).select(
1103                'firstname', 'middlename', 'lastname', 'email', 'phone')
1104        return form_fields
1105
1106    @property
[7368]1107    def label(self):
[8078]1108        return _('Apply for ${a}',
[7714]1109            mapping = {'a':self.context.title})
[7368]1110
[7363]1111    def update(self):
[8665]1112        if self.context.expired:
1113            self.flash(_('Outside application period.'))
[7368]1114            self.redirect(self.url(self.context))
1115            return
1116        # Handle captcha
[7363]1117        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1118        self.captcha_result = self.captcha.verify(self.request)
1119        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1120        return
1121
[8629]1122    def _redirect(self, email, password, applicant_id):
1123        # Forward only email to landing page in base package.
1124        self.redirect(self.url(self.context, 'registration_complete',
1125            data = dict(email=email)))
1126        return
1127
[9178]1128    @action(_('Send login credentials to email address'), style='primary')
[7363]1129    def register(self, **data):
1130        if not self.captcha_result.is_valid:
[8037]1131            # Captcha will display error messages automatically.
[7363]1132            # No need to flash something.
1133            return
[8033]1134        if self.context.mode == 'create':
1135            # Add applicant
1136            applicant = createObject(u'waeup.Applicant')
1137            self.applyData(applicant, **data)
1138            self.context.addApplicant(applicant)
[8042]1139            applicant.reg_number = applicant.applicant_id
1140            notify(grok.ObjectModifiedEvent(applicant))
[8033]1141        elif self.context.mode == 'update':
1142            # Update applicant
[8037]1143            reg_number = data.get('reg_number','')
1144            firstname = data.get('firstname','')
[8033]1145            cat = getUtility(ICatalog, name='applicants_catalog')
1146            results = list(
1147                cat.searchResults(reg_number=(reg_number, reg_number)))
1148            if results:
1149                applicant = results[0]
[8042]1150                if getattr(applicant,'firstname',None) is None:
[8037]1151                    self.flash(_('An error occurred.'))
1152                    return
1153                elif applicant.firstname.lower() != firstname.lower():
[8042]1154                    # Don't tell the truth here. Anonymous must not
1155                    # know that a record was found and only the firstname
1156                    # verification failed.
[8037]1157                    self.flash(_('No application record found.'))
1158                    return
[8627]1159                elif applicant.password is not None and \
1160                    applicant.state != INITIALIZED:
1161                    self.flash(_('Your password has already been set and used. '
[8042]1162                                 'Please proceed to the login page.'))
1163                    return
1164                # Store email address but nothing else.
[8033]1165                applicant.email = data['email']
[8042]1166                notify(grok.ObjectModifiedEvent(applicant))
[8033]1167            else:
[8042]1168                # No record found, this is the truth.
[8033]1169                self.flash(_('No application record found.'))
1170                return
1171        else:
[8042]1172            # Does not happen but anyway ...
[8033]1173            return
[7819]1174        kofa_utils = getUtility(IKofaUtils)
[7811]1175        password = kofa_utils.genPassword()
[7380]1176        IUserAccount(applicant).setPassword(password)
[7365]1177        # Send email with credentials
[7399]1178        login_url = self.url(grok.getSite(), 'login')
[8853]1179        url_info = u'Login: %s' % login_url
[7714]1180        msg = _('You have successfully been registered for the')
[7811]1181        if kofa_utils.sendCredentials(IUserAccount(applicant),
[8853]1182            password, url_info, msg):
[8629]1183            email_sent = applicant.email
[7380]1184        else:
[8629]1185            email_sent = None
1186        self._redirect(email=email_sent, password=password,
1187            applicant_id=applicant.applicant_id)
[7380]1188        return
1189
[7819]1190class ApplicantRegistrationEmailSent(KofaPage):
[7380]1191    """Landing page after successful registration.
[8629]1192
[7380]1193    """
1194    grok.name('registration_complete')
1195    grok.require('waeup.Public')
1196    grok.template('applicantregemailsent')
[7714]1197    label = _('Your registration was successful.')
[7380]1198
[8629]1199    def update(self, email=None, applicant_id=None, password=None):
[7380]1200        self.email = email
[8629]1201        self.password = password
1202        self.applicant_id = applicant_id
[7380]1203        return
[10655]1204
1205class ExportJobContainerOverview(KofaPage):
1206    """Page that lists active applicant data export jobs and provides links
1207    to discard or download CSV files.
1208
1209    """
1210    grok.context(VirtualApplicantsExportJobContainer)
1211    grok.require('waeup.manageApplication')
1212    grok.name('index.html')
1213    grok.template('exportjobsindex')
1214    label = _('Applicant Data Exports')
1215    pnav = 3
1216
1217    def update(self, CREATE=None, DISCARD=None, job_id=None):
1218        if CREATE:
1219            self.redirect(self.url('@@start_export'))
1220            return
1221        if DISCARD and job_id:
1222            entry = self.context.entry_from_job_id(job_id)
1223            self.context.delete_export_entry(entry)
1224            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1225            self.context.logger.info(
1226                '%s - discarded: job_id=%s' % (ob_class, job_id))
1227            self.flash(_('Discarded export') + ' %s' % job_id)
1228        self.entries = doll_up(self, user=self.request.principal.id)
1229        return
1230
1231class ExportJobContainerJobStart(KofaPage):
1232    """Page that starts an applicants export job.
1233
1234    """
1235    grok.context(VirtualApplicantsExportJobContainer)
1236    grok.require('waeup.manageApplication')
1237    grok.name('start_export')
1238
1239    def update(self):
1240        exporter = 'applicants'
1241        container_code = self.context.__parent__.code
1242        job_id = self.context.start_export_job(exporter,
1243                                      self.request.principal.id,
1244                                      container=container_code)
1245
1246        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1247        self.context.logger.info(
1248            '%s - exported: %s (%s), job_id=%s'
1249            % (ob_class, exporter, container_code, job_id))
1250        self.flash(_('Export started.'))
1251        self.redirect(self.url(self.context))
1252        return
1253
1254    def render(self):
1255        return
1256
1257class ExportJobContainerDownload(ExportCSVView):
1258    """Page that downloads a students export csv file.
1259
1260    """
1261    grok.context(VirtualApplicantsExportJobContainer)
1262    grok.require('waeup.manageApplication')
Note: See TracBrowser for help on using the repository browser.