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

Last change on this file since 17296 was 17245, checked in by Henrik Bettermann, 23 months ago

Save p_option.

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