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

Last change on this file since 16210 was 16207, checked in by Henrik Bettermann, 4 years ago

Increase mandate expiration time.

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