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

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

Ease customization of exporter list.

  • Property svn:keywords set to Id
File size: 81.9 KB
Line 
1## $Id: browser.py 17200 2022-12-01 15:40:10Z 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   
1070    @property
1071    def display_actions(self):
1072        actions = [[_('Save'), _('Finally Submit')],
1073                   [_('Add online payment ticket'),
1074                    _('Add balance payment ticket'),
1075                    _('Remove selected tickets')]]
1076        applicants_utils = getUtility(IApplicantsUtils)
1077        if self.context.state not in applicants_utils.BALANCE_PAYMENT_STATES:
1078            actions[1].pop(1)
1079        return actions
1080
1081    @property
1082    def display_payments(self):
1083        if self.context.payments:
1084            return True
1085        if self.context.special:
1086            return True
1087        return getattr(self.context.__parent__, 'application_fee', None)
1088
1089    @property
1090    def display_refereereports(self):
1091        if self.context.refereereports:
1092            return True
1093        return False
1094
1095    def display_fileupload(self, filename):
1096        """This method can be used in custom packages to avoid unneccessary
1097        file uploads.
1098        """
1099        return True
1100
1101    @property
1102    def form_fields(self):
1103        if self.context.special:
1104            form_fields = grok.AutoFields(ISpecialApplicant)
1105            form_fields['applicant_id'].for_display = True
1106        else:
1107            form_fields = grok.AutoFields(IApplicant)
1108            form_fields['student_id'].for_display = True
1109            form_fields['applicant_id'].for_display = True
1110        return form_fields
1111
1112    @property
1113    def target(self):
1114        return getattr(self.context.__parent__, 'prefix', None)
1115
1116    @property
1117    def separators(self):
1118        return getUtility(IApplicantsUtils).SEPARATORS_DICT
1119
1120    @property
1121    def custom_upload_requirements(self):
1122        return ''
1123
1124    def update(self):
1125        super(ApplicantManageFormPage, self).update()
1126        max_upload_size = getUtility(IKofaUtils).MAX_PASSPORT_SIZE
1127        self.wf_info = IWorkflowInfo(self.context)
1128        self.max_upload_size = string_from_bytes(max_upload_size)
1129        self.upload_success = None
1130        upload = self.request.form.get('form.passport', None)
1131        if upload:
1132            if self.custom_upload_requirements:
1133                self.flash(
1134                    self.custom_upload_requirements,
1135                    type='danger')
1136                self.redirect(self.url(self.context))
1137                return
1138            # We got a fresh upload, upload_success is
1139            # either True or False
1140            self.upload_success = handle_img_upload(
1141                upload, self.context, self)
1142            if self.upload_success:
1143                self.context.writeLogMessage(self, 'saved: passport')
1144        file_store = getUtility(IExtFileStore)
1145        self.additional_files = getUtility(IApplicantsUtils).ADDITIONAL_FILES
1146        for filename in self.additional_files:
1147            upload = self.request.form.get(filename[1], None)
1148            if upload:
1149                # We got a fresh file upload
1150                success = handle_file_upload(
1151                    upload, self.context, self, attr=filename[1])
1152                if success:
1153                    self.context.writeLogMessage(
1154                        self, 'saved: %s' % filename[1])
1155                else:
1156                    self.upload_success = False
1157        self.max_file_upload_size = string_from_bytes(
1158            1024*getUtility(IStudentsUtils).MAX_KB)
1159        return
1160
1161    @property
1162    def label(self):
1163        container_title = self.context.__parent__.title
1164        return _('${a} <br /> Application Form ${b}', mapping = {
1165            'a':container_title, 'b':self.context.application_number})
1166
1167    def getTransitions(self):
1168        """Return a list of dicts of allowed transition ids and titles.
1169
1170        Each list entry provides keys ``name`` and ``title`` for
1171        internal name and (human readable) title of a single
1172        transition.
1173        """
1174        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
1175            if not t[0] in ('pay', 'create')]
1176        return [dict(name='', title=_('No transition'))] +[
1177            dict(name=x, title=y) for x, y in allowed_transitions]
1178
1179    def saveCourses(self):
1180        """In custom packages we needed to customize the certificate
1181        select widget. We just save course1 and course2 if these customized
1182        fields appear in the form.
1183        """
1184        return None, []
1185
1186    @action(_('Save'), style='primary')
1187    def save(self, **data):
1188        error, changed_courses = self.saveCourses()
1189        if error:
1190            self.flash(error, type='danger')
1191            return
1192        form = self.request.form
1193        password = form.get('password', None)
1194        password_ctl = form.get('control_password', None)
1195        if password:
1196            validator = getUtility(IPasswordValidator)
1197            errors = validator.validate_password(password, password_ctl)
1198            if errors:
1199                self.flash( ' '.join(errors), type='danger')
1200                return
1201        if self.upload_success is False:  # False is not None!
1202            # Error during image upload. Ignore other values.
1203            return
1204        changed_fields = self.applyData(self.context, **data)
1205        # Turn list of lists into single list
1206        if changed_fields:
1207            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1208        else:
1209            changed_fields = []
1210        changed_fields += changed_courses
1211        if password:
1212            # Now we know that the form has no errors and can set password ...
1213            IUserAccount(self.context).setPassword(password)
1214            changed_fields.append('password')
1215        fields_string = ' + '.join(changed_fields)
1216        trans_id = form.get('transition', None)
1217        if trans_id:
1218            self.wf_info.fireTransition(trans_id)
1219        self.flash(_('Form has been saved.'))
1220        if fields_string:
1221            self.context.writeLogMessage(self, 'saved: %s' % fields_string)
1222        return
1223
1224    def unremovable(self, ticket):
1225        return False
1226
1227    @property
1228    def picture_editable(self):
1229        return self.context.__parent__.with_picture
1230
1231    # This method is also used by the ApplicantEditFormPage
1232    def delPaymentTickets(self, **data):
1233        form = self.request.form
1234        if 'val_id' in form:
1235            child_id = form['val_id']
1236        else:
1237            self.flash(_('No payment selected.'), type='warning')
1238            self.redirect(self.url(self.context))
1239            return
1240        if not isinstance(child_id, list):
1241            child_id = [child_id]
1242        deleted = []
1243        for id in child_id:
1244            # Applicants are not allowed to remove used payment tickets
1245            if not self.unremovable(self.context[id]):
1246                try:
1247                    del self.context[id]
1248                    deleted.append(id)
1249                except:
1250                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
1251                      id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
1252        if len(deleted):
1253            self.flash(_('Successfully removed: ${a}',
1254                mapping = {'a':', '.join(deleted)}))
1255            self.context.writeLogMessage(
1256                self, 'removed: % s' % ', '.join(deleted))
1257        return
1258
1259    # We explicitely want the forms to be validated before payment tickets
1260    # can be created. If no validation is requested, use
1261    # 'validator=NullValidator' in the action directive
1262    @action(_('Add online payment ticket'), style='primary')
1263    def addPaymentTicket(self, **data):
1264        self.redirect(self.url(self.context, '@@addafp'))
1265        return
1266
1267    @action(_('Add balance payment ticket'), style='primary')
1268    def addBalancePaymentTicket(self, **data):
1269        self.redirect(self.url(self.context, '@@addbp'))
1270        return
1271
1272    @jsaction(_('Remove selected tickets'))
1273    def removePaymentTickets(self, **data):
1274        self.delPaymentTickets(**data)
1275        self.redirect(self.url(self.context) + '/@@manage')
1276        return
1277
1278    # Not used in base package
1279    def file_exists(self, attr):
1280        file = getUtility(IExtFileStore).getFileByContext(
1281            self.context, attr=attr)
1282        if file:
1283            return True
1284        else:
1285            return False
1286
1287class ApplicantEditFormPage(ApplicantManageFormPage):
1288    """An applicant-centered edit view for applicant data.
1289    """
1290    grok.context(IApplicantEdit)
1291    grok.name('edit')
1292    grok.require('waeup.handleApplication')
1293    grok.template('applicanteditpage')
1294    manage_applications = False
1295    submit_state = PAID
1296    mandate_days = 31
1297
1298    @property
1299    def display_refereereports(self):
1300        return False
1301
1302    @property
1303    def form_fields(self):
1304        if self.context.special:
1305            form_fields = grok.AutoFields(ISpecialApplicant).omit(
1306                'locked', 'suspended')
1307            form_fields['applicant_id'].for_display = True
1308        else:
1309            form_fields = grok.AutoFields(IApplicantEdit).omit(
1310                'locked', 'course_admitted', 'student_id',
1311                'suspended'
1312                )
1313            form_fields['applicant_id'].for_display = True
1314            form_fields['reg_number'].for_display = True
1315        return form_fields
1316
1317    @property
1318    def display_actions(self):
1319        state = IWorkflowState(self.context).getState()
1320        # If the form is unlocked, applicants are allowed to save the form
1321        # and remove unused tickets.
1322        actions = [[_('Save')], [_('Remove selected tickets')]]
1323        # Only in state started they can also add tickets.
1324        if state == STARTED:
1325            actions = [[_('Save')],
1326                [_('Add online payment ticket'),_('Remove selected tickets')]]
1327        # In state paid, they can submit the data and further add tickets
1328        # if the application is special.
1329        elif self.context.special and state == PAID:
1330            actions = [[_('Save'), _('Finally Submit')],
1331                [_('Add online payment ticket'),_('Remove selected tickets')]]
1332        elif state == PAID:
1333            actions = [[_('Save'), _('Finally Submit')],
1334                [_('Remove selected tickets')]]
1335        applicants_utils = getUtility(IApplicantsUtils)
1336        if self.context.state in applicants_utils.BALANCE_PAYMENT_STATES:
1337            actions[1].append(_('Add balance payment ticket'))
1338        return actions
1339
1340    @property
1341    def picture_editable(self):
1342        return self.context.__parent__.picture_editable
1343
1344    def unremovable(self, ticket):
1345        return ticket.r_code
1346
1347    def emit_lock_message(self):
1348        self.flash(_('The requested form is locked (read-only).'),
1349                   type='warning')
1350        self.redirect(self.url(self.context))
1351        return
1352
1353    def update(self):
1354        if self.context.locked or (
1355            self.context.__parent__.expired and
1356            self.context.__parent__.strict_deadline):
1357            self.emit_lock_message()
1358            return
1359        super(ApplicantEditFormPage, self).update()
1360        return
1361
1362    def dataNotComplete(self, data):
1363        if self.context.__parent__.with_picture:
1364            store = getUtility(IExtFileStore)
1365            if not store.getFileByContext(self.context, attr=u'passport.jpg'):
1366                return _('No passport picture uploaded.')
1367            if not self.request.form.get('confirm_passport', False):
1368                return _('Passport picture confirmation box not ticked.')
1369        return False
1370
1371    # We explicitely want the forms to be validated before payment tickets
1372    # can be created. If no validation is requested, use
1373    # 'validator=NullValidator' in the action directive
1374    @action(_('Add online payment ticket'), style='primary')
1375    def addPaymentTicket(self, **data):
1376        self.redirect(self.url(self.context, '@@addafp'))
1377        return
1378
1379    @action(_('Add balance payment ticket'), style='primary')
1380    def addBalancePaymentTicket(self, **data):
1381        self.redirect(self.url(self.context, '@@addbp'))
1382        return
1383
1384    @jsaction(_('Remove selected tickets'))
1385    def removePaymentTickets(self, **data):
1386        self.delPaymentTickets(**data)
1387        self.redirect(self.url(self.context) + '/@@edit')
1388        return
1389
1390    @action(_('Save'), style='primary')
1391    def save(self, **data):
1392        if self.upload_success is False:  # False is not None!
1393            # Error during image upload. Ignore other values.
1394            return
1395        self.applyData(self.context, **data)
1396        error, dummy = self.saveCourses()
1397        if error:
1398            self.flash(error, type='danger')
1399            return
1400        self.flash(_('Form has been saved.'))
1401        return
1402
1403    def informReferees(self):
1404        site = grok.getSite()
1405        kofa_utils = getUtility(IKofaUtils)
1406        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1407        failed = ''
1408        emails_sent = 0
1409        for referee in self.context.referees:
1410            if referee.email_sent:
1411                continue
1412            mandate = RefereeReportMandate(days=self.mandate_days)
1413            mandate.params['name'] = referee.name
1414            mandate.params['email'] = referee.email
1415            mandate.params[
1416                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1417                    self.context.__parent__.code,
1418                    self.context.application_number)
1419            mandate.params['redirect_path2'] = ''
1420            mandate.params['applicant_id'] = self.context.applicant_id
1421            site['mandates'].addMandate(mandate)
1422            # Send invitation email
1423            args = {'mandate_id':mandate.mandate_id}
1424            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1425            url_info = u'Report link: %s' % mandate_url
1426            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1427            if success:
1428                emails_sent += 1
1429                self.context.writeLogMessage(
1430                    self, 'email sent: %s' % referee.email)
1431                referee.email_sent = True
1432            else:
1433                failed += '%s ' % referee.email
1434        return failed, emails_sent
1435
1436    @property
1437    def _finalsubmit_msg(self):
1438        return _('Form has been submitted.')
1439
1440    @action(_('Finally Submit'), warning=WARNING)
1441    def finalsubmit(self, **data):
1442        if self.upload_success is False:  # False is not None!
1443            return # error during image upload. Ignore other values
1444        dnt = self.dataNotComplete(data)
1445        if dnt:
1446            self.flash(dnt, type='danger')
1447            return
1448        self.applyData(self.context, **data)
1449        error, dummy = self.saveCourses()
1450        if error:
1451            self.flash(error, type='danger')
1452            return
1453        state = IWorkflowState(self.context).getState()
1454        # This shouldn't happen, but the application officer
1455        # might have forgotten to lock the form after changing the state
1456        if state != self.submit_state:
1457            self.flash(_('The form cannot be submitted. Wrong state!'),
1458                       type='danger')
1459            return
1460        msg = self._finalsubmit_msg
1461        # Create mandates and send emails to referees
1462        if getattr(self.context, 'referees', None):
1463            failed, emails_sent = self.informReferees()
1464            if failed:
1465                self.flash(
1466                    _('Some invitation emails could not be sent:') + failed,
1467                    type='danger')
1468                return
1469            msg = _('Form has been successfully submitted and '
1470                    '${a} invitation emails were sent.',
1471                    mapping = {'a':  emails_sent})
1472        IWorkflowInfo(self.context).fireTransition('submit')
1473        # Send confirmation email
1474        getUtility(IKofaUtils).informApplicant(self.context)
1475        # application_date is used in export files for sorting.
1476        # We can thus store utc.
1477        self.context.application_date = datetime.utcnow()
1478        self.flash(msg)
1479        self.redirect(self.url(self.context))
1480        return
1481
1482class PassportImage(grok.View):
1483    """Renders the passport image for applicants.
1484    """
1485    grok.name('passport.jpg')
1486    grok.context(IApplicant)
1487    grok.require('waeup.viewApplication')
1488
1489    def render(self):
1490        # A filename chooser turns a context into a filename suitable
1491        # for file storage.
1492        image = getUtility(IExtFileStore).getFileByContext(self.context)
1493        self.response.setHeader('Content-Type', 'image/jpeg')
1494        if image is None:
1495            # show placeholder image
1496            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
1497        return image
1498
1499class PassportImageForReport(PassportImage):
1500    """Renders the passport image for applicants for referee reports.
1501    """
1502    grok.name('passport_for_report.jpg')
1503    grok.context(IApplicant)
1504    grok.require('waeup.Public')
1505
1506    def render(self):
1507        # Check mandate
1508        form = self.request.form
1509        self.mandate_id = form.get('mandate_id', None)
1510        self.mandates = grok.getSite()['mandates']
1511        mandate = self.mandates.get(self.mandate_id, None)
1512        if mandate is None:
1513            self.flash(_('No mandate.'), type='warning')
1514            self.redirect(self.application_url())
1515            return
1516        if mandate:
1517            # Check the mandate expiration date after redirect again
1518            if mandate.expires < datetime.utcnow():
1519                self.flash(_('Mandate expired.'),
1520                           type='warning')
1521                self.redirect(self.application_url())
1522                return
1523            # Check if mandate allows access
1524            if mandate.params.get('applicant_id') != self.context.applicant_id:
1525                self.flash(_('Wrong mandate.'),
1526                           type='warning')
1527                self.redirect(self.application_url())
1528                return
1529            return super(PassportImageForReport, self).render()
1530        return
1531
1532class ApplicantRegistrationPage(KofaAddFormPage):
1533    """Captcha'd registration page for applicants.
1534    """
1535    grok.context(IApplicantsContainer)
1536    grok.name('register')
1537    grok.require('waeup.Anonymous')
1538    grok.template('applicantregister')
1539
1540    @property
1541    def form_fields(self):
1542        form_fields = None
1543        if self.context.mode == 'update':
1544            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
1545                'lastname','reg_number','email')
1546        else: #if self.context.mode == 'create':
1547            form_fields = grok.AutoFields(IApplicantEdit).select(
1548                'firstname', 'middlename', 'lastname', 'email', 'phone')
1549        return form_fields
1550
1551    @property
1552    def label(self):
1553        return _('Apply for ${a}',
1554            mapping = {'a':self.context.title})
1555
1556    def update(self):
1557        if self.context.expired:
1558            self.flash(_('Outside application period.'), type='warning')
1559            self.redirect(self.url(self.context))
1560            return
1561        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1562        if blocker:
1563            self.flash(_('The portal is in maintenance mode '
1564                        'and registration temporarily disabled.'),
1565                       type='warning')
1566            self.redirect(self.url(self.context))
1567            return
1568        # Handle captcha
1569        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1570        self.captcha_result = self.captcha.verify(self.request)
1571        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1572        return
1573
1574    def _redirect(self, email, password, applicant_id):
1575        # Forward only email to landing page in base package.
1576        self.redirect(self.url(self.context, 'registration_complete',
1577            data = dict(email=email)))
1578        return
1579
1580    @property
1581    def _postfix(self):
1582        """In customized packages we can add a container dependent string if
1583        applicants have been imported into several containers.
1584        """
1585        return ''
1586
1587    @action(_('Send login credentials to email address'), style='primary')
1588    def register(self, **data):
1589        if not self.captcha_result.is_valid:
1590            # Captcha will display error messages automatically.
1591            # No need to flash something.
1592            return
1593        if self.context.mode == 'create':
1594            # Check if there are unused records in this container which
1595            # can be taken
1596            applicant = self.context.first_unused
1597            if applicant is None:
1598                # Add applicant
1599                applicant = createObject(u'waeup.Applicant')
1600                self.context.addApplicant(applicant)
1601            else:
1602                applicants_root = grok.getSite()['applicants']
1603                ob_class = self.__implemented__.__name__.replace(
1604                    'waeup.kofa.','')
1605                applicants_root.logger.info('%s - used: %s' % (
1606                    ob_class, applicant.applicant_id))
1607            self.applyData(applicant, **data)
1608            # applicant.reg_number = applicant.applicant_id
1609            notify(grok.ObjectModifiedEvent(applicant))
1610        elif self.context.mode == 'update':
1611            # Update applicant
1612            reg_number = data.get('reg_number','')
1613            lastname = data.get('lastname','')
1614            cat = getUtility(ICatalog, name='applicants_catalog')
1615            searchstr = reg_number + self._postfix
1616            results = list(
1617                cat.searchResults(reg_number=(searchstr, searchstr)))
1618            if results:
1619                applicant = results[0]
1620                if getattr(applicant,'lastname',None) is None:
1621                    self.flash(_('An error occurred.'), type='danger')
1622                    return
1623                elif applicant.lastname.lower() != lastname.lower():
1624                    # Don't tell the truth here. Anonymous must not
1625                    # know that a record was found and only the lastname
1626                    # verification failed.
1627                    self.flash(
1628                        _('No application record found.'), type='warning')
1629                    return
1630                elif applicant.password is not None and \
1631                    applicant.state != INITIALIZED:
1632                    self.flash(_('Your password has already been set and used. '
1633                                 'Please proceed to the login page.'),
1634                               type='warning')
1635                    return
1636                # Store email address but nothing else.
1637                applicant.email = data['email']
1638                notify(grok.ObjectModifiedEvent(applicant))
1639            else:
1640                # No record found, this is the truth.
1641                self.flash(_('No application record found.'), type='warning')
1642                return
1643        else:
1644            # Does not happen but anyway ...
1645            return
1646        kofa_utils = getUtility(IKofaUtils)
1647        password = kofa_utils.genPassword()
1648        IUserAccount(applicant).setPassword(password)
1649        # Send email with credentials
1650        args = {'login':applicant.applicant_id, 'password':password}
1651        login_url = self.url(grok.getSite()) + '/login?%s' % urlencode(args)
1652        url_info = u'Login: %s' % login_url
1653        msg = _('You have successfully been registered for the')
1654        if kofa_utils.sendCredentials(IUserAccount(applicant),
1655            password, url_info, msg):
1656            email_sent = applicant.email
1657        else:
1658            email_sent = None
1659        self._redirect(email=email_sent, password=password,
1660            applicant_id=applicant.applicant_id)
1661        return
1662
1663class ApplicantRegistrationEmailSent(KofaPage):
1664    """Landing page after successful registration.
1665
1666    """
1667    grok.name('registration_complete')
1668    grok.require('waeup.Public')
1669    grok.template('applicantregemailsent')
1670    label = _('Your registration was successful.')
1671
1672    def update(self, email=None, applicant_id=None, password=None):
1673        self.email = email
1674        self.password = password
1675        self.applicant_id = applicant_id
1676        return
1677
1678class ApplicantCheckStatusPage(KofaPage):
1679    """Captcha'd status checking page for applicants.
1680    """
1681    grok.context(IApplicantsRoot)
1682    grok.name('checkstatus')
1683    grok.require('waeup.Anonymous')
1684    grok.template('applicantcheckstatus')
1685    buttonname = _('Submit')
1686    pnav = 7
1687
1688    def label(self):
1689        if self.result:
1690            return _('Admission status of ${a}',
1691                     mapping = {'a':self.applicant.applicant_id})
1692        return _('Check your admission status')
1693
1694    def update(self, SUBMIT=None):
1695        form = self.request.form
1696        self.result = False
1697        # Handle captcha
1698        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1699        self.captcha_result = self.captcha.verify(self.request)
1700        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1701        if SUBMIT:
1702            if not self.captcha_result.is_valid:
1703                # Captcha will display error messages automatically.
1704                # No need to flash something.
1705                return
1706            unique_id = form.get('unique_id', None)
1707            lastname = form.get('lastname', None)
1708            if not unique_id or not lastname:
1709                self.flash(
1710                    _('Required input missing.'), type='warning')
1711                return
1712            cat = getUtility(ICatalog, name='applicants_catalog')
1713            results = list(
1714                cat.searchResults(applicant_id=(unique_id, unique_id)))
1715            if not results:
1716                results = list(
1717                    cat.searchResults(reg_number=(unique_id, unique_id)))
1718            if results:
1719                applicant = results[0]
1720                if applicant.lastname.lower().strip() != lastname.lower():
1721                    # Don't tell the truth here. Anonymous must not
1722                    # know that a record was found and only the lastname
1723                    # verification failed.
1724                    self.flash(
1725                        _('No application record found.'), type='warning')
1726                    return
1727            else:
1728                self.flash(_('No application record found.'), type='warning')
1729                return
1730            self.applicant = applicant
1731            self.entry_session = "%s/%s" % (
1732                applicant.__parent__.year,
1733                applicant.__parent__.year+1)
1734            course_admitted = getattr(applicant, 'course_admitted', None)
1735            self.course_admitted = False
1736            if course_admitted is not None:
1737                try:
1738                    self.course_admitted = True
1739                    self.longtitle = course_admitted.longtitle
1740                    self.department = course_admitted.__parent__.__parent__.longtitle
1741                    self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1742                except AttributeError:
1743                    self.flash(_('Application record invalid.'), type='warning')
1744                    return
1745            self.result = True
1746            self.admitted = False
1747            self.not_admitted = False
1748            self.submitted = False
1749            self.not_submitted = False
1750            self.created = False
1751            if applicant.state in (ADMITTED, CREATED):
1752                self.admitted = True
1753            if applicant.state in (CREATED):
1754                self.created = True
1755                self.student_id = applicant.student_id
1756                self.password = applicant.application_number
1757            if applicant.state in (NOT_ADMITTED,):
1758                self.not_admitted = True
1759            if applicant.state in (SUBMITTED,):
1760                self.submitted = True
1761            if applicant.state in (INITIALIZED, STARTED, PAID):
1762                self.not_submitted = True
1763        return
1764
1765class CheckTranscriptStatus(KofaPage):
1766    """A display page for checking transcript processing status.
1767    """
1768    grok.context(IApplicantsRoot)
1769    grok.name('checktranscript')
1770    grok.require('waeup.Public')
1771    label = _('Check transcript status')
1772    buttonname = _('Check status now')
1773    pnav = 8
1774    websites = (('DemoPortal', 'https://kofa-demo.waeup.org/'),)
1775    #websites = (('DemoPortal', 'http://localhost:8080/app/'),)
1776    appl_url1 = 'https://kofa-demo.waeup.org/applicants'
1777    appl_url2 = 'https://kofa-demo.waeup.org/applicants'
1778
1779    def update(self, SUBMIT=None):
1780        form = self.request.form
1781        self.button = False
1782        # Handle captcha
1783        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1784        self.captcha_result = self.captcha.verify(self.request)
1785        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1786        if SUBMIT:
1787            self.results = []
1788            if not self.captcha_result.is_valid:
1789                # Captcha will display error messages automatically.
1790                # No need to flash something.
1791                return
1792            unique_id = form.get('unique_id', None)
1793            email = form.get('email', None)
1794            if not unique_id or not email:
1795                self.flash(
1796                    _('Required input missing.'), type='warning')
1797                return
1798            self.button = True
1799            # Call webservice of all websites
1800            for website in self.websites:
1801                server = xmlrpclib.ServerProxy(website[1])
1802                result = server.get_grad_student(unique_id, email)
1803                if not result:
1804                    continue
1805                self.results.append((result, website))
1806        return
1807
1808class ExportJobContainerOverview(KofaPage):
1809    """Page that lists active applicant data export jobs and provides links
1810    to discard or download CSV files.
1811
1812    """
1813    grok.context(VirtualApplicantsExportJobContainer)
1814    grok.require('waeup.manageApplication')
1815    grok.name('index.html')
1816    grok.template('exportjobsindex')
1817    label = _('Data Exports')
1818    pnav = 3
1819
1820    def update(self, CREATE=None, DISCARD=None, job_id=None):
1821        if CREATE:
1822            self.redirect(self.url('@@start_export'))
1823            return
1824        if DISCARD and job_id:
1825            entry = self.context.entry_from_job_id(job_id)
1826            self.context.delete_export_entry(entry)
1827            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1828            self.context.logger.info(
1829                '%s - discarded: job_id=%s' % (ob_class, job_id))
1830            self.flash(_('Discarded export') + ' %s' % job_id)
1831        self.entries = doll_up(self, user=self.request.principal.id)
1832        return
1833
1834class ExportJobContainerJobStart(UtilityView, grok.View):
1835    """View that starts three export jobs, one for applicants, a second
1836    one for applicant payments and a third for referee reports.
1837    """
1838    grok.context(VirtualApplicantsExportJobContainer)
1839    grok.require('waeup.manageApplication')
1840    grok.name('start_export')
1841
1842    EXPORTER_LIST = ('applicants',
1843                   'applicantpayments',
1844                   'applicantrefereereports')
1845
1846    def update(self):
1847        utils = queryUtility(IKofaUtils)
1848        if not utils.expensive_actions_allowed():
1849            self.flash(_(
1850                "Currently, exporters cannot be started due to high "
1851                "system load. Please try again later."), type='danger')
1852            self.entries = doll_up(self, user=None)
1853            return
1854
1855        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1856        container_code = self.context.__parent__.code
1857        # Start first exporter
1858        for exporter in self.EXPORTER_LIST:
1859            job_id = self.context.start_export_job(exporter,
1860                                          self.request.principal.id,
1861                                          container=container_code)
1862            self.context.logger.info(
1863                '%s - exported: %s (%s), job_id=%s'
1864                % (ob_class, exporter, container_code, job_id))
1865            # Commit transaction so that job is stored in the ZODB
1866            transaction.commit()
1867        self.flash(_('Exports started.'))
1868        self.redirect(self.url(self.context))
1869        return
1870
1871    def render(self):
1872        return
1873
1874class ExportJobContainerDownload(ExportCSVView):
1875    """Page that downloads a students export csv file.
1876
1877    """
1878    grok.context(VirtualApplicantsExportJobContainer)
1879    grok.require('waeup.manageApplication')
1880
1881class RefereesRemindPage(UtilityView, grok.View):
1882    """A display view for referee reports.
1883    """
1884    grok.context(IApplicant)
1885    grok.name('remind_referees')
1886    grok.require('waeup.manageApplication')
1887
1888    mandate_days = 31
1889
1890    def remindReferees(self):
1891        site = grok.getSite()
1892        kofa_utils = getUtility(IKofaUtils)
1893        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1894        failed = ''
1895        emails_sent = 0
1896        for referee in self.context.referees:
1897            #if not referee.email_sent:
1898            #    continue
1899            # Check if referee has already created a report
1900            report_exists = False
1901            for report in self.context.refereereports:
1902                if report.email == referee.email:
1903                    report_exists = True
1904            if report_exists:
1905                continue
1906            # If not, create new mandate
1907            mandate = RefereeReportMandate(days=self.mandate_days)
1908            mandate.params['name'] = referee.name
1909            mandate.params['email'] = referee.email
1910            mandate.params[
1911                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1912                    self.context.__parent__.code,
1913                    self.context.application_number)
1914            mandate.params['redirect_path2'] = ''
1915            mandate.params['applicant_id'] = self.context.applicant_id
1916            site['mandates'].addMandate(mandate)
1917            # Send invitation email
1918            args = {'mandate_id':mandate.mandate_id}
1919            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
1920            url_info = u'Report link: %s' % mandate_url
1921            success = kofa_utils.inviteReferee(referee, self.context, url_info)
1922            if success:
1923                emails_sent += 1
1924                self.context.writeLogMessage(
1925                    self, 'email sent: %s' % referee.email)
1926                referee.email_sent = True
1927            else:
1928                failed += '%s ' % referee.email
1929        return failed, emails_sent
1930
1931    def update(self):
1932        if self.context.state != 'submitted':
1933            self.flash(
1934                _('Not allowed!'), type='danger')
1935            return self.redirect(self.url(self.context))
1936        failed, emails_sent = self.remindReferees()
1937        msg = _('${a} referee(s) have been reminded by email.',
1938                mapping = {'a':  emails_sent})
1939        self.flash(msg)
1940        return self.redirect(self.url(self.context))
1941
1942    def render(self):
1943        return
1944
1945class RefereeReportDisplayFormPage(KofaDisplayFormPage):
1946    """A display view for referee reports.
1947    """
1948    grok.context(IApplicantRefereeReport)
1949    grok.name('index')
1950    grok.require('waeup.manageApplication')
1951    label = _('Referee Report')
1952    pnav = 3
1953    form_fields = grok.AutoFields(IApplicantRefereeReport)
1954    form_fields[
1955        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
1956
1957class RefereeReportManageFormPage(KofaEditFormPage):
1958    """A displaymanage for referee reports.
1959    """
1960    grok.context(IApplicantRefereeReport)
1961    grok.name('manage')
1962    grok.require('waeup.managePortal')
1963    label = _('Manage Referee Report')
1964    pnav = 3
1965    form_fields = grok.AutoFields(IApplicantRefereeReport).omit('creation_date')
1966
1967    @action(_('Save'), style='primary')
1968    def save(self, **data):
1969        changed_fields = self.applyData(self.context, **data)
1970        # Turn list of lists into single list
1971        if changed_fields:
1972            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
1973        else:
1974            changed_fields = []
1975        fields_string = ' + '.join(changed_fields)
1976        self.flash(_('Form has been saved.'))
1977        if fields_string:
1978            self.context.__parent__.writeLogMessage(
1979                self, '%s - saved: %s' % (self.context.r_id, fields_string))
1980        return
1981
1982class RemoveRefereeReportPage(UtilityView, grok.View):
1983    """
1984    """
1985    grok.context(IApplicantRefereeReport)
1986    grok.name('remove')
1987    grok.require('waeup.manageApplication')
1988
1989    def update(self):
1990        redirect_url = self.url(self.context.__parent__)
1991        self.context.__parent__.writeLogMessage(
1992            self, 'removed: %s' % self.context.r_id)
1993        del self.context.__parent__[self.context.r_id]
1994        self.flash(_('Referee report removed.'))
1995        self.redirect(redirect_url)
1996        return
1997
1998    def render(self):
1999        return
2000
2001class RefereeReportAddFormPage(KofaAddFormPage):
2002    """Add-form to add an referee report. This form
2003    is protected by a mandate.
2004    """
2005    grok.context(IApplicant)
2006    grok.require('waeup.Public')
2007    grok.name('addrefereereport')
2008    form_fields = grok.AutoFields(
2009        IApplicantRefereeReport).omit('creation_date')
2010    grok.template('refereereportpage')
2011    label = _('Referee Report Form')
2012    pnav = 3
2013    #doclink = DOCLINK + '/refereereports.html'
2014
2015    def update(self):
2016        blocker = grok.getSite()['configuration'].maintmode_enabled_by
2017        if blocker:
2018            self.flash(_('The portal is in maintenance mode. '
2019                        'Referee report forms are temporarily disabled.'),
2020                       type='warning')
2021            self.redirect(self.application_url())
2022            return
2023        # Check mandate
2024        form = self.request.form
2025        self.mandate_id = form.get('mandate_id', None)
2026        self.mandates = grok.getSite()['mandates']
2027        mandate = self.mandates.get(self.mandate_id, None)
2028        if mandate is None and not self.request.form.get('form.actions.submit'):
2029            self.flash(_('No mandate.'), type='warning')
2030            self.redirect(self.application_url())
2031            return
2032        if mandate:
2033            # Check the mandate expiration date after redirect again
2034            if mandate.expires < datetime.utcnow():
2035                self.flash(_('Mandate expired.'),
2036                           type='warning')
2037                self.redirect(self.application_url())
2038                return
2039            args = {'mandate_id':mandate.mandate_id}
2040            # Check if report exists.
2041            # (1) If mandate has been used to create a report,
2042            # redirect to the pdf file.
2043            if mandate.params.get('redirect_path2'):
2044                self.redirect(
2045                    self.application_url() +
2046                    mandate.params.get('redirect_path2') +
2047                    '?%s' % urlencode(args))
2048                return
2049            # (2) Report exists but was created with another mandate.
2050            for report in self.context.refereereports:
2051                if report.email == mandate.params.get('email'):
2052                    self.flash(_('You have already created a '
2053                                 'report with another mandate.'),
2054                               type='warning')
2055                    self.redirect(self.application_url())
2056                    return
2057            # Prefill form with mandate params
2058            self.form_fields.get(
2059                'name').field.default = mandate.params['name']
2060            self.form_fields.get(
2061                'email_pref').field.default = mandate.params['email']
2062            self.passport_url = self.url(
2063                self.context, 'passport_for_report.jpg') + '?%s' % urlencode(args)
2064        super(RefereeReportAddFormPage, self).update()
2065        return
2066
2067    @action(_('Submit'),
2068              warning=_('Are you really sure? '
2069                        'Reports can neither be modified or added '
2070                        'after submission.'),
2071              style='primary')
2072    def addRefereeReport(self, **data):
2073        report = createObject(u'waeup.ApplicantRefereeReport')
2074        timestamp = ("%d" % int(time()*10000))[1:]
2075        report.r_id = "r%s" % timestamp
2076        report.email = self.mandates[self.mandate_id].params['email']
2077        self.applyData(report, **data)
2078        self.context[report.r_id] = report
2079        # self.flash(_('Referee report has been saved. Thank you!'))
2080        self.context.writeLogMessage(self, 'added: %s' % report.r_id)
2081        # Changed on 19/04/20: We do no longer delete the mandate
2082        # but set path to redirect to the pdf file
2083        self.mandates[self.mandate_id].params[
2084            'redirect_path2'] = '/applicants/%s/%s/%s/referee_report.pdf' % (
2085                self.context.__parent__.code,
2086                self.context.application_number,
2087                report.r_id)
2088        notify(grok.ObjectModifiedEvent(self.mandates[self.mandate_id]))
2089        args = {'mandate_id':self.mandate_id}
2090        self.flash(_('Your report has been successfully submitted. '
2091                     'Please use the report link in the email again to download '
2092                     'a pdf slip of your report.'))
2093        #self.redirect(self.url(report, 'referee_report.pdf')
2094        #              + '?%s' % urlencode(args))
2095        self.redirect(self.application_url())
2096        return
2097
2098class ExportPDFReportSlipPage(UtilityView, grok.View):
2099    """Deliver a PDF slip of the context.
2100    """
2101    grok.context(IApplicantRefereeReport)
2102    grok.name('referee_report_slip.pdf')
2103    grok.require('waeup.manageApplication')
2104    form_fields = grok.AutoFields(IApplicantRefereeReport)
2105    form_fields[
2106        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
2107    #prefix = 'form'
2108    note = None
2109
2110    @property
2111    def title(self):
2112        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2113        return translate(_('Referee Report'), 'waeup.kofa',
2114            target_language=portal_language)
2115
2116    @property
2117    def label(self):
2118        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
2119        return translate(_('Referee Report Slip'),
2120            'waeup.kofa', target_language=portal_language) \
2121            + ' %s' % self.context.r_id
2122
2123    def render(self):
2124        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
2125            self.request)
2126        students_utils = getUtility(IStudentsUtils)
2127        return students_utils.renderPDF(self,'referee_report_slip.pdf',
2128            self.context.__parent__, applicantview, note=self.note)
2129
2130class ExportPDFReportSlipPage2(ExportPDFReportSlipPage):
2131    """Deliver a PDF slip of the context to referees.
2132    """
2133    grok.name('referee_report.pdf')
2134    grok.require('waeup.Public')
2135
2136    def update(self):
2137        # Check mandate
2138        form = self.request.form
2139        self.mandate_id = form.get('mandate_id', None)
2140        self.mandates = grok.getSite()['mandates']
2141        mandate = self.mandates.get(self.mandate_id, None)
2142        if mandate is None:
2143            self.flash(_('No mandate.'), type='warning')
2144            self.redirect(self.application_url())
2145            return
2146        if mandate:
2147            # Check the mandate expiration date after redirect again
2148            if mandate.expires < datetime.utcnow():
2149                self.flash(_('Mandate expired.'),
2150                           type='warning')
2151                self.redirect(self.application_url())
2152                return
2153            # Check if form has really been submitted
2154            if not mandate.params.get('redirect_path2') \
2155                or mandate.params.get(
2156                    'applicant_id') != self.context.__parent__.applicant_id:
2157                self.flash(_('Wrong mandate.'),
2158                           type='warning')
2159                self.redirect(self.application_url())
2160                return
2161            super(ExportPDFReportSlipPage2, self).update()
2162        return
2163
2164class AdditionalFile(grok.View):
2165    """Renders additional files for applicants.
2166    This is a baseclass.
2167    """
2168    grok.baseclass()
2169    grok.context(IApplicant)
2170    grok.require('waeup.viewApplication')
2171
2172    def render(self):
2173        #image = getUtility(IExtFileStore).getFileByContext(
2174        #    self.context, attr=self.download_name)
2175        file = getUtility(IExtFileStore).getFileByContext(
2176            self.context, attr=self.__name__)
2177        dummy,ext = os.path.splitext(file.name)
2178        if ext == '.jpg':
2179            self.response.setHeader('Content-Type', 'image/jpeg')
2180        elif ext == '.pdf':
2181            self.response.setHeader('Content-Type', 'application/pdf')
2182        return file
2183
2184class TestFile(AdditionalFile):
2185    """Renders testfile.
2186    """
2187    grok.name('testfile')
Note: See TracBrowser for help on using the repository browser.