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

Last change on this file since 16959 was 16959, checked in by Henrik Bettermann, 2 years ago

Enable customization of application form final submission message.

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