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

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

Start ApplicantExporter and ApplicantPaymentExporter at the same
time when calling ExportJobContainerJobStart.

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