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

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

Remove application fee fallback option. This option has never been used and is confusing.

Hide Payment Tickets section on application pages if fee isn't set.

  • Property svn:keywords set to Id
File size: 54.1 KB
Line 
1## $Id: browser.py 13886 2016-06-07 20:08:05Z 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 display_payments(self):
560        if self.context.special:
561            return True
562        return getattr(self.context.__parent__, 'application_fee', None)
563
564    @property
565    def form_fields(self):
566        if self.context.special:
567            form_fields = grok.AutoFields(ISpecialApplicant).omit('locked')
568        else:
569            form_fields = grok.AutoFields(IApplicant).omit(
570                'locked', 'course_admitted', 'password', 'suspended')
571        return form_fields
572
573    @property
574    def target(self):
575        return getattr(self.context.__parent__, 'prefix', None)
576
577    @property
578    def separators(self):
579        return getUtility(IApplicantsUtils).SEPARATORS_DICT
580
581    def update(self):
582        self.passport_url = self.url(self.context, 'passport.jpg')
583        # Mark application as started if applicant logs in for the first time
584        usertype = getattr(self.request.principal, 'user_type', None)
585        if usertype == 'applicant' and \
586            IWorkflowState(self.context).getState() == INITIALIZED:
587            IWorkflowInfo(self.context).fireTransition('start')
588        if usertype == 'applicant' and self.context.state == 'created':
589            session = '%s/%s' % (self.context.__parent__.year,
590                                 self.context.__parent__.year+1)
591            title = getattr(grok.getSite()['configuration'], 'name', u'Sample University')
592            msg = _(
593                '\n <strong>Congratulations!</strong>' +
594                ' You have been offered provisional admission into the' +
595                ' ${c} Academic Session of ${d}.'
596                ' Your student record has been created for you.' +
597                ' Please, logout again and proceed to the' +
598                ' login page of the portal.'
599                ' Then enter your new student credentials:' +
600                ' user name= ${a}, password = ${b}.' +
601                ' Change your password when you have logged in.',
602                mapping = {
603                    'a':self.context.student_id,
604                    'b':self.context.application_number,
605                    'c':session,
606                    'd':title}
607                )
608            self.flash(msg)
609        return
610
611    @property
612    def hasPassword(self):
613        if self.context.password:
614            return _('set')
615        return _('unset')
616
617    @property
618    def label(self):
619        container_title = self.context.__parent__.title
620        return _('${a} <br /> Application Record ${b}', mapping = {
621            'a':container_title, 'b':self.context.application_number})
622
623    def getCourseAdmitted(self):
624        """Return link, title and code in html format to the certificate
625           admitted.
626        """
627        course_admitted = self.context.course_admitted
628        if getattr(course_admitted, '__parent__',None):
629            url = self.url(course_admitted)
630            title = course_admitted.title
631            code = course_admitted.code
632            return '<a href="%s">%s - %s</a>' %(url,code,title)
633        return ''
634
635class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
636    grok.context(IApplicant)
637    grok.name('base')
638    form_fields = grok.AutoFields(IApplicant).select(
639        'applicant_id','email', 'course1')
640
641class CreateStudentPage(UtilityView, grok.View):
642    """Create a student object from applicant data.
643    """
644    grok.context(IApplicant)
645    grok.name('createstudent')
646    grok.require('waeup.manageStudent')
647
648    def update(self):
649        msg = self.context.createStudent(view=self)[1]
650        self.flash(msg, type='warning')
651        self.redirect(self.url(self.context))
652        return
653
654    def render(self):
655        return
656
657class CreateAllStudentsPage(UtilityView, grok.View):
658    """Create all student objects from applicant data
659    in the root container or in a specific  applicants container only.
660    Only PortalManagers can do this.
661    """
662    #grok.context(IApplicantsContainer)
663    grok.name('createallstudents')
664    grok.require('waeup.managePortal')
665
666    def update(self):
667        cat = getUtility(ICatalog, name='applicants_catalog')
668        results = list(cat.searchResults(state=(ADMITTED, ADMITTED)))
669        created = []
670        container_only = False
671        applicants_root = grok.getSite()['applicants']
672        if isinstance(self.context, ApplicantsContainer):
673            container_only = True
674        for result in results:
675            if container_only and result.__parent__ is not self.context:
676                continue
677            success, msg = result.createStudent(view=self)
678            if success:
679                created.append(result.applicant_id)
680            else:
681                ob_class = self.__implemented__.__name__.replace(
682                    'waeup.kofa.','')
683                applicants_root.logger.info(
684                    '%s - %s - %s' % (ob_class, result.applicant_id, msg))
685        if len(created):
686            self.flash(_('${a} students successfully created.',
687                mapping = {'a': len(created)}))
688        else:
689            self.flash(_('No student could be created.'), type='warning')
690        self.redirect(self.url(self.context))
691        return
692
693    def render(self):
694        return
695
696class ApplicationFeePaymentAddPage(UtilityView, grok.View):
697    """ Page to add an online payment ticket
698    """
699    grok.context(IApplicant)
700    grok.name('addafp')
701    grok.require('waeup.payApplicant')
702    factory = u'waeup.ApplicantOnlinePayment'
703
704    @property
705    def custom_requirements(self):
706        return ''
707
708    def update(self):
709        # Additional requirements in custom packages.
710        if self.custom_requirements:
711            self.flash(
712                self.custom_requirements,
713                type='danger')
714            self.redirect(self.url(self.context))
715            return
716        if not self.context.special:
717            for key in self.context.keys():
718                ticket = self.context[key]
719                if ticket.p_state == 'paid':
720                      self.flash(
721                          _('This type of payment has already been made.'),
722                          type='warning')
723                      self.redirect(self.url(self.context))
724                      return
725        applicants_utils = getUtility(IApplicantsUtils)
726        container = self.context.__parent__
727        payment = createObject(self.factory)
728        failure = applicants_utils.setPaymentDetails(
729            container, payment, self.context)
730        if failure is not None:
731            self.flash(failure, type='danger')
732            self.redirect(self.url(self.context))
733            return
734        self.context[payment.p_id] = payment
735        self.context.writeLogMessage(self, 'added: %s' % payment.p_id)
736        self.flash(_('Payment ticket created.'))
737        self.redirect(self.url(payment))
738        return
739
740    def render(self):
741        return
742
743
744class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
745    """ Page to view an online payment ticket
746    """
747    grok.context(IApplicantOnlinePayment)
748    grok.name('index')
749    grok.require('waeup.viewApplication')
750    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
751    form_fields[
752        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
753    form_fields[
754        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
755    pnav = 3
756
757    @property
758    def label(self):
759        return _('${a}: Online Payment Ticket ${b}', mapping = {
760            'a':self.context.__parent__.display_fullname,
761            'b':self.context.p_id})
762
763class OnlinePaymentApprovePage(UtilityView, grok.View):
764    """ Approval view
765    """
766    grok.context(IApplicantOnlinePayment)
767    grok.name('approve')
768    grok.require('waeup.managePortal')
769
770    def update(self):
771        flashtype, msg, log = self.context.approveApplicantPayment()
772        if log is not None:
773            applicant = self.context.__parent__
774            # Add log message to applicants.log
775            applicant.writeLogMessage(self, log)
776            # Add log message to payments.log
777            self.context.logger.info(
778                '%s,%s,%s,%s,%s,,,,,,' % (
779                applicant.applicant_id,
780                self.context.p_id, self.context.p_category,
781                self.context.amount_auth, self.context.r_code))
782        self.flash(msg, type=flashtype)
783        return
784
785    def render(self):
786        self.redirect(self.url(self.context, '@@index'))
787        return
788
789class ExportPDFPaymentSlipPage(UtilityView, grok.View):
790    """Deliver a PDF slip of the context.
791    """
792    grok.context(IApplicantOnlinePayment)
793    grok.name('payment_slip.pdf')
794    grok.require('waeup.viewApplication')
795    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
796    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
797    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
798    prefix = 'form'
799    note = None
800
801    @property
802    def title(self):
803        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
804        return translate(_('Payment Data'), 'waeup.kofa',
805            target_language=portal_language)
806
807    @property
808    def label(self):
809        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
810        return translate(_('Online Payment Slip'),
811            'waeup.kofa', target_language=portal_language) \
812            + ' %s' % self.context.p_id
813
814    @property
815    def payment_slip_download_warning(self):
816        if self.context.__parent__.state != SUBMITTED:
817            return _('Please submit the application form before '
818                     'trying to download payment slips.')
819        return ''
820
821    def render(self):
822        if self.payment_slip_download_warning:
823            self.flash(self.payment_slip_download_warning, type='danger')
824            self.redirect(self.url(self.context))
825            return
826        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
827            self.request)
828        students_utils = getUtility(IStudentsUtils)
829        return students_utils.renderPDF(self,'payment_slip.pdf',
830            self.context.__parent__, applicantview, note=self.note)
831
832class ExportPDFPageApplicationSlip(UtilityView, grok.View):
833    """Deliver a PDF slip of the context.
834    """
835    grok.context(IApplicant)
836    grok.name('application_slip.pdf')
837    grok.require('waeup.viewApplication')
838    prefix = 'form'
839
840    def update(self):
841        if self.context.state in ('initialized', 'started', 'paid'):
842            self.flash(
843                _('Please pay and submit before trying to download '
844                  'the application slip.'), type='warning')
845            return self.redirect(self.url(self.context))
846        return
847
848    def render(self):
849        try:
850            pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
851                view=self)
852        except IOError:
853            self.flash(
854                _('Your image file is corrupted. '
855                  'Please replace.'), type='danger')
856            return self.redirect(self.url(self.context))
857        self.response.setHeader(
858            'Content-Type', 'application/pdf')
859        return pdfstream
860
861def handle_img_upload(upload, context, view):
862    """Handle upload of applicant image.
863
864    Returns `True` in case of success or `False`.
865
866    Please note that file pointer passed in (`upload`) most probably
867    points to end of file when leaving this function.
868    """
869    size = file_size(upload)
870    if size > MAX_UPLOAD_SIZE:
871        view.flash(_('Uploaded image is too big!'), type='danger')
872        return False
873    dummy, ext = os.path.splitext(upload.filename)
874    ext.lower()
875    if ext != '.jpg':
876        view.flash(_('jpg file extension expected.'), type='danger')
877        return False
878    upload.seek(0) # file pointer moved when determining size
879    store = getUtility(IExtFileStore)
880    file_id = IFileStoreNameChooser(context).chooseName()
881    store.createFile(file_id, upload)
882    return True
883
884class ApplicantManageFormPage(KofaEditFormPage):
885    """A full edit view for applicant data.
886    """
887    grok.context(IApplicant)
888    grok.name('manage')
889    grok.require('waeup.manageApplication')
890    grok.template('applicanteditpage')
891    manage_applications = True
892    pnav = 3
893    display_actions = [[_('Save'), _('Finally Submit')],
894        [_('Add online payment ticket'),_('Remove selected tickets')]]
895
896    @property
897    def display_payments(self):
898        if self.context.special:
899            return True
900        return getattr(self.context.__parent__, 'application_fee', None)
901
902    @property
903    def form_fields(self):
904        if self.context.special:
905            form_fields = grok.AutoFields(ISpecialApplicant)
906            form_fields['applicant_id'].for_display = True
907        else:
908            form_fields = grok.AutoFields(IApplicant)
909            form_fields['student_id'].for_display = True
910            form_fields['applicant_id'].for_display = True
911        return form_fields
912
913    @property
914    def target(self):
915        return getattr(self.context.__parent__, 'prefix', None)
916
917    @property
918    def separators(self):
919        return getUtility(IApplicantsUtils).SEPARATORS_DICT
920
921    @property
922    def custom_upload_requirements(self):
923        return ''
924
925    def update(self):
926        super(ApplicantManageFormPage, self).update()
927        self.wf_info = IWorkflowInfo(self.context)
928        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
929        self.upload_success = None
930        upload = self.request.form.get('form.passport', None)
931        if upload:
932            if self.custom_upload_requirements:
933                self.flash(
934                    self.custom_upload_requirements,
935                    type='danger')
936                self.redirect(self.url(self.context))
937                return
938            # We got a fresh upload, upload_success is
939            # either True or False
940            self.upload_success = handle_img_upload(
941                upload, self.context, self)
942            if self.upload_success:
943                self.context.writeLogMessage(self, 'saved: passport')
944        return
945
946    @property
947    def label(self):
948        container_title = self.context.__parent__.title
949        return _('${a} <br /> Application Form ${b}', mapping = {
950            'a':container_title, 'b':self.context.application_number})
951
952    def getTransitions(self):
953        """Return a list of dicts of allowed transition ids and titles.
954
955        Each list entry provides keys ``name`` and ``title`` for
956        internal name and (human readable) title of a single
957        transition.
958        """
959        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
960            if not t[0] in ('pay', 'create')]
961        return [dict(name='', title=_('No transition'))] +[
962            dict(name=x, title=y) for x, y in allowed_transitions]
963
964    @action(_('Save'), style='primary')
965    def save(self, **data):
966        form = self.request.form
967        password = form.get('password', None)
968        password_ctl = form.get('control_password', None)
969        if password:
970            validator = getUtility(IPasswordValidator)
971            errors = validator.validate_password(password, password_ctl)
972            if errors:
973                self.flash( ' '.join(errors), type='danger')
974                return
975        if self.upload_success is False:  # False is not None!
976            # Error during image upload. Ignore other values.
977            return
978        changed_fields = self.applyData(self.context, **data)
979        # Turn list of lists into single list
980        if changed_fields:
981            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
982        else:
983            changed_fields = []
984        if password:
985            # Now we know that the form has no errors and can set password ...
986            IUserAccount(self.context).setPassword(password)
987            changed_fields.append('password')
988        fields_string = ' + '.join(changed_fields)
989        trans_id = form.get('transition', None)
990        if trans_id:
991            self.wf_info.fireTransition(trans_id)
992        self.flash(_('Form has been saved.'))
993        if fields_string:
994            self.context.writeLogMessage(self, 'saved: %s' % fields_string)
995        return
996
997    def unremovable(self, ticket):
998        return False
999
1000    # This method is also used by the ApplicantEditFormPage
1001    def delPaymentTickets(self, **data):
1002        form = self.request.form
1003        if 'val_id' in form:
1004            child_id = form['val_id']
1005        else:
1006            self.flash(_('No payment selected.'), type='warning')
1007            self.redirect(self.url(self.context))
1008            return
1009        if not isinstance(child_id, list):
1010            child_id = [child_id]
1011        deleted = []
1012        for id in child_id:
1013            # Applicants are not allowed to remove used payment tickets
1014            if not self.unremovable(self.context[id]):
1015                try:
1016                    del self.context[id]
1017                    deleted.append(id)
1018                except:
1019                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
1020                      id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
1021        if len(deleted):
1022            self.flash(_('Successfully removed: ${a}',
1023                mapping = {'a':', '.join(deleted)}))
1024            self.context.writeLogMessage(
1025                self, 'removed: % s' % ', '.join(deleted))
1026        return
1027
1028    # We explicitely want the forms to be validated before payment tickets
1029    # can be created. If no validation is requested, use
1030    # 'validator=NullValidator' in the action directive
1031    @action(_('Add online payment ticket'), style='primary')
1032    def addPaymentTicket(self, **data):
1033        self.redirect(self.url(self.context, '@@addafp'))
1034        return
1035
1036    @jsaction(_('Remove selected tickets'))
1037    def removePaymentTickets(self, **data):
1038        self.delPaymentTickets(**data)
1039        self.redirect(self.url(self.context) + '/@@manage')
1040        return
1041
1042    # Not used in base package
1043    def file_exists(self, attr):
1044        file = getUtility(IExtFileStore).getFileByContext(
1045            self.context, attr=attr)
1046        if file:
1047            return True
1048        else:
1049            return False
1050
1051class ApplicantEditFormPage(ApplicantManageFormPage):
1052    """An applicant-centered edit view for applicant data.
1053    """
1054    grok.context(IApplicantEdit)
1055    grok.name('edit')
1056    grok.require('waeup.handleApplication')
1057    grok.template('applicanteditpage')
1058    manage_applications = False
1059    submit_state = PAID
1060
1061    @property
1062    def form_fields(self):
1063        if self.context.special:
1064            form_fields = grok.AutoFields(ISpecialApplicant).omit(
1065                'locked', 'suspended')
1066            form_fields['applicant_id'].for_display = True
1067        else:
1068            form_fields = grok.AutoFields(IApplicantEdit).omit(
1069                'locked', 'course_admitted', 'student_id',
1070                'suspended'
1071                )
1072            form_fields['applicant_id'].for_display = True
1073            form_fields['reg_number'].for_display = True
1074        return form_fields
1075
1076    @property
1077    def display_actions(self):
1078        state = IWorkflowState(self.context).getState()
1079        # If the form is unlocked, applicants are allowed to save the form
1080        # and remove unused tickets.
1081        actions = [[_('Save')], [_('Remove selected tickets')]]
1082        # Only in state started they can also add tickets.
1083        if state == STARTED:
1084            actions = [[_('Save')],
1085                [_('Add online payment ticket'),_('Remove selected tickets')]]
1086        # In state paid, they can submit the data and further add tickets
1087        # if the application is special.
1088        elif self.context.special and state == PAID:
1089            actions = [[_('Save'), _('Finally Submit')],
1090                [_('Add online payment ticket'),_('Remove selected tickets')]]
1091        elif state == PAID:
1092            actions = [[_('Save'), _('Finally Submit')],
1093                [_('Remove selected tickets')]]
1094        return actions
1095
1096    def unremovable(self, ticket):
1097        return ticket.r_code
1098
1099    def emit_lock_message(self):
1100        self.flash(_('The requested form is locked (read-only).'),
1101                   type='warning')
1102        self.redirect(self.url(self.context))
1103        return
1104
1105    def update(self):
1106        if self.context.locked or (
1107            self.context.__parent__.expired and
1108            self.context.__parent__.strict_deadline):
1109            self.emit_lock_message()
1110            return
1111        super(ApplicantEditFormPage, self).update()
1112        return
1113
1114    def dataNotComplete(self):
1115        store = getUtility(IExtFileStore)
1116        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
1117            return _('No passport picture uploaded.')
1118        if not self.request.form.get('confirm_passport', False):
1119            return _('Passport picture confirmation box not ticked.')
1120        return False
1121
1122    # We explicitely want the forms to be validated before payment tickets
1123    # can be created. If no validation is requested, use
1124    # 'validator=NullValidator' in the action directive
1125    @action(_('Add online payment ticket'), style='primary')
1126    def addPaymentTicket(self, **data):
1127        self.redirect(self.url(self.context, '@@addafp'))
1128        return
1129
1130    @jsaction(_('Remove selected tickets'))
1131    def removePaymentTickets(self, **data):
1132        self.delPaymentTickets(**data)
1133        self.redirect(self.url(self.context) + '/@@edit')
1134        return
1135
1136    @action(_('Save'), style='primary')
1137    def save(self, **data):
1138        if self.upload_success is False:  # False is not None!
1139            # Error during image upload. Ignore other values.
1140            return
1141        self.applyData(self.context, **data)
1142        self.flash(_('Form has been saved.'))
1143        return
1144
1145    @action(_('Finally Submit'), warning=WARNING)
1146    def finalsubmit(self, **data):
1147        if self.upload_success is False:  # False is not None!
1148            return # error during image upload. Ignore other values
1149        if self.dataNotComplete():
1150            self.flash(self.dataNotComplete(), type='danger')
1151            return
1152        self.applyData(self.context, **data)
1153        state = IWorkflowState(self.context).getState()
1154        # This shouldn't happen, but the application officer
1155        # might have forgotten to lock the form after changing the state
1156        if state != self.submit_state:
1157            self.flash(_('The form cannot be submitted. Wrong state!'),
1158                       type='danger')
1159            return
1160        IWorkflowInfo(self.context).fireTransition('submit')
1161        # application_date is used in export files for sorting.
1162        # We can thus store utc.
1163        self.context.application_date = datetime.utcnow()
1164        self.flash(_('Form has been submitted.'))
1165        self.redirect(self.url(self.context))
1166        return
1167
1168class PassportImage(grok.View):
1169    """Renders the passport image for applicants.
1170    """
1171    grok.name('passport.jpg')
1172    grok.context(IApplicant)
1173    grok.require('waeup.viewApplication')
1174
1175    def render(self):
1176        # A filename chooser turns a context into a filename suitable
1177        # for file storage.
1178        image = getUtility(IExtFileStore).getFileByContext(self.context)
1179        self.response.setHeader(
1180            'Content-Type', 'image/jpeg')
1181        if image is None:
1182            # show placeholder image
1183            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
1184        return image
1185
1186class ApplicantRegistrationPage(KofaAddFormPage):
1187    """Captcha'd registration page for applicants.
1188    """
1189    grok.context(IApplicantsContainer)
1190    grok.name('register')
1191    grok.require('waeup.Anonymous')
1192    grok.template('applicantregister')
1193
1194    @property
1195    def form_fields(self):
1196        form_fields = None
1197        if self.context.mode == 'update':
1198            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1199                'lastname','reg_number','email')
1200        else: #if self.context.mode == 'create':
1201            form_fields = grok.AutoFields(IApplicantEdit).select(
1202                'firstname', 'middlename', 'lastname', 'email', 'phone')
1203        return form_fields
1204
1205    @property
1206    def label(self):
1207        return _('Apply for ${a}',
1208            mapping = {'a':self.context.title})
1209
1210    def update(self):
1211        if self.context.expired:
1212            self.flash(_('Outside application period.'), type='warning')
1213            self.redirect(self.url(self.context))
1214            return
1215        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1216        if blocker:
1217            self.flash(_('The portal is in maintenance mode '
1218                        'and registration temporarily disabled.'),
1219                       type='warning')
1220            self.redirect(self.url(self.context))
1221            return
1222        # Handle captcha
1223        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1224        self.captcha_result = self.captcha.verify(self.request)
1225        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1226        return
1227
1228    def _redirect(self, email, password, applicant_id):
1229        # Forward only email to landing page in base package.
1230        self.redirect(self.url(self.context, 'registration_complete',
1231            data = dict(email=email)))
1232        return
1233
1234    @action(_('Send login credentials to email address'), style='primary')
1235    def register(self, **data):
1236        if not self.captcha_result.is_valid:
1237            # Captcha will display error messages automatically.
1238            # No need to flash something.
1239            return
1240        if self.context.mode == 'create':
1241            # Check if there are unused records in this container which
1242            # can be taken
1243            applicant = self.context.first_unused
1244            if applicant is None:
1245                # Add applicant
1246                applicant = createObject(u'waeup.Applicant')
1247                self.context.addApplicant(applicant)
1248            self.applyData(applicant, **data)
1249            applicant.reg_number = applicant.applicant_id
1250            notify(grok.ObjectModifiedEvent(applicant))
1251        elif self.context.mode == 'update':
1252            # Update applicant
1253            reg_number = data.get('reg_number','')
1254            lastname = data.get('lastname','')
1255            cat = getUtility(ICatalog, name='applicants_catalog')
1256            results = list(
1257                cat.searchResults(reg_number=(reg_number, reg_number)))
1258            if results:
1259                applicant = results[0]
1260                if getattr(applicant,'lastname',None) is None:
1261                    self.flash(_('An error occurred.'), type='danger')
1262                    return
1263                elif applicant.lastname.lower() != lastname.lower():
1264                    # Don't tell the truth here. Anonymous must not
1265                    # know that a record was found and only the lastname
1266                    # verification failed.
1267                    self.flash(
1268                        _('No application record found.'), type='warning')
1269                    return
1270                elif applicant.password is not None and \
1271                    applicant.state != INITIALIZED:
1272                    self.flash(_('Your password has already been set and used. '
1273                                 'Please proceed to the login page.'),
1274                               type='warning')
1275                    return
1276                # Store email address but nothing else.
1277                applicant.email = data['email']
1278                notify(grok.ObjectModifiedEvent(applicant))
1279            else:
1280                # No record found, this is the truth.
1281                self.flash(_('No application record found.'), type='warning')
1282                return
1283        else:
1284            # Does not happen but anyway ...
1285            return
1286        kofa_utils = getUtility(IKofaUtils)
1287        password = kofa_utils.genPassword()
1288        IUserAccount(applicant).setPassword(password)
1289        # Send email with credentials
1290        login_url = self.url(grok.getSite(), 'login')
1291        url_info = u'Login: %s' % login_url
1292        msg = _('You have successfully been registered for the')
1293        if kofa_utils.sendCredentials(IUserAccount(applicant),
1294            password, url_info, msg):
1295            email_sent = applicant.email
1296        else:
1297            email_sent = None
1298        self._redirect(email=email_sent, password=password,
1299            applicant_id=applicant.applicant_id)
1300        return
1301
1302class ApplicantRegistrationEmailSent(KofaPage):
1303    """Landing page after successful registration.
1304
1305    """
1306    grok.name('registration_complete')
1307    grok.require('waeup.Public')
1308    grok.template('applicantregemailsent')
1309    label = _('Your registration was successful.')
1310
1311    def update(self, email=None, applicant_id=None, password=None):
1312        self.email = email
1313        self.password = password
1314        self.applicant_id = applicant_id
1315        return
1316
1317class ApplicantCheckStatusPage(KofaPage):
1318    """Captcha'd status checking page for applicants.
1319    """
1320    grok.context(IApplicantsRoot)
1321    grok.name('checkstatus')
1322    grok.require('waeup.Anonymous')
1323    grok.template('applicantcheckstatus')
1324    buttonname = _('Submit')
1325
1326    def label(self):
1327        if self.result:
1328            return _('Admission status of ${a}',
1329                     mapping = {'a':self.applicant.applicant_id})
1330        return _('Check your admission status')
1331
1332    def update(self, SUBMIT=None):
1333        form = self.request.form
1334        self.result = False
1335        # Handle captcha
1336        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1337        self.captcha_result = self.captcha.verify(self.request)
1338        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1339        if SUBMIT:
1340            if not self.captcha_result.is_valid:
1341                # Captcha will display error messages automatically.
1342                # No need to flash something.
1343                return
1344            unique_id = form.get('unique_id', None)
1345            lastname = form.get('lastname', None)
1346            if not unique_id or not lastname:
1347                self.flash(
1348                    _('Required input missing.'), type='warning')
1349                return
1350            cat = getUtility(ICatalog, name='applicants_catalog')
1351            results = list(
1352                cat.searchResults(applicant_id=(unique_id, unique_id)))
1353            if not results:
1354                results = list(
1355                    cat.searchResults(reg_number=(unique_id, unique_id)))
1356            if results:
1357                applicant = results[0]
1358                if applicant.lastname.lower().strip() != lastname.lower():
1359                    # Don't tell the truth here. Anonymous must not
1360                    # know that a record was found and only the lastname
1361                    # verification failed.
1362                    self.flash(
1363                        _('No application record found.'), type='warning')
1364                    return
1365            else:
1366                self.flash(_('No application record found.'), type='warning')
1367                return
1368            self.applicant = applicant
1369            self.entry_session = "%s/%s" % (
1370                applicant.__parent__.year,
1371                applicant.__parent__.year+1)
1372            course_admitted = getattr(applicant, 'course_admitted', None)
1373            self.course_admitted = False
1374            if course_admitted is not None:
1375                self.course_admitted = True
1376                self.longtitle = course_admitted.longtitle
1377                self.department = course_admitted.__parent__.__parent__.longtitle
1378                self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1379            self.result = True
1380            self.admitted = False
1381            self.not_admitted = False
1382            self.submitted = False
1383            self.not_submitted = False
1384            self.created = False
1385            if applicant.state in (ADMITTED, CREATED):
1386                self.admitted = True
1387            if applicant.state in (CREATED):
1388                self.created = True
1389                self.student_id = applicant.student_id
1390                self.password = applicant.application_number
1391            if applicant.state in (NOT_ADMITTED,):
1392                self.not_admitted = True
1393            if applicant.state in (SUBMITTED,):
1394                self.submitted = True
1395            if applicant.state in (INITIALIZED, STARTED, PAID):
1396                self.not_submitted = True
1397        return
1398
1399class ExportJobContainerOverview(KofaPage):
1400    """Page that lists active applicant data export jobs and provides links
1401    to discard or download CSV files.
1402
1403    """
1404    grok.context(VirtualApplicantsExportJobContainer)
1405    grok.require('waeup.manageApplication')
1406    grok.name('index.html')
1407    grok.template('exportjobsindex')
1408    label = _('Data Exports')
1409    pnav = 3
1410
1411    def update(self, CREATE=None, DISCARD=None, job_id=None):
1412        if CREATE:
1413            self.redirect(self.url('@@start_export'))
1414            return
1415        if DISCARD and job_id:
1416            entry = self.context.entry_from_job_id(job_id)
1417            self.context.delete_export_entry(entry)
1418            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1419            self.context.logger.info(
1420                '%s - discarded: job_id=%s' % (ob_class, job_id))
1421            self.flash(_('Discarded export') + ' %s' % job_id)
1422        self.entries = doll_up(self, user=self.request.principal.id)
1423        return
1424
1425class ExportJobContainerJobStart(KofaPage):
1426    """Page that starts an applicants export job.
1427
1428    """
1429    grok.context(VirtualApplicantsExportJobContainer)
1430    grok.require('waeup.manageApplication')
1431    grok.name('start_export')
1432
1433    def update(self):
1434        utils = queryUtility(IKofaUtils)
1435        if not utils.expensive_actions_allowed():
1436            self.flash(_(
1437                "Currently, exporters cannot be started due to high "
1438                "system load. Please try again later."), type='danger')
1439            self.entries = doll_up(self, user=None)
1440            return
1441        exporter = 'applicants'
1442        container_code = self.context.__parent__.code
1443        job_id = self.context.start_export_job(exporter,
1444                                      self.request.principal.id,
1445                                      container=container_code)
1446
1447        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1448        self.context.logger.info(
1449            '%s - exported: %s (%s), job_id=%s'
1450            % (ob_class, exporter, container_code, job_id))
1451        self.flash(_('Export started.'))
1452        self.redirect(self.url(self.context))
1453        return
1454
1455    def render(self):
1456        return
1457
1458class ExportJobContainerDownload(ExportCSVView):
1459    """Page that downloads a students export csv file.
1460
1461    """
1462    grok.context(VirtualApplicantsExportJobContainer)
1463    grok.require('waeup.manageApplication')
Note: See TracBrowser for help on using the repository browser.