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

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

Add payment option (p_option) field to payment tickets and add
select box on online payment add form pages. Disable this feature
in the base package.

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