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

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

Save email address provided by mandate when referee report
is created. Add RefereeReportManageFormPage (no button available).

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