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

Last change on this file since 16575 was 16564, checked in by Henrik Bettermann, 4 years ago

Edit warning message.

  • Property svn:keywords set to Id
File size: 79.1 KB
Line 
1## $Id: browser.py 16564 2021-08-06 06:56:11Z 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 cgi import escape
25from urllib import urlencode
26from datetime import datetime, date
27from time import time, sleep
28import xmlrpclib
29from zope.event import notify
30from zope.component import getUtility, queryUtility, createObject, getAdapter
31from zope.catalog.interfaces import ICatalog
32from zope.i18n import translate
33from zope.security import checkPermission
34from hurry.workflow.interfaces import (
35    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
36from reportlab.platypus.doctemplate import LayoutError
37from waeup.kofa.mandates.mandate import RefereeReportMandate
38from waeup.kofa.applicants.interfaces import (
39    IApplicant, IApplicantEdit, IApplicantsRoot,
40    IApplicantsContainer, IApplicantsContainerAdd,
41    IApplicantOnlinePayment, IApplicantsUtils,
42    IApplicantRegisterUpdate, ISpecialApplicant,
43    IApplicantRefereeReport
44    )
45from waeup.kofa.utils.helpers import (html2dict,
46    string_from_bytes, file_size, get_fileformat)
47from waeup.kofa.applicants.container import (
48    ApplicantsContainer, VirtualApplicantsExportJobContainer)
49from waeup.kofa.applicants.applicant import search
50from waeup.kofa.applicants.workflow import (
51    INITIALIZED, STARTED, PAID, SUBMITTED, ADMITTED, NOT_ADMITTED, CREATED)
52from waeup.kofa.browser import (
53#    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,
54    DEFAULT_PASSPORT_IMAGE_PATH)
55from waeup.kofa.browser.layout import (
56    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage)
57from waeup.kofa.browser.interfaces import ICaptchaManager
58from waeup.kofa.browser.breadcrumbs import Breadcrumb
59from waeup.kofa.browser.layout import (
60    NullValidator, jsaction, action, UtilityView)
61from waeup.kofa.browser.pages import (
62    add_local_role, del_local_roles, doll_up, ExportCSVView)
63from waeup.kofa.interfaces import (
64    IKofaObject, ILocalRolesAssignable, IExtFileStore, IPDF, DOCLINK,
65    IFileStoreNameChooser, IPasswordValidator, IUserAccount, IKofaUtils)
66from waeup.kofa.interfaces import MessageFactory as _
67from waeup.kofa.permissions import get_users_with_local_roles
68from waeup.kofa.students.interfaces import IStudentsUtils
69from waeup.kofa.students.browser import ContactStudentFormPage
70from waeup.kofa.utils.helpers import string_from_bytes, file_size, now
71from waeup.kofa.widgets.datewidget import (
72    FriendlyDateDisplayWidget,
73    FriendlyDatetimeDisplayWidget)
74
75grok.context(IKofaObject) # Make IKofaObject the default context
76
77WARNING = _('Please always save the form before final submission and note that'
78            ' you can not edit your application records after submission.'
79            ' You really want to submit?')
80
81class ApplicantsRootPage(KofaDisplayFormPage):
82    grok.context(IApplicantsRoot)
83    grok.name('index')
84    grok.require('waeup.Public')
85    form_fields = grok.AutoFields(IApplicantsRoot)
86    label = _('Applicants Section')
87    pnav = 3
88
89    def update(self):
90        super(ApplicantsRootPage, self).update()
91        return
92
93    @property
94    def introduction(self):
95        # Here we know that the cookie has been set
96        lang = self.request.cookies.get('kofa.language')
97        html = self.context.description_dict.get(lang,'')
98        if html == '':
99            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
100            html = self.context.description_dict.get(portal_language,'')
101        return html
102
103    @property
104    def containers(self):
105        if self.layout.isAuthenticated():
106            return self.context.values()
107        values = sorted([container for container in self.context.values()
108                         if not container.hidden and container.enddate],
109                        key=lambda value: value.enddate, reverse=True)
110        return values
111
112class ApplicantsSearchPage(KofaPage):
113    grok.context(IApplicantsRoot)
114    grok.name('search')
115    grok.require('waeup.viewApplication')
116    label = _('Find applicants')
117    search_button = _('Find applicant')
118    pnav = 3
119
120    def update(self, *args, **kw):
121        form = self.request.form
122        self.results = []
123        if 'searchterm' in form and form['searchterm']:
124            self.searchterm = form['searchterm']
125            self.searchtype = form['searchtype']
126        elif 'old_searchterm' in form:
127            self.searchterm = form['old_searchterm']
128            self.searchtype = form['old_searchtype']
129        else:
130            if 'search' in form:
131                self.flash(_('Empty search string'), type='warning')
132            return
133        self.results = search(query=self.searchterm,
134            searchtype=self.searchtype, view=self)
135        if not self.results:
136            self.flash(_('No applicant found.'), type='warning')
137        return
138
139class ApplicantsRootManageFormPage(KofaEditFormPage):
140    grok.context(IApplicantsRoot)
141    grok.name('manage')
142    grok.template('applicantsrootmanagepage')
143    form_fields = grok.AutoFields(IApplicantsRoot)
144    label = _('Manage applicants section')
145    pnav = 3
146    grok.require('waeup.manageApplication')
147    taboneactions = [_('Save')]
148    tabtwoactions = [_('Add applicants container'), _('Remove selected')]
149    tabthreeactions1 = [_('Remove selected local roles')]
150    tabthreeactions2 = [_('Add local role')]
151    subunits = _('Applicants Containers')
152    doclink = DOCLINK + '/applicants.html'
153
154    def getLocalRoles(self):
155        roles = ILocalRolesAssignable(self.context)
156        return roles()
157
158    def getUsers(self):
159        return getUtility(IKofaUtils).getUsers()
160
161    #def getUsers(self):
162    #    """Get a list of all users.
163    #    """
164    #    for key, val in grok.getSite()['users'].items():
165    #        url = self.url(val)
166    #        yield(dict(url=url, name=key, val=val))
167
168    def getUsersWithLocalRoles(self):
169        return get_users_with_local_roles(self.context)
170
171    @jsaction(_('Remove selected'))
172    def delApplicantsContainers(self, **data):
173        form = self.request.form
174        if 'val_id' in form:
175            child_id = form['val_id']
176        else:
177            self.flash(_('No container selected!'), type='warning')
178            self.redirect(self.url(self.context, '@@manage')+'#tab2')
179            return
180        if not isinstance(child_id, list):
181            child_id = [child_id]
182        deleted = []
183        for id in child_id:
184            try:
185                del self.context[id]
186                deleted.append(id)
187            except:
188                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
189                    id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
190        if len(deleted):
191            self.flash(_('Successfully removed: ${a}',
192                mapping = {'a':', '.join(deleted)}))
193        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
194        self.context.logger.info(
195            '%s - removed: %s' % (ob_class, ', '.join(deleted)))
196        self.redirect(self.url(self.context, '@@manage')+'#tab2')
197        return
198
199    @action(_('Add applicants container'), validator=NullValidator)
200    def addApplicantsContainer(self, **data):
201        self.redirect(self.url(self.context, '@@add'))
202        return
203
204    @action(_('Add local role'), validator=NullValidator)
205    def addLocalRole(self, **data):
206        return add_local_role(self,3, **data)
207
208    @action(_('Remove selected local roles'))
209    def delLocalRoles(self, **data):
210        return del_local_roles(self,3,**data)
211
212    @action(_('Save'), style='primary')
213    def save(self, **data):
214        self.applyData(self.context, **data)
215        description = getattr(self.context, 'description', None)
216        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
217        self.context.description_dict = html2dict(description, portal_language)
218        self.flash(_('Form has been saved.'))
219        return
220
221class ApplicantsContainerAddFormPage(KofaAddFormPage):
222    grok.context(IApplicantsRoot)
223    grok.require('waeup.manageApplication')
224    grok.name('add')
225    grok.template('applicantscontaineraddpage')
226    label = _('Add applicants container')
227    pnav = 3
228
229    form_fields = grok.AutoFields(
230        IApplicantsContainerAdd).omit('code').omit('title')
231
232    @action(_('Add applicants container'))
233    def addApplicantsContainer(self, **data):
234        year = data['year']
235        if not data['container_number'] and not data['year']:
236            self.flash(
237              _('You must select either Year of Entrance or Container Number.'),
238                type='warning')
239            return
240        if not data['container_number']:
241            code = u'%s%s' % (data['prefix'], year)
242        else:
243            code = u'%s%s' % (data['prefix'], data['container_number'])
244        apptypes_dict = getUtility(IApplicantsUtils).APP_TYPES_DICT
245        title = u'%s' % apptypes_dict[data['prefix']][0]
246        if year:
247            title = u'%s %s/%s' % (title, year, year + 1)
248        if code in self.context.keys():
249            self.flash(
250              _('An applicants container for the same application '
251                'type and entrance year or container number '
252                'already exists in the database.'),
253                type='warning')
254            return
255        # Add new applicants container...
256        container = createObject(u'waeup.ApplicantsContainer')
257        self.applyData(container, **data)
258        container.code = code
259        container.title = title
260        self.context[code] = container
261        self.flash(_('Added:') + ' "%s".' % code)
262        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
263        self.context.logger.info('%s - added: %s' % (ob_class, code))
264        self.redirect(self.url(self.context, u'@@manage'))
265        return
266
267    @action(_('Cancel'), validator=NullValidator)
268    def cancel(self, **data):
269        self.redirect(self.url(self.context, '@@manage'))
270
271class ApplicantsRootBreadcrumb(Breadcrumb):
272    """A breadcrumb for applicantsroot.
273    """
274    grok.context(IApplicantsRoot)
275    title = _(u'Applicants')
276
277class ApplicantsContainerBreadcrumb(Breadcrumb):
278    """A breadcrumb for applicantscontainers.
279    """
280    grok.context(IApplicantsContainer)
281
282
283class ApplicantsExportsBreadcrumb(Breadcrumb):
284    """A breadcrumb for exports.
285    """
286    grok.context(VirtualApplicantsExportJobContainer)
287    title = _(u'Applicant Data Exports')
288    target = None
289
290class ApplicantBreadcrumb(Breadcrumb):
291    """A breadcrumb for applicants.
292    """
293    grok.context(IApplicant)
294
295    @property
296    def title(self):
297        """Get a title for a context.
298        """
299        return self.context.application_number
300
301class OnlinePaymentBreadcrumb(Breadcrumb):
302    """A breadcrumb for payments.
303    """
304    grok.context(IApplicantOnlinePayment)
305
306    @property
307    def title(self):
308        return self.context.p_id
309
310class RefereeReportBreadcrumb(Breadcrumb):
311    """A breadcrumb for referee reports.
312    """
313    grok.context(IApplicantRefereeReport)
314
315    @property
316    def title(self):
317        return self.context.r_id
318
319class ApplicantsStatisticsPage(KofaDisplayFormPage):
320    """Some statistics about applicants in a container.
321    """
322    grok.context(IApplicantsContainer)
323    grok.name('statistics')
324    grok.require('waeup.viewApplicationStatistics')
325    grok.template('applicantcontainerstatistics')
326
327    @property
328    def label(self):
329        return "%s" % self.context.title
330
331class ApplicantsContainerPage(KofaDisplayFormPage):
332    """The standard view for regular applicant containers.
333    """
334    grok.context(IApplicantsContainer)
335    grok.name('index')
336    grok.require('waeup.Public')
337    grok.template('applicantscontainerpage')
338    pnav = 3
339
340    @property
341    def form_fields(self):
342        form_fields = grok.AutoFields(IApplicantsContainer).omit(
343            'title', 'description')
344        form_fields[
345            'startdate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
346        form_fields[
347            'enddate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
348        if self.request.principal.id == 'zope.anybody':
349            form_fields = form_fields.omit(
350                'code', 'prefix', 'year', 'mode', 'hidden',
351                'strict_deadline', 'application_category',
352                'application_slip_notice', 'with_picture')
353        return form_fields
354
355    @property
356    def introduction(self):
357        # Here we know that the cookie has been set
358        lang = self.request.cookies.get('kofa.language')
359        html = self.context.description_dict.get(lang,'')
360        if html == '':
361            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
362            html = self.context.description_dict.get(portal_language,'')
363        return html
364
365    @property
366    def label(self):
367        return "%s" % self.context.title
368
369class ApplicantsContainerManageFormPage(KofaEditFormPage):
370    grok.context(IApplicantsContainer)
371    grok.name('manage')
372    grok.template('applicantscontainermanagepage')
373    form_fields = grok.AutoFields(IApplicantsContainer)
374    taboneactions = [_('Save'),_('Cancel')]
375    tabtwoactions = [_('Remove selected'),_('Cancel'),
376        _('Create students from selected')]
377    tabthreeactions1 = [_('Remove selected local roles')]
378    tabthreeactions2 = [_('Add local role')]
379    # Use friendlier date widget...
380    grok.require('waeup.manageApplication')
381    doclink = DOCLINK + '/applicants.html'
382    max_applicants = 2000
383
384    @property
385    def label(self):
386        return _('Manage applicants container')
387
388    pnav = 3
389
390    @property
391    def showApplicants(self):
392        if self.context.counts[1] < self.max_applicants:
393            return True
394        return False
395
396    def getLocalRoles(self):
397        roles = ILocalRolesAssignable(self.context)
398        return roles()
399
400    #def getUsers(self):
401    #    """Get a list of all users.
402    #    """
403    #    for key, val in grok.getSite()['users'].items():
404    #        url = self.url(val)
405    #        yield(dict(url=url, name=key, val=val))
406
407    def getUsers(self):
408        return getUtility(IKofaUtils).getUsers()
409
410    def getUsersWithLocalRoles(self):
411        return get_users_with_local_roles(self.context)
412
413    @action(_('Save'), style='primary')
414    def save(self, **data):
415        changed_fields = self.applyData(self.context, **data)
416        if changed_fields:
417            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
418        else:
419            changed_fields = []
420        description = getattr(self.context, 'description', None)
421        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
422        self.context.description_dict = html2dict(description, portal_language)
423        self.flash(_('Form has been saved.'))
424        fields_string = ' + '.join(changed_fields)
425        self.context.writeLogMessage(self, 'saved: %s' % fields_string)
426        return
427
428    @jsaction(_('Remove selected'))
429    def delApplicant(self, **data):
430        form = self.request.form
431        if 'val_id' in form:
432            child_id = form['val_id']
433        else:
434            self.flash(_('No applicant selected!'), type='warning')
435            self.redirect(self.url(self.context, '@@manage')+'#tab2')
436            return
437        if not isinstance(child_id, list):
438            child_id = [child_id]
439        deleted = []
440        for id in child_id:
441            try:
442                del self.context[id]
443                deleted.append(id)
444            except:
445                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
446                    id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
447        if len(deleted):
448            self.flash(_('Successfully removed: ${a}',
449                mapping = {'a':', '.join(deleted)}))
450        self.redirect(self.url(self.context, u'@@manage')+'#tab2')
451        return
452
453    @action(_('Create students from selected'))
454    def createStudents(self, **data):
455        if not checkPermission('waeup.createStudents', self.context):
456            self.flash(
457                _('You don\'t have permission to create student records.'),
458                type='warning')
459            self.redirect(self.url(self.context, '@@manage')+'#tab2')
460            return
461        form = self.request.form
462        if 'val_id' in form:
463            child_id = form['val_id']
464        else:
465            self.flash(_('No applicant selected!'), type='warning')
466            self.redirect(self.url(self.context, '@@manage')+'#tab2')
467            return
468        if not isinstance(child_id, list):
469            child_id = [child_id]
470        created = []
471        if len(child_id) > 10 and self.request.principal.id != 'admin':
472            self.flash(_('A maximum of 10 applicants can be selected!'),
473                       type='warning')
474            self.redirect(self.url(self.context, '@@manage')+'#tab2')
475            return
476        for id in child_id:
477            success, msg = self.context[id].createStudent(view=self)
478            if success:
479                created.append(id)
480        if len(created):
481            self.flash(_('${a} students successfully created.',
482                mapping = {'a': len(created)}))
483        else:
484            self.flash(_('No student could be created.'), type='warning')
485        self.redirect(self.url(self.context, u'@@manage')+'#tab2')
486        return
487
488    @action(_('Cancel'), validator=NullValidator)
489    def cancel(self, **data):
490        self.redirect(self.url(self.context))
491        return
492
493    @action(_('Add local role'), validator=NullValidator)
494    def addLocalRole(self, **data):
495        return add_local_role(self,3, **data)
496
497    @action(_('Remove selected local roles'))
498    def delLocalRoles(self, **data):
499        return del_local_roles(self,3,**data)
500
501class ApplicantAddFormPage(KofaAddFormPage):
502    """Add-form to add an applicant.
503    """
504    grok.context(IApplicantsContainer)
505    grok.require('waeup.manageApplication')
506    grok.name('addapplicant')
507    #grok.template('applicantaddpage')
508    form_fields = grok.AutoFields(IApplicant).select(
509        'firstname', 'middlename', 'lastname',
510        'email', 'phone')
511    label = _('Add applicant')
512    pnav = 3
513    doclink = DOCLINK + '/applicants.html'
514
515    @action(_('Create application record'))
516    def addApplicant(self, **data):
517        applicant = createObject(u'waeup.Applicant')
518        self.applyData(applicant, **data)
519        self.context.addApplicant(applicant)
520        self.flash(_('Application record created.'))
521        self.redirect(
522            self.url(self.context[applicant.application_number], 'index'))
523        return
524
525class ApplicantsContainerPrefillFormPage(KofaAddFormPage):
526    """Form to pre-fill applicants containers.
527    """
528    grok.context(IApplicantsContainer)
529    grok.require('waeup.manageApplication')
530    grok.name('prefill')
531    grok.template('prefillcontainer')
532    label = _('Pre-fill container')
533    pnav = 3
534    doclink = DOCLINK + '/applicants/browser.html#preparation-and-maintenance-of-applicants-containers'
535
536    def update(self):
537        if self.context.mode == 'update':
538            self.flash(_('Container must be in create mode to be pre-filled.'),
539                type='danger')
540            self.redirect(self.url(self.context))
541            return
542        super(ApplicantsContainerPrefillFormPage, self).update()
543        return
544
545    @action(_('Pre-fill now'), style='primary')
546    def addApplicants(self):
547        form = self.request.form
548        if 'number' in form and form['number']:
549            number = int(form['number'])
550        for i in range(number):
551            applicant = createObject(u'waeup.Applicant')
552            self.context.addApplicant(applicant)
553        self.flash(_('%s application records created.' % number))
554        self.context.writeLogMessage(self, '%s applicants created' % (number))
555        self.redirect(self.url(self.context, 'index'))
556        return
557
558    @action(_('Cancel'), validator=NullValidator)
559    def cancel(self, **data):
560        self.redirect(self.url(self.context))
561        return
562
563class ApplicantsContainerPurgeFormPage(KofaEditFormPage):
564    """Form to purge applicants containers.
565    """
566    grok.context(IApplicantsContainer)
567    grok.require('waeup.manageApplication')
568    grok.name('purge')
569    grok.template('purgecontainer')
570    label = _('Purge container')
571    pnav = 3
572    doclink = DOCLINK + '/applicants/browser.html#preparation-and-maintenance-of-applicants-containers'
573
574    @action(_('Remove initialized records'),
575              tooltip=_('Don\'t use if application is in progress!'),
576              warning=_('Are you really sure?'),
577              style='primary')
578    def purgeInitialized(self):
579        form = self.request.form
580        purged = 0
581        keys = [key for key in self.context.keys()]
582        for key in keys:
583            if self.context[key].state == 'initialized':
584                del self.context[key]
585                purged += 1
586        self.flash(_('%s application records purged.' % purged))
587        self.context.writeLogMessage(self, '%s applicants purged' % (purged))
588        self.redirect(self.url(self.context, 'index'))
589        return
590
591    @action(_('Cancel'), validator=NullValidator)
592    def cancel(self, **data):
593        self.redirect(self.url(self.context))
594        return
595
596class ApplicantDisplayFormPage(KofaDisplayFormPage):
597    """A display view for applicant data.
598    """
599    grok.context(IApplicant)
600    grok.name('index')
601    grok.require('waeup.viewApplication')
602    grok.template('applicantdisplaypage')
603    label = _('Applicant')
604    pnav = 3
605    hide_hint = False
606
607    @property
608    def display_refereereports(self):
609        if self.context.refereereports:
610            return True
611        return False
612
613    @property
614    def file_links(self):
615        html = ''
616        file_store = getUtility(IExtFileStore)
617        additional_files = getUtility(IApplicantsUtils).ADDITIONAL_FILES
618        for filename in additional_files:
619            pdf = getUtility(IExtFileStore).getFileByContext(
620                self.context, attr=filename[1])
621            if pdf:
622                html += '<a href="%s">%s</a>, ' % (self.url(
623                    self.context, filename[1]), filename[0])
624        html = html.strip(', ')
625        return html
626
627    @property
628    def display_payments(self):
629        if self.context.payments:
630            return True
631        if self.context.special:
632            return True
633        return getattr(self.context.__parent__, 'application_fee', None)
634
635    @property
636    def form_fields(self):
637        if self.context.special:
638            form_fields = grok.AutoFields(ISpecialApplicant).omit('locked')
639        else:
640            form_fields = grok.AutoFields(IApplicant).omit(
641                'locked', 'course_admitted', 'password', 'suspended')
642        return form_fields
643
644    @property
645    def target(self):
646        return getattr(self.context.__parent__, 'prefix', None)
647
648    @property
649    def separators(self):
650        return getUtility(IApplicantsUtils).SEPARATORS_DICT
651
652    def update(self):
653        self.passport_url = self.url(self.context, 'passport.jpg')
654        # Mark application as started if applicant logs in for the first time
655        usertype = getattr(self.request.principal, 'user_type', None)
656        if usertype == 'applicant' and \
657            IWorkflowState(self.context).getState() == INITIALIZED:
658            IWorkflowInfo(self.context).fireTransition('start')
659        if usertype == 'applicant' and self.context.state == 'created':
660            session = '%s/%s' % (self.context.__parent__.year,
661                                 self.context.__parent__.year+1)
662            title = getattr(grok.getSite()['configuration'], 'name', u'Sample University')
663            msg = _(
664                '\n <strong>Congratulations!</strong>' +
665                ' You have been offered provisional admission into the' +
666                ' ${c} Academic Session of ${d}.'
667                ' Your student record has been created for you.' +
668                ' Please, logout again and proceed to the' +
669                ' login page of the portal.'
670                ' Then enter your new student credentials:' +
671                ' user name= ${a}, password = ${b}.' +
672                ' Change your password when you have logged in.',
673                mapping = {
674                    'a':self.context.student_id,
675                    'b':self.context.application_number,
676                    'c':session,
677                    'd':title}
678                )
679            self.flash(msg)
680        return
681
682    @property
683    def hasPassword(self):
684        if self.context.password:
685            return _('set')
686        return _('unset')
687
688    @property
689    def label(self):
690        container_title = self.context.__parent__.title
691        return _('${a} <br /> Application Record ${b}', mapping = {
692            'a':container_title, 'b':self.context.application_number})
693
694    def getCourseAdmitted(self):
695        """Return link, title and code in html format to the certificate
696           admitted.
697        """
698        course_admitted = self.context.course_admitted
699        if getattr(course_admitted, '__parent__',None):
700            url = self.url(course_admitted)
701            title = course_admitted.title
702            code = course_admitted.code
703            return '<a href="%s">%s - %s</a>' %(url,code,title)
704        return ''
705
706class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
707    grok.context(IApplicant)
708    grok.name('base')
709
710    @property
711    def form_fields(self):
712        form_fields = grok.AutoFields(IApplicant).select(
713            'applicant_id', 'reg_number', 'email', 'course1')
714        if self.context.__parent__.prefix in ('special',):
715            form_fields['reg_number'].field.title = u'Identification Number'
716            return form_fields
717        return form_fields
718
719class ContactApplicantFormPage(ContactStudentFormPage):
720    grok.context(IApplicant)
721    grok.name('contactapplicant')
722    grok.require('waeup.viewApplication')
723    pnav = 3
724
725class CreateStudentPage(UtilityView, grok.View):
726    """Create a student object from applicant data.
727    """
728    grok.context(IApplicant)
729    grok.name('createstudent')
730    grok.require('waeup.createStudents')
731
732    def update(self):
733        success, msg = self.context.createStudent(view=self, send_email=True)
734        if success:
735            self.flash(msg)
736        else:
737            self.flash(msg, type='warning')
738        self.redirect(self.url(self.context))
739        return
740
741    def render(self):
742        return
743
744class CreateAllStudentsPage(KofaPage):
745    """Create all student objects from applicant data
746    in the root container or in a specific applicants container only.
747    Only PortalManagers or StudentCreators can do this.
748    """
749    #grok.context(IApplicantsContainer)
750    grok.name('createallstudents')
751    grok.require('waeup.createStudents')
752    label = _('Student Record Creation Report')
753
754    def update(self):
755        grok.getSite()['configuration'].maintmode_enabled_by = u'admin'
756        transaction.commit()
757        # Wait 10 seconds for all transactions to be finished.
758        # Do not wait in tests.
759        if not self.request.principal.id == 'zope.mgr':
760            sleep(10)
761        cat = getUtility(ICatalog, name='applicants_catalog')
762        results = list(cat.searchResults(state=(ADMITTED, ADMITTED)))
763        created = []
764        failed = []
765        container_only = False
766        applicants_root = grok.getSite()['applicants']
767        if isinstance(self.context, ApplicantsContainer):
768            container_only = True
769        for result in results:
770            if container_only and result.__parent__ is not self.context:
771                continue
772            success, msg = result.createStudent(view=self)
773            if success:
774                created.append(result.applicant_id)
775            else:
776                failed.append(
777                    (result.applicant_id, self.url(result, 'manage'), msg))
778                ob_class = self.__implemented__.__name__.replace(
779                    'waeup.kofa.','')
780        grok.getSite()['configuration'].maintmode_enabled_by = None
781        self.successful = ', '.join(created)
782        self.failed = failed
783        return
784
785
786class ApplicationFeePaymentAddPage(UtilityView, grok.View):
787    """ Page to add an online payment ticket
788    """
789    grok.context(IApplicant)
790    grok.name('addafp')
791    grok.require('waeup.payApplicant')
792    factory = u'waeup.ApplicantOnlinePayment'
793
794    @property
795    def custom_requirements(self):
796        return ''
797
798    def update(self):
799        # Additional requirements in custom packages.
800        if self.custom_requirements:
801            self.flash(
802                self.custom_requirements,
803                type='danger')
804            self.redirect(self.url(self.context))
805            return
806        if not self.context.special:
807            for ticket in self.context.payments:
808                if ticket.p_state == 'paid':
809                      self.flash(
810                          _('This type of payment has already been made.'),
811                          type='warning')
812                      self.redirect(self.url(self.context))
813                      return
814        applicants_utils = getUtility(IApplicantsUtils)
815        container = self.context.__parent__
816        payment = createObject(self.factory)
817        failure = applicants_utils.setPaymentDetails(
818            container, payment, self.context)
819        if failure is not None:
820            self.flash(failure, type='danger')
821            self.redirect(self.url(self.context))
822            return
823        if getattr(self.context, 'p_option', None):
824            payment.p_option = self.context.p_option
825        self.context[payment.p_id] = payment
826        self.context.writeLogMessage(self, 'added: %s' % payment.p_id)
827        self.flash(_('Payment ticket created.'))
828        self.redirect(self.url(payment))
829        return
830
831    def render(self):
832        return
833
834
835class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
836    """ Page to view an online payment ticket
837    """
838    grok.context(IApplicantOnlinePayment)
839    grok.name('index')
840    grok.require('waeup.viewApplication')
841    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
842    form_fields[
843        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
844    form_fields[
845        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
846    pnav = 3
847
848    @property
849    def label(self):
850        return _('${a}: Online Payment Ticket ${b}', mapping = {
851            'a':self.context.__parent__.display_fullname,
852            'b':self.context.p_id})
853
854class OnlinePaymentApprovePage(UtilityView, grok.View):
855    """ Approval view
856    """
857    grok.context(IApplicantOnlinePayment)
858    grok.name('approve')
859    grok.require('waeup.managePortal')
860
861    def update(self):
862        flashtype, msg, log = self.context.approveApplicantPayment()
863        if log is not None:
864            applicant = self.context.__parent__
865            # Add log message to applicants.log
866            applicant.writeLogMessage(self, log)
867            # Add log message to payments.log
868            self.context.logger.info(
869                '%s,%s,%s,%s,%s,,,,,,' % (
870                applicant.applicant_id,
871                self.context.p_id, self.context.p_category,
872                self.context.amount_auth, self.context.r_code))
873        self.flash(msg, type=flashtype)
874        return
875
876    def render(self):
877        self.redirect(self.url(self.context, '@@index'))
878        return
879
880class ExportPDFPaymentSlipPage(UtilityView, grok.View):
881    """Deliver a PDF slip of the context.
882    """
883    grok.context(IApplicantOnlinePayment)
884    grok.name('payment_slip.pdf')
885    grok.require('waeup.viewApplication')
886    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
887    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
888    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
889    #prefix = 'form'
890    note = None
891
892    @property
893    def title(self):
894        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
895        return translate(_('Payment Data'), 'waeup.kofa',
896            target_language=portal_language)
897
898    @property
899    def label(self):
900        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
901        return translate(_('Online Payment Slip'),
902            'waeup.kofa', target_language=portal_language) \
903            + ' %s' % self.context.p_id
904
905    @property
906    def payment_slip_download_warning(self):
907        if self.context.__parent__.state not in (
908            SUBMITTED, ADMITTED, NOT_ADMITTED, CREATED):
909            return _('Please submit the application form before '
910                     'trying to download payment slips.')
911        return ''
912
913    def render(self):
914        if self.payment_slip_download_warning:
915            self.flash(self.payment_slip_download_warning, type='danger')
916            self.redirect(self.url(self.context))
917            return
918        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
919            self.request)
920        students_utils = getUtility(IStudentsUtils)
921        return students_utils.renderPDF(self,'payment_slip.pdf',
922            self.context.__parent__, applicantview, note=self.note)
923
924class ExportPDFPageApplicationSlip(UtilityView, grok.View):
925    """Deliver a PDF slip of the context.
926    """
927    grok.context(IApplicant)
928    grok.name('application_slip.pdf')
929    grok.require('waeup.viewApplication')
930    #prefix = 'form'
931
932    def update(self):
933        if self.context.state in ('initialized', 'started', 'paid'):
934            self.flash(
935                _('Please pay and submit before trying to download '
936                  'the application slip.'), type='warning')
937            return self.redirect(self.url(self.context))
938        return
939
940    def render(self):
941        try:
942            pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
943                view=self)
944        except IOError:
945            self.flash(
946                _('Your image file is corrupted. '
947                  'Please replace.'), type='danger')
948            return self.redirect(self.url(self.context))
949        except LayoutError, err:
950            self.flash(
951                'PDF file could not be created. Reportlab error message: %s'
952                % escape(err.message),
953                type="danger")
954            return self.redirect(self.url(self.context))
955        self.response.setHeader(
956            'Content-Type', 'application/pdf')
957        return pdfstream
958
959def handle_img_upload(upload, context, view):
960    """Handle upload of applicant image.
961
962    Returns `True` in case of success or `False`.
963
964    Please note that file pointer passed in (`upload`) most probably
965    points to end of file when leaving this function.
966    """
967    max_upload_size = getUtility(IKofaUtils).MAX_PASSPORT_SIZE
968    size = file_size(upload)
969    if size > max_upload_size:
970        view.flash(_('Uploaded image is too big!'), type='danger')
971        return False
972    dummy, ext = os.path.splitext(upload.filename)
973    ext.lower()
974    if ext != '.jpg':
975        view.flash(_('jpg file extension expected.'), type='danger')
976        return False
977    upload.seek(0) # file pointer moved when determining size
978    store = getUtility(IExtFileStore)
979    file_id = IFileStoreNameChooser(context).chooseName()
980    try:
981        store.createFile(file_id, upload)
982    except IOError:
983        view.flash(_('Image file cannot be changed.'), type='danger')
984        return False
985    return True
986
987def handle_file_upload(upload, context, view, attr=None):
988    """Handle upload of applicant files.
989
990    Returns `True` in case of success or `False`.
991
992    Please note that file pointer passed in (`upload`) most probably
993    points to end of file when leaving this function.
994    """
995    size = file_size(upload)
996    max_upload_size = 1024 * getUtility(IStudentsUtils).MAX_KB
997    if size > max_upload_size:
998        view.flash(_('Uploaded file is too big!'))
999        return False
1000    upload.seek(0)  # file pointer moved when determining size
1001    dummy,ext = os.path.splitext(upload.filename)
1002    file_format = get_fileformat(None, upload.read(512))
1003    upload.seek(0)  # same here
1004    if file_format is None:
1005        view.flash(_('Could not determine file type.'), type="danger")
1006        return False
1007    ext.lower()
1008    if ext not in ('.pdf', '.jpg'):
1009        view.flash(_('pdf or jpg file extension expected.'), type='danger')
1010        return False
1011    download_name = attr + '.' + file_format
1012    store = getUtility(IExtFileStore)
1013    file_id = IFileStoreNameChooser(context).chooseName(attr=download_name)
1014    store.createFile(file_id, upload)
1015    return True
1016
1017class ApplicantManageFormPage(KofaEditFormPage):
1018    """A full edit view for applicant data.
1019    """
1020    grok.context(IApplicant)
1021    grok.name('manage')
1022    grok.require('waeup.manageApplication')
1023    grok.template('applicanteditpage')
1024    manage_applications = True
1025    pnav = 3
1026    display_actions = [[_('Save'), _('Finally Submit')],
1027        [_('Add online payment ticket'),_('Remove selected tickets')]]
1028
1029    @property
1030    def display_payments(self):
1031        if self.context.payments:
1032            return True
1033        if self.context.special:
1034            return True
1035        return getattr(self.context.__parent__, 'application_fee', None)
1036
1037    @property
1038    def display_refereereports(self):
1039        if self.context.refereereports:
1040            return True
1041        return False
1042
1043    def display_fileupload(self, filename):
1044        """This method can be used in custom packages to avoid unneccessary
1045        file uploads.
1046        """
1047        return True
1048
1049    @property
1050    def form_fields(self):
1051        if self.context.special:
1052            form_fields = grok.AutoFields(ISpecialApplicant)
1053            form_fields['applicant_id'].for_display = True
1054        else:
1055            form_fields = grok.AutoFields(IApplicant)
1056            form_fields['student_id'].for_display = True
1057            form_fields['applicant_id'].for_display = True
1058        return form_fields
1059
1060    @property
1061    def target(self):
1062        return getattr(self.context.__parent__, 'prefix', None)
1063
1064    @property
1065    def separators(self):
1066        return getUtility(IApplicantsUtils).SEPARATORS_DICT
1067
1068    @property
1069    def custom_upload_requirements(self):
1070        return ''
1071
1072    def update(self):
1073        super(ApplicantManageFormPage, self).update()
1074        max_upload_size = getUtility(IKofaUtils).MAX_PASSPORT_SIZE
1075        self.wf_info = IWorkflowInfo(self.context)
1076        self.max_upload_size = string_from_bytes(max_upload_size)
1077        self.upload_success = None
1078        upload = self.request.form.get('form.passport', None)
1079        if upload:
1080            if self.custom_upload_requirements:
1081                self.flash(
1082                    self.custom_upload_requirements,
1083                    type='danger')
1084                self.redirect(self.url(self.context))
1085                return
1086            # We got a fresh upload, upload_success is
1087            # either True or False
1088            self.upload_success = handle_img_upload(
1089                upload, self.context, self)
1090            if self.upload_success:
1091                self.context.writeLogMessage(self, 'saved: passport')
1092        file_store = getUtility(IExtFileStore)
1093        self.additional_files = getUtility(IApplicantsUtils).ADDITIONAL_FILES
1094        for filename in self.additional_files:
1095            upload = self.request.form.get(filename[1], None)
1096            if upload:
1097                # We got a fresh file upload
1098                success = handle_file_upload(
1099                    upload, self.context, self, attr=filename[1])
1100                if success:
1101                    self.context.writeLogMessage(
1102                        self, 'saved: %s' % filename[1])
1103                else:
1104                    self.upload_success = False
1105        self.max_file_upload_size = string_from_bytes(
1106            1024*getUtility(IStudentsUtils).MAX_KB)
1107        return
1108
1109    @property
1110    def label(self):
1111        container_title = self.context.__parent__.title
1112        return _('${a} <br /> Application Form ${b}', mapping = {
1113            'a':container_title, 'b':self.context.application_number})
1114
1115    def getTransitions(self):
1116        """Return a list of dicts of allowed transition ids and titles.
1117
1118        Each list entry provides keys ``name`` and ``title`` for
1119        internal name and (human readable) title of a single
1120        transition.
1121        """
1122        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
1123            if not t[0] in ('pay', 'create')]
1124        return [dict(name='', title=_('No transition'))] +[
1125            dict(name=x, title=y) for x, y in allowed_transitions]
1126
1127    def saveCourses(self):
1128        """In custom packages we needed to customize the certificate
1129        select widget. We just save course1 and course2 if these customized
1130        fields appear in the form.
1131        """
1132        return None, []
1133
1134    @action(_('Save'), style='primary')
1135    def save(self, **data):
1136        error, changed_courses = self.saveCourses()
1137        if error:
1138            self.flash(error, type='danger')
1139            return
1140        form = self.request.form
1141        password = form.get('password', None)
1142        password_ctl = form.get('control_password', None)
1143        if password:
1144            validator = getUtility(IPasswordValidator)
1145            errors = validator.validate_password(password, password_ctl)
1146            if errors:
1147                self.flash( ' '.join(errors), type='danger')
1148                return
1149        if self.upload_success is False:  # False is not None!
1150            # Error during image upload. Ignore other values.
1151            return
1152        changed_fields = self.applyData(self.context, **data)
1153        # Turn list of lists into single list
1154        if changed_fields:
1155            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1156        else:
1157            changed_fields = []
1158        changed_fields += changed_courses
1159        if password:
1160            # Now we know that the form has no errors and can set password ...
1161            IUserAccount(self.context).setPassword(password)
1162            changed_fields.append('password')
1163        fields_string = ' + '.join(changed_fields)
1164        trans_id = form.get('transition', None)
1165        if trans_id:
1166            self.wf_info.fireTransition(trans_id)
1167        self.flash(_('Form has been saved.'))
1168        if fields_string:
1169            self.context.writeLogMessage(self, 'saved: %s' % fields_string)
1170        return
1171
1172    def unremovable(self, ticket):
1173        return False
1174
1175    # This method is also used by the ApplicantEditFormPage
1176    def delPaymentTickets(self, **data):
1177        form = self.request.form
1178        if 'val_id' in form:
1179            child_id = form['val_id']
1180        else:
1181            self.flash(_('No payment selected.'), type='warning')
1182            self.redirect(self.url(self.context))
1183            return
1184        if not isinstance(child_id, list):
1185            child_id = [child_id]
1186        deleted = []
1187        for id in child_id:
1188            # Applicants are not allowed to remove used payment tickets
1189            if not self.unremovable(self.context[id]):
1190                try:
1191                    del self.context[id]
1192                    deleted.append(id)
1193                except:
1194                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
1195                      id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
1196        if len(deleted):
1197            self.flash(_('Successfully removed: ${a}',
1198                mapping = {'a':', '.join(deleted)}))
1199            self.context.writeLogMessage(
1200                self, 'removed: % s' % ', '.join(deleted))
1201        return
1202
1203    # We explicitely want the forms to be validated before payment tickets
1204    # can be created. If no validation is requested, use
1205    # 'validator=NullValidator' in the action directive
1206    @action(_('Add online payment ticket'), style='primary')
1207    def addPaymentTicket(self, **data):
1208        self.redirect(self.url(self.context, '@@addafp'))
1209        return
1210
1211    @jsaction(_('Remove selected tickets'))
1212    def removePaymentTickets(self, **data):
1213        self.delPaymentTickets(**data)
1214        self.redirect(self.url(self.context) + '/@@manage')
1215        return
1216
1217    # Not used in base package
1218    def file_exists(self, attr):
1219        file = getUtility(IExtFileStore).getFileByContext(
1220            self.context, attr=attr)
1221        if file:
1222            return True
1223        else:
1224            return False
1225
1226class ApplicantEditFormPage(ApplicantManageFormPage):
1227    """An applicant-centered edit view for applicant data.
1228    """
1229    grok.context(IApplicantEdit)
1230    grok.name('edit')
1231    grok.require('waeup.handleApplication')
1232    grok.template('applicanteditpage')
1233    manage_applications = False
1234    submit_state = PAID
1235    mandate_days = 31
1236
1237    @property
1238    def display_refereereports(self):
1239        return False
1240
1241    @property
1242    def form_fields(self):
1243        if self.context.special:
1244            form_fields = grok.AutoFields(ISpecialApplicant).omit(
1245                'locked', 'suspended')
1246            form_fields['applicant_id'].for_display = True
1247        else:
1248            form_fields = grok.AutoFields(IApplicantEdit).omit(
1249                'locked', 'course_admitted', 'student_id',
1250                'suspended'
1251                )
1252            form_fields['applicant_id'].for_display = True
1253            form_fields['reg_number'].for_display = True
1254        return form_fields
1255
1256    @property
1257    def display_actions(self):
1258        state = IWorkflowState(self.context).getState()
1259        # If the form is unlocked, applicants are allowed to save the form
1260        # and remove unused tickets.
1261        actions = [[_('Save')], [_('Remove selected tickets')]]
1262        # Only in state started they can also add tickets.
1263        if state == STARTED:
1264            actions = [[_('Save')],
1265                [_('Add online payment ticket'),_('Remove selected tickets')]]
1266        # In state paid, they can submit the data and further add tickets
1267        # if the application is special.
1268        elif self.context.special and state == PAID:
1269            actions = [[_('Save'), _('Finally Submit')],
1270                [_('Add online payment ticket'),_('Remove selected tickets')]]
1271        elif state == PAID:
1272            actions = [[_('Save'), _('Finally Submit')],
1273                [_('Remove selected tickets')]]
1274        return actions
1275
1276    def unremovable(self, ticket):
1277        return ticket.r_code
1278
1279    def emit_lock_message(self):
1280        self.flash(_('The requested form is locked (read-only).'),
1281                   type='warning')
1282        self.redirect(self.url(self.context))
1283        return
1284
1285    def update(self):
1286        if self.context.locked or (
1287            self.context.__parent__.expired and
1288            self.context.__parent__.strict_deadline):
1289            self.emit_lock_message()
1290            return
1291        super(ApplicantEditFormPage, self).update()
1292        return
1293
1294    def dataNotComplete(self, data):
1295        if self.context.__parent__.with_picture:
1296            store = getUtility(IExtFileStore)
1297            if not store.getFileByContext(self.context, attr=u'passport.jpg'):
1298                return _('No passport picture uploaded.')
1299            if not self.request.form.get('confirm_passport', False):
1300                return _('Passport picture confirmation box not ticked.')
1301        return False
1302
1303    # We explicitely want the forms to be validated before payment tickets
1304    # can be created. If no validation is requested, use
1305    # 'validator=NullValidator' in the action directive
1306    @action(_('Add online payment ticket'), style='primary')
1307    def addPaymentTicket(self, **data):
1308        self.redirect(self.url(self.context, '@@addafp'))
1309        return
1310
1311    @jsaction(_('Remove selected tickets'))
1312    def removePaymentTickets(self, **data):
1313        self.delPaymentTickets(**data)
1314        self.redirect(self.url(self.context) + '/@@edit')
1315        return
1316
1317    @action(_('Save'), style='primary')
1318    def save(self, **data):
1319        if self.upload_success is False:  # False is not None!
1320            # Error during image upload. Ignore other values.
1321            return
1322        self.applyData(self.context, **data)
1323        error, dummy = self.saveCourses()
1324        if error:
1325            self.flash(error, type='danger')
1326            return
1327        self.flash(_('Form has been saved.'))
1328        return
1329
1330    def informReferees(self):
1331        site = grok.getSite()
1332        kofa_utils = getUtility(IKofaUtils)
1333        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1334        failed = ''
1335        emails_sent = 0
1336        for referee in self.context.referees:
1337            if referee.email_sent:
1338                continue
1339            mandate = RefereeReportMandate(days=self.mandate_days)
1340            mandate.params['name'] = referee.name
1341            mandate.params['email'] = referee.email
1342            mandate.params[
1343                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1344                    self.context.__parent__.code,
1345                    self.context.application_number)
1346            mandate.params['redirect_path2'] = ''
1347            mandate.params['applicant_id'] = self.context.applicant_id
1348            site['mandates'].addMandate(mandate)
1349            # Send invitation email
1350            args = {'mandate_id':mandate.mandate_id}
1351            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1352            url_info = u'Report link: %s' % mandate_url
1353            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1354            if success:
1355                emails_sent += 1
1356                self.context.writeLogMessage(
1357                    self, 'email sent: %s' % referee.email)
1358                referee.email_sent = True
1359            else:
1360                failed += '%s ' % referee.email
1361        return failed, emails_sent
1362
1363    @action(_('Finally Submit'), warning=WARNING)
1364    def finalsubmit(self, **data):
1365        if self.upload_success is False:  # False is not None!
1366            return # error during image upload. Ignore other values
1367        dnt = self.dataNotComplete(data)
1368        if dnt:
1369            self.flash(dnt, type='danger')
1370            return
1371        self.applyData(self.context, **data)
1372        error, dummy = self.saveCourses()
1373        if error:
1374            self.flash(error, type='danger')
1375            return
1376        state = IWorkflowState(self.context).getState()
1377        # This shouldn't happen, but the application officer
1378        # might have forgotten to lock the form after changing the state
1379        if state != self.submit_state:
1380            self.flash(_('The form cannot be submitted. Wrong state!'),
1381                       type='danger')
1382            return
1383        msg = _('Form has been submitted.')
1384        # Create mandates and send emails to referees
1385        if getattr(self.context, 'referees', None):
1386            failed, emails_sent = self.informReferees()
1387            if failed:
1388                self.flash(
1389                    _('Some invitation emails could not be sent:') + failed,
1390                    type='danger')
1391                return
1392            msg = _('Form has been successfully submitted and '
1393                    '${a} invitation emails were sent.',
1394                    mapping = {'a':  emails_sent})
1395        IWorkflowInfo(self.context).fireTransition('submit')
1396        # application_date is used in export files for sorting.
1397        # We can thus store utc.
1398        self.context.application_date = datetime.utcnow()
1399        self.flash(msg)
1400        self.redirect(self.url(self.context))
1401        return
1402
1403class PassportImage(grok.View):
1404    """Renders the passport image for applicants.
1405    """
1406    grok.name('passport.jpg')
1407    grok.context(IApplicant)
1408    grok.require('waeup.viewApplication')
1409
1410    def render(self):
1411        # A filename chooser turns a context into a filename suitable
1412        # for file storage.
1413        image = getUtility(IExtFileStore).getFileByContext(self.context)
1414        self.response.setHeader('Content-Type', 'image/jpeg')
1415        if image is None:
1416            # show placeholder image
1417            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
1418        return image
1419
1420class PassportImageForReport(PassportImage):
1421    """Renders the passport image for applicants for referee reports.
1422    """
1423    grok.name('passport_for_report.jpg')
1424    grok.context(IApplicant)
1425    grok.require('waeup.Public')
1426
1427    def render(self):
1428        # Check mandate
1429        form = self.request.form
1430        self.mandate_id = form.get('mandate_id', None)
1431        self.mandates = grok.getSite()['mandates']
1432        mandate = self.mandates.get(self.mandate_id, None)
1433        if mandate is None:
1434            self.flash(_('No mandate.'), type='warning')
1435            self.redirect(self.application_url())
1436            return
1437        if mandate:
1438            # Check the mandate expiration date after redirect again
1439            if mandate.expires < datetime.utcnow():
1440                self.flash(_('Mandate expired.'),
1441                           type='warning')
1442                self.redirect(self.application_url())
1443                return
1444            # Check if mandate allows access
1445            if mandate.params.get('applicant_id') != self.context.applicant_id:
1446                self.flash(_('Wrong mandate.'),
1447                           type='warning')
1448                self.redirect(self.application_url())
1449                return
1450            return super(PassportImageForReport, self).render()
1451        return
1452
1453class ApplicantRegistrationPage(KofaAddFormPage):
1454    """Captcha'd registration page for applicants.
1455    """
1456    grok.context(IApplicantsContainer)
1457    grok.name('register')
1458    grok.require('waeup.Anonymous')
1459    grok.template('applicantregister')
1460
1461    @property
1462    def form_fields(self):
1463        form_fields = None
1464        if self.context.mode == 'update':
1465            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1466                'lastname','reg_number','email')
1467        else: #if self.context.mode == 'create':
1468            form_fields = grok.AutoFields(IApplicantEdit).select(
1469                'firstname', 'middlename', 'lastname', 'email', 'phone')
1470        return form_fields
1471
1472    @property
1473    def label(self):
1474        return _('Apply for ${a}',
1475            mapping = {'a':self.context.title})
1476
1477    def update(self):
1478        if self.context.expired:
1479            self.flash(_('Outside application period.'), type='warning')
1480            self.redirect(self.url(self.context))
1481            return
1482        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1483        if blocker:
1484            self.flash(_('The portal is in maintenance mode '
1485                        'and registration temporarily disabled.'),
1486                       type='warning')
1487            self.redirect(self.url(self.context))
1488            return
1489        # Handle captcha
1490        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1491        self.captcha_result = self.captcha.verify(self.request)
1492        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1493        return
1494
1495    def _redirect(self, email, password, applicant_id):
1496        # Forward only email to landing page in base package.
1497        self.redirect(self.url(self.context, 'registration_complete',
1498            data = dict(email=email)))
1499        return
1500
1501    @property
1502    def _postfix(self):
1503        """In customized packages we can add a container dependent string if
1504        applicants have been imported into several containers.
1505        """
1506        return ''
1507
1508    @action(_('Send login credentials to email address'), style='primary')
1509    def register(self, **data):
1510        if not self.captcha_result.is_valid:
1511            # Captcha will display error messages automatically.
1512            # No need to flash something.
1513            return
1514        if self.context.mode == 'create':
1515            # Check if there are unused records in this container which
1516            # can be taken
1517            applicant = self.context.first_unused
1518            if applicant is None:
1519                # Add applicant
1520                applicant = createObject(u'waeup.Applicant')
1521                self.context.addApplicant(applicant)
1522            else:
1523                applicants_root = grok.getSite()['applicants']
1524                ob_class = self.__implemented__.__name__.replace(
1525                    'waeup.kofa.','')
1526                applicants_root.logger.info('%s - used: %s' % (
1527                    ob_class, applicant.applicant_id))
1528            self.applyData(applicant, **data)
1529            # applicant.reg_number = applicant.applicant_id
1530            notify(grok.ObjectModifiedEvent(applicant))
1531        elif self.context.mode == 'update':
1532            # Update applicant
1533            reg_number = data.get('reg_number','')
1534            lastname = data.get('lastname','')
1535            cat = getUtility(ICatalog, name='applicants_catalog')
1536            searchstr = reg_number + self._postfix
1537            results = list(
1538                cat.searchResults(reg_number=(searchstr, searchstr)))
1539            if results:
1540                applicant = results[0]
1541                if getattr(applicant,'lastname',None) is None:
1542                    self.flash(_('An error occurred.'), type='danger')
1543                    return
1544                elif applicant.lastname.lower() != lastname.lower():
1545                    # Don't tell the truth here. Anonymous must not
1546                    # know that a record was found and only the lastname
1547                    # verification failed.
1548                    self.flash(
1549                        _('No application record found.'), type='warning')
1550                    return
1551                elif applicant.password is not None and \
1552                    applicant.state != INITIALIZED:
1553                    self.flash(_('Your password has already been set and used. '
1554                                 'Please proceed to the login page.'),
1555                               type='warning')
1556                    return
1557                # Store email address but nothing else.
1558                applicant.email = data['email']
1559                notify(grok.ObjectModifiedEvent(applicant))
1560            else:
1561                # No record found, this is the truth.
1562                self.flash(_('No application record found.'), type='warning')
1563                return
1564        else:
1565            # Does not happen but anyway ...
1566            return
1567        kofa_utils = getUtility(IKofaUtils)
1568        password = kofa_utils.genPassword()
1569        IUserAccount(applicant).setPassword(password)
1570        # Send email with credentials
1571        args = {'login':applicant.applicant_id, 'password':password}
1572        login_url = self.url(grok.getSite()) + '/login?%s' % urlencode(args)
1573        url_info = u'Login: %s' % login_url
1574        msg = _('You have successfully been registered for the')
1575        if kofa_utils.sendCredentials(IUserAccount(applicant),
1576            password, url_info, msg):
1577            email_sent = applicant.email
1578        else:
1579            email_sent = None
1580        self._redirect(email=email_sent, password=password,
1581            applicant_id=applicant.applicant_id)
1582        return
1583
1584class ApplicantRegistrationEmailSent(KofaPage):
1585    """Landing page after successful registration.
1586
1587    """
1588    grok.name('registration_complete')
1589    grok.require('waeup.Public')
1590    grok.template('applicantregemailsent')
1591    label = _('Your registration was successful.')
1592
1593    def update(self, email=None, applicant_id=None, password=None):
1594        self.email = email
1595        self.password = password
1596        self.applicant_id = applicant_id
1597        return
1598
1599class ApplicantCheckStatusPage(KofaPage):
1600    """Captcha'd status checking page for applicants.
1601    """
1602    grok.context(IApplicantsRoot)
1603    grok.name('checkstatus')
1604    grok.require('waeup.Anonymous')
1605    grok.template('applicantcheckstatus')
1606    buttonname = _('Submit')
1607    pnav = 7
1608
1609    def label(self):
1610        if self.result:
1611            return _('Admission status of ${a}',
1612                     mapping = {'a':self.applicant.applicant_id})
1613        return _('Check your admission status')
1614
1615    def update(self, SUBMIT=None):
1616        form = self.request.form
1617        self.result = False
1618        # Handle captcha
1619        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1620        self.captcha_result = self.captcha.verify(self.request)
1621        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1622        if SUBMIT:
1623            if not self.captcha_result.is_valid:
1624                # Captcha will display error messages automatically.
1625                # No need to flash something.
1626                return
1627            unique_id = form.get('unique_id', None)
1628            lastname = form.get('lastname', None)
1629            if not unique_id or not lastname:
1630                self.flash(
1631                    _('Required input missing.'), type='warning')
1632                return
1633            cat = getUtility(ICatalog, name='applicants_catalog')
1634            results = list(
1635                cat.searchResults(applicant_id=(unique_id, unique_id)))
1636            if not results:
1637                results = list(
1638                    cat.searchResults(reg_number=(unique_id, unique_id)))
1639            if results:
1640                applicant = results[0]
1641                if applicant.lastname.lower().strip() != lastname.lower():
1642                    # Don't tell the truth here. Anonymous must not
1643                    # know that a record was found and only the lastname
1644                    # verification failed.
1645                    self.flash(
1646                        _('No application record found.'), type='warning')
1647                    return
1648            else:
1649                self.flash(_('No application record found.'), type='warning')
1650                return
1651            self.applicant = applicant
1652            self.entry_session = "%s/%s" % (
1653                applicant.__parent__.year,
1654                applicant.__parent__.year+1)
1655            course_admitted = getattr(applicant, 'course_admitted', None)
1656            self.course_admitted = False
1657            if course_admitted is not None:
1658                try:
1659                    self.course_admitted = True
1660                    self.longtitle = course_admitted.longtitle
1661                    self.department = course_admitted.__parent__.__parent__.longtitle
1662                    self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1663                except AttributeError:
1664                    self.flash(_('Application record invalid.'), type='warning')
1665                    return
1666            self.result = True
1667            self.admitted = False
1668            self.not_admitted = False
1669            self.submitted = False
1670            self.not_submitted = False
1671            self.created = False
1672            if applicant.state in (ADMITTED, CREATED):
1673                self.admitted = True
1674            if applicant.state in (CREATED):
1675                self.created = True
1676                self.student_id = applicant.student_id
1677                self.password = applicant.application_number
1678            if applicant.state in (NOT_ADMITTED,):
1679                self.not_admitted = True
1680            if applicant.state in (SUBMITTED,):
1681                self.submitted = True
1682            if applicant.state in (INITIALIZED, STARTED, PAID):
1683                self.not_submitted = True
1684        return
1685
1686class CheckTranscriptStatus(KofaPage):
1687    """A display page for checking transcript processing status.
1688    """
1689    grok.context(IApplicantsRoot)
1690    grok.name('checktranscript')
1691    grok.require('waeup.Public')
1692    label = _('Check transcript status')
1693    buttonname = _('Check status now')
1694    pnav = 8
1695    websites = (('DemoPortal', 'https://kofa-demo.waeup.org/'),)
1696    #websites = (('DemoPortal', 'http://localhost:8080/app/'),)
1697    appl_url1 = 'https://kofa-demo.waeup.org/applicants'
1698    appl_url2 = 'https://kofa-demo.waeup.org/applicants'
1699
1700    def update(self, SUBMIT=None):
1701        form = self.request.form
1702        self.button = False
1703        # Handle captcha
1704        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1705        self.captcha_result = self.captcha.verify(self.request)
1706        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1707        if SUBMIT:
1708            self.results = []
1709            if not self.captcha_result.is_valid:
1710                # Captcha will display error messages automatically.
1711                # No need to flash something.
1712                return
1713            unique_id = form.get('unique_id', None)
1714            email = form.get('email', None)
1715            if not unique_id or not email:
1716                self.flash(
1717                    _('Required input missing.'), type='warning')
1718                return
1719            self.button = True
1720            # Call webservice of all websites
1721            for website in self.websites:
1722                server = xmlrpclib.ServerProxy(website[1])
1723                result = server.get_grad_student(unique_id, email)
1724                if not result:
1725                    continue
1726                self.results.append((result, website))
1727        return
1728
1729class ExportJobContainerOverview(KofaPage):
1730    """Page that lists active applicant data export jobs and provides links
1731    to discard or download CSV files.
1732
1733    """
1734    grok.context(VirtualApplicantsExportJobContainer)
1735    grok.require('waeup.manageApplication')
1736    grok.name('index.html')
1737    grok.template('exportjobsindex')
1738    label = _('Data Exports')
1739    pnav = 3
1740
1741    def update(self, CREATE=None, DISCARD=None, job_id=None):
1742        if CREATE:
1743            self.redirect(self.url('@@start_export'))
1744            return
1745        if DISCARD and job_id:
1746            entry = self.context.entry_from_job_id(job_id)
1747            self.context.delete_export_entry(entry)
1748            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1749            self.context.logger.info(
1750                '%s - discarded: job_id=%s' % (ob_class, job_id))
1751            self.flash(_('Discarded export') + ' %s' % job_id)
1752        self.entries = doll_up(self, user=self.request.principal.id)
1753        return
1754
1755class ExportJobContainerJobStart(UtilityView, grok.View):
1756    """View that starts three export jobs, one for applicants, a second
1757    one for applicant payments and a third for referee reports.
1758    """
1759    grok.context(VirtualApplicantsExportJobContainer)
1760    grok.require('waeup.manageApplication')
1761    grok.name('start_export')
1762
1763    def update(self):
1764        utils = queryUtility(IKofaUtils)
1765        if not utils.expensive_actions_allowed():
1766            self.flash(_(
1767                "Currently, exporters cannot be started due to high "
1768                "system load. Please try again later."), type='danger')
1769            self.entries = doll_up(self, user=None)
1770            return
1771
1772        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1773        container_code = self.context.__parent__.code
1774        # Start first exporter
1775        for exporter in ('applicants',
1776                         'applicantpayments',
1777                         'applicantrefereereports'):
1778            job_id = self.context.start_export_job(exporter,
1779                                          self.request.principal.id,
1780                                          container=container_code)
1781            self.context.logger.info(
1782                '%s - exported: %s (%s), job_id=%s'
1783                % (ob_class, exporter, container_code, job_id))
1784            # Commit transaction so that job is stored in the ZODB
1785            transaction.commit()
1786        self.flash(_('Exports started.'))
1787        self.redirect(self.url(self.context))
1788        return
1789
1790    def render(self):
1791        return
1792
1793class ExportJobContainerDownload(ExportCSVView):
1794    """Page that downloads a students export csv file.
1795
1796    """
1797    grok.context(VirtualApplicantsExportJobContainer)
1798    grok.require('waeup.manageApplication')
1799
1800class RefereesRemindPage(UtilityView, grok.View):
1801    """A display view for referee reports.
1802    """
1803    grok.context(IApplicant)
1804    grok.name('remind_referees')
1805    grok.require('waeup.manageApplication')
1806
1807    mandate_days = 31
1808
1809    def remindReferees(self):
1810        site = grok.getSite()
1811        kofa_utils = getUtility(IKofaUtils)
1812        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1813        failed = ''
1814        emails_sent = 0
1815        for referee in self.context.referees:
1816            #if not referee.email_sent:
1817            #    continue
1818            # Check if referee has already created a report
1819            report_exists = False
1820            for report in self.context.refereereports:
1821                if report.email == referee.email:
1822                    report_exists = True
1823            if report_exists:
1824                continue
1825            # If not, create new mandate
1826            mandate = RefereeReportMandate(days=self.mandate_days)
1827            mandate.params['name'] = referee.name
1828            mandate.params['email'] = referee.email
1829            mandate.params[
1830                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1831                    self.context.__parent__.code,
1832                    self.context.application_number)
1833            mandate.params['redirect_path2'] = ''
1834            mandate.params['applicant_id'] = self.context.applicant_id
1835            site['mandates'].addMandate(mandate)
1836            # Send invitation email
1837            args = {'mandate_id':mandate.mandate_id}
1838            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1839            url_info = u'Report link: %s' % mandate_url
1840            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1841            if success:
1842                emails_sent += 1
1843                self.context.writeLogMessage(
1844                    self, 'email sent: %s' % referee.email)
1845                referee.email_sent = True
1846            else:
1847                failed += '%s ' % referee.email
1848        return failed, emails_sent
1849
1850    def update(self):
1851        if self.context.state != 'submitted':
1852            self.flash(
1853                _('Not allowed!'), type='danger')
1854            return self.redirect(self.url(self.context))
1855        failed, emails_sent = self.remindReferees()
1856        msg = _('${a} referee(s) have been reminded by email.',
1857                mapping = {'a':  emails_sent})
1858        self.flash(msg)
1859        return self.redirect(self.url(self.context))
1860
1861    def render(self):
1862        return
1863
1864class RefereeReportDisplayFormPage(KofaDisplayFormPage):
1865    """A display view for referee reports.
1866    """
1867    grok.context(IApplicantRefereeReport)
1868    grok.name('index')
1869    grok.require('waeup.manageApplication')
1870    label = _('Referee Report')
1871    pnav = 3
1872    form_fields = grok.AutoFields(IApplicantRefereeReport)
1873    form_fields[
1874        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1875
1876class RefereeReportManageFormPage(KofaEditFormPage):
1877    """A displaymanage for referee reports.
1878    """
1879    grok.context(IApplicantRefereeReport)
1880    grok.name('manage')
1881    grok.require('waeup.managePortal')
1882    label = _('Manage Referee Report')
1883    pnav = 3
1884    form_fields = grok.AutoFields(IApplicantRefereeReport).omit('creation_date')
1885
1886    @action(_('Save'), style='primary')
1887    def save(self, **data):
1888        changed_fields = self.applyData(self.context, **data)
1889        # Turn list of lists into single list
1890        if changed_fields:
1891            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1892        else:
1893            changed_fields = []
1894        fields_string = ' + '.join(changed_fields)
1895        self.flash(_('Form has been saved.'))
1896        if fields_string:
1897            self.context.__parent__.writeLogMessage(
1898                self, '%s - saved: %s' % (self.context.r_id, fields_string))
1899        return
1900
1901class RemoveRefereeReportPage(UtilityView, grok.View):
1902    """
1903    """
1904    grok.context(IApplicantRefereeReport)
1905    grok.name('remove')
1906    grok.require('waeup.manageApplication')
1907
1908    def update(self):
1909        redirect_url = self.url(self.context.__parent__)
1910        self.context.__parent__.writeLogMessage(
1911            self, 'removed: %s' % self.context.r_id)
1912        del self.context.__parent__[self.context.r_id]
1913        self.flash(_('Referee report removed.'))
1914        self.redirect(redirect_url)
1915        return
1916
1917    def render(self):
1918        return
1919
1920class RefereeReportAddFormPage(KofaAddFormPage):
1921    """Add-form to add an referee report. This form
1922    is protected by a mandate.
1923    """
1924    grok.context(IApplicant)
1925    grok.require('waeup.Public')
1926    grok.name('addrefereereport')
1927    form_fields = grok.AutoFields(
1928        IApplicantRefereeReport).omit('creation_date')
1929    grok.template('refereereportpage')
1930    label = _('Referee Report Form')
1931    pnav = 3
1932    #doclink = DOCLINK + '/refereereports.html'
1933
1934    def update(self):
1935        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1936        if blocker:
1937            self.flash(_('The portal is in maintenance mode. '
1938                        'Referee report forms are temporarily disabled.'),
1939                       type='warning')
1940            self.redirect(self.application_url())
1941            return
1942        # Check mandate
1943        form = self.request.form
1944        self.mandate_id = form.get('mandate_id', None)
1945        self.mandates = grok.getSite()['mandates']
1946        mandate = self.mandates.get(self.mandate_id, None)
1947        if mandate is None and not self.request.form.get('form.actions.submit'):
1948            self.flash(_('No mandate.'), type='warning')
1949            self.redirect(self.application_url())
1950            return
1951        if mandate:
1952            # Check the mandate expiration date after redirect again
1953            if mandate.expires < datetime.utcnow():
1954                self.flash(_('Mandate expired.'),
1955                           type='warning')
1956                self.redirect(self.application_url())
1957                return
1958            args = {'mandate_id':mandate.mandate_id}
1959            # Check if report exists.
1960            # (1) If mandate has been used to create a report,
1961            # redirect to the pdf file.
1962            if mandate.params.get('redirect_path2'):
1963                self.redirect(
1964                    self.application_url() +
1965                    mandate.params.get('redirect_path2') +
1966                    '?%s' % urlencode(args))
1967                return
1968            # (2) Report exists but was created with another mandate.
1969            for report in self.context.refereereports:
1970                if report.email == mandate.params.get('email'):
1971                    self.flash(_('You have already created a '
1972                                 'report with another mandate.'),
1973                               type='warning')
1974                    self.redirect(self.application_url())
1975                    return
1976            # Prefill form with mandate params
1977            self.form_fields.get(
1978                'name').field.default = mandate.params['name']
1979            self.form_fields.get(
1980                'email_pref').field.default = mandate.params['email']
1981            self.passport_url = self.url(
1982                self.context, 'passport_for_report.jpg') + '?%s' % urlencode(args)
1983        super(RefereeReportAddFormPage, self).update()
1984        return
1985
1986    @action(_('Submit'),
1987              warning=_('Are you really sure? '
1988                        'Reports can neither be modified or added '
1989                        'after submission.'),
1990              style='primary')
1991    def addRefereeReport(self, **data):
1992        report = createObject(u'waeup.ApplicantRefereeReport')
1993        timestamp = ("%d" % int(time()*10000))[1:]
1994        report.r_id = "r%s" % timestamp
1995        report.email = self.mandates[self.mandate_id].params['email']
1996        self.applyData(report, **data)
1997        self.context[report.r_id] = report
1998        # self.flash(_('Referee report has been saved. Thank you!'))
1999        self.context.writeLogMessage(self, 'added: %s' % report.r_id)
2000        # Changed on 19/04/20: We do no longer delete the mandate
2001        # but set path to redirect to the pdf file
2002        self.mandates[self.mandate_id].params[
2003            'redirect_path2'] = '/applicants/%s/%s/%s/referee_report.pdf' % (
2004                self.context.__parent__.code,
2005                self.context.application_number,
2006                report.r_id)
2007        notify(grok.ObjectModifiedEvent(self.mandates[self.mandate_id]))
2008        args = {'mandate_id':self.mandate_id}
2009        self.flash(_('Your report has been successfully submitted. '
2010                     'Please use the report link in the email again to download '
2011                     'a pdf slip of your report.'))
2012        #self.redirect(self.url(report, 'referee_report.pdf')
2013        #              + '?%s' % urlencode(args))
2014        self.redirect(self.application_url())
2015        return
2016
2017class ExportPDFReportSlipPage(UtilityView, grok.View):
2018    """Deliver a PDF slip of the context.
2019    """
2020    grok.context(IApplicantRefereeReport)
2021    grok.name('referee_report_slip.pdf')
2022    grok.require('waeup.manageApplication')
2023    form_fields = grok.AutoFields(IApplicantRefereeReport)
2024    form_fields[
2025        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
2026    #prefix = 'form'
2027    note = None
2028
2029    @property
2030    def title(self):
2031        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2032        return translate(_('Referee Report'), 'waeup.kofa',
2033            target_language=portal_language)
2034
2035    @property
2036    def label(self):
2037        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2038        return translate(_('Referee Report Slip'),
2039            'waeup.kofa', target_language=portal_language) \
2040            + ' %s' % self.context.r_id
2041
2042    def render(self):
2043        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
2044            self.request)
2045        students_utils = getUtility(IStudentsUtils)
2046        return students_utils.renderPDF(self,'referee_report_slip.pdf',
2047            self.context.__parent__, applicantview, note=self.note)
2048
2049class ExportPDFReportSlipPage2(ExportPDFReportSlipPage):
2050    """Deliver a PDF slip of the context to referees.
2051    """
2052    grok.name('referee_report.pdf')
2053    grok.require('waeup.Public')
2054
2055    def update(self):
2056        # Check mandate
2057        form = self.request.form
2058        self.mandate_id = form.get('mandate_id', None)
2059        self.mandates = grok.getSite()['mandates']
2060        mandate = self.mandates.get(self.mandate_id, None)
2061        if mandate is None:
2062            self.flash(_('No mandate.'), type='warning')
2063            self.redirect(self.application_url())
2064            return
2065        if mandate:
2066            # Check the mandate expiration date after redirect again
2067            if mandate.expires < datetime.utcnow():
2068                self.flash(_('Mandate expired.'),
2069                           type='warning')
2070                self.redirect(self.application_url())
2071                return
2072            # Check if form has really been submitted
2073            if not mandate.params.get('redirect_path2') \
2074                or mandate.params.get(
2075                    'applicant_id') != self.context.__parent__.applicant_id:
2076                self.flash(_('Wrong mandate.'),
2077                           type='warning')
2078                self.redirect(self.application_url())
2079                return
2080            super(ExportPDFReportSlipPage2, self).update()
2081        return
2082
2083class AdditionalFile(grok.View):
2084    """Renders additional files for applicants.
2085    This is a baseclass.
2086    """
2087    grok.baseclass()
2088    grok.context(IApplicant)
2089    grok.require('waeup.viewApplication')
2090
2091    def render(self):
2092        #image = getUtility(IExtFileStore).getFileByContext(
2093        #    self.context, attr=self.download_name)
2094        file = getUtility(IExtFileStore).getFileByContext(
2095            self.context, attr=self.__name__)
2096        dummy,ext = os.path.splitext(file.name)
2097        if ext == '.jpg':
2098            self.response.setHeader('Content-Type', 'image/jpeg')
2099        elif ext == '.pdf':
2100            self.response.setHeader('Content-Type', 'application/pdf')
2101        return file
2102
2103class TestFile(AdditionalFile):
2104    """Renders testfile.
2105    """
2106    grok.name('testfile')
Note: See TracBrowser for help on using the repository browser.