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

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

Make maximum number of applicants on ApplicantsContainerManageFormPage
customizable.

  • Property svn:keywords set to Id
File size: 78.2 KB
Line 
1## $Id: browser.py 16327 2020-11-21 14:27:22Z 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        self.context[payment.p_id] = payment
822        self.context.writeLogMessage(self, 'added: %s' % payment.p_id)
823        self.flash(_('Payment ticket created.'))
824        self.redirect(self.url(payment))
825        return
826
827    def render(self):
828        return
829
830
831class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
832    """ Page to view an online payment ticket
833    """
834    grok.context(IApplicantOnlinePayment)
835    grok.name('index')
836    grok.require('waeup.viewApplication')
837    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
838    form_fields[
839        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
840    form_fields[
841        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
842    pnav = 3
843
844    @property
845    def label(self):
846        return _('${a}: Online Payment Ticket ${b}', mapping = {
847            'a':self.context.__parent__.display_fullname,
848            'b':self.context.p_id})
849
850class OnlinePaymentApprovePage(UtilityView, grok.View):
851    """ Approval view
852    """
853    grok.context(IApplicantOnlinePayment)
854    grok.name('approve')
855    grok.require('waeup.managePortal')
856
857    def update(self):
858        flashtype, msg, log = self.context.approveApplicantPayment()
859        if log is not None:
860            applicant = self.context.__parent__
861            # Add log message to applicants.log
862            applicant.writeLogMessage(self, log)
863            # Add log message to payments.log
864            self.context.logger.info(
865                '%s,%s,%s,%s,%s,,,,,,' % (
866                applicant.applicant_id,
867                self.context.p_id, self.context.p_category,
868                self.context.amount_auth, self.context.r_code))
869        self.flash(msg, type=flashtype)
870        return
871
872    def render(self):
873        self.redirect(self.url(self.context, '@@index'))
874        return
875
876class ExportPDFPaymentSlipPage(UtilityView, grok.View):
877    """Deliver a PDF slip of the context.
878    """
879    grok.context(IApplicantOnlinePayment)
880    grok.name('payment_slip.pdf')
881    grok.require('waeup.viewApplication')
882    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
883    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
884    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
885    #prefix = 'form'
886    note = None
887
888    @property
889    def title(self):
890        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
891        return translate(_('Payment Data'), 'waeup.kofa',
892            target_language=portal_language)
893
894    @property
895    def label(self):
896        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
897        return translate(_('Online Payment Slip'),
898            'waeup.kofa', target_language=portal_language) \
899            + ' %s' % self.context.p_id
900
901    @property
902    def payment_slip_download_warning(self):
903        if self.context.__parent__.state not in (
904            SUBMITTED, ADMITTED, NOT_ADMITTED, CREATED):
905            return _('Please submit the application form before '
906                     'trying to download payment slips.')
907        return ''
908
909    def render(self):
910        if self.payment_slip_download_warning:
911            self.flash(self.payment_slip_download_warning, type='danger')
912            self.redirect(self.url(self.context))
913            return
914        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
915            self.request)
916        students_utils = getUtility(IStudentsUtils)
917        return students_utils.renderPDF(self,'payment_slip.pdf',
918            self.context.__parent__, applicantview, note=self.note)
919
920class ExportPDFPageApplicationSlip(UtilityView, grok.View):
921    """Deliver a PDF slip of the context.
922    """
923    grok.context(IApplicant)
924    grok.name('application_slip.pdf')
925    grok.require('waeup.viewApplication')
926    #prefix = 'form'
927
928    def update(self):
929        if self.context.state in ('initialized', 'started', 'paid'):
930            self.flash(
931                _('Please pay and submit before trying to download '
932                  'the application slip.'), type='warning')
933            return self.redirect(self.url(self.context))
934        return
935
936    def render(self):
937        try:
938            pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
939                view=self)
940        except IOError:
941            self.flash(
942                _('Your image file is corrupted. '
943                  'Please replace.'), type='danger')
944            return self.redirect(self.url(self.context))
945        except LayoutError, err:
946            self.flash(
947                'PDF file could not be created. Reportlab error message: %s'
948                % escape(err.message),
949                type="danger")
950            return self.redirect(self.url(self.context))
951        self.response.setHeader(
952            'Content-Type', 'application/pdf')
953        return pdfstream
954
955def handle_img_upload(upload, context, view):
956    """Handle upload of applicant image.
957
958    Returns `True` in case of success or `False`.
959
960    Please note that file pointer passed in (`upload`) most probably
961    points to end of file when leaving this function.
962    """
963    max_upload_size = getUtility(IKofaUtils).MAX_PASSPORT_SIZE
964    size = file_size(upload)
965    if size > max_upload_size:
966        view.flash(_('Uploaded image is too big!'), type='danger')
967        return False
968    dummy, ext = os.path.splitext(upload.filename)
969    ext.lower()
970    if ext != '.jpg':
971        view.flash(_('jpg file extension expected.'), type='danger')
972        return False
973    upload.seek(0) # file pointer moved when determining size
974    store = getUtility(IExtFileStore)
975    file_id = IFileStoreNameChooser(context).chooseName()
976    try:
977        store.createFile(file_id, upload)
978    except IOError:
979        view.flash(_('Image file cannot be changed.'), type='danger')
980        return False
981    return True
982
983def handle_file_upload(upload, context, view, attr=None):
984    """Handle upload of applicant files.
985
986    Returns `True` in case of success or `False`.
987
988    Please note that file pointer passed in (`upload`) most probably
989    points to end of file when leaving this function.
990    """
991    size = file_size(upload)
992    max_upload_size = 1024 * getUtility(IStudentsUtils).MAX_KB
993    if size > max_upload_size:
994        view.flash(_('Uploaded file is too big!'))
995        return False
996    dummy, ext = os.path.splitext(upload.filename)
997    ext.lower()
998    if ext != '.pdf':
999        view.flash(_('pdf file extension expected.'))
1000        return False
1001    upload.seek(0) # file pointer moved when determining size
1002    store = getUtility(IExtFileStore)
1003    file_id = IFileStoreNameChooser(context).chooseName(attr=attr)
1004    store.createFile(file_id, upload)
1005    return True
1006
1007class ApplicantManageFormPage(KofaEditFormPage):
1008    """A full edit view for applicant data.
1009    """
1010    grok.context(IApplicant)
1011    grok.name('manage')
1012    grok.require('waeup.manageApplication')
1013    grok.template('applicanteditpage')
1014    manage_applications = True
1015    pnav = 3
1016    display_actions = [[_('Save'), _('Finally Submit')],
1017        [_('Add online payment ticket'),_('Remove selected tickets')]]
1018
1019    @property
1020    def display_payments(self):
1021        if self.context.payments:
1022            return True
1023        if self.context.special:
1024            return True
1025        return getattr(self.context.__parent__, 'application_fee', None)
1026
1027    @property
1028    def display_refereereports(self):
1029        if self.context.refereereports:
1030            return True
1031        return False
1032
1033    def display_fileupload(self, filename):
1034        """This method can be used in custom packages to avoid unneccessary
1035        file uploads.
1036        """
1037        return True
1038
1039    @property
1040    def form_fields(self):
1041        if self.context.special:
1042            form_fields = grok.AutoFields(ISpecialApplicant)
1043            form_fields['applicant_id'].for_display = True
1044        else:
1045            form_fields = grok.AutoFields(IApplicant)
1046            form_fields['student_id'].for_display = True
1047            form_fields['applicant_id'].for_display = True
1048        return form_fields
1049
1050    @property
1051    def target(self):
1052        return getattr(self.context.__parent__, 'prefix', None)
1053
1054    @property
1055    def separators(self):
1056        return getUtility(IApplicantsUtils).SEPARATORS_DICT
1057
1058    @property
1059    def custom_upload_requirements(self):
1060        return ''
1061
1062    def update(self):
1063        super(ApplicantManageFormPage, self).update()
1064        max_upload_size = getUtility(IKofaUtils).MAX_PASSPORT_SIZE
1065        self.wf_info = IWorkflowInfo(self.context)
1066        self.max_upload_size = string_from_bytes(max_upload_size)
1067        self.upload_success = None
1068        upload = self.request.form.get('form.passport', None)
1069        if upload:
1070            if self.custom_upload_requirements:
1071                self.flash(
1072                    self.custom_upload_requirements,
1073                    type='danger')
1074                self.redirect(self.url(self.context))
1075                return
1076            # We got a fresh upload, upload_success is
1077            # either True or False
1078            self.upload_success = handle_img_upload(
1079                upload, self.context, self)
1080            if self.upload_success:
1081                self.context.writeLogMessage(self, 'saved: passport')
1082        file_store = getUtility(IExtFileStore)
1083        self.additional_files = getUtility(IApplicantsUtils).ADDITIONAL_FILES
1084        for filename in self.additional_files:
1085            upload = self.request.form.get(filename[1], None)
1086            if upload:
1087                # We got a fresh file upload
1088                success = handle_file_upload(
1089                    upload, self.context, self, attr=filename[1])
1090                if success:
1091                    self.context.writeLogMessage(
1092                        self, 'saved: %s' % filename[1])
1093                else:
1094                    self.upload_success = False
1095        self.max_file_upload_size = string_from_bytes(
1096            1024*getUtility(IStudentsUtils).MAX_KB)
1097        return
1098
1099    @property
1100    def label(self):
1101        container_title = self.context.__parent__.title
1102        return _('${a} <br /> Application Form ${b}', mapping = {
1103            'a':container_title, 'b':self.context.application_number})
1104
1105    def getTransitions(self):
1106        """Return a list of dicts of allowed transition ids and titles.
1107
1108        Each list entry provides keys ``name`` and ``title`` for
1109        internal name and (human readable) title of a single
1110        transition.
1111        """
1112        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
1113            if not t[0] in ('pay', 'create')]
1114        return [dict(name='', title=_('No transition'))] +[
1115            dict(name=x, title=y) for x, y in allowed_transitions]
1116
1117    def saveCourses(self):
1118        """In custom packages we needed to customize the certificate
1119        select widget. We just save course1 and course2 if these customized
1120        fields appear in the form.
1121        """
1122        return None, []
1123
1124    @action(_('Save'), style='primary')
1125    def save(self, **data):
1126        error, changed_courses = self.saveCourses()
1127        if error:
1128            self.flash(error, type='danger')
1129            return
1130        form = self.request.form
1131        password = form.get('password', None)
1132        password_ctl = form.get('control_password', None)
1133        if password:
1134            validator = getUtility(IPasswordValidator)
1135            errors = validator.validate_password(password, password_ctl)
1136            if errors:
1137                self.flash( ' '.join(errors), type='danger')
1138                return
1139        if self.upload_success is False:  # False is not None!
1140            # Error during image upload. Ignore other values.
1141            return
1142        changed_fields = self.applyData(self.context, **data)
1143        # Turn list of lists into single list
1144        if changed_fields:
1145            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1146        else:
1147            changed_fields = []
1148        changed_fields += changed_courses
1149        if password:
1150            # Now we know that the form has no errors and can set password ...
1151            IUserAccount(self.context).setPassword(password)
1152            changed_fields.append('password')
1153        fields_string = ' + '.join(changed_fields)
1154        trans_id = form.get('transition', None)
1155        if trans_id:
1156            self.wf_info.fireTransition(trans_id)
1157        self.flash(_('Form has been saved.'))
1158        if fields_string:
1159            self.context.writeLogMessage(self, 'saved: %s' % fields_string)
1160        return
1161
1162    def unremovable(self, ticket):
1163        return False
1164
1165    # This method is also used by the ApplicantEditFormPage
1166    def delPaymentTickets(self, **data):
1167        form = self.request.form
1168        if 'val_id' in form:
1169            child_id = form['val_id']
1170        else:
1171            self.flash(_('No payment selected.'), type='warning')
1172            self.redirect(self.url(self.context))
1173            return
1174        if not isinstance(child_id, list):
1175            child_id = [child_id]
1176        deleted = []
1177        for id in child_id:
1178            # Applicants are not allowed to remove used payment tickets
1179            if not self.unremovable(self.context[id]):
1180                try:
1181                    del self.context[id]
1182                    deleted.append(id)
1183                except:
1184                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
1185                      id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
1186        if len(deleted):
1187            self.flash(_('Successfully removed: ${a}',
1188                mapping = {'a':', '.join(deleted)}))
1189            self.context.writeLogMessage(
1190                self, 'removed: % s' % ', '.join(deleted))
1191        return
1192
1193    # We explicitely want the forms to be validated before payment tickets
1194    # can be created. If no validation is requested, use
1195    # 'validator=NullValidator' in the action directive
1196    @action(_('Add online payment ticket'), style='primary')
1197    def addPaymentTicket(self, **data):
1198        self.redirect(self.url(self.context, '@@addafp'))
1199        return
1200
1201    @jsaction(_('Remove selected tickets'))
1202    def removePaymentTickets(self, **data):
1203        self.delPaymentTickets(**data)
1204        self.redirect(self.url(self.context) + '/@@manage')
1205        return
1206
1207    # Not used in base package
1208    def file_exists(self, attr):
1209        file = getUtility(IExtFileStore).getFileByContext(
1210            self.context, attr=attr)
1211        if file:
1212            return True
1213        else:
1214            return False
1215
1216class ApplicantEditFormPage(ApplicantManageFormPage):
1217    """An applicant-centered edit view for applicant data.
1218    """
1219    grok.context(IApplicantEdit)
1220    grok.name('edit')
1221    grok.require('waeup.handleApplication')
1222    grok.template('applicanteditpage')
1223    manage_applications = False
1224    submit_state = PAID
1225    mandate_days = 31
1226
1227    @property
1228    def display_refereereports(self):
1229        return False
1230
1231    @property
1232    def form_fields(self):
1233        if self.context.special:
1234            form_fields = grok.AutoFields(ISpecialApplicant).omit(
1235                'locked', 'suspended')
1236            form_fields['applicant_id'].for_display = True
1237        else:
1238            form_fields = grok.AutoFields(IApplicantEdit).omit(
1239                'locked', 'course_admitted', 'student_id',
1240                'suspended'
1241                )
1242            form_fields['applicant_id'].for_display = True
1243            form_fields['reg_number'].for_display = True
1244        return form_fields
1245
1246    @property
1247    def display_actions(self):
1248        state = IWorkflowState(self.context).getState()
1249        # If the form is unlocked, applicants are allowed to save the form
1250        # and remove unused tickets.
1251        actions = [[_('Save')], [_('Remove selected tickets')]]
1252        # Only in state started they can also add tickets.
1253        if state == STARTED:
1254            actions = [[_('Save')],
1255                [_('Add online payment ticket'),_('Remove selected tickets')]]
1256        # In state paid, they can submit the data and further add tickets
1257        # if the application is special.
1258        elif self.context.special and state == PAID:
1259            actions = [[_('Save'), _('Finally Submit')],
1260                [_('Add online payment ticket'),_('Remove selected tickets')]]
1261        elif state == PAID:
1262            actions = [[_('Save'), _('Finally Submit')],
1263                [_('Remove selected tickets')]]
1264        return actions
1265
1266    def unremovable(self, ticket):
1267        return ticket.r_code
1268
1269    def emit_lock_message(self):
1270        self.flash(_('The requested form is locked (read-only).'),
1271                   type='warning')
1272        self.redirect(self.url(self.context))
1273        return
1274
1275    def update(self):
1276        if self.context.locked or (
1277            self.context.__parent__.expired and
1278            self.context.__parent__.strict_deadline):
1279            self.emit_lock_message()
1280            return
1281        super(ApplicantEditFormPage, self).update()
1282        return
1283
1284    def dataNotComplete(self, data):
1285        if self.context.__parent__.with_picture:
1286            store = getUtility(IExtFileStore)
1287            if not store.getFileByContext(self.context, attr=u'passport.jpg'):
1288                return _('No passport picture uploaded.')
1289            if not self.request.form.get('confirm_passport', False):
1290                return _('Passport picture confirmation box not ticked.')
1291        return False
1292
1293    # We explicitely want the forms to be validated before payment tickets
1294    # can be created. If no validation is requested, use
1295    # 'validator=NullValidator' in the action directive
1296    @action(_('Add online payment ticket'), style='primary')
1297    def addPaymentTicket(self, **data):
1298        self.redirect(self.url(self.context, '@@addafp'))
1299        return
1300
1301    @jsaction(_('Remove selected tickets'))
1302    def removePaymentTickets(self, **data):
1303        self.delPaymentTickets(**data)
1304        self.redirect(self.url(self.context) + '/@@edit')
1305        return
1306
1307    @action(_('Save'), style='primary')
1308    def save(self, **data):
1309        if self.upload_success is False:  # False is not None!
1310            # Error during image upload. Ignore other values.
1311            return
1312        self.applyData(self.context, **data)
1313        error, dummy = self.saveCourses()
1314        if error:
1315            self.flash(error, type='danger')
1316            return
1317        self.flash(_('Form has been saved.'))
1318        return
1319
1320    def informReferees(self):
1321        site = grok.getSite()
1322        kofa_utils = getUtility(IKofaUtils)
1323        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1324        failed = ''
1325        emails_sent = 0
1326        for referee in self.context.referees:
1327            if referee.email_sent:
1328                continue
1329            mandate = RefereeReportMandate(days=self.mandate_days)
1330            mandate.params['name'] = referee.name
1331            mandate.params['email'] = referee.email
1332            mandate.params[
1333                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1334                    self.context.__parent__.code,
1335                    self.context.application_number)
1336            mandate.params['redirect_path2'] = ''
1337            mandate.params['applicant_id'] = self.context.applicant_id
1338            site['mandates'].addMandate(mandate)
1339            # Send invitation email
1340            args = {'mandate_id':mandate.mandate_id}
1341            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1342            url_info = u'Report link: %s' % mandate_url
1343            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1344            if success:
1345                emails_sent += 1
1346                self.context.writeLogMessage(
1347                    self, 'email sent: %s' % referee.email)
1348                referee.email_sent = True
1349            else:
1350                failed += '%s ' % referee.email
1351        return failed, emails_sent
1352
1353    @action(_('Finally Submit'), warning=WARNING)
1354    def finalsubmit(self, **data):
1355        if self.upload_success is False:  # False is not None!
1356            return # error during image upload. Ignore other values
1357        dnt = self.dataNotComplete(data)
1358        if dnt:
1359            self.flash(dnt, type='danger')
1360            return
1361        self.applyData(self.context, **data)
1362        error, dummy = self.saveCourses()
1363        if error:
1364            self.flash(error, type='danger')
1365            return
1366        state = IWorkflowState(self.context).getState()
1367        # This shouldn't happen, but the application officer
1368        # might have forgotten to lock the form after changing the state
1369        if state != self.submit_state:
1370            self.flash(_('The form cannot be submitted. Wrong state!'),
1371                       type='danger')
1372            return
1373        msg = _('Form has been submitted.')
1374        # Create mandates and send emails to referees
1375        if getattr(self.context, 'referees', None):
1376            failed, emails_sent = self.informReferees()
1377            if failed:
1378                self.flash(
1379                    _('Some invitation emails could not be sent:') + failed,
1380                    type='danger')
1381                return
1382            msg = _('Form has been successfully submitted and '
1383                    '${a} invitation emails were sent.',
1384                    mapping = {'a':  emails_sent})
1385        IWorkflowInfo(self.context).fireTransition('submit')
1386        # application_date is used in export files for sorting.
1387        # We can thus store utc.
1388        self.context.application_date = datetime.utcnow()
1389        self.flash(msg)
1390        self.redirect(self.url(self.context))
1391        return
1392
1393class PassportImage(grok.View):
1394    """Renders the passport image for applicants.
1395    """
1396    grok.name('passport.jpg')
1397    grok.context(IApplicant)
1398    grok.require('waeup.viewApplication')
1399
1400    def render(self):
1401        # A filename chooser turns a context into a filename suitable
1402        # for file storage.
1403        image = getUtility(IExtFileStore).getFileByContext(self.context)
1404        self.response.setHeader('Content-Type', 'image/jpeg')
1405        if image is None:
1406            # show placeholder image
1407            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
1408        return image
1409
1410class PassportImageForReport(PassportImage):
1411    """Renders the passport image for applicants for referee reports.
1412    """
1413    grok.name('passport_for_report.jpg')
1414    grok.context(IApplicant)
1415    grok.require('waeup.Public')
1416
1417    def render(self):
1418        # Check mandate
1419        form = self.request.form
1420        self.mandate_id = form.get('mandate_id', None)
1421        self.mandates = grok.getSite()['mandates']
1422        mandate = self.mandates.get(self.mandate_id, None)
1423        if mandate is None:
1424            self.flash(_('No mandate.'), type='warning')
1425            self.redirect(self.application_url())
1426            return
1427        if mandate:
1428            # Check the mandate expiration date after redirect again
1429            if mandate.expires < datetime.utcnow():
1430                self.flash(_('Mandate expired.'),
1431                           type='warning')
1432                self.redirect(self.application_url())
1433                return
1434            # Check if mandate allows access
1435            if mandate.params.get('applicant_id') != self.context.applicant_id:
1436                self.flash(_('Wrong mandate.'),
1437                           type='warning')
1438                self.redirect(self.application_url())
1439                return
1440            return super(PassportImageForReport, self).render()
1441        return
1442
1443class ApplicantRegistrationPage(KofaAddFormPage):
1444    """Captcha'd registration page for applicants.
1445    """
1446    grok.context(IApplicantsContainer)
1447    grok.name('register')
1448    grok.require('waeup.Anonymous')
1449    grok.template('applicantregister')
1450
1451    @property
1452    def form_fields(self):
1453        form_fields = None
1454        if self.context.mode == 'update':
1455            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1456                'lastname','reg_number','email')
1457        else: #if self.context.mode == 'create':
1458            form_fields = grok.AutoFields(IApplicantEdit).select(
1459                'firstname', 'middlename', 'lastname', 'email', 'phone')
1460        return form_fields
1461
1462    @property
1463    def label(self):
1464        return _('Apply for ${a}',
1465            mapping = {'a':self.context.title})
1466
1467    def update(self):
1468        if self.context.expired:
1469            self.flash(_('Outside application period.'), type='warning')
1470            self.redirect(self.url(self.context))
1471            return
1472        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1473        if blocker:
1474            self.flash(_('The portal is in maintenance mode '
1475                        'and registration temporarily disabled.'),
1476                       type='warning')
1477            self.redirect(self.url(self.context))
1478            return
1479        # Handle captcha
1480        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1481        self.captcha_result = self.captcha.verify(self.request)
1482        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1483        return
1484
1485    def _redirect(self, email, password, applicant_id):
1486        # Forward only email to landing page in base package.
1487        self.redirect(self.url(self.context, 'registration_complete',
1488            data = dict(email=email)))
1489        return
1490
1491    @property
1492    def _postfix(self):
1493        """In customized packages we can add a container dependent string if
1494        applicants have been imported into several containers.
1495        """
1496        return ''
1497
1498    @action(_('Send login credentials to email address'), style='primary')
1499    def register(self, **data):
1500        if not self.captcha_result.is_valid:
1501            # Captcha will display error messages automatically.
1502            # No need to flash something.
1503            return
1504        if self.context.mode == 'create':
1505            # Check if there are unused records in this container which
1506            # can be taken
1507            applicant = self.context.first_unused
1508            if applicant is None:
1509                # Add applicant
1510                applicant = createObject(u'waeup.Applicant')
1511                self.context.addApplicant(applicant)
1512            else:
1513                applicants_root = grok.getSite()['applicants']
1514                ob_class = self.__implemented__.__name__.replace(
1515                    'waeup.kofa.','')
1516                applicants_root.logger.info('%s - used: %s' % (
1517                    ob_class, applicant.applicant_id))
1518            self.applyData(applicant, **data)
1519            # applicant.reg_number = applicant.applicant_id
1520            notify(grok.ObjectModifiedEvent(applicant))
1521        elif self.context.mode == 'update':
1522            # Update applicant
1523            reg_number = data.get('reg_number','')
1524            lastname = data.get('lastname','')
1525            cat = getUtility(ICatalog, name='applicants_catalog')
1526            searchstr = reg_number + self._postfix
1527            results = list(
1528                cat.searchResults(reg_number=(searchstr, searchstr)))
1529            if results:
1530                applicant = results[0]
1531                if getattr(applicant,'lastname',None) is None:
1532                    self.flash(_('An error occurred.'), type='danger')
1533                    return
1534                elif applicant.lastname.lower() != lastname.lower():
1535                    # Don't tell the truth here. Anonymous must not
1536                    # know that a record was found and only the lastname
1537                    # verification failed.
1538                    self.flash(
1539                        _('No application record found.'), type='warning')
1540                    return
1541                elif applicant.password is not None and \
1542                    applicant.state != INITIALIZED:
1543                    self.flash(_('Your password has already been set and used. '
1544                                 'Please proceed to the login page.'),
1545                               type='warning')
1546                    return
1547                # Store email address but nothing else.
1548                applicant.email = data['email']
1549                notify(grok.ObjectModifiedEvent(applicant))
1550            else:
1551                # No record found, this is the truth.
1552                self.flash(_('No application record found.'), type='warning')
1553                return
1554        else:
1555            # Does not happen but anyway ...
1556            return
1557        kofa_utils = getUtility(IKofaUtils)
1558        password = kofa_utils.genPassword()
1559        IUserAccount(applicant).setPassword(password)
1560        # Send email with credentials
1561        login_url = self.url(grok.getSite(), 'login')
1562        url_info = u'Login: %s' % login_url
1563        msg = _('You have successfully been registered for the')
1564        if kofa_utils.sendCredentials(IUserAccount(applicant),
1565            password, url_info, msg):
1566            email_sent = applicant.email
1567        else:
1568            email_sent = None
1569        self._redirect(email=email_sent, password=password,
1570            applicant_id=applicant.applicant_id)
1571        return
1572
1573class ApplicantRegistrationEmailSent(KofaPage):
1574    """Landing page after successful registration.
1575
1576    """
1577    grok.name('registration_complete')
1578    grok.require('waeup.Public')
1579    grok.template('applicantregemailsent')
1580    label = _('Your registration was successful.')
1581
1582    def update(self, email=None, applicant_id=None, password=None):
1583        self.email = email
1584        self.password = password
1585        self.applicant_id = applicant_id
1586        return
1587
1588class ApplicantCheckStatusPage(KofaPage):
1589    """Captcha'd status checking page for applicants.
1590    """
1591    grok.context(IApplicantsRoot)
1592    grok.name('checkstatus')
1593    grok.require('waeup.Anonymous')
1594    grok.template('applicantcheckstatus')
1595    buttonname = _('Submit')
1596    pnav = 7
1597
1598    def label(self):
1599        if self.result:
1600            return _('Admission status of ${a}',
1601                     mapping = {'a':self.applicant.applicant_id})
1602        return _('Check your admission status')
1603
1604    def update(self, SUBMIT=None):
1605        form = self.request.form
1606        self.result = False
1607        # Handle captcha
1608        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1609        self.captcha_result = self.captcha.verify(self.request)
1610        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1611        if SUBMIT:
1612            if not self.captcha_result.is_valid:
1613                # Captcha will display error messages automatically.
1614                # No need to flash something.
1615                return
1616            unique_id = form.get('unique_id', None)
1617            lastname = form.get('lastname', None)
1618            if not unique_id or not lastname:
1619                self.flash(
1620                    _('Required input missing.'), type='warning')
1621                return
1622            cat = getUtility(ICatalog, name='applicants_catalog')
1623            results = list(
1624                cat.searchResults(applicant_id=(unique_id, unique_id)))
1625            if not results:
1626                results = list(
1627                    cat.searchResults(reg_number=(unique_id, unique_id)))
1628            if results:
1629                applicant = results[0]
1630                if applicant.lastname.lower().strip() != lastname.lower():
1631                    # Don't tell the truth here. Anonymous must not
1632                    # know that a record was found and only the lastname
1633                    # verification failed.
1634                    self.flash(
1635                        _('No application record found.'), type='warning')
1636                    return
1637            else:
1638                self.flash(_('No application record found.'), type='warning')
1639                return
1640            self.applicant = applicant
1641            self.entry_session = "%s/%s" % (
1642                applicant.__parent__.year,
1643                applicant.__parent__.year+1)
1644            course_admitted = getattr(applicant, 'course_admitted', None)
1645            self.course_admitted = False
1646            if course_admitted is not None:
1647                try:
1648                    self.course_admitted = True
1649                    self.longtitle = course_admitted.longtitle
1650                    self.department = course_admitted.__parent__.__parent__.longtitle
1651                    self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1652                except AttributeError:
1653                    self.flash(_('Application record invalid.'), type='warning')
1654                    return
1655            self.result = True
1656            self.admitted = False
1657            self.not_admitted = False
1658            self.submitted = False
1659            self.not_submitted = False
1660            self.created = False
1661            if applicant.state in (ADMITTED, CREATED):
1662                self.admitted = True
1663            if applicant.state in (CREATED):
1664                self.created = True
1665                self.student_id = applicant.student_id
1666                self.password = applicant.application_number
1667            if applicant.state in (NOT_ADMITTED,):
1668                self.not_admitted = True
1669            if applicant.state in (SUBMITTED,):
1670                self.submitted = True
1671            if applicant.state in (INITIALIZED, STARTED, PAID):
1672                self.not_submitted = True
1673        return
1674
1675class CheckTranscriptStatus(KofaPage):
1676    """A display page for checking transcript processing status.
1677    """
1678    grok.context(IApplicantsRoot)
1679    grok.name('checktranscript')
1680    grok.require('waeup.Public')
1681    label = _('Check transcript status')
1682    buttonname = _('Check status now')
1683    pnav = 8
1684    websites = (('DemoPortal', 'https://kofa-demo.waeup.org/'),)
1685    #websites = (('DemoPortal', 'http://localhost:8080/app/'),)
1686    appl_url1 = 'https://kofa-demo.waeup.org/applicants'
1687    appl_url2 = 'https://kofa-demo.waeup.org/applicants'
1688
1689    def update(self, SUBMIT=None):
1690        form = self.request.form
1691        self.button = False
1692        # Handle captcha
1693        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1694        self.captcha_result = self.captcha.verify(self.request)
1695        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1696        if SUBMIT:
1697            self.results = []
1698            if not self.captcha_result.is_valid:
1699                # Captcha will display error messages automatically.
1700                # No need to flash something.
1701                return
1702            unique_id = form.get('unique_id', None)
1703            email = form.get('email', None)
1704            if not unique_id or not email:
1705                self.flash(
1706                    _('Required input missing.'), type='warning')
1707                return
1708            self.button = True
1709            # Call webservice of all websites
1710            for website in self.websites:
1711                server = xmlrpclib.ServerProxy(website[1])
1712                result = server.get_grad_student(unique_id, email)
1713                if not result:
1714                    continue
1715                self.results.append((result, website))
1716        return
1717
1718class ExportJobContainerOverview(KofaPage):
1719    """Page that lists active applicant data export jobs and provides links
1720    to discard or download CSV files.
1721
1722    """
1723    grok.context(VirtualApplicantsExportJobContainer)
1724    grok.require('waeup.manageApplication')
1725    grok.name('index.html')
1726    grok.template('exportjobsindex')
1727    label = _('Data Exports')
1728    pnav = 3
1729
1730    def update(self, CREATE=None, DISCARD=None, job_id=None):
1731        if CREATE:
1732            self.redirect(self.url('@@start_export'))
1733            return
1734        if DISCARD and job_id:
1735            entry = self.context.entry_from_job_id(job_id)
1736            self.context.delete_export_entry(entry)
1737            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1738            self.context.logger.info(
1739                '%s - discarded: job_id=%s' % (ob_class, job_id))
1740            self.flash(_('Discarded export') + ' %s' % job_id)
1741        self.entries = doll_up(self, user=self.request.principal.id)
1742        return
1743
1744class ExportJobContainerJobStart(UtilityView, grok.View):
1745    """View that starts three export jobs, one for applicants, a second
1746    one for applicant payments and a third for referee reports.
1747    """
1748    grok.context(VirtualApplicantsExportJobContainer)
1749    grok.require('waeup.manageApplication')
1750    grok.name('start_export')
1751
1752    def update(self):
1753        utils = queryUtility(IKofaUtils)
1754        if not utils.expensive_actions_allowed():
1755            self.flash(_(
1756                "Currently, exporters cannot be started due to high "
1757                "system load. Please try again later."), type='danger')
1758            self.entries = doll_up(self, user=None)
1759            return
1760
1761        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1762        container_code = self.context.__parent__.code
1763        # Start first exporter
1764        for exporter in ('applicants',
1765                         'applicantpayments',
1766                         'applicantrefereereports'):
1767            job_id = self.context.start_export_job(exporter,
1768                                          self.request.principal.id,
1769                                          container=container_code)
1770            self.context.logger.info(
1771                '%s - exported: %s (%s), job_id=%s'
1772                % (ob_class, exporter, container_code, job_id))
1773            # Commit transaction so that job is stored in the ZODB
1774            transaction.commit()
1775        self.flash(_('Exports started.'))
1776        self.redirect(self.url(self.context))
1777        return
1778
1779    def render(self):
1780        return
1781
1782class ExportJobContainerDownload(ExportCSVView):
1783    """Page that downloads a students export csv file.
1784
1785    """
1786    grok.context(VirtualApplicantsExportJobContainer)
1787    grok.require('waeup.manageApplication')
1788
1789class RefereesRemindPage(UtilityView, grok.View):
1790    """A display view for referee reports.
1791    """
1792    grok.context(IApplicant)
1793    grok.name('remind_referees')
1794    grok.require('waeup.manageApplication')
1795
1796    mandate_days = 31
1797
1798    def remindReferees(self):
1799        site = grok.getSite()
1800        kofa_utils = getUtility(IKofaUtils)
1801        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1802        failed = ''
1803        emails_sent = 0
1804        for referee in self.context.referees:
1805            #if not referee.email_sent:
1806            #    continue
1807            # Check if referee has already created a report
1808            report_exists = False
1809            for report in self.context.refereereports:
1810                if report.email == referee.email:
1811                    report_exists = True
1812            if report_exists:
1813                continue
1814            # If not, create new mandate
1815            mandate = RefereeReportMandate(days=self.mandate_days)
1816            mandate.params['name'] = referee.name
1817            mandate.params['email'] = referee.email
1818            mandate.params[
1819                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1820                    self.context.__parent__.code,
1821                    self.context.application_number)
1822            mandate.params['redirect_path2'] = ''
1823            mandate.params['applicant_id'] = self.context.applicant_id
1824            site['mandates'].addMandate(mandate)
1825            # Send invitation email
1826            args = {'mandate_id':mandate.mandate_id}
1827            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1828            url_info = u'Report link: %s' % mandate_url
1829            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1830            if success:
1831                emails_sent += 1
1832                self.context.writeLogMessage(
1833                    self, 'email sent: %s' % referee.email)
1834                referee.email_sent = True
1835            else:
1836                failed += '%s ' % referee.email
1837        return failed, emails_sent
1838
1839    def update(self):
1840        if self.context.state != 'submitted':
1841            self.flash(
1842                _('Not allowed!'), type='danger')
1843            return self.redirect(self.url(self.context))
1844        failed, emails_sent = self.remindReferees()
1845        msg = _('${a} referee(s) have been reminded by email.',
1846                mapping = {'a':  emails_sent})
1847        self.flash(msg)
1848        return self.redirect(self.url(self.context))
1849
1850    def render(self):
1851        return
1852
1853class RefereeReportDisplayFormPage(KofaDisplayFormPage):
1854    """A display view for referee reports.
1855    """
1856    grok.context(IApplicantRefereeReport)
1857    grok.name('index')
1858    grok.require('waeup.manageApplication')
1859    label = _('Referee Report')
1860    pnav = 3
1861    form_fields = grok.AutoFields(IApplicantRefereeReport)
1862    form_fields[
1863        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1864
1865class RefereeReportManageFormPage(KofaEditFormPage):
1866    """A displaymanage for referee reports.
1867    """
1868    grok.context(IApplicantRefereeReport)
1869    grok.name('manage')
1870    grok.require('waeup.managePortal')
1871    label = _('Manage Referee Report')
1872    pnav = 3
1873    form_fields = grok.AutoFields(IApplicantRefereeReport).omit('creation_date')
1874
1875    @action(_('Save'), style='primary')
1876    def save(self, **data):
1877        changed_fields = self.applyData(self.context, **data)
1878        # Turn list of lists into single list
1879        if changed_fields:
1880            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1881        else:
1882            changed_fields = []
1883        fields_string = ' + '.join(changed_fields)
1884        self.flash(_('Form has been saved.'))
1885        if fields_string:
1886            self.context.__parent__.writeLogMessage(
1887                self, '%s - saved: %s' % (self.context.r_id, fields_string))
1888        return
1889
1890class RemoveRefereeReportPage(UtilityView, grok.View):
1891    """
1892    """
1893    grok.context(IApplicantRefereeReport)
1894    grok.name('remove')
1895    grok.require('waeup.manageApplication')
1896
1897    def update(self):
1898        redirect_url = self.url(self.context.__parent__)
1899        self.context.__parent__.writeLogMessage(
1900            self, 'removed: %s' % self.context.r_id)
1901        del self.context.__parent__[self.context.r_id]
1902        self.flash(_('Referee report removed.'))
1903        self.redirect(redirect_url)
1904        return
1905
1906    def render(self):
1907        return
1908
1909class RefereeReportAddFormPage(KofaAddFormPage):
1910    """Add-form to add an referee report. This form
1911    is protected by a mandate.
1912    """
1913    grok.context(IApplicant)
1914    grok.require('waeup.Public')
1915    grok.name('addrefereereport')
1916    form_fields = grok.AutoFields(
1917        IApplicantRefereeReport).omit('creation_date')
1918    grok.template('refereereportpage')
1919    label = _('Referee Report Form')
1920    pnav = 3
1921    #doclink = DOCLINK + '/refereereports.html'
1922
1923    def update(self):
1924        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1925        if blocker:
1926            self.flash(_('The portal is in maintenance mode. '
1927                        'Referee report forms are temporarily disabled.'),
1928                       type='warning')
1929            self.redirect(self.application_url())
1930            return
1931        # Check mandate
1932        form = self.request.form
1933        self.mandate_id = form.get('mandate_id', None)
1934        self.mandates = grok.getSite()['mandates']
1935        mandate = self.mandates.get(self.mandate_id, None)
1936        if mandate is None and not self.request.form.get('form.actions.submit'):
1937            self.flash(_('No mandate.'), type='warning')
1938            self.redirect(self.application_url())
1939            return
1940        if mandate:
1941            # Check the mandate expiration date after redirect again
1942            if mandate.expires < datetime.utcnow():
1943                self.flash(_('Mandate expired.'),
1944                           type='warning')
1945                self.redirect(self.application_url())
1946                return
1947            args = {'mandate_id':mandate.mandate_id}
1948            # Check if report exists.
1949            # (1) If mandate has been used to create a report,
1950            # redirect to the pdf file.
1951            if mandate.params.get('redirect_path2'):
1952                self.redirect(
1953                    self.application_url() +
1954                    mandate.params.get('redirect_path2') +
1955                    '?%s' % urlencode(args))
1956                return
1957            # (2) Report exists but was created with another mandate.
1958            for report in self.context.refereereports:
1959                if report.email == mandate.params.get('email'):
1960                    self.flash(_('You have already created a '
1961                                 'report with another mandate.'),
1962                               type='warning')
1963                    self.redirect(self.application_url())
1964                    return
1965            # Prefill form with mandate params
1966            self.form_fields.get(
1967                'name').field.default = mandate.params['name']
1968            self.form_fields.get(
1969                'email_pref').field.default = mandate.params['email']
1970            self.passport_url = self.url(
1971                self.context, 'passport_for_report.jpg') + '?%s' % urlencode(args)
1972        super(RefereeReportAddFormPage, self).update()
1973        return
1974
1975    @action(_('Submit'),
1976              warning=_('Are you really sure? '
1977                        'Reports can neither be modified or added '
1978                        'after submission.'),
1979              style='primary')
1980    def addRefereeReport(self, **data):
1981        report = createObject(u'waeup.ApplicantRefereeReport')
1982        timestamp = ("%d" % int(time()*10000))[1:]
1983        report.r_id = "r%s" % timestamp
1984        report.email = self.mandates[self.mandate_id].params['email']
1985        self.applyData(report, **data)
1986        self.context[report.r_id] = report
1987        # self.flash(_('Referee report has been saved. Thank you!'))
1988        self.context.writeLogMessage(self, 'added: %s' % report.r_id)
1989        # Changed on 19/04/20: We do no longer delete the mandate
1990        # but set path to redirect to the pdf file
1991        self.mandates[self.mandate_id].params[
1992            'redirect_path2'] = '/applicants/%s/%s/%s/referee_report.pdf' % (
1993                self.context.__parent__.code,
1994                self.context.application_number,
1995                report.r_id)
1996        notify(grok.ObjectModifiedEvent(self.mandates[self.mandate_id]))
1997        args = {'mandate_id':self.mandate_id}
1998        self.flash(_('Your report has been successfully submitted. '
1999                     'Please use the report link in the email again to download '
2000                     'a pdf slip of your report.'))
2001        #self.redirect(self.url(report, 'referee_report.pdf')
2002        #              + '?%s' % urlencode(args))
2003        self.redirect(self.application_url())
2004        return
2005
2006class ExportPDFReportSlipPage(UtilityView, grok.View):
2007    """Deliver a PDF slip of the context.
2008    """
2009    grok.context(IApplicantRefereeReport)
2010    grok.name('referee_report_slip.pdf')
2011    grok.require('waeup.manageApplication')
2012    form_fields = grok.AutoFields(IApplicantRefereeReport)
2013    form_fields[
2014        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
2015    #prefix = 'form'
2016    note = None
2017
2018    @property
2019    def title(self):
2020        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2021        return translate(_('Referee Report'), 'waeup.kofa',
2022            target_language=portal_language)
2023
2024    @property
2025    def label(self):
2026        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2027        return translate(_('Referee Report Slip'),
2028            'waeup.kofa', target_language=portal_language) \
2029            + ' %s' % self.context.r_id
2030
2031    def render(self):
2032        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
2033            self.request)
2034        students_utils = getUtility(IStudentsUtils)
2035        return students_utils.renderPDF(self,'referee_report_slip.pdf',
2036            self.context.__parent__, applicantview, note=self.note)
2037
2038class ExportPDFReportSlipPage2(ExportPDFReportSlipPage):
2039    """Deliver a PDF slip of the context to referees.
2040    """
2041    grok.name('referee_report.pdf')
2042    grok.require('waeup.Public')
2043
2044    def update(self):
2045        # Check mandate
2046        form = self.request.form
2047        self.mandate_id = form.get('mandate_id', None)
2048        self.mandates = grok.getSite()['mandates']
2049        mandate = self.mandates.get(self.mandate_id, None)
2050        if mandate is None:
2051            self.flash(_('No mandate.'), type='warning')
2052            self.redirect(self.application_url())
2053            return
2054        if mandate:
2055            # Check the mandate expiration date after redirect again
2056            if mandate.expires < datetime.utcnow():
2057                self.flash(_('Mandate expired.'),
2058                           type='warning')
2059                self.redirect(self.application_url())
2060                return
2061            # Check if form has really been submitted
2062            if not mandate.params.get('redirect_path2') \
2063                or mandate.params.get(
2064                    'applicant_id') != self.context.__parent__.applicant_id:
2065                self.flash(_('Wrong mandate.'),
2066                           type='warning')
2067                self.redirect(self.application_url())
2068                return
2069            super(ExportPDFReportSlipPage2, self).update()
2070        return
2071
2072class AdditionalFile(grok.View):
2073    """Renders additional pdf files for applicants.
2074    This is a baseclass.
2075    """
2076    grok.baseclass()
2077    grok.context(IApplicant)
2078    grok.require('waeup.viewApplication')
2079
2080    def render(self):
2081        pdf = getUtility(IExtFileStore).getFileByContext(
2082            self.context, attr=self.__name__)
2083        self.response.setHeader('Content-Type', 'application/pdf')
2084        return pdf
2085
2086class TestFile(AdditionalFile):
2087    """Renders testfile.pdf.
2088    """
2089    grok.name('testfile.pdf')
Note: See TracBrowser for help on using the repository browser.