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

Last change on this file since 13657 was 13429, checked in by Henrik Bettermann, 9 years ago

... also if record was found.

  • Property svn:keywords set to Id
File size: 53.8 KB
Line 
1## $Id: browser.py 13429 2015-11-10 08:07:44Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
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.
8##
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.
13##
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##
18"""UI components for basic applicants and related components.
19"""
20import os
21import sys
22import grok
23from datetime import datetime, date
24from zope.event import notify
25from zope.component import getUtility, queryUtility, createObject, getAdapter
26from zope.catalog.interfaces import ICatalog
27from zope.i18n import translate
28from hurry.workflow.interfaces import (
29    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
30from waeup.kofa.applicants.interfaces import (
31    IApplicant, IApplicantEdit, IApplicantsRoot,
32    IApplicantsContainer, IApplicantsContainerAdd,
33    MAX_UPLOAD_SIZE, IApplicantOnlinePayment, IApplicantsUtils,
34    IApplicantRegisterUpdate, ISpecialApplicant
35    )
36from waeup.kofa.utils.helpers import html2dict
37from waeup.kofa.applicants.container import (
38    ApplicantsContainer, VirtualApplicantsExportJobContainer)
39from waeup.kofa.applicants.applicant import search
40from waeup.kofa.applicants.workflow import (
41    INITIALIZED, STARTED, PAID, SUBMITTED, ADMITTED, NOT_ADMITTED, CREATED)
42from waeup.kofa.browser import (
43#    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,
44    DEFAULT_PASSPORT_IMAGE_PATH)
45from waeup.kofa.browser.layout import (
46    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage)
47from waeup.kofa.browser.interfaces import ICaptchaManager
48from waeup.kofa.browser.breadcrumbs import Breadcrumb
49from waeup.kofa.browser.layout import (
50    NullValidator, jsaction, action, UtilityView)
51from waeup.kofa.browser.pages import (
52    add_local_role, del_local_roles, doll_up, ExportCSVView)
53from waeup.kofa.interfaces import (
54    IKofaObject, ILocalRolesAssignable, IExtFileStore, IPDF, DOCLINK,
55    IFileStoreNameChooser, IPasswordValidator, IUserAccount, IKofaUtils)
56from waeup.kofa.interfaces import MessageFactory as _
57from waeup.kofa.permissions import get_users_with_local_roles
58from waeup.kofa.students.interfaces import IStudentsUtils
59from waeup.kofa.utils.helpers import string_from_bytes, file_size, now
60from waeup.kofa.widgets.datewidget import (
61    FriendlyDateDisplayWidget,
62    FriendlyDatetimeDisplayWidget)
63
64grok.context(IKofaObject) # Make IKofaObject the default context
65
66WARNING = _('You can not edit your application records after final submission.'
67            ' You really want to submit?')
68
69class ApplicantsRootPage(KofaDisplayFormPage):
70    grok.context(IApplicantsRoot)
71    grok.name('index')
72    grok.require('waeup.Public')
73    form_fields = grok.AutoFields(IApplicantsRoot)
74    label = _('Applicants Section')
75    pnav = 3
76
77    def update(self):
78        super(ApplicantsRootPage, self).update()
79        return
80
81    @property
82    def introduction(self):
83        # Here we know that the cookie has been set
84        lang = self.request.cookies.get('kofa.language')
85        html = self.context.description_dict.get(lang,'')
86        if html == '':
87            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
88            html = self.context.description_dict.get(portal_language,'')
89        return html
90
91    @property
92    def containers(self):
93        if self.layout.isAuthenticated():
94            return self.context.values()
95        values = sorted([container for container in self.context.values()
96                         if not container.hidden and container.enddate],
97                        key=lambda value: value.enddate, reverse=True)
98        return values
99
100class ApplicantsSearchPage(KofaPage):
101    grok.context(IApplicantsRoot)
102    grok.name('search')
103    grok.require('waeup.viewApplication')
104    label = _('Find applicants')
105    search_button = _('Find applicant')
106    pnav = 3
107
108    def update(self, *args, **kw):
109        form = self.request.form
110        self.results = []
111        if 'searchterm' in form and form['searchterm']:
112            self.searchterm = form['searchterm']
113            self.searchtype = form['searchtype']
114        elif 'old_searchterm' in form:
115            self.searchterm = form['old_searchterm']
116            self.searchtype = form['old_searchtype']
117        else:
118            if 'search' in form:
119                self.flash(_('Empty search string'), type='warning')
120            return
121        self.results = search(query=self.searchterm,
122            searchtype=self.searchtype, view=self)
123        if not self.results:
124            self.flash(_('No applicant found.'), type='warning')
125        return
126
127class ApplicantsRootManageFormPage(KofaEditFormPage):
128    grok.context(IApplicantsRoot)
129    grok.name('manage')
130    grok.template('applicantsrootmanagepage')
131    form_fields = grok.AutoFields(IApplicantsRoot)
132    label = _('Manage applicants section')
133    pnav = 3
134    grok.require('waeup.manageApplication')
135    taboneactions = [_('Save')]
136    tabtwoactions = [_('Add applicants container'), _('Remove selected')]
137    tabthreeactions1 = [_('Remove selected local roles')]
138    tabthreeactions2 = [_('Add local role')]
139    subunits = _('Applicants Containers')
140    doclink = DOCLINK + '/applicants.html'
141
142    def getLocalRoles(self):
143        roles = ILocalRolesAssignable(self.context)
144        return roles()
145
146    def getUsers(self):
147        """Get a list of all users.
148        """
149        for key, val in grok.getSite()['users'].items():
150            url = self.url(val)
151            yield(dict(url=url, name=key, val=val))
152
153    def getUsersWithLocalRoles(self):
154        return get_users_with_local_roles(self.context)
155
156    @jsaction(_('Remove selected'))
157    def delApplicantsContainers(self, **data):
158        form = self.request.form
159        if 'val_id' in form:
160            child_id = form['val_id']
161        else:
162            self.flash(_('No container selected!'), type='warning')
163            self.redirect(self.url(self.context, '@@manage')+'#tab2')
164            return
165        if not isinstance(child_id, list):
166            child_id = [child_id]
167        deleted = []
168        for id in child_id:
169            try:
170                del self.context[id]
171                deleted.append(id)
172            except:
173                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
174                    id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
175        if len(deleted):
176            self.flash(_('Successfully removed: ${a}',
177                mapping = {'a':', '.join(deleted)}))
178        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
179        self.context.logger.info(
180            '%s - removed: %s' % (ob_class, ', '.join(deleted)))
181        self.redirect(self.url(self.context, '@@manage')+'#tab2')
182        return
183
184    @action(_('Add applicants container'), validator=NullValidator)
185    def addApplicantsContainer(self, **data):
186        self.redirect(self.url(self.context, '@@add'))
187        return
188
189    @action(_('Add local role'), validator=NullValidator)
190    def addLocalRole(self, **data):
191        return add_local_role(self,3, **data)
192
193    @action(_('Remove selected local roles'))
194    def delLocalRoles(self, **data):
195        return del_local_roles(self,3,**data)
196
197    @action(_('Save'), style='primary')
198    def save(self, **data):
199        self.applyData(self.context, **data)
200        description = getattr(self.context, 'description', None)
201        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
202        self.context.description_dict = html2dict(description, portal_language)
203        self.flash(_('Form has been saved.'))
204        return
205
206class ApplicantsContainerAddFormPage(KofaAddFormPage):
207    grok.context(IApplicantsRoot)
208    grok.require('waeup.manageApplication')
209    grok.name('add')
210    grok.template('applicantscontaineraddpage')
211    label = _('Add applicants container')
212    pnav = 3
213
214    form_fields = grok.AutoFields(
215        IApplicantsContainerAdd).omit('code').omit('title')
216
217    @action(_('Add applicants container'))
218    def addApplicantsContainer(self, **data):
219        year = data['year']
220        code = u'%s%s' % (data['prefix'], year)
221        apptypes_dict = getUtility(IApplicantsUtils).APP_TYPES_DICT
222        title = apptypes_dict[data['prefix']][0]
223        title = u'%s %s/%s' % (title, year, year + 1)
224        if code in self.context.keys():
225            self.flash(
226              _('An applicants container for the same application '
227                'type and entrance year exists already in the database.'),
228                type='warning')
229            return
230        # Add new applicants container...
231        container = createObject(u'waeup.ApplicantsContainer')
232        self.applyData(container, **data)
233        container.code = code
234        container.title = title
235        self.context[code] = container
236        self.flash(_('Added:') + ' "%s".' % code)
237        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
238        self.context.logger.info('%s - added: %s' % (ob_class, code))
239        self.redirect(self.url(self.context, u'@@manage'))
240        return
241
242    @action(_('Cancel'), validator=NullValidator)
243    def cancel(self, **data):
244        self.redirect(self.url(self.context, '@@manage'))
245
246class ApplicantsRootBreadcrumb(Breadcrumb):
247    """A breadcrumb for applicantsroot.
248    """
249    grok.context(IApplicantsRoot)
250    title = _(u'Applicants')
251
252class ApplicantsContainerBreadcrumb(Breadcrumb):
253    """A breadcrumb for applicantscontainers.
254    """
255    grok.context(IApplicantsContainer)
256
257
258class ApplicantsExportsBreadcrumb(Breadcrumb):
259    """A breadcrumb for exports.
260    """
261    grok.context(VirtualApplicantsExportJobContainer)
262    title = _(u'Applicant Data Exports')
263    target = None
264
265class ApplicantBreadcrumb(Breadcrumb):
266    """A breadcrumb for applicants.
267    """
268    grok.context(IApplicant)
269
270    @property
271    def title(self):
272        """Get a title for a context.
273        """
274        return self.context.application_number
275
276class OnlinePaymentBreadcrumb(Breadcrumb):
277    """A breadcrumb for payments.
278    """
279    grok.context(IApplicantOnlinePayment)
280
281    @property
282    def title(self):
283        return self.context.p_id
284
285class ApplicantsStatisticsPage(KofaDisplayFormPage):
286    """Some statistics about applicants in a container.
287    """
288    grok.context(IApplicantsContainer)
289    grok.name('statistics')
290    grok.require('waeup.viewApplicationStatistics')
291    grok.template('applicantcontainerstatistics')
292
293    @property
294    def label(self):
295        return "%s" % self.context.title
296
297class ApplicantsContainerPage(KofaDisplayFormPage):
298    """The standard view for regular applicant containers.
299    """
300    grok.context(IApplicantsContainer)
301    grok.name('index')
302    grok.require('waeup.Public')
303    grok.template('applicantscontainerpage')
304    pnav = 3
305
306    @property
307    def form_fields(self):
308        form_fields = grok.AutoFields(IApplicantsContainer).omit(
309            'title', 'description')
310        form_fields[
311            'startdate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
312        form_fields[
313            'enddate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
314        if self.request.principal.id == 'zope.anybody':
315            form_fields = form_fields.omit(
316                'code', 'prefix', 'year', 'mode', 'hidden',
317                'strict_deadline', 'application_category',
318                'application_slip_notice')
319        return form_fields
320
321    @property
322    def introduction(self):
323        # Here we know that the cookie has been set
324        lang = self.request.cookies.get('kofa.language')
325        html = self.context.description_dict.get(lang,'')
326        if html == '':
327            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
328            html = self.context.description_dict.get(portal_language,'')
329        return html
330
331    @property
332    def label(self):
333        return "%s" % self.context.title
334
335class ApplicantsContainerManageFormPage(KofaEditFormPage):
336    grok.context(IApplicantsContainer)
337    grok.name('manage')
338    grok.template('applicantscontainermanagepage')
339    form_fields = grok.AutoFields(IApplicantsContainer)
340    taboneactions = [_('Save'),_('Cancel')]
341    tabtwoactions = [_('Remove selected'),_('Cancel'),
342        _('Create students from selected')]
343    tabthreeactions1 = [_('Remove selected local roles')]
344    tabthreeactions2 = [_('Add local role')]
345    # Use friendlier date widget...
346    grok.require('waeup.manageApplication')
347    doclink = DOCLINK + '/applicants.html'
348
349    @property
350    def label(self):
351        return _('Manage applicants container')
352
353    pnav = 3
354
355    @property
356    def showApplicants(self):
357        if self.context.counts[1] < 1000:
358            return True
359        return False
360
361    def getLocalRoles(self):
362        roles = ILocalRolesAssignable(self.context)
363        return roles()
364
365    def getUsers(self):
366        """Get a list of all users.
367        """
368        for key, val in grok.getSite()['users'].items():
369            url = self.url(val)
370            yield(dict(url=url, name=key, val=val))
371
372    def getUsersWithLocalRoles(self):
373        return get_users_with_local_roles(self.context)
374
375    @action(_('Save'), style='primary')
376    def save(self, **data):
377        changed_fields = self.applyData(self.context, **data)
378        if changed_fields:
379            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
380        else:
381            changed_fields = []
382        description = getattr(self.context, 'description', None)
383        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
384        self.context.description_dict = html2dict(description, portal_language)
385        self.flash(_('Form has been saved.'))
386        fields_string = ' + '.join(changed_fields)
387        self.context.writeLogMessage(self, 'saved: %s' % fields_string)
388        return
389
390    @jsaction(_('Remove selected'))
391    def delApplicant(self, **data):
392        form = self.request.form
393        if 'val_id' in form:
394            child_id = form['val_id']
395        else:
396            self.flash(_('No applicant selected!'), type='warning')
397            self.redirect(self.url(self.context, '@@manage')+'#tab2')
398            return
399        if not isinstance(child_id, list):
400            child_id = [child_id]
401        deleted = []
402        for id in child_id:
403            try:
404                del self.context[id]
405                deleted.append(id)
406            except:
407                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
408                    id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
409        if len(deleted):
410            self.flash(_('Successfully removed: ${a}',
411                mapping = {'a':', '.join(deleted)}))
412        self.redirect(self.url(self.context, u'@@manage')+'#tab2')
413        return
414
415    @action(_('Create students from selected'))
416    def createStudents(self, **data):
417        form = self.request.form
418        if 'val_id' in form:
419            child_id = form['val_id']
420        else:
421            self.flash(_('No applicant selected!'), type='warning')
422            self.redirect(self.url(self.context, '@@manage')+'#tab2')
423            return
424        if not isinstance(child_id, list):
425            child_id = [child_id]
426        created = []
427        for id in child_id:
428            success, msg = self.context[id].createStudent(view=self)
429            if success:
430                created.append(id)
431        if len(created):
432            self.flash(_('${a} students successfully created.',
433                mapping = {'a': len(created)}))
434        else:
435            self.flash(_('No student could be created.'), type='warning')
436        self.redirect(self.url(self.context, u'@@manage')+'#tab2')
437        return
438
439    @action(_('Cancel'), validator=NullValidator)
440    def cancel(self, **data):
441        self.redirect(self.url(self.context))
442        return
443
444    @action(_('Add local role'), validator=NullValidator)
445    def addLocalRole(self, **data):
446        return add_local_role(self,3, **data)
447
448    @action(_('Remove selected local roles'))
449    def delLocalRoles(self, **data):
450        return del_local_roles(self,3,**data)
451
452class ApplicantAddFormPage(KofaAddFormPage):
453    """Add-form to add an applicant.
454    """
455    grok.context(IApplicantsContainer)
456    grok.require('waeup.manageApplication')
457    grok.name('addapplicant')
458    #grok.template('applicantaddpage')
459    form_fields = grok.AutoFields(IApplicant).select(
460        'firstname', 'middlename', 'lastname',
461        'email', 'phone')
462    label = _('Add applicant')
463    pnav = 3
464    doclink = DOCLINK + '/applicants.html'
465
466    @action(_('Create application record'))
467    def addApplicant(self, **data):
468        applicant = createObject(u'waeup.Applicant')
469        self.applyData(applicant, **data)
470        self.context.addApplicant(applicant)
471        self.flash(_('Application record created.'))
472        self.redirect(
473            self.url(self.context[applicant.application_number], 'index'))
474        return
475
476class ApplicantsContainerPrefillFormPage(KofaAddFormPage):
477    """Form to pre-fill applicants containers.
478    """
479    grok.context(IApplicantsContainer)
480    grok.require('waeup.manageApplication')
481    grok.name('prefill')
482    grok.template('prefillcontainer')
483    label = _('Pre-fill container')
484    pnav = 3
485    doclink = DOCLINK + '/applicants/browser.html#preparation-and-maintenance-of-applicants-containers'
486
487    def update(self):
488        if self.context.mode == 'update':
489            self.flash(_('Container must be in create mode to be pre-filled.'),
490                type='danger')
491            self.redirect(self.url(self.context))
492            return
493        super(ApplicantsContainerPrefillFormPage, self).update()
494        return
495
496    @action(_('Pre-fill now'), style='primary')
497    def addApplicants(self):
498        form = self.request.form
499        if 'number' in form and form['number']:
500            number = int(form['number'])
501        for i in range(number):
502            applicant = createObject(u'waeup.Applicant')
503            self.context.addApplicant(applicant)
504        self.flash(_('%s application records created.' % number))
505        self.context.writeLogMessage(self, '%s applicants created' % (number))
506        self.redirect(self.url(self.context, 'index'))
507        return
508
509    @action(_('Cancel'), validator=NullValidator)
510    def cancel(self, **data):
511        self.redirect(self.url(self.context))
512        return
513
514class ApplicantsContainerPurgeFormPage(KofaEditFormPage):
515    """Form to pre-fill applicants containers.
516    """
517    grok.context(IApplicantsContainer)
518    grok.require('waeup.manageApplication')
519    grok.name('purge')
520    grok.template('purgecontainer')
521    label = _('Purge container')
522    pnav = 3
523    doclink = DOCLINK + '/applicants/browser.html#preparation-and-maintenance-of-applicants-containers'
524
525    @action(_('Remove initialized records'),
526              tooltip=_('Don\'t use if application is in progress!'),
527              warning=_('Are you really sure?'),
528              style='primary')
529    def purgeInitialized(self):
530        form = self.request.form
531        purged = 0
532        keys = [key for key in self.context.keys()]
533        for key in keys:
534            if self.context[key].state == 'initialized':
535                del self.context[key]
536                purged += 1
537        self.flash(_('%s application records purged.' % purged))
538        self.context.writeLogMessage(self, '%s applicants purged' % (purged))
539        self.redirect(self.url(self.context, 'index'))
540        return
541
542    @action(_('Cancel'), validator=NullValidator)
543    def cancel(self, **data):
544        self.redirect(self.url(self.context))
545        return
546
547class ApplicantDisplayFormPage(KofaDisplayFormPage):
548    """A display view for applicant data.
549    """
550    grok.context(IApplicant)
551    grok.name('index')
552    grok.require('waeup.viewApplication')
553    grok.template('applicantdisplaypage')
554    label = _('Applicant')
555    pnav = 3
556    hide_hint = False
557
558    @property
559    def form_fields(self):
560        if self.context.special:
561            form_fields = grok.AutoFields(ISpecialApplicant).omit('locked')
562        else:
563            form_fields = grok.AutoFields(IApplicant).omit(
564                'locked', 'course_admitted', 'password', 'suspended')
565        return form_fields
566
567    @property
568    def target(self):
569        return getattr(self.context.__parent__, 'prefix', None)
570
571    @property
572    def separators(self):
573        return getUtility(IApplicantsUtils).SEPARATORS_DICT
574
575    def update(self):
576        self.passport_url = self.url(self.context, 'passport.jpg')
577        # Mark application as started if applicant logs in for the first time
578        usertype = getattr(self.request.principal, 'user_type', None)
579        if usertype == 'applicant' and \
580            IWorkflowState(self.context).getState() == INITIALIZED:
581            IWorkflowInfo(self.context).fireTransition('start')
582        if usertype == 'applicant' and self.context.state == 'created':
583            session = '%s/%s' % (self.context.__parent__.year,
584                                 self.context.__parent__.year+1)
585            title = getattr(grok.getSite()['configuration'], 'name', u'Sample University')
586            msg = _(
587                '\n <strong>Congratulations!</strong>' +
588                ' You have been offered provisional admission into the' +
589                ' ${c} Academic Session of ${d}.'
590                ' Your student record has been created for you.' +
591                ' Please, logout again and proceed to the' +
592                ' login page of the portal.'
593                ' Then enter your new student credentials:' +
594                ' user name= ${a}, password = ${b}.' +
595                ' Change your password when you have logged in.',
596                mapping = {
597                    'a':self.context.student_id,
598                    'b':self.context.application_number,
599                    'c':session,
600                    'd':title}
601                )
602            self.flash(msg)
603        return
604
605    @property
606    def hasPassword(self):
607        if self.context.password:
608            return _('set')
609        return _('unset')
610
611    @property
612    def label(self):
613        container_title = self.context.__parent__.title
614        return _('${a} <br /> Application Record ${b}', mapping = {
615            'a':container_title, 'b':self.context.application_number})
616
617    def getCourseAdmitted(self):
618        """Return link, title and code in html format to the certificate
619           admitted.
620        """
621        course_admitted = self.context.course_admitted
622        if getattr(course_admitted, '__parent__',None):
623            url = self.url(course_admitted)
624            title = course_admitted.title
625            code = course_admitted.code
626            return '<a href="%s">%s - %s</a>' %(url,code,title)
627        return ''
628
629class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
630    grok.context(IApplicant)
631    grok.name('base')
632    form_fields = grok.AutoFields(IApplicant).select(
633        'applicant_id','email', 'course1')
634
635class CreateStudentPage(UtilityView, grok.View):
636    """Create a student object from applicant data.
637    """
638    grok.context(IApplicant)
639    grok.name('createstudent')
640    grok.require('waeup.manageStudent')
641
642    def update(self):
643        msg = self.context.createStudent(view=self)[1]
644        self.flash(msg, type='warning')
645        self.redirect(self.url(self.context))
646        return
647
648    def render(self):
649        return
650
651class CreateAllStudentsPage(UtilityView, grok.View):
652    """Create all student objects from applicant data
653    in the root container or in a specific  applicants container only.
654    Only PortalManagers can do this.
655    """
656    #grok.context(IApplicantsContainer)
657    grok.name('createallstudents')
658    grok.require('waeup.managePortal')
659
660    def update(self):
661        cat = getUtility(ICatalog, name='applicants_catalog')
662        results = list(cat.searchResults(state=(ADMITTED, ADMITTED)))
663        created = []
664        container_only = False
665        applicants_root = grok.getSite()['applicants']
666        if isinstance(self.context, ApplicantsContainer):
667            container_only = True
668        for result in results:
669            if container_only and result.__parent__ is not self.context:
670                continue
671            success, msg = result.createStudent(view=self)
672            if success:
673                created.append(result.applicant_id)
674            else:
675                ob_class = self.__implemented__.__name__.replace(
676                    'waeup.kofa.','')
677                applicants_root.logger.info(
678                    '%s - %s - %s' % (ob_class, result.applicant_id, msg))
679        if len(created):
680            self.flash(_('${a} students successfully created.',
681                mapping = {'a': len(created)}))
682        else:
683            self.flash(_('No student could be created.'), type='warning')
684        self.redirect(self.url(self.context))
685        return
686
687    def render(self):
688        return
689
690class ApplicationFeePaymentAddPage(UtilityView, grok.View):
691    """ Page to add an online payment ticket
692    """
693    grok.context(IApplicant)
694    grok.name('addafp')
695    grok.require('waeup.payApplicant')
696    factory = u'waeup.ApplicantOnlinePayment'
697
698    @property
699    def custom_requirements(self):
700        return ''
701
702    def update(self):
703        # Additional requirements in custom packages.
704        if self.custom_requirements:
705            self.flash(
706                self.custom_requirements,
707                type='danger')
708            self.redirect(self.url(self.context))
709            return
710        if not self.context.special:
711            for key in self.context.keys():
712                ticket = self.context[key]
713                if ticket.p_state == 'paid':
714                      self.flash(
715                          _('This type of payment has already been made.'),
716                          type='warning')
717                      self.redirect(self.url(self.context))
718                      return
719        applicants_utils = getUtility(IApplicantsUtils)
720        container = self.context.__parent__
721        payment = createObject(self.factory)
722        failure = applicants_utils.setPaymentDetails(
723            container, payment, self.context)
724        if failure is not None:
725            self.flash(failure, type='danger')
726            self.redirect(self.url(self.context))
727            return
728        self.context[payment.p_id] = payment
729        self.context.writeLogMessage(self, 'added: %s' % payment.p_id)
730        self.flash(_('Payment ticket created.'))
731        self.redirect(self.url(payment))
732        return
733
734    def render(self):
735        return
736
737
738class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
739    """ Page to view an online payment ticket
740    """
741    grok.context(IApplicantOnlinePayment)
742    grok.name('index')
743    grok.require('waeup.viewApplication')
744    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
745    form_fields[
746        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
747    form_fields[
748        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
749    pnav = 3
750
751    @property
752    def label(self):
753        return _('${a}: Online Payment Ticket ${b}', mapping = {
754            'a':self.context.__parent__.display_fullname,
755            'b':self.context.p_id})
756
757class OnlinePaymentApprovePage(UtilityView, grok.View):
758    """ Approval view
759    """
760    grok.context(IApplicantOnlinePayment)
761    grok.name('approve')
762    grok.require('waeup.managePortal')
763
764    def update(self):
765        flashtype, msg, log = self.context.approveApplicantPayment()
766        if log is not None:
767            applicant = self.context.__parent__
768            # Add log message to applicants.log
769            applicant.writeLogMessage(self, log)
770            # Add log message to payments.log
771            self.context.logger.info(
772                '%s,%s,%s,%s,%s,,,,,,' % (
773                applicant.applicant_id,
774                self.context.p_id, self.context.p_category,
775                self.context.amount_auth, self.context.r_code))
776        self.flash(msg, type=flashtype)
777        return
778
779    def render(self):
780        self.redirect(self.url(self.context, '@@index'))
781        return
782
783class ExportPDFPaymentSlipPage(UtilityView, grok.View):
784    """Deliver a PDF slip of the context.
785    """
786    grok.context(IApplicantOnlinePayment)
787    grok.name('payment_slip.pdf')
788    grok.require('waeup.viewApplication')
789    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
790    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
791    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
792    prefix = 'form'
793    note = None
794
795    @property
796    def title(self):
797        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
798        return translate(_('Payment Data'), 'waeup.kofa',
799            target_language=portal_language)
800
801    @property
802    def label(self):
803        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
804        return translate(_('Online Payment Slip'),
805            'waeup.kofa', target_language=portal_language) \
806            + ' %s' % self.context.p_id
807
808    @property
809    def payment_slip_download_warning(self):
810        if self.context.__parent__.state != SUBMITTED:
811            return _('Please submit the application form before '
812                     'trying to download payment slips.')
813        return ''
814
815    def render(self):
816        if self.payment_slip_download_warning:
817            self.flash(self.payment_slip_download_warning, type='danger')
818            self.redirect(self.url(self.context))
819            return
820        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
821            self.request)
822        students_utils = getUtility(IStudentsUtils)
823        return students_utils.renderPDF(self,'payment_slip.pdf',
824            self.context.__parent__, applicantview, note=self.note)
825
826class ExportPDFPageApplicationSlip(UtilityView, grok.View):
827    """Deliver a PDF slip of the context.
828    """
829    grok.context(IApplicant)
830    grok.name('application_slip.pdf')
831    grok.require('waeup.viewApplication')
832    prefix = 'form'
833
834    def update(self):
835        if self.context.state in ('initialized', 'started', 'paid'):
836            self.flash(
837                _('Please pay and submit before trying to download '
838                  'the application slip.'), type='warning')
839            return self.redirect(self.url(self.context))
840        return
841
842    def render(self):
843        try:
844            pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
845                view=self)
846        except IOError:
847            self.flash(
848                _('Your image file is corrupted. '
849                  'Please replace.'), type='danger')
850            return self.redirect(self.url(self.context))
851        self.response.setHeader(
852            'Content-Type', 'application/pdf')
853        return pdfstream
854
855def handle_img_upload(upload, context, view):
856    """Handle upload of applicant image.
857
858    Returns `True` in case of success or `False`.
859
860    Please note that file pointer passed in (`upload`) most probably
861    points to end of file when leaving this function.
862    """
863    size = file_size(upload)
864    if size > MAX_UPLOAD_SIZE:
865        view.flash(_('Uploaded image is too big!'), type='danger')
866        return False
867    dummy, ext = os.path.splitext(upload.filename)
868    ext.lower()
869    if ext != '.jpg':
870        view.flash(_('jpg file extension expected.'), type='danger')
871        return False
872    upload.seek(0) # file pointer moved when determining size
873    store = getUtility(IExtFileStore)
874    file_id = IFileStoreNameChooser(context).chooseName()
875    store.createFile(file_id, upload)
876    return True
877
878class ApplicantManageFormPage(KofaEditFormPage):
879    """A full edit view for applicant data.
880    """
881    grok.context(IApplicant)
882    grok.name('manage')
883    grok.require('waeup.manageApplication')
884    grok.template('applicanteditpage')
885    manage_applications = True
886    pnav = 3
887    display_actions = [[_('Save'), _('Finally Submit')],
888        [_('Add online payment ticket'),_('Remove selected tickets')]]
889
890    @property
891    def form_fields(self):
892        if self.context.special:
893            form_fields = grok.AutoFields(ISpecialApplicant)
894            form_fields['applicant_id'].for_display = True
895        else:
896            form_fields = grok.AutoFields(IApplicant)
897            form_fields['student_id'].for_display = True
898            form_fields['applicant_id'].for_display = True
899        return form_fields
900
901    @property
902    def target(self):
903        return getattr(self.context.__parent__, 'prefix', None)
904
905    @property
906    def separators(self):
907        return getUtility(IApplicantsUtils).SEPARATORS_DICT
908
909    @property
910    def custom_upload_requirements(self):
911        return ''
912
913    def update(self):
914        super(ApplicantManageFormPage, self).update()
915        self.wf_info = IWorkflowInfo(self.context)
916        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
917        self.upload_success = None
918        upload = self.request.form.get('form.passport', None)
919        if upload:
920            if self.custom_upload_requirements:
921                self.flash(
922                    self.custom_upload_requirements,
923                    type='danger')
924                self.redirect(self.url(self.context))
925                return
926            # We got a fresh upload, upload_success is
927            # either True or False
928            self.upload_success = handle_img_upload(
929                upload, self.context, self)
930            if self.upload_success:
931                self.context.writeLogMessage(self, 'saved: passport')
932        return
933
934    @property
935    def label(self):
936        container_title = self.context.__parent__.title
937        return _('${a} <br /> Application Form ${b}', mapping = {
938            'a':container_title, 'b':self.context.application_number})
939
940    def getTransitions(self):
941        """Return a list of dicts of allowed transition ids and titles.
942
943        Each list entry provides keys ``name`` and ``title`` for
944        internal name and (human readable) title of a single
945        transition.
946        """
947        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
948            if not t[0] in ('pay', 'create')]
949        return [dict(name='', title=_('No transition'))] +[
950            dict(name=x, title=y) for x, y in allowed_transitions]
951
952    @action(_('Save'), style='primary')
953    def save(self, **data):
954        form = self.request.form
955        password = form.get('password', None)
956        password_ctl = form.get('control_password', None)
957        if password:
958            validator = getUtility(IPasswordValidator)
959            errors = validator.validate_password(password, password_ctl)
960            if errors:
961                self.flash( ' '.join(errors), type='danger')
962                return
963        if self.upload_success is False:  # False is not None!
964            # Error during image upload. Ignore other values.
965            return
966        changed_fields = self.applyData(self.context, **data)
967        # Turn list of lists into single list
968        if changed_fields:
969            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
970        else:
971            changed_fields = []
972        if password:
973            # Now we know that the form has no errors and can set password ...
974            IUserAccount(self.context).setPassword(password)
975            changed_fields.append('password')
976        fields_string = ' + '.join(changed_fields)
977        trans_id = form.get('transition', None)
978        if trans_id:
979            self.wf_info.fireTransition(trans_id)
980        self.flash(_('Form has been saved.'))
981        if fields_string:
982            self.context.writeLogMessage(self, 'saved: %s' % fields_string)
983        return
984
985    def unremovable(self, ticket):
986        return False
987
988    # This method is also used by the ApplicantEditFormPage
989    def delPaymentTickets(self, **data):
990        form = self.request.form
991        if 'val_id' in form:
992            child_id = form['val_id']
993        else:
994            self.flash(_('No payment selected.'), type='warning')
995            self.redirect(self.url(self.context))
996            return
997        if not isinstance(child_id, list):
998            child_id = [child_id]
999        deleted = []
1000        for id in child_id:
1001            # Applicants are not allowed to remove used payment tickets
1002            if not self.unremovable(self.context[id]):
1003                try:
1004                    del self.context[id]
1005                    deleted.append(id)
1006                except:
1007                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
1008                      id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
1009        if len(deleted):
1010            self.flash(_('Successfully removed: ${a}',
1011                mapping = {'a':', '.join(deleted)}))
1012            self.context.writeLogMessage(
1013                self, 'removed: % s' % ', '.join(deleted))
1014        return
1015
1016    # We explicitely want the forms to be validated before payment tickets
1017    # can be created. If no validation is requested, use
1018    # 'validator=NullValidator' in the action directive
1019    @action(_('Add online payment ticket'), style='primary')
1020    def addPaymentTicket(self, **data):
1021        self.redirect(self.url(self.context, '@@addafp'))
1022        return
1023
1024    @jsaction(_('Remove selected tickets'))
1025    def removePaymentTickets(self, **data):
1026        self.delPaymentTickets(**data)
1027        self.redirect(self.url(self.context) + '/@@manage')
1028        return
1029
1030    # Not used in base package
1031    def file_exists(self, attr):
1032        file = getUtility(IExtFileStore).getFileByContext(
1033            self.context, attr=attr)
1034        if file:
1035            return True
1036        else:
1037            return False
1038
1039class ApplicantEditFormPage(ApplicantManageFormPage):
1040    """An applicant-centered edit view for applicant data.
1041    """
1042    grok.context(IApplicantEdit)
1043    grok.name('edit')
1044    grok.require('waeup.handleApplication')
1045    grok.template('applicanteditpage')
1046    manage_applications = False
1047    submit_state = PAID
1048
1049    @property
1050    def form_fields(self):
1051        if self.context.special:
1052            form_fields = grok.AutoFields(ISpecialApplicant).omit(
1053                'locked', 'suspended')
1054            form_fields['applicant_id'].for_display = True
1055        else:
1056            form_fields = grok.AutoFields(IApplicantEdit).omit(
1057                'locked', 'course_admitted', 'student_id',
1058                'suspended'
1059                )
1060            form_fields['applicant_id'].for_display = True
1061            form_fields['reg_number'].for_display = True
1062        return form_fields
1063
1064    @property
1065    def display_actions(self):
1066        state = IWorkflowState(self.context).getState()
1067        # If the form is unlocked, applicants are allowed to save the form
1068        # and remove unused tickets.
1069        actions = [[_('Save')], [_('Remove selected tickets')]]
1070        # Only in state started they can also add tickets.
1071        if state == STARTED:
1072            actions = [[_('Save')],
1073                [_('Add online payment ticket'),_('Remove selected tickets')]]
1074        # In state paid, they can submit the data and further add tickets
1075        # if the application is special.
1076        elif self.context.special and state == PAID:
1077            actions = [[_('Save'), _('Finally Submit')],
1078                [_('Add online payment ticket'),_('Remove selected tickets')]]
1079        elif state == PAID:
1080            actions = [[_('Save'), _('Finally Submit')],
1081                [_('Remove selected tickets')]]
1082        return actions
1083
1084    def unremovable(self, ticket):
1085        return ticket.r_code
1086
1087    def emit_lock_message(self):
1088        self.flash(_('The requested form is locked (read-only).'),
1089                   type='warning')
1090        self.redirect(self.url(self.context))
1091        return
1092
1093    def update(self):
1094        if self.context.locked or (
1095            self.context.__parent__.expired and
1096            self.context.__parent__.strict_deadline):
1097            self.emit_lock_message()
1098            return
1099        super(ApplicantEditFormPage, self).update()
1100        return
1101
1102    def dataNotComplete(self):
1103        store = getUtility(IExtFileStore)
1104        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
1105            return _('No passport picture uploaded.')
1106        if not self.request.form.get('confirm_passport', False):
1107            return _('Passport picture confirmation box not ticked.')
1108        return False
1109
1110    # We explicitely want the forms to be validated before payment tickets
1111    # can be created. If no validation is requested, use
1112    # 'validator=NullValidator' in the action directive
1113    @action(_('Add online payment ticket'), style='primary')
1114    def addPaymentTicket(self, **data):
1115        self.redirect(self.url(self.context, '@@addafp'))
1116        return
1117
1118    @jsaction(_('Remove selected tickets'))
1119    def removePaymentTickets(self, **data):
1120        self.delPaymentTickets(**data)
1121        self.redirect(self.url(self.context) + '/@@edit')
1122        return
1123
1124    @action(_('Save'), style='primary')
1125    def save(self, **data):
1126        if self.upload_success is False:  # False is not None!
1127            # Error during image upload. Ignore other values.
1128            return
1129        self.applyData(self.context, **data)
1130        self.flash(_('Form has been saved.'))
1131        return
1132
1133    @action(_('Finally Submit'), warning=WARNING)
1134    def finalsubmit(self, **data):
1135        if self.upload_success is False:  # False is not None!
1136            return # error during image upload. Ignore other values
1137        if self.dataNotComplete():
1138            self.flash(self.dataNotComplete(), type='danger')
1139            return
1140        self.applyData(self.context, **data)
1141        state = IWorkflowState(self.context).getState()
1142        # This shouldn't happen, but the application officer
1143        # might have forgotten to lock the form after changing the state
1144        if state != self.submit_state:
1145            self.flash(_('The form cannot be submitted. Wrong state!'),
1146                       type='danger')
1147            return
1148        IWorkflowInfo(self.context).fireTransition('submit')
1149        # application_date is used in export files for sorting.
1150        # We can thus store utc.
1151        self.context.application_date = datetime.utcnow()
1152        self.flash(_('Form has been submitted.'))
1153        self.redirect(self.url(self.context))
1154        return
1155
1156class PassportImage(grok.View):
1157    """Renders the passport image for applicants.
1158    """
1159    grok.name('passport.jpg')
1160    grok.context(IApplicant)
1161    grok.require('waeup.viewApplication')
1162
1163    def render(self):
1164        # A filename chooser turns a context into a filename suitable
1165        # for file storage.
1166        image = getUtility(IExtFileStore).getFileByContext(self.context)
1167        self.response.setHeader(
1168            'Content-Type', 'image/jpeg')
1169        if image is None:
1170            # show placeholder image
1171            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
1172        return image
1173
1174class ApplicantRegistrationPage(KofaAddFormPage):
1175    """Captcha'd registration page for applicants.
1176    """
1177    grok.context(IApplicantsContainer)
1178    grok.name('register')
1179    grok.require('waeup.Anonymous')
1180    grok.template('applicantregister')
1181
1182    @property
1183    def form_fields(self):
1184        form_fields = None
1185        if self.context.mode == 'update':
1186            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1187                'lastname','reg_number','email')
1188        else: #if self.context.mode == 'create':
1189            form_fields = grok.AutoFields(IApplicantEdit).select(
1190                'firstname', 'middlename', 'lastname', 'email', 'phone')
1191        return form_fields
1192
1193    @property
1194    def label(self):
1195        return _('Apply for ${a}',
1196            mapping = {'a':self.context.title})
1197
1198    def update(self):
1199        if self.context.expired:
1200            self.flash(_('Outside application period.'), type='warning')
1201            self.redirect(self.url(self.context))
1202            return
1203        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1204        if blocker:
1205            self.flash(_('The portal is in maintenance mode '
1206                        'and registration temporarily disabled.'),
1207                       type='warning')
1208            self.redirect(self.url(self.context))
1209            return
1210        # Handle captcha
1211        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1212        self.captcha_result = self.captcha.verify(self.request)
1213        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1214        return
1215
1216    def _redirect(self, email, password, applicant_id):
1217        # Forward only email to landing page in base package.
1218        self.redirect(self.url(self.context, 'registration_complete',
1219            data = dict(email=email)))
1220        return
1221
1222    @action(_('Send login credentials to email address'), style='primary')
1223    def register(self, **data):
1224        if not self.captcha_result.is_valid:
1225            # Captcha will display error messages automatically.
1226            # No need to flash something.
1227            return
1228        if self.context.mode == 'create':
1229            # Check if there are unused records in this container which
1230            # can be taken
1231            applicant = self.context.first_unused
1232            if applicant is None:
1233                # Add applicant
1234                applicant = createObject(u'waeup.Applicant')
1235                self.context.addApplicant(applicant)
1236            self.applyData(applicant, **data)
1237            applicant.reg_number = applicant.applicant_id
1238            notify(grok.ObjectModifiedEvent(applicant))
1239        elif self.context.mode == 'update':
1240            # Update applicant
1241            reg_number = data.get('reg_number','')
1242            lastname = data.get('lastname','')
1243            cat = getUtility(ICatalog, name='applicants_catalog')
1244            results = list(
1245                cat.searchResults(reg_number=(reg_number, reg_number)))
1246            if results:
1247                applicant = results[0]
1248                if getattr(applicant,'lastname',None) is None:
1249                    self.flash(_('An error occurred.'), type='danger')
1250                    return
1251                elif applicant.lastname.lower() != lastname.lower():
1252                    # Don't tell the truth here. Anonymous must not
1253                    # know that a record was found and only the lastname
1254                    # verification failed.
1255                    self.flash(
1256                        _('No application record found.'), type='warning')
1257                    return
1258                elif applicant.password is not None and \
1259                    applicant.state != INITIALIZED:
1260                    self.flash(_('Your password has already been set and used. '
1261                                 'Please proceed to the login page.'),
1262                               type='warning')
1263                    return
1264                # Store email address but nothing else.
1265                applicant.email = data['email']
1266                notify(grok.ObjectModifiedEvent(applicant))
1267            else:
1268                # No record found, this is the truth.
1269                self.flash(_('No application record found.'), type='warning')
1270                return
1271        else:
1272            # Does not happen but anyway ...
1273            return
1274        kofa_utils = getUtility(IKofaUtils)
1275        password = kofa_utils.genPassword()
1276        IUserAccount(applicant).setPassword(password)
1277        # Send email with credentials
1278        login_url = self.url(grok.getSite(), 'login')
1279        url_info = u'Login: %s' % login_url
1280        msg = _('You have successfully been registered for the')
1281        if kofa_utils.sendCredentials(IUserAccount(applicant),
1282            password, url_info, msg):
1283            email_sent = applicant.email
1284        else:
1285            email_sent = None
1286        self._redirect(email=email_sent, password=password,
1287            applicant_id=applicant.applicant_id)
1288        return
1289
1290class ApplicantRegistrationEmailSent(KofaPage):
1291    """Landing page after successful registration.
1292
1293    """
1294    grok.name('registration_complete')
1295    grok.require('waeup.Public')
1296    grok.template('applicantregemailsent')
1297    label = _('Your registration was successful.')
1298
1299    def update(self, email=None, applicant_id=None, password=None):
1300        self.email = email
1301        self.password = password
1302        self.applicant_id = applicant_id
1303        return
1304
1305class ApplicantCheckStatusPage(KofaPage):
1306    """Captcha'd status checking page for applicants.
1307    """
1308    grok.context(IApplicantsRoot)
1309    grok.name('checkstatus')
1310    grok.require('waeup.Anonymous')
1311    grok.template('applicantcheckstatus')
1312    buttonname = _('Submit')
1313
1314    def label(self):
1315        if self.result:
1316            return _('Admission status of ${a}',
1317                     mapping = {'a':self.applicant.applicant_id})
1318        return _('Check your admission status')
1319
1320    def update(self, SUBMIT=None):
1321        form = self.request.form
1322        self.result = False
1323        # Handle captcha
1324        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1325        self.captcha_result = self.captcha.verify(self.request)
1326        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1327        if SUBMIT:
1328            if not self.captcha_result.is_valid:
1329                # Captcha will display error messages automatically.
1330                # No need to flash something.
1331                return
1332            unique_id = form.get('unique_id', None)
1333            lastname = form.get('lastname', None)
1334            if not unique_id or not lastname:
1335                self.flash(
1336                    _('Required input missing.'), type='warning')
1337                return
1338            cat = getUtility(ICatalog, name='applicants_catalog')
1339            results = list(
1340                cat.searchResults(applicant_id=(unique_id, unique_id)))
1341            if not results:
1342                results = list(
1343                    cat.searchResults(reg_number=(unique_id, unique_id)))
1344            if results:
1345                applicant = results[0]
1346                if applicant.lastname.lower().strip() != lastname.lower():
1347                    # Don't tell the truth here. Anonymous must not
1348                    # know that a record was found and only the lastname
1349                    # verification failed.
1350                    self.flash(
1351                        _('No application record found.'), type='warning')
1352                    return
1353            else:
1354                self.flash(_('No application record found.'), type='warning')
1355                return
1356            self.applicant = applicant
1357            self.entry_session = "%s/%s" % (
1358                applicant.__parent__.year,
1359                applicant.__parent__.year+1)
1360            course_admitted = getattr(applicant, 'course_admitted', None)
1361            self.course_admitted = False
1362            if course_admitted is not None:
1363                self.course_admitted = True
1364                self.longtitle = course_admitted.longtitle
1365                self.department = course_admitted.__parent__.__parent__.longtitle
1366                self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1367            self.result = True
1368            self.admitted = False
1369            self.not_admitted = False
1370            self.submitted = False
1371            self.not_submitted = False
1372            self.created = False
1373            if applicant.state in (ADMITTED, CREATED):
1374                self.admitted = True
1375            if applicant.state in (CREATED):
1376                self.created = True
1377                self.student_id = applicant.student_id
1378                self.password = applicant.application_number
1379            if applicant.state in (NOT_ADMITTED,):
1380                self.not_admitted = True
1381            if applicant.state in (SUBMITTED,):
1382                self.submitted = True
1383            if applicant.state in (INITIALIZED, STARTED, PAID):
1384                self.not_submitted = True
1385        return
1386
1387class ExportJobContainerOverview(KofaPage):
1388    """Page that lists active applicant data export jobs and provides links
1389    to discard or download CSV files.
1390
1391    """
1392    grok.context(VirtualApplicantsExportJobContainer)
1393    grok.require('waeup.manageApplication')
1394    grok.name('index.html')
1395    grok.template('exportjobsindex')
1396    label = _('Data Exports')
1397    pnav = 3
1398
1399    def update(self, CREATE=None, DISCARD=None, job_id=None):
1400        if CREATE:
1401            self.redirect(self.url('@@start_export'))
1402            return
1403        if DISCARD and job_id:
1404            entry = self.context.entry_from_job_id(job_id)
1405            self.context.delete_export_entry(entry)
1406            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1407            self.context.logger.info(
1408                '%s - discarded: job_id=%s' % (ob_class, job_id))
1409            self.flash(_('Discarded export') + ' %s' % job_id)
1410        self.entries = doll_up(self, user=self.request.principal.id)
1411        return
1412
1413class ExportJobContainerJobStart(KofaPage):
1414    """Page that starts an applicants export job.
1415
1416    """
1417    grok.context(VirtualApplicantsExportJobContainer)
1418    grok.require('waeup.manageApplication')
1419    grok.name('start_export')
1420
1421    def update(self):
1422        utils = queryUtility(IKofaUtils)
1423        if not utils.expensive_actions_allowed():
1424            self.flash(_(
1425                "Currently, exporters cannot be started due to high "
1426                "system load. Please try again later."), type='danger')
1427            self.entries = doll_up(self, user=None)
1428            return
1429        exporter = 'applicants'
1430        container_code = self.context.__parent__.code
1431        job_id = self.context.start_export_job(exporter,
1432                                      self.request.principal.id,
1433                                      container=container_code)
1434
1435        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1436        self.context.logger.info(
1437            '%s - exported: %s (%s), job_id=%s'
1438            % (ob_class, exporter, container_code, job_id))
1439        self.flash(_('Export started.'))
1440        self.redirect(self.url(self.context))
1441        return
1442
1443    def render(self):
1444        return
1445
1446class ExportJobContainerDownload(ExportCSVView):
1447    """Page that downloads a students export csv file.
1448
1449    """
1450    grok.context(VirtualApplicantsExportJobContainer)
1451    grok.require('waeup.manageApplication')
Note: See TracBrowser for help on using the repository browser.