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

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

Jason is requesting customization of registration number field. This can't be done with the IApplicantBaseData based interfaces. It requires a new interface ISpecialApplicant.

Attention: The changes here are not yet compatible with the custom packages. Do not checkout!

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