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

Last change on this file since 14001 was 14001, checked in by Henrik Bettermann, 8 years ago

Catch IOError if file or folder is readonly.

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