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

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

No function necessary.

  • Property svn:keywords set to Id
File size: 79.4 KB
Line 
1## $Id: browser.py 16960 2022-06-13 14:36:22Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""UI components for basic applicants and related components.
19"""
20import os
21import sys
22import grok
23import transaction
24from cgi import escape
25from urllib import urlencode
26from datetime import datetime, date
27from time import time, sleep
28import xmlrpclib
29from zope.event import notify
30from zope.component import getUtility, queryUtility, createObject, getAdapter
31from zope.catalog.interfaces import ICatalog
32from zope.i18n import translate
33from zope.security import checkPermission
34from hurry.workflow.interfaces import (
35    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
36from reportlab.platypus.doctemplate import LayoutError
37from waeup.kofa.mandates.mandate import RefereeReportMandate
38from waeup.kofa.applicants.interfaces import (
39    IApplicant, IApplicantEdit, IApplicantsRoot,
40    IApplicantsContainer, IApplicantsContainerAdd,
41    IApplicantOnlinePayment, IApplicantsUtils,
42    IApplicantRegisterUpdate, ISpecialApplicant,
43    IApplicantRefereeReport
44    )
45from waeup.kofa.utils.helpers import (html2dict,
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    @property
1372    def _finalsubmit_msg(self):
1373        return _('Form has been submitted.')
1374
1375    @action(_('Finally Submit'), warning=WARNING)
1376    def finalsubmit(self, **data):
1377        if self.upload_success is False:  # False is not None!
1378            return # error during image upload. Ignore other values
1379        dnt = self.dataNotComplete(data)
1380        if dnt:
1381            self.flash(dnt, type='danger')
1382            return
1383        self.applyData(self.context, **data)
1384        error, dummy = self.saveCourses()
1385        if error:
1386            self.flash(error, type='danger')
1387            return
1388        state = IWorkflowState(self.context).getState()
1389        # This shouldn't happen, but the application officer
1390        # might have forgotten to lock the form after changing the state
1391        if state != self.submit_state:
1392            self.flash(_('The form cannot be submitted. Wrong state!'),
1393                       type='danger')
1394            return
1395        msg = self._finalsubmit_msg
1396        # Create mandates and send emails to referees
1397        if getattr(self.context, 'referees', None):
1398            failed, emails_sent = self.informReferees()
1399            if failed:
1400                self.flash(
1401                    _('Some invitation emails could not be sent:') + failed,
1402                    type='danger')
1403                return
1404            msg = _('Form has been successfully submitted and '
1405                    '${a} invitation emails were sent.',
1406                    mapping = {'a':  emails_sent})
1407        IWorkflowInfo(self.context).fireTransition('submit')
1408        # application_date is used in export files for sorting.
1409        # We can thus store utc.
1410        self.context.application_date = datetime.utcnow()
1411        self.flash(msg)
1412        self.redirect(self.url(self.context))
1413        return
1414
1415class PassportImage(grok.View):
1416    """Renders the passport image for applicants.
1417    """
1418    grok.name('passport.jpg')
1419    grok.context(IApplicant)
1420    grok.require('waeup.viewApplication')
1421
1422    def render(self):
1423        # A filename chooser turns a context into a filename suitable
1424        # for file storage.
1425        image = getUtility(IExtFileStore).getFileByContext(self.context)
1426        self.response.setHeader('Content-Type', 'image/jpeg')
1427        if image is None:
1428            # show placeholder image
1429            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
1430        return image
1431
1432class PassportImageForReport(PassportImage):
1433    """Renders the passport image for applicants for referee reports.
1434    """
1435    grok.name('passport_for_report.jpg')
1436    grok.context(IApplicant)
1437    grok.require('waeup.Public')
1438
1439    def render(self):
1440        # Check mandate
1441        form = self.request.form
1442        self.mandate_id = form.get('mandate_id', None)
1443        self.mandates = grok.getSite()['mandates']
1444        mandate = self.mandates.get(self.mandate_id, None)
1445        if mandate is None:
1446            self.flash(_('No mandate.'), type='warning')
1447            self.redirect(self.application_url())
1448            return
1449        if mandate:
1450            # Check the mandate expiration date after redirect again
1451            if mandate.expires < datetime.utcnow():
1452                self.flash(_('Mandate expired.'),
1453                           type='warning')
1454                self.redirect(self.application_url())
1455                return
1456            # Check if mandate allows access
1457            if mandate.params.get('applicant_id') != self.context.applicant_id:
1458                self.flash(_('Wrong mandate.'),
1459                           type='warning')
1460                self.redirect(self.application_url())
1461                return
1462            return super(PassportImageForReport, self).render()
1463        return
1464
1465class ApplicantRegistrationPage(KofaAddFormPage):
1466    """Captcha'd registration page for applicants.
1467    """
1468    grok.context(IApplicantsContainer)
1469    grok.name('register')
1470    grok.require('waeup.Anonymous')
1471    grok.template('applicantregister')
1472
1473    @property
1474    def form_fields(self):
1475        form_fields = None
1476        if self.context.mode == 'update':
1477            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1478                'lastname','reg_number','email')
1479        else: #if self.context.mode == 'create':
1480            form_fields = grok.AutoFields(IApplicantEdit).select(
1481                'firstname', 'middlename', 'lastname', 'email', 'phone')
1482        return form_fields
1483
1484    @property
1485    def label(self):
1486        return _('Apply for ${a}',
1487            mapping = {'a':self.context.title})
1488
1489    def update(self):
1490        if self.context.expired:
1491            self.flash(_('Outside application period.'), type='warning')
1492            self.redirect(self.url(self.context))
1493            return
1494        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1495        if blocker:
1496            self.flash(_('The portal is in maintenance mode '
1497                        'and registration temporarily disabled.'),
1498                       type='warning')
1499            self.redirect(self.url(self.context))
1500            return
1501        # Handle captcha
1502        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1503        self.captcha_result = self.captcha.verify(self.request)
1504        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1505        return
1506
1507    def _redirect(self, email, password, applicant_id):
1508        # Forward only email to landing page in base package.
1509        self.redirect(self.url(self.context, 'registration_complete',
1510            data = dict(email=email)))
1511        return
1512
1513    @property
1514    def _postfix(self):
1515        """In customized packages we can add a container dependent string if
1516        applicants have been imported into several containers.
1517        """
1518        return ''
1519
1520    @action(_('Send login credentials to email address'), style='primary')
1521    def register(self, **data):
1522        if not self.captcha_result.is_valid:
1523            # Captcha will display error messages automatically.
1524            # No need to flash something.
1525            return
1526        if self.context.mode == 'create':
1527            # Check if there are unused records in this container which
1528            # can be taken
1529            applicant = self.context.first_unused
1530            if applicant is None:
1531                # Add applicant
1532                applicant = createObject(u'waeup.Applicant')
1533                self.context.addApplicant(applicant)
1534            else:
1535                applicants_root = grok.getSite()['applicants']
1536                ob_class = self.__implemented__.__name__.replace(
1537                    'waeup.kofa.','')
1538                applicants_root.logger.info('%s - used: %s' % (
1539                    ob_class, applicant.applicant_id))
1540            self.applyData(applicant, **data)
1541            # applicant.reg_number = applicant.applicant_id
1542            notify(grok.ObjectModifiedEvent(applicant))
1543        elif self.context.mode == 'update':
1544            # Update applicant
1545            reg_number = data.get('reg_number','')
1546            lastname = data.get('lastname','')
1547            cat = getUtility(ICatalog, name='applicants_catalog')
1548            searchstr = reg_number + self._postfix
1549            results = list(
1550                cat.searchResults(reg_number=(searchstr, searchstr)))
1551            if results:
1552                applicant = results[0]
1553                if getattr(applicant,'lastname',None) is None:
1554                    self.flash(_('An error occurred.'), type='danger')
1555                    return
1556                elif applicant.lastname.lower() != lastname.lower():
1557                    # Don't tell the truth here. Anonymous must not
1558                    # know that a record was found and only the lastname
1559                    # verification failed.
1560                    self.flash(
1561                        _('No application record found.'), type='warning')
1562                    return
1563                elif applicant.password is not None and \
1564                    applicant.state != INITIALIZED:
1565                    self.flash(_('Your password has already been set and used. '
1566                                 'Please proceed to the login page.'),
1567                               type='warning')
1568                    return
1569                # Store email address but nothing else.
1570                applicant.email = data['email']
1571                notify(grok.ObjectModifiedEvent(applicant))
1572            else:
1573                # No record found, this is the truth.
1574                self.flash(_('No application record found.'), type='warning')
1575                return
1576        else:
1577            # Does not happen but anyway ...
1578            return
1579        kofa_utils = getUtility(IKofaUtils)
1580        password = kofa_utils.genPassword()
1581        IUserAccount(applicant).setPassword(password)
1582        # Send email with credentials
1583        args = {'login':applicant.applicant_id, 'password':password}
1584        login_url = self.url(grok.getSite()) + '/login?%s' % urlencode(args)
1585        url_info = u'Login: %s' % login_url
1586        msg = _('You have successfully been registered for the')
1587        if kofa_utils.sendCredentials(IUserAccount(applicant),
1588            password, url_info, msg):
1589            email_sent = applicant.email
1590        else:
1591            email_sent = None
1592        self._redirect(email=email_sent, password=password,
1593            applicant_id=applicant.applicant_id)
1594        return
1595
1596class ApplicantRegistrationEmailSent(KofaPage):
1597    """Landing page after successful registration.
1598
1599    """
1600    grok.name('registration_complete')
1601    grok.require('waeup.Public')
1602    grok.template('applicantregemailsent')
1603    label = _('Your registration was successful.')
1604
1605    def update(self, email=None, applicant_id=None, password=None):
1606        self.email = email
1607        self.password = password
1608        self.applicant_id = applicant_id
1609        return
1610
1611class ApplicantCheckStatusPage(KofaPage):
1612    """Captcha'd status checking page for applicants.
1613    """
1614    grok.context(IApplicantsRoot)
1615    grok.name('checkstatus')
1616    grok.require('waeup.Anonymous')
1617    grok.template('applicantcheckstatus')
1618    buttonname = _('Submit')
1619    pnav = 7
1620
1621    def label(self):
1622        if self.result:
1623            return _('Admission status of ${a}',
1624                     mapping = {'a':self.applicant.applicant_id})
1625        return _('Check your admission status')
1626
1627    def update(self, SUBMIT=None):
1628        form = self.request.form
1629        self.result = False
1630        # Handle captcha
1631        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1632        self.captcha_result = self.captcha.verify(self.request)
1633        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1634        if SUBMIT:
1635            if not self.captcha_result.is_valid:
1636                # Captcha will display error messages automatically.
1637                # No need to flash something.
1638                return
1639            unique_id = form.get('unique_id', None)
1640            lastname = form.get('lastname', None)
1641            if not unique_id or not lastname:
1642                self.flash(
1643                    _('Required input missing.'), type='warning')
1644                return
1645            cat = getUtility(ICatalog, name='applicants_catalog')
1646            results = list(
1647                cat.searchResults(applicant_id=(unique_id, unique_id)))
1648            if not results:
1649                results = list(
1650                    cat.searchResults(reg_number=(unique_id, unique_id)))
1651            if results:
1652                applicant = results[0]
1653                if applicant.lastname.lower().strip() != lastname.lower():
1654                    # Don't tell the truth here. Anonymous must not
1655                    # know that a record was found and only the lastname
1656                    # verification failed.
1657                    self.flash(
1658                        _('No application record found.'), type='warning')
1659                    return
1660            else:
1661                self.flash(_('No application record found.'), type='warning')
1662                return
1663            self.applicant = applicant
1664            self.entry_session = "%s/%s" % (
1665                applicant.__parent__.year,
1666                applicant.__parent__.year+1)
1667            course_admitted = getattr(applicant, 'course_admitted', None)
1668            self.course_admitted = False
1669            if course_admitted is not None:
1670                try:
1671                    self.course_admitted = True
1672                    self.longtitle = course_admitted.longtitle
1673                    self.department = course_admitted.__parent__.__parent__.longtitle
1674                    self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1675                except AttributeError:
1676                    self.flash(_('Application record invalid.'), type='warning')
1677                    return
1678            self.result = True
1679            self.admitted = False
1680            self.not_admitted = False
1681            self.submitted = False
1682            self.not_submitted = False
1683            self.created = False
1684            if applicant.state in (ADMITTED, CREATED):
1685                self.admitted = True
1686            if applicant.state in (CREATED):
1687                self.created = True
1688                self.student_id = applicant.student_id
1689                self.password = applicant.application_number
1690            if applicant.state in (NOT_ADMITTED,):
1691                self.not_admitted = True
1692            if applicant.state in (SUBMITTED,):
1693                self.submitted = True
1694            if applicant.state in (INITIALIZED, STARTED, PAID):
1695                self.not_submitted = True
1696        return
1697
1698class CheckTranscriptStatus(KofaPage):
1699    """A display page for checking transcript processing status.
1700    """
1701    grok.context(IApplicantsRoot)
1702    grok.name('checktranscript')
1703    grok.require('waeup.Public')
1704    label = _('Check transcript status')
1705    buttonname = _('Check status now')
1706    pnav = 8
1707    websites = (('DemoPortal', 'https://kofa-demo.waeup.org/'),)
1708    #websites = (('DemoPortal', 'http://localhost:8080/app/'),)
1709    appl_url1 = 'https://kofa-demo.waeup.org/applicants'
1710    appl_url2 = 'https://kofa-demo.waeup.org/applicants'
1711
1712    def update(self, SUBMIT=None):
1713        form = self.request.form
1714        self.button = False
1715        # Handle captcha
1716        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1717        self.captcha_result = self.captcha.verify(self.request)
1718        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1719        if SUBMIT:
1720            self.results = []
1721            if not self.captcha_result.is_valid:
1722                # Captcha will display error messages automatically.
1723                # No need to flash something.
1724                return
1725            unique_id = form.get('unique_id', None)
1726            email = form.get('email', None)
1727            if not unique_id or not email:
1728                self.flash(
1729                    _('Required input missing.'), type='warning')
1730                return
1731            self.button = True
1732            # Call webservice of all websites
1733            for website in self.websites:
1734                server = xmlrpclib.ServerProxy(website[1])
1735                result = server.get_grad_student(unique_id, email)
1736                if not result:
1737                    continue
1738                self.results.append((result, website))
1739        return
1740
1741class ExportJobContainerOverview(KofaPage):
1742    """Page that lists active applicant data export jobs and provides links
1743    to discard or download CSV files.
1744
1745    """
1746    grok.context(VirtualApplicantsExportJobContainer)
1747    grok.require('waeup.manageApplication')
1748    grok.name('index.html')
1749    grok.template('exportjobsindex')
1750    label = _('Data Exports')
1751    pnav = 3
1752
1753    def update(self, CREATE=None, DISCARD=None, job_id=None):
1754        if CREATE:
1755            self.redirect(self.url('@@start_export'))
1756            return
1757        if DISCARD and job_id:
1758            entry = self.context.entry_from_job_id(job_id)
1759            self.context.delete_export_entry(entry)
1760            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1761            self.context.logger.info(
1762                '%s - discarded: job_id=%s' % (ob_class, job_id))
1763            self.flash(_('Discarded export') + ' %s' % job_id)
1764        self.entries = doll_up(self, user=self.request.principal.id)
1765        return
1766
1767class ExportJobContainerJobStart(UtilityView, grok.View):
1768    """View that starts three export jobs, one for applicants, a second
1769    one for applicant payments and a third for referee reports.
1770    """
1771    grok.context(VirtualApplicantsExportJobContainer)
1772    grok.require('waeup.manageApplication')
1773    grok.name('start_export')
1774
1775    def update(self):
1776        utils = queryUtility(IKofaUtils)
1777        if not utils.expensive_actions_allowed():
1778            self.flash(_(
1779                "Currently, exporters cannot be started due to high "
1780                "system load. Please try again later."), type='danger')
1781            self.entries = doll_up(self, user=None)
1782            return
1783
1784        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1785        container_code = self.context.__parent__.code
1786        # Start first exporter
1787        for exporter in ('applicants',
1788                         'applicantpayments',
1789                         'applicantrefereereports'):
1790            job_id = self.context.start_export_job(exporter,
1791                                          self.request.principal.id,
1792                                          container=container_code)
1793            self.context.logger.info(
1794                '%s - exported: %s (%s), job_id=%s'
1795                % (ob_class, exporter, container_code, job_id))
1796            # Commit transaction so that job is stored in the ZODB
1797            transaction.commit()
1798        self.flash(_('Exports started.'))
1799        self.redirect(self.url(self.context))
1800        return
1801
1802    def render(self):
1803        return
1804
1805class ExportJobContainerDownload(ExportCSVView):
1806    """Page that downloads a students export csv file.
1807
1808    """
1809    grok.context(VirtualApplicantsExportJobContainer)
1810    grok.require('waeup.manageApplication')
1811
1812class RefereesRemindPage(UtilityView, grok.View):
1813    """A display view for referee reports.
1814    """
1815    grok.context(IApplicant)
1816    grok.name('remind_referees')
1817    grok.require('waeup.manageApplication')
1818
1819    mandate_days = 31
1820
1821    def remindReferees(self):
1822        site = grok.getSite()
1823        kofa_utils = getUtility(IKofaUtils)
1824        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1825        failed = ''
1826        emails_sent = 0
1827        for referee in self.context.referees:
1828            #if not referee.email_sent:
1829            #    continue
1830            # Check if referee has already created a report
1831            report_exists = False
1832            for report in self.context.refereereports:
1833                if report.email == referee.email:
1834                    report_exists = True
1835            if report_exists:
1836                continue
1837            # If not, create new mandate
1838            mandate = RefereeReportMandate(days=self.mandate_days)
1839            mandate.params['name'] = referee.name
1840            mandate.params['email'] = referee.email
1841            mandate.params[
1842                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1843                    self.context.__parent__.code,
1844                    self.context.application_number)
1845            mandate.params['redirect_path2'] = ''
1846            mandate.params['applicant_id'] = self.context.applicant_id
1847            site['mandates'].addMandate(mandate)
1848            # Send invitation email
1849            args = {'mandate_id':mandate.mandate_id}
1850            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1851            url_info = u'Report link: %s' % mandate_url
1852            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1853            if success:
1854                emails_sent += 1
1855                self.context.writeLogMessage(
1856                    self, 'email sent: %s' % referee.email)
1857                referee.email_sent = True
1858            else:
1859                failed += '%s ' % referee.email
1860        return failed, emails_sent
1861
1862    def update(self):
1863        if self.context.state != 'submitted':
1864            self.flash(
1865                _('Not allowed!'), type='danger')
1866            return self.redirect(self.url(self.context))
1867        failed, emails_sent = self.remindReferees()
1868        msg = _('${a} referee(s) have been reminded by email.',
1869                mapping = {'a':  emails_sent})
1870        self.flash(msg)
1871        return self.redirect(self.url(self.context))
1872
1873    def render(self):
1874        return
1875
1876class RefereeReportDisplayFormPage(KofaDisplayFormPage):
1877    """A display view for referee reports.
1878    """
1879    grok.context(IApplicantRefereeReport)
1880    grok.name('index')
1881    grok.require('waeup.manageApplication')
1882    label = _('Referee Report')
1883    pnav = 3
1884    form_fields = grok.AutoFields(IApplicantRefereeReport)
1885    form_fields[
1886        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1887
1888class RefereeReportManageFormPage(KofaEditFormPage):
1889    """A displaymanage for referee reports.
1890    """
1891    grok.context(IApplicantRefereeReport)
1892    grok.name('manage')
1893    grok.require('waeup.managePortal')
1894    label = _('Manage Referee Report')
1895    pnav = 3
1896    form_fields = grok.AutoFields(IApplicantRefereeReport).omit('creation_date')
1897
1898    @action(_('Save'), style='primary')
1899    def save(self, **data):
1900        changed_fields = self.applyData(self.context, **data)
1901        # Turn list of lists into single list
1902        if changed_fields:
1903            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1904        else:
1905            changed_fields = []
1906        fields_string = ' + '.join(changed_fields)
1907        self.flash(_('Form has been saved.'))
1908        if fields_string:
1909            self.context.__parent__.writeLogMessage(
1910                self, '%s - saved: %s' % (self.context.r_id, fields_string))
1911        return
1912
1913class RemoveRefereeReportPage(UtilityView, grok.View):
1914    """
1915    """
1916    grok.context(IApplicantRefereeReport)
1917    grok.name('remove')
1918    grok.require('waeup.manageApplication')
1919
1920    def update(self):
1921        redirect_url = self.url(self.context.__parent__)
1922        self.context.__parent__.writeLogMessage(
1923            self, 'removed: %s' % self.context.r_id)
1924        del self.context.__parent__[self.context.r_id]
1925        self.flash(_('Referee report removed.'))
1926        self.redirect(redirect_url)
1927        return
1928
1929    def render(self):
1930        return
1931
1932class RefereeReportAddFormPage(KofaAddFormPage):
1933    """Add-form to add an referee report. This form
1934    is protected by a mandate.
1935    """
1936    grok.context(IApplicant)
1937    grok.require('waeup.Public')
1938    grok.name('addrefereereport')
1939    form_fields = grok.AutoFields(
1940        IApplicantRefereeReport).omit('creation_date')
1941    grok.template('refereereportpage')
1942    label = _('Referee Report Form')
1943    pnav = 3
1944    #doclink = DOCLINK + '/refereereports.html'
1945
1946    def update(self):
1947        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1948        if blocker:
1949            self.flash(_('The portal is in maintenance mode. '
1950                        'Referee report forms are temporarily disabled.'),
1951                       type='warning')
1952            self.redirect(self.application_url())
1953            return
1954        # Check mandate
1955        form = self.request.form
1956        self.mandate_id = form.get('mandate_id', None)
1957        self.mandates = grok.getSite()['mandates']
1958        mandate = self.mandates.get(self.mandate_id, None)
1959        if mandate is None and not self.request.form.get('form.actions.submit'):
1960            self.flash(_('No mandate.'), type='warning')
1961            self.redirect(self.application_url())
1962            return
1963        if mandate:
1964            # Check the mandate expiration date after redirect again
1965            if mandate.expires < datetime.utcnow():
1966                self.flash(_('Mandate expired.'),
1967                           type='warning')
1968                self.redirect(self.application_url())
1969                return
1970            args = {'mandate_id':mandate.mandate_id}
1971            # Check if report exists.
1972            # (1) If mandate has been used to create a report,
1973            # redirect to the pdf file.
1974            if mandate.params.get('redirect_path2'):
1975                self.redirect(
1976                    self.application_url() +
1977                    mandate.params.get('redirect_path2') +
1978                    '?%s' % urlencode(args))
1979                return
1980            # (2) Report exists but was created with another mandate.
1981            for report in self.context.refereereports:
1982                if report.email == mandate.params.get('email'):
1983                    self.flash(_('You have already created a '
1984                                 'report with another mandate.'),
1985                               type='warning')
1986                    self.redirect(self.application_url())
1987                    return
1988            # Prefill form with mandate params
1989            self.form_fields.get(
1990                'name').field.default = mandate.params['name']
1991            self.form_fields.get(
1992                'email_pref').field.default = mandate.params['email']
1993            self.passport_url = self.url(
1994                self.context, 'passport_for_report.jpg') + '?%s' % urlencode(args)
1995        super(RefereeReportAddFormPage, self).update()
1996        return
1997
1998    @action(_('Submit'),
1999              warning=_('Are you really sure? '
2000                        'Reports can neither be modified or added '
2001                        'after submission.'),
2002              style='primary')
2003    def addRefereeReport(self, **data):
2004        report = createObject(u'waeup.ApplicantRefereeReport')
2005        timestamp = ("%d" % int(time()*10000))[1:]
2006        report.r_id = "r%s" % timestamp
2007        report.email = self.mandates[self.mandate_id].params['email']
2008        self.applyData(report, **data)
2009        self.context[report.r_id] = report
2010        # self.flash(_('Referee report has been saved. Thank you!'))
2011        self.context.writeLogMessage(self, 'added: %s' % report.r_id)
2012        # Changed on 19/04/20: We do no longer delete the mandate
2013        # but set path to redirect to the pdf file
2014        self.mandates[self.mandate_id].params[
2015            'redirect_path2'] = '/applicants/%s/%s/%s/referee_report.pdf' % (
2016                self.context.__parent__.code,
2017                self.context.application_number,
2018                report.r_id)
2019        notify(grok.ObjectModifiedEvent(self.mandates[self.mandate_id]))
2020        args = {'mandate_id':self.mandate_id}
2021        self.flash(_('Your report has been successfully submitted. '
2022                     'Please use the report link in the email again to download '
2023                     'a pdf slip of your report.'))
2024        #self.redirect(self.url(report, 'referee_report.pdf')
2025        #              + '?%s' % urlencode(args))
2026        self.redirect(self.application_url())
2027        return
2028
2029class ExportPDFReportSlipPage(UtilityView, grok.View):
2030    """Deliver a PDF slip of the context.
2031    """
2032    grok.context(IApplicantRefereeReport)
2033    grok.name('referee_report_slip.pdf')
2034    grok.require('waeup.manageApplication')
2035    form_fields = grok.AutoFields(IApplicantRefereeReport)
2036    form_fields[
2037        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
2038    #prefix = 'form'
2039    note = None
2040
2041    @property
2042    def title(self):
2043        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2044        return translate(_('Referee Report'), 'waeup.kofa',
2045            target_language=portal_language)
2046
2047    @property
2048    def label(self):
2049        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2050        return translate(_('Referee Report Slip'),
2051            'waeup.kofa', target_language=portal_language) \
2052            + ' %s' % self.context.r_id
2053
2054    def render(self):
2055        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
2056            self.request)
2057        students_utils = getUtility(IStudentsUtils)
2058        return students_utils.renderPDF(self,'referee_report_slip.pdf',
2059            self.context.__parent__, applicantview, note=self.note)
2060
2061class ExportPDFReportSlipPage2(ExportPDFReportSlipPage):
2062    """Deliver a PDF slip of the context to referees.
2063    """
2064    grok.name('referee_report.pdf')
2065    grok.require('waeup.Public')
2066
2067    def update(self):
2068        # Check mandate
2069        form = self.request.form
2070        self.mandate_id = form.get('mandate_id', None)
2071        self.mandates = grok.getSite()['mandates']
2072        mandate = self.mandates.get(self.mandate_id, None)
2073        if mandate is None:
2074            self.flash(_('No mandate.'), type='warning')
2075            self.redirect(self.application_url())
2076            return
2077        if mandate:
2078            # Check the mandate expiration date after redirect again
2079            if mandate.expires < datetime.utcnow():
2080                self.flash(_('Mandate expired.'),
2081                           type='warning')
2082                self.redirect(self.application_url())
2083                return
2084            # Check if form has really been submitted
2085            if not mandate.params.get('redirect_path2') \
2086                or mandate.params.get(
2087                    'applicant_id') != self.context.__parent__.applicant_id:
2088                self.flash(_('Wrong mandate.'),
2089                           type='warning')
2090                self.redirect(self.application_url())
2091                return
2092            super(ExportPDFReportSlipPage2, self).update()
2093        return
2094
2095class AdditionalFile(grok.View):
2096    """Renders additional files for applicants.
2097    This is a baseclass.
2098    """
2099    grok.baseclass()
2100    grok.context(IApplicant)
2101    grok.require('waeup.viewApplication')
2102
2103    def render(self):
2104        #image = getUtility(IExtFileStore).getFileByContext(
2105        #    self.context, attr=self.download_name)
2106        file = getUtility(IExtFileStore).getFileByContext(
2107            self.context, attr=self.__name__)
2108        dummy,ext = os.path.splitext(file.name)
2109        if ext == '.jpg':
2110            self.response.setHeader('Content-Type', 'image/jpeg')
2111        elif ext == '.pdf':
2112            self.response.setHeader('Content-Type', 'application/pdf')
2113        return file
2114
2115class TestFile(AdditionalFile):
2116    """Renders testfile.
2117    """
2118    grok.name('testfile')
Note: See TracBrowser for help on using the repository browser.