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

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

Do encoding right.

  • Property svn:keywords set to Id
File size: 78.4 KB
Line 
1## $Id: browser.py 16539 2021-07-09 08:05:19Z 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.'), type='danger')
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        args = {'login':applicant.applicant_id, 'password':password}
1564        login_url = self.url(grok.getSite()) + '/login?%s' % urlencode(args)
1565        url_info = u'Login: %s' % login_url
1566        msg = _('You have successfully been registered for the')
1567        if kofa_utils.sendCredentials(IUserAccount(applicant),
1568            password, url_info, msg):
1569            email_sent = applicant.email
1570        else:
1571            email_sent = None
1572        self._redirect(email=email_sent, password=password,
1573            applicant_id=applicant.applicant_id)
1574        return
1575
1576class ApplicantRegistrationEmailSent(KofaPage):
1577    """Landing page after successful registration.
1578
1579    """
1580    grok.name('registration_complete')
1581    grok.require('waeup.Public')
1582    grok.template('applicantregemailsent')
1583    label = _('Your registration was successful.')
1584
1585    def update(self, email=None, applicant_id=None, password=None):
1586        self.email = email
1587        self.password = password
1588        self.applicant_id = applicant_id
1589        return
1590
1591class ApplicantCheckStatusPage(KofaPage):
1592    """Captcha'd status checking page for applicants.
1593    """
1594    grok.context(IApplicantsRoot)
1595    grok.name('checkstatus')
1596    grok.require('waeup.Anonymous')
1597    grok.template('applicantcheckstatus')
1598    buttonname = _('Submit')
1599    pnav = 7
1600
1601    def label(self):
1602        if self.result:
1603            return _('Admission status of ${a}',
1604                     mapping = {'a':self.applicant.applicant_id})
1605        return _('Check your admission status')
1606
1607    def update(self, SUBMIT=None):
1608        form = self.request.form
1609        self.result = False
1610        # Handle captcha
1611        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1612        self.captcha_result = self.captcha.verify(self.request)
1613        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1614        if SUBMIT:
1615            if not self.captcha_result.is_valid:
1616                # Captcha will display error messages automatically.
1617                # No need to flash something.
1618                return
1619            unique_id = form.get('unique_id', None)
1620            lastname = form.get('lastname', None)
1621            if not unique_id or not lastname:
1622                self.flash(
1623                    _('Required input missing.'), type='warning')
1624                return
1625            cat = getUtility(ICatalog, name='applicants_catalog')
1626            results = list(
1627                cat.searchResults(applicant_id=(unique_id, unique_id)))
1628            if not results:
1629                results = list(
1630                    cat.searchResults(reg_number=(unique_id, unique_id)))
1631            if results:
1632                applicant = results[0]
1633                if applicant.lastname.lower().strip() != lastname.lower():
1634                    # Don't tell the truth here. Anonymous must not
1635                    # know that a record was found and only the lastname
1636                    # verification failed.
1637                    self.flash(
1638                        _('No application record found.'), type='warning')
1639                    return
1640            else:
1641                self.flash(_('No application record found.'), type='warning')
1642                return
1643            self.applicant = applicant
1644            self.entry_session = "%s/%s" % (
1645                applicant.__parent__.year,
1646                applicant.__parent__.year+1)
1647            course_admitted = getattr(applicant, 'course_admitted', None)
1648            self.course_admitted = False
1649            if course_admitted is not None:
1650                try:
1651                    self.course_admitted = True
1652                    self.longtitle = course_admitted.longtitle
1653                    self.department = course_admitted.__parent__.__parent__.longtitle
1654                    self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1655                except AttributeError:
1656                    self.flash(_('Application record invalid.'), type='warning')
1657                    return
1658            self.result = True
1659            self.admitted = False
1660            self.not_admitted = False
1661            self.submitted = False
1662            self.not_submitted = False
1663            self.created = False
1664            if applicant.state in (ADMITTED, CREATED):
1665                self.admitted = True
1666            if applicant.state in (CREATED):
1667                self.created = True
1668                self.student_id = applicant.student_id
1669                self.password = applicant.application_number
1670            if applicant.state in (NOT_ADMITTED,):
1671                self.not_admitted = True
1672            if applicant.state in (SUBMITTED,):
1673                self.submitted = True
1674            if applicant.state in (INITIALIZED, STARTED, PAID):
1675                self.not_submitted = True
1676        return
1677
1678class CheckTranscriptStatus(KofaPage):
1679    """A display page for checking transcript processing status.
1680    """
1681    grok.context(IApplicantsRoot)
1682    grok.name('checktranscript')
1683    grok.require('waeup.Public')
1684    label = _('Check transcript status')
1685    buttonname = _('Check status now')
1686    pnav = 8
1687    websites = (('DemoPortal', 'https://kofa-demo.waeup.org/'),)
1688    #websites = (('DemoPortal', 'http://localhost:8080/app/'),)
1689    appl_url1 = 'https://kofa-demo.waeup.org/applicants'
1690    appl_url2 = 'https://kofa-demo.waeup.org/applicants'
1691
1692    def update(self, SUBMIT=None):
1693        form = self.request.form
1694        self.button = False
1695        # Handle captcha
1696        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1697        self.captcha_result = self.captcha.verify(self.request)
1698        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1699        if SUBMIT:
1700            self.results = []
1701            if not self.captcha_result.is_valid:
1702                # Captcha will display error messages automatically.
1703                # No need to flash something.
1704                return
1705            unique_id = form.get('unique_id', None)
1706            email = form.get('email', None)
1707            if not unique_id or not email:
1708                self.flash(
1709                    _('Required input missing.'), type='warning')
1710                return
1711            self.button = True
1712            # Call webservice of all websites
1713            for website in self.websites:
1714                server = xmlrpclib.ServerProxy(website[1])
1715                result = server.get_grad_student(unique_id, email)
1716                if not result:
1717                    continue
1718                self.results.append((result, website))
1719        return
1720
1721class ExportJobContainerOverview(KofaPage):
1722    """Page that lists active applicant data export jobs and provides links
1723    to discard or download CSV files.
1724
1725    """
1726    grok.context(VirtualApplicantsExportJobContainer)
1727    grok.require('waeup.manageApplication')
1728    grok.name('index.html')
1729    grok.template('exportjobsindex')
1730    label = _('Data Exports')
1731    pnav = 3
1732
1733    def update(self, CREATE=None, DISCARD=None, job_id=None):
1734        if CREATE:
1735            self.redirect(self.url('@@start_export'))
1736            return
1737        if DISCARD and job_id:
1738            entry = self.context.entry_from_job_id(job_id)
1739            self.context.delete_export_entry(entry)
1740            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1741            self.context.logger.info(
1742                '%s - discarded: job_id=%s' % (ob_class, job_id))
1743            self.flash(_('Discarded export') + ' %s' % job_id)
1744        self.entries = doll_up(self, user=self.request.principal.id)
1745        return
1746
1747class ExportJobContainerJobStart(UtilityView, grok.View):
1748    """View that starts three export jobs, one for applicants, a second
1749    one for applicant payments and a third for referee reports.
1750    """
1751    grok.context(VirtualApplicantsExportJobContainer)
1752    grok.require('waeup.manageApplication')
1753    grok.name('start_export')
1754
1755    def update(self):
1756        utils = queryUtility(IKofaUtils)
1757        if not utils.expensive_actions_allowed():
1758            self.flash(_(
1759                "Currently, exporters cannot be started due to high "
1760                "system load. Please try again later."), type='danger')
1761            self.entries = doll_up(self, user=None)
1762            return
1763
1764        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1765        container_code = self.context.__parent__.code
1766        # Start first exporter
1767        for exporter in ('applicants',
1768                         'applicantpayments',
1769                         'applicantrefereereports'):
1770            job_id = self.context.start_export_job(exporter,
1771                                          self.request.principal.id,
1772                                          container=container_code)
1773            self.context.logger.info(
1774                '%s - exported: %s (%s), job_id=%s'
1775                % (ob_class, exporter, container_code, job_id))
1776            # Commit transaction so that job is stored in the ZODB
1777            transaction.commit()
1778        self.flash(_('Exports started.'))
1779        self.redirect(self.url(self.context))
1780        return
1781
1782    def render(self):
1783        return
1784
1785class ExportJobContainerDownload(ExportCSVView):
1786    """Page that downloads a students export csv file.
1787
1788    """
1789    grok.context(VirtualApplicantsExportJobContainer)
1790    grok.require('waeup.manageApplication')
1791
1792class RefereesRemindPage(UtilityView, grok.View):
1793    """A display view for referee reports.
1794    """
1795    grok.context(IApplicant)
1796    grok.name('remind_referees')
1797    grok.require('waeup.manageApplication')
1798
1799    mandate_days = 31
1800
1801    def remindReferees(self):
1802        site = grok.getSite()
1803        kofa_utils = getUtility(IKofaUtils)
1804        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1805        failed = ''
1806        emails_sent = 0
1807        for referee in self.context.referees:
1808            #if not referee.email_sent:
1809            #    continue
1810            # Check if referee has already created a report
1811            report_exists = False
1812            for report in self.context.refereereports:
1813                if report.email == referee.email:
1814                    report_exists = True
1815            if report_exists:
1816                continue
1817            # If not, create new mandate
1818            mandate = RefereeReportMandate(days=self.mandate_days)
1819            mandate.params['name'] = referee.name
1820            mandate.params['email'] = referee.email
1821            mandate.params[
1822                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1823                    self.context.__parent__.code,
1824                    self.context.application_number)
1825            mandate.params['redirect_path2'] = ''
1826            mandate.params['applicant_id'] = self.context.applicant_id
1827            site['mandates'].addMandate(mandate)
1828            # Send invitation email
1829            args = {'mandate_id':mandate.mandate_id}
1830            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1831            url_info = u'Report link: %s' % mandate_url
1832            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1833            if success:
1834                emails_sent += 1
1835                self.context.writeLogMessage(
1836                    self, 'email sent: %s' % referee.email)
1837                referee.email_sent = True
1838            else:
1839                failed += '%s ' % referee.email
1840        return failed, emails_sent
1841
1842    def update(self):
1843        if self.context.state != 'submitted':
1844            self.flash(
1845                _('Not allowed!'), type='danger')
1846            return self.redirect(self.url(self.context))
1847        failed, emails_sent = self.remindReferees()
1848        msg = _('${a} referee(s) have been reminded by email.',
1849                mapping = {'a':  emails_sent})
1850        self.flash(msg)
1851        return self.redirect(self.url(self.context))
1852
1853    def render(self):
1854        return
1855
1856class RefereeReportDisplayFormPage(KofaDisplayFormPage):
1857    """A display view for referee reports.
1858    """
1859    grok.context(IApplicantRefereeReport)
1860    grok.name('index')
1861    grok.require('waeup.manageApplication')
1862    label = _('Referee Report')
1863    pnav = 3
1864    form_fields = grok.AutoFields(IApplicantRefereeReport)
1865    form_fields[
1866        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1867
1868class RefereeReportManageFormPage(KofaEditFormPage):
1869    """A displaymanage for referee reports.
1870    """
1871    grok.context(IApplicantRefereeReport)
1872    grok.name('manage')
1873    grok.require('waeup.managePortal')
1874    label = _('Manage Referee Report')
1875    pnav = 3
1876    form_fields = grok.AutoFields(IApplicantRefereeReport).omit('creation_date')
1877
1878    @action(_('Save'), style='primary')
1879    def save(self, **data):
1880        changed_fields = self.applyData(self.context, **data)
1881        # Turn list of lists into single list
1882        if changed_fields:
1883            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1884        else:
1885            changed_fields = []
1886        fields_string = ' + '.join(changed_fields)
1887        self.flash(_('Form has been saved.'))
1888        if fields_string:
1889            self.context.__parent__.writeLogMessage(
1890                self, '%s - saved: %s' % (self.context.r_id, fields_string))
1891        return
1892
1893class RemoveRefereeReportPage(UtilityView, grok.View):
1894    """
1895    """
1896    grok.context(IApplicantRefereeReport)
1897    grok.name('remove')
1898    grok.require('waeup.manageApplication')
1899
1900    def update(self):
1901        redirect_url = self.url(self.context.__parent__)
1902        self.context.__parent__.writeLogMessage(
1903            self, 'removed: %s' % self.context.r_id)
1904        del self.context.__parent__[self.context.r_id]
1905        self.flash(_('Referee report removed.'))
1906        self.redirect(redirect_url)
1907        return
1908
1909    def render(self):
1910        return
1911
1912class RefereeReportAddFormPage(KofaAddFormPage):
1913    """Add-form to add an referee report. This form
1914    is protected by a mandate.
1915    """
1916    grok.context(IApplicant)
1917    grok.require('waeup.Public')
1918    grok.name('addrefereereport')
1919    form_fields = grok.AutoFields(
1920        IApplicantRefereeReport).omit('creation_date')
1921    grok.template('refereereportpage')
1922    label = _('Referee Report Form')
1923    pnav = 3
1924    #doclink = DOCLINK + '/refereereports.html'
1925
1926    def update(self):
1927        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1928        if blocker:
1929            self.flash(_('The portal is in maintenance mode. '
1930                        'Referee report forms are temporarily disabled.'),
1931                       type='warning')
1932            self.redirect(self.application_url())
1933            return
1934        # Check mandate
1935        form = self.request.form
1936        self.mandate_id = form.get('mandate_id', None)
1937        self.mandates = grok.getSite()['mandates']
1938        mandate = self.mandates.get(self.mandate_id, None)
1939        if mandate is None and not self.request.form.get('form.actions.submit'):
1940            self.flash(_('No mandate.'), type='warning')
1941            self.redirect(self.application_url())
1942            return
1943        if mandate:
1944            # Check the mandate expiration date after redirect again
1945            if mandate.expires < datetime.utcnow():
1946                self.flash(_('Mandate expired.'),
1947                           type='warning')
1948                self.redirect(self.application_url())
1949                return
1950            args = {'mandate_id':mandate.mandate_id}
1951            # Check if report exists.
1952            # (1) If mandate has been used to create a report,
1953            # redirect to the pdf file.
1954            if mandate.params.get('redirect_path2'):
1955                self.redirect(
1956                    self.application_url() +
1957                    mandate.params.get('redirect_path2') +
1958                    '?%s' % urlencode(args))
1959                return
1960            # (2) Report exists but was created with another mandate.
1961            for report in self.context.refereereports:
1962                if report.email == mandate.params.get('email'):
1963                    self.flash(_('You have already created a '
1964                                 'report with another mandate.'),
1965                               type='warning')
1966                    self.redirect(self.application_url())
1967                    return
1968            # Prefill form with mandate params
1969            self.form_fields.get(
1970                'name').field.default = mandate.params['name']
1971            self.form_fields.get(
1972                'email_pref').field.default = mandate.params['email']
1973            self.passport_url = self.url(
1974                self.context, 'passport_for_report.jpg') + '?%s' % urlencode(args)
1975        super(RefereeReportAddFormPage, self).update()
1976        return
1977
1978    @action(_('Submit'),
1979              warning=_('Are you really sure? '
1980                        'Reports can neither be modified or added '
1981                        'after submission.'),
1982              style='primary')
1983    def addRefereeReport(self, **data):
1984        report = createObject(u'waeup.ApplicantRefereeReport')
1985        timestamp = ("%d" % int(time()*10000))[1:]
1986        report.r_id = "r%s" % timestamp
1987        report.email = self.mandates[self.mandate_id].params['email']
1988        self.applyData(report, **data)
1989        self.context[report.r_id] = report
1990        # self.flash(_('Referee report has been saved. Thank you!'))
1991        self.context.writeLogMessage(self, 'added: %s' % report.r_id)
1992        # Changed on 19/04/20: We do no longer delete the mandate
1993        # but set path to redirect to the pdf file
1994        self.mandates[self.mandate_id].params[
1995            'redirect_path2'] = '/applicants/%s/%s/%s/referee_report.pdf' % (
1996                self.context.__parent__.code,
1997                self.context.application_number,
1998                report.r_id)
1999        notify(grok.ObjectModifiedEvent(self.mandates[self.mandate_id]))
2000        args = {'mandate_id':self.mandate_id}
2001        self.flash(_('Your report has been successfully submitted. '
2002                     'Please use the report link in the email again to download '
2003                     'a pdf slip of your report.'))
2004        #self.redirect(self.url(report, 'referee_report.pdf')
2005        #              + '?%s' % urlencode(args))
2006        self.redirect(self.application_url())
2007        return
2008
2009class ExportPDFReportSlipPage(UtilityView, grok.View):
2010    """Deliver a PDF slip of the context.
2011    """
2012    grok.context(IApplicantRefereeReport)
2013    grok.name('referee_report_slip.pdf')
2014    grok.require('waeup.manageApplication')
2015    form_fields = grok.AutoFields(IApplicantRefereeReport)
2016    form_fields[
2017        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
2018    #prefix = 'form'
2019    note = None
2020
2021    @property
2022    def title(self):
2023        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2024        return translate(_('Referee Report'), 'waeup.kofa',
2025            target_language=portal_language)
2026
2027    @property
2028    def label(self):
2029        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2030        return translate(_('Referee Report Slip'),
2031            'waeup.kofa', target_language=portal_language) \
2032            + ' %s' % self.context.r_id
2033
2034    def render(self):
2035        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
2036            self.request)
2037        students_utils = getUtility(IStudentsUtils)
2038        return students_utils.renderPDF(self,'referee_report_slip.pdf',
2039            self.context.__parent__, applicantview, note=self.note)
2040
2041class ExportPDFReportSlipPage2(ExportPDFReportSlipPage):
2042    """Deliver a PDF slip of the context to referees.
2043    """
2044    grok.name('referee_report.pdf')
2045    grok.require('waeup.Public')
2046
2047    def update(self):
2048        # Check mandate
2049        form = self.request.form
2050        self.mandate_id = form.get('mandate_id', None)
2051        self.mandates = grok.getSite()['mandates']
2052        mandate = self.mandates.get(self.mandate_id, None)
2053        if mandate is None:
2054            self.flash(_('No mandate.'), type='warning')
2055            self.redirect(self.application_url())
2056            return
2057        if mandate:
2058            # Check the mandate expiration date after redirect again
2059            if mandate.expires < datetime.utcnow():
2060                self.flash(_('Mandate expired.'),
2061                           type='warning')
2062                self.redirect(self.application_url())
2063                return
2064            # Check if form has really been submitted
2065            if not mandate.params.get('redirect_path2') \
2066                or mandate.params.get(
2067                    'applicant_id') != self.context.__parent__.applicant_id:
2068                self.flash(_('Wrong mandate.'),
2069                           type='warning')
2070                self.redirect(self.application_url())
2071                return
2072            super(ExportPDFReportSlipPage2, self).update()
2073        return
2074
2075class AdditionalFile(grok.View):
2076    """Renders additional pdf files for applicants.
2077    This is a baseclass.
2078    """
2079    grok.baseclass()
2080    grok.context(IApplicant)
2081    grok.require('waeup.viewApplication')
2082
2083    def render(self):
2084        pdf = getUtility(IExtFileStore).getFileByContext(
2085            self.context, attr=self.__name__)
2086        self.response.setHeader('Content-Type', 'application/pdf')
2087        return pdf
2088
2089class TestFile(AdditionalFile):
2090    """Renders testfile.pdf.
2091    """
2092    grok.name('testfile.pdf')
Note: See TracBrowser for help on using the repository browser.