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

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

Switch test URL.

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