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

Last change on this file since 16545 was 16545, checked in by Henrik Bettermann, 3 years ago

Enable applicants to upload also additional jpg files.

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