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

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

Add buttons.

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