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

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

Send email after final submission of application form.

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