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

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

Make pictures editable by officers even if applicants are not allowed to change their picture.

  • Property svn:keywords set to Id
File size: 79.3 KB
Line 
1## $Id: browser.py 16578 2021-08-19 20:28:54Z 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    @property
1176    def picture_editable(self):
1177        return self.context.__parent__.with_picture
1178
1179    # This method is also used by the ApplicantEditFormPage
1180    def delPaymentTickets(self, **data):
1181        form = self.request.form
1182        if 'val_id' in form:
1183            child_id = form['val_id']
1184        else:
1185            self.flash(_('No payment selected.'), type='warning')
1186            self.redirect(self.url(self.context))
1187            return
1188        if not isinstance(child_id, list):
1189            child_id = [child_id]
1190        deleted = []
1191        for id in child_id:
1192            # Applicants are not allowed to remove used payment tickets
1193            if not self.unremovable(self.context[id]):
1194                try:
1195                    del self.context[id]
1196                    deleted.append(id)
1197                except:
1198                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
1199                      id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
1200        if len(deleted):
1201            self.flash(_('Successfully removed: ${a}',
1202                mapping = {'a':', '.join(deleted)}))
1203            self.context.writeLogMessage(
1204                self, 'removed: % s' % ', '.join(deleted))
1205        return
1206
1207    # We explicitely want the forms to be validated before payment tickets
1208    # can be created. If no validation is requested, use
1209    # 'validator=NullValidator' in the action directive
1210    @action(_('Add online payment ticket'), style='primary')
1211    def addPaymentTicket(self, **data):
1212        self.redirect(self.url(self.context, '@@addafp'))
1213        return
1214
1215    @jsaction(_('Remove selected tickets'))
1216    def removePaymentTickets(self, **data):
1217        self.delPaymentTickets(**data)
1218        self.redirect(self.url(self.context) + '/@@manage')
1219        return
1220
1221    # Not used in base package
1222    def file_exists(self, attr):
1223        file = getUtility(IExtFileStore).getFileByContext(
1224            self.context, attr=attr)
1225        if file:
1226            return True
1227        else:
1228            return False
1229
1230class ApplicantEditFormPage(ApplicantManageFormPage):
1231    """An applicant-centered edit view for applicant data.
1232    """
1233    grok.context(IApplicantEdit)
1234    grok.name('edit')
1235    grok.require('waeup.handleApplication')
1236    grok.template('applicanteditpage')
1237    manage_applications = False
1238    submit_state = PAID
1239    mandate_days = 31
1240
1241    @property
1242    def display_refereereports(self):
1243        return False
1244
1245    @property
1246    def form_fields(self):
1247        if self.context.special:
1248            form_fields = grok.AutoFields(ISpecialApplicant).omit(
1249                'locked', 'suspended')
1250            form_fields['applicant_id'].for_display = True
1251        else:
1252            form_fields = grok.AutoFields(IApplicantEdit).omit(
1253                'locked', 'course_admitted', 'student_id',
1254                'suspended'
1255                )
1256            form_fields['applicant_id'].for_display = True
1257            form_fields['reg_number'].for_display = True
1258        return form_fields
1259
1260    @property
1261    def display_actions(self):
1262        state = IWorkflowState(self.context).getState()
1263        # If the form is unlocked, applicants are allowed to save the form
1264        # and remove unused tickets.
1265        actions = [[_('Save')], [_('Remove selected tickets')]]
1266        # Only in state started they can also add tickets.
1267        if state == STARTED:
1268            actions = [[_('Save')],
1269                [_('Add online payment ticket'),_('Remove selected tickets')]]
1270        # In state paid, they can submit the data and further add tickets
1271        # if the application is special.
1272        elif self.context.special and state == PAID:
1273            actions = [[_('Save'), _('Finally Submit')],
1274                [_('Add online payment ticket'),_('Remove selected tickets')]]
1275        elif state == PAID:
1276            actions = [[_('Save'), _('Finally Submit')],
1277                [_('Remove selected tickets')]]
1278        return actions
1279
1280    @property
1281    def picture_editable(self):
1282        return self.context.__parent__.picture_editable
1283
1284    def unremovable(self, ticket):
1285        return ticket.r_code
1286
1287    def emit_lock_message(self):
1288        self.flash(_('The requested form is locked (read-only).'),
1289                   type='warning')
1290        self.redirect(self.url(self.context))
1291        return
1292
1293    def update(self):
1294        if self.context.locked or (
1295            self.context.__parent__.expired and
1296            self.context.__parent__.strict_deadline):
1297            self.emit_lock_message()
1298            return
1299        super(ApplicantEditFormPage, self).update()
1300        return
1301
1302    def dataNotComplete(self, data):
1303        if self.context.__parent__.with_picture:
1304            store = getUtility(IExtFileStore)
1305            if not store.getFileByContext(self.context, attr=u'passport.jpg'):
1306                return _('No passport picture uploaded.')
1307            if not self.request.form.get('confirm_passport', False):
1308                return _('Passport picture confirmation box not ticked.')
1309        return False
1310
1311    # We explicitely want the forms to be validated before payment tickets
1312    # can be created. If no validation is requested, use
1313    # 'validator=NullValidator' in the action directive
1314    @action(_('Add online payment ticket'), style='primary')
1315    def addPaymentTicket(self, **data):
1316        self.redirect(self.url(self.context, '@@addafp'))
1317        return
1318
1319    @jsaction(_('Remove selected tickets'))
1320    def removePaymentTickets(self, **data):
1321        self.delPaymentTickets(**data)
1322        self.redirect(self.url(self.context) + '/@@edit')
1323        return
1324
1325    @action(_('Save'), style='primary')
1326    def save(self, **data):
1327        if self.upload_success is False:  # False is not None!
1328            # Error during image upload. Ignore other values.
1329            return
1330        self.applyData(self.context, **data)
1331        error, dummy = self.saveCourses()
1332        if error:
1333            self.flash(error, type='danger')
1334            return
1335        self.flash(_('Form has been saved.'))
1336        return
1337
1338    def informReferees(self):
1339        site = grok.getSite()
1340        kofa_utils = getUtility(IKofaUtils)
1341        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1342        failed = ''
1343        emails_sent = 0
1344        for referee in self.context.referees:
1345            if referee.email_sent:
1346                continue
1347            mandate = RefereeReportMandate(days=self.mandate_days)
1348            mandate.params['name'] = referee.name
1349            mandate.params['email'] = referee.email
1350            mandate.params[
1351                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1352                    self.context.__parent__.code,
1353                    self.context.application_number)
1354            mandate.params['redirect_path2'] = ''
1355            mandate.params['applicant_id'] = self.context.applicant_id
1356            site['mandates'].addMandate(mandate)
1357            # Send invitation email
1358            args = {'mandate_id':mandate.mandate_id}
1359            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1360            url_info = u'Report link: %s' % mandate_url
1361            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1362            if success:
1363                emails_sent += 1
1364                self.context.writeLogMessage(
1365                    self, 'email sent: %s' % referee.email)
1366                referee.email_sent = True
1367            else:
1368                failed += '%s ' % referee.email
1369        return failed, emails_sent
1370
1371    @action(_('Finally Submit'), warning=WARNING)
1372    def finalsubmit(self, **data):
1373        if self.upload_success is False:  # False is not None!
1374            return # error during image upload. Ignore other values
1375        dnt = self.dataNotComplete(data)
1376        if dnt:
1377            self.flash(dnt, type='danger')
1378            return
1379        self.applyData(self.context, **data)
1380        error, dummy = self.saveCourses()
1381        if error:
1382            self.flash(error, type='danger')
1383            return
1384        state = IWorkflowState(self.context).getState()
1385        # This shouldn't happen, but the application officer
1386        # might have forgotten to lock the form after changing the state
1387        if state != self.submit_state:
1388            self.flash(_('The form cannot be submitted. Wrong state!'),
1389                       type='danger')
1390            return
1391        msg = _('Form has been submitted.')
1392        # Create mandates and send emails to referees
1393        if getattr(self.context, 'referees', None):
1394            failed, emails_sent = self.informReferees()
1395            if failed:
1396                self.flash(
1397                    _('Some invitation emails could not be sent:') + failed,
1398                    type='danger')
1399                return
1400            msg = _('Form has been successfully submitted and '
1401                    '${a} invitation emails were sent.',
1402                    mapping = {'a':  emails_sent})
1403        IWorkflowInfo(self.context).fireTransition('submit')
1404        # application_date is used in export files for sorting.
1405        # We can thus store utc.
1406        self.context.application_date = datetime.utcnow()
1407        self.flash(msg)
1408        self.redirect(self.url(self.context))
1409        return
1410
1411class PassportImage(grok.View):
1412    """Renders the passport image for applicants.
1413    """
1414    grok.name('passport.jpg')
1415    grok.context(IApplicant)
1416    grok.require('waeup.viewApplication')
1417
1418    def render(self):
1419        # A filename chooser turns a context into a filename suitable
1420        # for file storage.
1421        image = getUtility(IExtFileStore).getFileByContext(self.context)
1422        self.response.setHeader('Content-Type', 'image/jpeg')
1423        if image is None:
1424            # show placeholder image
1425            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
1426        return image
1427
1428class PassportImageForReport(PassportImage):
1429    """Renders the passport image for applicants for referee reports.
1430    """
1431    grok.name('passport_for_report.jpg')
1432    grok.context(IApplicant)
1433    grok.require('waeup.Public')
1434
1435    def render(self):
1436        # Check mandate
1437        form = self.request.form
1438        self.mandate_id = form.get('mandate_id', None)
1439        self.mandates = grok.getSite()['mandates']
1440        mandate = self.mandates.get(self.mandate_id, None)
1441        if mandate is None:
1442            self.flash(_('No mandate.'), type='warning')
1443            self.redirect(self.application_url())
1444            return
1445        if mandate:
1446            # Check the mandate expiration date after redirect again
1447            if mandate.expires < datetime.utcnow():
1448                self.flash(_('Mandate expired.'),
1449                           type='warning')
1450                self.redirect(self.application_url())
1451                return
1452            # Check if mandate allows access
1453            if mandate.params.get('applicant_id') != self.context.applicant_id:
1454                self.flash(_('Wrong mandate.'),
1455                           type='warning')
1456                self.redirect(self.application_url())
1457                return
1458            return super(PassportImageForReport, self).render()
1459        return
1460
1461class ApplicantRegistrationPage(KofaAddFormPage):
1462    """Captcha'd registration page for applicants.
1463    """
1464    grok.context(IApplicantsContainer)
1465    grok.name('register')
1466    grok.require('waeup.Anonymous')
1467    grok.template('applicantregister')
1468
1469    @property
1470    def form_fields(self):
1471        form_fields = None
1472        if self.context.mode == 'update':
1473            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1474                'lastname','reg_number','email')
1475        else: #if self.context.mode == 'create':
1476            form_fields = grok.AutoFields(IApplicantEdit).select(
1477                'firstname', 'middlename', 'lastname', 'email', 'phone')
1478        return form_fields
1479
1480    @property
1481    def label(self):
1482        return _('Apply for ${a}',
1483            mapping = {'a':self.context.title})
1484
1485    def update(self):
1486        if self.context.expired:
1487            self.flash(_('Outside application period.'), type='warning')
1488            self.redirect(self.url(self.context))
1489            return
1490        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1491        if blocker:
1492            self.flash(_('The portal is in maintenance mode '
1493                        'and registration temporarily disabled.'),
1494                       type='warning')
1495            self.redirect(self.url(self.context))
1496            return
1497        # Handle captcha
1498        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1499        self.captcha_result = self.captcha.verify(self.request)
1500        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1501        return
1502
1503    def _redirect(self, email, password, applicant_id):
1504        # Forward only email to landing page in base package.
1505        self.redirect(self.url(self.context, 'registration_complete',
1506            data = dict(email=email)))
1507        return
1508
1509    @property
1510    def _postfix(self):
1511        """In customized packages we can add a container dependent string if
1512        applicants have been imported into several containers.
1513        """
1514        return ''
1515
1516    @action(_('Send login credentials to email address'), style='primary')
1517    def register(self, **data):
1518        if not self.captcha_result.is_valid:
1519            # Captcha will display error messages automatically.
1520            # No need to flash something.
1521            return
1522        if self.context.mode == 'create':
1523            # Check if there are unused records in this container which
1524            # can be taken
1525            applicant = self.context.first_unused
1526            if applicant is None:
1527                # Add applicant
1528                applicant = createObject(u'waeup.Applicant')
1529                self.context.addApplicant(applicant)
1530            else:
1531                applicants_root = grok.getSite()['applicants']
1532                ob_class = self.__implemented__.__name__.replace(
1533                    'waeup.kofa.','')
1534                applicants_root.logger.info('%s - used: %s' % (
1535                    ob_class, applicant.applicant_id))
1536            self.applyData(applicant, **data)
1537            # applicant.reg_number = applicant.applicant_id
1538            notify(grok.ObjectModifiedEvent(applicant))
1539        elif self.context.mode == 'update':
1540            # Update applicant
1541            reg_number = data.get('reg_number','')
1542            lastname = data.get('lastname','')
1543            cat = getUtility(ICatalog, name='applicants_catalog')
1544            searchstr = reg_number + self._postfix
1545            results = list(
1546                cat.searchResults(reg_number=(searchstr, searchstr)))
1547            if results:
1548                applicant = results[0]
1549                if getattr(applicant,'lastname',None) is None:
1550                    self.flash(_('An error occurred.'), type='danger')
1551                    return
1552                elif applicant.lastname.lower() != lastname.lower():
1553                    # Don't tell the truth here. Anonymous must not
1554                    # know that a record was found and only the lastname
1555                    # verification failed.
1556                    self.flash(
1557                        _('No application record found.'), type='warning')
1558                    return
1559                elif applicant.password is not None and \
1560                    applicant.state != INITIALIZED:
1561                    self.flash(_('Your password has already been set and used. '
1562                                 'Please proceed to the login page.'),
1563                               type='warning')
1564                    return
1565                # Store email address but nothing else.
1566                applicant.email = data['email']
1567                notify(grok.ObjectModifiedEvent(applicant))
1568            else:
1569                # No record found, this is the truth.
1570                self.flash(_('No application record found.'), type='warning')
1571                return
1572        else:
1573            # Does not happen but anyway ...
1574            return
1575        kofa_utils = getUtility(IKofaUtils)
1576        password = kofa_utils.genPassword()
1577        IUserAccount(applicant).setPassword(password)
1578        # Send email with credentials
1579        args = {'login':applicant.applicant_id, 'password':password}
1580        login_url = self.url(grok.getSite()) + '/login?%s' % urlencode(args)
1581        url_info = u'Login: %s' % login_url
1582        msg = _('You have successfully been registered for the')
1583        if kofa_utils.sendCredentials(IUserAccount(applicant),
1584            password, url_info, msg):
1585            email_sent = applicant.email
1586        else:
1587            email_sent = None
1588        self._redirect(email=email_sent, password=password,
1589            applicant_id=applicant.applicant_id)
1590        return
1591
1592class ApplicantRegistrationEmailSent(KofaPage):
1593    """Landing page after successful registration.
1594
1595    """
1596    grok.name('registration_complete')
1597    grok.require('waeup.Public')
1598    grok.template('applicantregemailsent')
1599    label = _('Your registration was successful.')
1600
1601    def update(self, email=None, applicant_id=None, password=None):
1602        self.email = email
1603        self.password = password
1604        self.applicant_id = applicant_id
1605        return
1606
1607class ApplicantCheckStatusPage(KofaPage):
1608    """Captcha'd status checking page for applicants.
1609    """
1610    grok.context(IApplicantsRoot)
1611    grok.name('checkstatus')
1612    grok.require('waeup.Anonymous')
1613    grok.template('applicantcheckstatus')
1614    buttonname = _('Submit')
1615    pnav = 7
1616
1617    def label(self):
1618        if self.result:
1619            return _('Admission status of ${a}',
1620                     mapping = {'a':self.applicant.applicant_id})
1621        return _('Check your admission status')
1622
1623    def update(self, SUBMIT=None):
1624        form = self.request.form
1625        self.result = False
1626        # Handle captcha
1627        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1628        self.captcha_result = self.captcha.verify(self.request)
1629        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1630        if SUBMIT:
1631            if not self.captcha_result.is_valid:
1632                # Captcha will display error messages automatically.
1633                # No need to flash something.
1634                return
1635            unique_id = form.get('unique_id', None)
1636            lastname = form.get('lastname', None)
1637            if not unique_id or not lastname:
1638                self.flash(
1639                    _('Required input missing.'), type='warning')
1640                return
1641            cat = getUtility(ICatalog, name='applicants_catalog')
1642            results = list(
1643                cat.searchResults(applicant_id=(unique_id, unique_id)))
1644            if not results:
1645                results = list(
1646                    cat.searchResults(reg_number=(unique_id, unique_id)))
1647            if results:
1648                applicant = results[0]
1649                if applicant.lastname.lower().strip() != lastname.lower():
1650                    # Don't tell the truth here. Anonymous must not
1651                    # know that a record was found and only the lastname
1652                    # verification failed.
1653                    self.flash(
1654                        _('No application record found.'), type='warning')
1655                    return
1656            else:
1657                self.flash(_('No application record found.'), type='warning')
1658                return
1659            self.applicant = applicant
1660            self.entry_session = "%s/%s" % (
1661                applicant.__parent__.year,
1662                applicant.__parent__.year+1)
1663            course_admitted = getattr(applicant, 'course_admitted', None)
1664            self.course_admitted = False
1665            if course_admitted is not None:
1666                try:
1667                    self.course_admitted = True
1668                    self.longtitle = course_admitted.longtitle
1669                    self.department = course_admitted.__parent__.__parent__.longtitle
1670                    self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1671                except AttributeError:
1672                    self.flash(_('Application record invalid.'), type='warning')
1673                    return
1674            self.result = True
1675            self.admitted = False
1676            self.not_admitted = False
1677            self.submitted = False
1678            self.not_submitted = False
1679            self.created = False
1680            if applicant.state in (ADMITTED, CREATED):
1681                self.admitted = True
1682            if applicant.state in (CREATED):
1683                self.created = True
1684                self.student_id = applicant.student_id
1685                self.password = applicant.application_number
1686            if applicant.state in (NOT_ADMITTED,):
1687                self.not_admitted = True
1688            if applicant.state in (SUBMITTED,):
1689                self.submitted = True
1690            if applicant.state in (INITIALIZED, STARTED, PAID):
1691                self.not_submitted = True
1692        return
1693
1694class CheckTranscriptStatus(KofaPage):
1695    """A display page for checking transcript processing status.
1696    """
1697    grok.context(IApplicantsRoot)
1698    grok.name('checktranscript')
1699    grok.require('waeup.Public')
1700    label = _('Check transcript status')
1701    buttonname = _('Check status now')
1702    pnav = 8
1703    websites = (('DemoPortal', 'https://kofa-demo.waeup.org/'),)
1704    #websites = (('DemoPortal', 'http://localhost:8080/app/'),)
1705    appl_url1 = 'https://kofa-demo.waeup.org/applicants'
1706    appl_url2 = 'https://kofa-demo.waeup.org/applicants'
1707
1708    def update(self, SUBMIT=None):
1709        form = self.request.form
1710        self.button = False
1711        # Handle captcha
1712        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1713        self.captcha_result = self.captcha.verify(self.request)
1714        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1715        if SUBMIT:
1716            self.results = []
1717            if not self.captcha_result.is_valid:
1718                # Captcha will display error messages automatically.
1719                # No need to flash something.
1720                return
1721            unique_id = form.get('unique_id', None)
1722            email = form.get('email', None)
1723            if not unique_id or not email:
1724                self.flash(
1725                    _('Required input missing.'), type='warning')
1726                return
1727            self.button = True
1728            # Call webservice of all websites
1729            for website in self.websites:
1730                server = xmlrpclib.ServerProxy(website[1])
1731                result = server.get_grad_student(unique_id, email)
1732                if not result:
1733                    continue
1734                self.results.append((result, website))
1735        return
1736
1737class ExportJobContainerOverview(KofaPage):
1738    """Page that lists active applicant data export jobs and provides links
1739    to discard or download CSV files.
1740
1741    """
1742    grok.context(VirtualApplicantsExportJobContainer)
1743    grok.require('waeup.manageApplication')
1744    grok.name('index.html')
1745    grok.template('exportjobsindex')
1746    label = _('Data Exports')
1747    pnav = 3
1748
1749    def update(self, CREATE=None, DISCARD=None, job_id=None):
1750        if CREATE:
1751            self.redirect(self.url('@@start_export'))
1752            return
1753        if DISCARD and job_id:
1754            entry = self.context.entry_from_job_id(job_id)
1755            self.context.delete_export_entry(entry)
1756            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1757            self.context.logger.info(
1758                '%s - discarded: job_id=%s' % (ob_class, job_id))
1759            self.flash(_('Discarded export') + ' %s' % job_id)
1760        self.entries = doll_up(self, user=self.request.principal.id)
1761        return
1762
1763class ExportJobContainerJobStart(UtilityView, grok.View):
1764    """View that starts three export jobs, one for applicants, a second
1765    one for applicant payments and a third for referee reports.
1766    """
1767    grok.context(VirtualApplicantsExportJobContainer)
1768    grok.require('waeup.manageApplication')
1769    grok.name('start_export')
1770
1771    def update(self):
1772        utils = queryUtility(IKofaUtils)
1773        if not utils.expensive_actions_allowed():
1774            self.flash(_(
1775                "Currently, exporters cannot be started due to high "
1776                "system load. Please try again later."), type='danger')
1777            self.entries = doll_up(self, user=None)
1778            return
1779
1780        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1781        container_code = self.context.__parent__.code
1782        # Start first exporter
1783        for exporter in ('applicants',
1784                         'applicantpayments',
1785                         'applicantrefereereports'):
1786            job_id = self.context.start_export_job(exporter,
1787                                          self.request.principal.id,
1788                                          container=container_code)
1789            self.context.logger.info(
1790                '%s - exported: %s (%s), job_id=%s'
1791                % (ob_class, exporter, container_code, job_id))
1792            # Commit transaction so that job is stored in the ZODB
1793            transaction.commit()
1794        self.flash(_('Exports started.'))
1795        self.redirect(self.url(self.context))
1796        return
1797
1798    def render(self):
1799        return
1800
1801class ExportJobContainerDownload(ExportCSVView):
1802    """Page that downloads a students export csv file.
1803
1804    """
1805    grok.context(VirtualApplicantsExportJobContainer)
1806    grok.require('waeup.manageApplication')
1807
1808class RefereesRemindPage(UtilityView, grok.View):
1809    """A display view for referee reports.
1810    """
1811    grok.context(IApplicant)
1812    grok.name('remind_referees')
1813    grok.require('waeup.manageApplication')
1814
1815    mandate_days = 31
1816
1817    def remindReferees(self):
1818        site = grok.getSite()
1819        kofa_utils = getUtility(IKofaUtils)
1820        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1821        failed = ''
1822        emails_sent = 0
1823        for referee in self.context.referees:
1824            #if not referee.email_sent:
1825            #    continue
1826            # Check if referee has already created a report
1827            report_exists = False
1828            for report in self.context.refereereports:
1829                if report.email == referee.email:
1830                    report_exists = True
1831            if report_exists:
1832                continue
1833            # If not, create new mandate
1834            mandate = RefereeReportMandate(days=self.mandate_days)
1835            mandate.params['name'] = referee.name
1836            mandate.params['email'] = referee.email
1837            mandate.params[
1838                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1839                    self.context.__parent__.code,
1840                    self.context.application_number)
1841            mandate.params['redirect_path2'] = ''
1842            mandate.params['applicant_id'] = self.context.applicant_id
1843            site['mandates'].addMandate(mandate)
1844            # Send invitation email
1845            args = {'mandate_id':mandate.mandate_id}
1846            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1847            url_info = u'Report link: %s' % mandate_url
1848            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1849            if success:
1850                emails_sent += 1
1851                self.context.writeLogMessage(
1852                    self, 'email sent: %s' % referee.email)
1853                referee.email_sent = True
1854            else:
1855                failed += '%s ' % referee.email
1856        return failed, emails_sent
1857
1858    def update(self):
1859        if self.context.state != 'submitted':
1860            self.flash(
1861                _('Not allowed!'), type='danger')
1862            return self.redirect(self.url(self.context))
1863        failed, emails_sent = self.remindReferees()
1864        msg = _('${a} referee(s) have been reminded by email.',
1865                mapping = {'a':  emails_sent})
1866        self.flash(msg)
1867        return self.redirect(self.url(self.context))
1868
1869    def render(self):
1870        return
1871
1872class RefereeReportDisplayFormPage(KofaDisplayFormPage):
1873    """A display view for referee reports.
1874    """
1875    grok.context(IApplicantRefereeReport)
1876    grok.name('index')
1877    grok.require('waeup.manageApplication')
1878    label = _('Referee Report')
1879    pnav = 3
1880    form_fields = grok.AutoFields(IApplicantRefereeReport)
1881    form_fields[
1882        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1883
1884class RefereeReportManageFormPage(KofaEditFormPage):
1885    """A displaymanage for referee reports.
1886    """
1887    grok.context(IApplicantRefereeReport)
1888    grok.name('manage')
1889    grok.require('waeup.managePortal')
1890    label = _('Manage Referee Report')
1891    pnav = 3
1892    form_fields = grok.AutoFields(IApplicantRefereeReport).omit('creation_date')
1893
1894    @action(_('Save'), style='primary')
1895    def save(self, **data):
1896        changed_fields = self.applyData(self.context, **data)
1897        # Turn list of lists into single list
1898        if changed_fields:
1899            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1900        else:
1901            changed_fields = []
1902        fields_string = ' + '.join(changed_fields)
1903        self.flash(_('Form has been saved.'))
1904        if fields_string:
1905            self.context.__parent__.writeLogMessage(
1906                self, '%s - saved: %s' % (self.context.r_id, fields_string))
1907        return
1908
1909class RemoveRefereeReportPage(UtilityView, grok.View):
1910    """
1911    """
1912    grok.context(IApplicantRefereeReport)
1913    grok.name('remove')
1914    grok.require('waeup.manageApplication')
1915
1916    def update(self):
1917        redirect_url = self.url(self.context.__parent__)
1918        self.context.__parent__.writeLogMessage(
1919            self, 'removed: %s' % self.context.r_id)
1920        del self.context.__parent__[self.context.r_id]
1921        self.flash(_('Referee report removed.'))
1922        self.redirect(redirect_url)
1923        return
1924
1925    def render(self):
1926        return
1927
1928class RefereeReportAddFormPage(KofaAddFormPage):
1929    """Add-form to add an referee report. This form
1930    is protected by a mandate.
1931    """
1932    grok.context(IApplicant)
1933    grok.require('waeup.Public')
1934    grok.name('addrefereereport')
1935    form_fields = grok.AutoFields(
1936        IApplicantRefereeReport).omit('creation_date')
1937    grok.template('refereereportpage')
1938    label = _('Referee Report Form')
1939    pnav = 3
1940    #doclink = DOCLINK + '/refereereports.html'
1941
1942    def update(self):
1943        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1944        if blocker:
1945            self.flash(_('The portal is in maintenance mode. '
1946                        'Referee report forms are temporarily disabled.'),
1947                       type='warning')
1948            self.redirect(self.application_url())
1949            return
1950        # Check mandate
1951        form = self.request.form
1952        self.mandate_id = form.get('mandate_id', None)
1953        self.mandates = grok.getSite()['mandates']
1954        mandate = self.mandates.get(self.mandate_id, None)
1955        if mandate is None and not self.request.form.get('form.actions.submit'):
1956            self.flash(_('No mandate.'), type='warning')
1957            self.redirect(self.application_url())
1958            return
1959        if mandate:
1960            # Check the mandate expiration date after redirect again
1961            if mandate.expires < datetime.utcnow():
1962                self.flash(_('Mandate expired.'),
1963                           type='warning')
1964                self.redirect(self.application_url())
1965                return
1966            args = {'mandate_id':mandate.mandate_id}
1967            # Check if report exists.
1968            # (1) If mandate has been used to create a report,
1969            # redirect to the pdf file.
1970            if mandate.params.get('redirect_path2'):
1971                self.redirect(
1972                    self.application_url() +
1973                    mandate.params.get('redirect_path2') +
1974                    '?%s' % urlencode(args))
1975                return
1976            # (2) Report exists but was created with another mandate.
1977            for report in self.context.refereereports:
1978                if report.email == mandate.params.get('email'):
1979                    self.flash(_('You have already created a '
1980                                 'report with another mandate.'),
1981                               type='warning')
1982                    self.redirect(self.application_url())
1983                    return
1984            # Prefill form with mandate params
1985            self.form_fields.get(
1986                'name').field.default = mandate.params['name']
1987            self.form_fields.get(
1988                'email_pref').field.default = mandate.params['email']
1989            self.passport_url = self.url(
1990                self.context, 'passport_for_report.jpg') + '?%s' % urlencode(args)
1991        super(RefereeReportAddFormPage, self).update()
1992        return
1993
1994    @action(_('Submit'),
1995              warning=_('Are you really sure? '
1996                        'Reports can neither be modified or added '
1997                        'after submission.'),
1998              style='primary')
1999    def addRefereeReport(self, **data):
2000        report = createObject(u'waeup.ApplicantRefereeReport')
2001        timestamp = ("%d" % int(time()*10000))[1:]
2002        report.r_id = "r%s" % timestamp
2003        report.email = self.mandates[self.mandate_id].params['email']
2004        self.applyData(report, **data)
2005        self.context[report.r_id] = report
2006        # self.flash(_('Referee report has been saved. Thank you!'))
2007        self.context.writeLogMessage(self, 'added: %s' % report.r_id)
2008        # Changed on 19/04/20: We do no longer delete the mandate
2009        # but set path to redirect to the pdf file
2010        self.mandates[self.mandate_id].params[
2011            'redirect_path2'] = '/applicants/%s/%s/%s/referee_report.pdf' % (
2012                self.context.__parent__.code,
2013                self.context.application_number,
2014                report.r_id)
2015        notify(grok.ObjectModifiedEvent(self.mandates[self.mandate_id]))
2016        args = {'mandate_id':self.mandate_id}
2017        self.flash(_('Your report has been successfully submitted. '
2018                     'Please use the report link in the email again to download '
2019                     'a pdf slip of your report.'))
2020        #self.redirect(self.url(report, 'referee_report.pdf')
2021        #              + '?%s' % urlencode(args))
2022        self.redirect(self.application_url())
2023        return
2024
2025class ExportPDFReportSlipPage(UtilityView, grok.View):
2026    """Deliver a PDF slip of the context.
2027    """
2028    grok.context(IApplicantRefereeReport)
2029    grok.name('referee_report_slip.pdf')
2030    grok.require('waeup.manageApplication')
2031    form_fields = grok.AutoFields(IApplicantRefereeReport)
2032    form_fields[
2033        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
2034    #prefix = 'form'
2035    note = None
2036
2037    @property
2038    def title(self):
2039        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2040        return translate(_('Referee Report'), 'waeup.kofa',
2041            target_language=portal_language)
2042
2043    @property
2044    def label(self):
2045        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2046        return translate(_('Referee Report Slip'),
2047            'waeup.kofa', target_language=portal_language) \
2048            + ' %s' % self.context.r_id
2049
2050    def render(self):
2051        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
2052            self.request)
2053        students_utils = getUtility(IStudentsUtils)
2054        return students_utils.renderPDF(self,'referee_report_slip.pdf',
2055            self.context.__parent__, applicantview, note=self.note)
2056
2057class ExportPDFReportSlipPage2(ExportPDFReportSlipPage):
2058    """Deliver a PDF slip of the context to referees.
2059    """
2060    grok.name('referee_report.pdf')
2061    grok.require('waeup.Public')
2062
2063    def update(self):
2064        # Check mandate
2065        form = self.request.form
2066        self.mandate_id = form.get('mandate_id', None)
2067        self.mandates = grok.getSite()['mandates']
2068        mandate = self.mandates.get(self.mandate_id, None)
2069        if mandate is None:
2070            self.flash(_('No mandate.'), type='warning')
2071            self.redirect(self.application_url())
2072            return
2073        if mandate:
2074            # Check the mandate expiration date after redirect again
2075            if mandate.expires < datetime.utcnow():
2076                self.flash(_('Mandate expired.'),
2077                           type='warning')
2078                self.redirect(self.application_url())
2079                return
2080            # Check if form has really been submitted
2081            if not mandate.params.get('redirect_path2') \
2082                or mandate.params.get(
2083                    'applicant_id') != self.context.__parent__.applicant_id:
2084                self.flash(_('Wrong mandate.'),
2085                           type='warning')
2086                self.redirect(self.application_url())
2087                return
2088            super(ExportPDFReportSlipPage2, self).update()
2089        return
2090
2091class AdditionalFile(grok.View):
2092    """Renders additional files for applicants.
2093    This is a baseclass.
2094    """
2095    grok.baseclass()
2096    grok.context(IApplicant)
2097    grok.require('waeup.viewApplication')
2098
2099    def render(self):
2100        #image = getUtility(IExtFileStore).getFileByContext(
2101        #    self.context, attr=self.download_name)
2102        file = getUtility(IExtFileStore).getFileByContext(
2103            self.context, attr=self.__name__)
2104        dummy,ext = os.path.splitext(file.name)
2105        if ext == '.jpg':
2106            self.response.setHeader('Content-Type', 'image/jpeg')
2107        elif ext == '.pdf':
2108            self.response.setHeader('Content-Type', 'application/pdf')
2109        return file
2110
2111class TestFile(AdditionalFile):
2112    """Renders testfile.
2113    """
2114    grok.name('testfile')
Note: See TracBrowser for help on using the repository browser.