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

Last change on this file since 13975 was 13950, checked in by Henrik Bettermann, 9 years ago

Start ApplicantExporter and ApplicantPaymentExporter at the same
time when calling ExportJobContainerJobStart.

  • Property svn:keywords set to Id
File size: 54.7 KB
RevLine 
[5273]1## $Id: browser.py 13950 2016-06-17 10:02:02Z 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
[7370]24from datetime import datetime, date
[8042]25from zope.event import notify
[13152]26from zope.component import getUtility, queryUtility, createObject, getAdapter
[8033]27from zope.catalog.interfaces import ICatalog
[7714]28from zope.i18n import translate
[7322]29from hurry.workflow.interfaces import (
30    IWorkflowInfo, IWorkflowState, InvalidTransitionError)
[7811]31from waeup.kofa.applicants.interfaces import (
[7363]32    IApplicant, IApplicantEdit, IApplicantsRoot,
[7683]33    IApplicantsContainer, IApplicantsContainerAdd,
[8033]34    MAX_UPLOAD_SIZE, IApplicantOnlinePayment, IApplicantsUtils,
[10845]35    IApplicantRegisterUpdate, ISpecialApplicant
[7363]36    )
[12247]37from waeup.kofa.utils.helpers import html2dict
[10655]38from waeup.kofa.applicants.container import (
39    ApplicantsContainer, VirtualApplicantsExportJobContainer)
[8404]40from waeup.kofa.applicants.applicant import search
[8636]41from waeup.kofa.applicants.workflow import (
[13254]42    INITIALIZED, STARTED, PAID, SUBMITTED, ADMITTED, NOT_ADMITTED, CREATED)
[7811]43from waeup.kofa.browser import (
[9217]44#    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,
[7363]45    DEFAULT_PASSPORT_IMAGE_PATH)
[9217]46from waeup.kofa.browser.layout import (
47    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage)
[7811]48from waeup.kofa.browser.interfaces import ICaptchaManager
49from waeup.kofa.browser.breadcrumbs import Breadcrumb
50from waeup.kofa.browser.layout import (
[11437]51    NullValidator, jsaction, action, UtilityView)
[10655]52from waeup.kofa.browser.pages import (
53    add_local_role, del_local_roles, doll_up, ExportCSVView)
[7811]54from waeup.kofa.interfaces import (
[13177]55    IKofaObject, ILocalRolesAssignable, IExtFileStore, IPDF, DOCLINK,
[7819]56    IFileStoreNameChooser, IPasswordValidator, IUserAccount, IKofaUtils)
[7811]57from waeup.kofa.interfaces import MessageFactory as _
58from waeup.kofa.permissions import get_users_with_local_roles
59from waeup.kofa.students.interfaces import IStudentsUtils
[8186]60from waeup.kofa.utils.helpers import string_from_bytes, file_size, now
[8170]61from waeup.kofa.widgets.datewidget import (
[10831]62    FriendlyDateDisplayWidget,
[8170]63    FriendlyDatetimeDisplayWidget)
[5320]64
[7819]65grok.context(IKofaObject) # Make IKofaObject the default context
[5273]66
[11437]67WARNING = _('You can not edit your application records after final submission.'
68            ' You really want to submit?')
[8550]69
[8388]70class ApplicantsRootPage(KofaDisplayFormPage):
[5822]71    grok.context(IApplicantsRoot)
72    grok.name('index')
[6153]73    grok.require('waeup.Public')
[8388]74    form_fields = grok.AutoFields(IApplicantsRoot)
[13076]75    label = _('Applicants Section')
[5843]76    pnav = 3
[6012]77
78    def update(self):
[6067]79        super(ApplicantsRootPage, self).update()
[6012]80        return
81
[8388]82    @property
83    def introduction(self):
84        # Here we know that the cookie has been set
85        lang = self.request.cookies.get('kofa.language')
86        html = self.context.description_dict.get(lang,'')
87        if html == '':
88            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
89            html = self.context.description_dict.get(portal_language,'')
90        return html
91
[10097]92    @property
93    def containers(self):
[10098]94        if self.layout.isAuthenticated():
[13249]95            return self.context.values()
[13217]96        values = sorted([container for container in self.context.values()
[13249]97                         if not container.hidden and container.enddate],
[13222]98                        key=lambda value: value.enddate, reverse=True)
[13217]99        return values
[10097]100
[8404]101class ApplicantsSearchPage(KofaPage):
102    grok.context(IApplicantsRoot)
103    grok.name('search')
104    grok.require('waeup.viewApplication')
[10644]105    label = _('Find applicants')
[10645]106    search_button = _('Find applicant')
[8404]107    pnav = 3
108
109    def update(self, *args, **kw):
110        form = self.request.form
111        self.results = []
112        if 'searchterm' in form and form['searchterm']:
113            self.searchterm = form['searchterm']
114            self.searchtype = form['searchtype']
115        elif 'old_searchterm' in form:
116            self.searchterm = form['old_searchterm']
117            self.searchtype = form['old_searchtype']
118        else:
119            if 'search' in form:
[11254]120                self.flash(_('Empty search string'), type='warning')
[8404]121            return
122        self.results = search(query=self.searchterm,
123            searchtype=self.searchtype, view=self)
124        if not self.results:
[11254]125            self.flash(_('No applicant found.'), type='warning')
[8404]126        return
127
[7819]128class ApplicantsRootManageFormPage(KofaEditFormPage):
[5828]129    grok.context(IApplicantsRoot)
130    grok.name('manage')
[6107]131    grok.template('applicantsrootmanagepage')
[8388]132    form_fields = grok.AutoFields(IApplicantsRoot)
[13076]133    label = _('Manage applicants section')
[5843]134    pnav = 3
[7136]135    grok.require('waeup.manageApplication')
[8388]136    taboneactions = [_('Save')]
137    tabtwoactions = [_('Add applicants container'), _('Remove selected')]
138    tabthreeactions1 = [_('Remove selected local roles')]
139    tabthreeactions2 = [_('Add local role')]
[7710]140    subunits = _('Applicants Containers')
[13177]141    doclink = DOCLINK + '/applicants.html'
[6078]142
[6184]143    def getLocalRoles(self):
144        roles = ILocalRolesAssignable(self.context)
145        return roles()
146
147    def getUsers(self):
148        """Get a list of all users.
149        """
150        for key, val in grok.getSite()['users'].items():
151            url = self.url(val)
152            yield(dict(url=url, name=key, val=val))
153
154    def getUsersWithLocalRoles(self):
155        return get_users_with_local_roles(self.context)
156
[7710]157    @jsaction(_('Remove selected'))
[6069]158    def delApplicantsContainers(self, **data):
159        form = self.request.form
[9701]160        if 'val_id' in form:
[8388]161            child_id = form['val_id']
162        else:
[11254]163            self.flash(_('No container selected!'), type='warning')
164            self.redirect(self.url(self.context, '@@manage')+'#tab2')
[8388]165            return
[6069]166        if not isinstance(child_id, list):
167            child_id = [child_id]
168        deleted = []
169        for id in child_id:
170            try:
171                del self.context[id]
172                deleted.append(id)
173            except:
[7710]174                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
[11254]175                    id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
[6069]176        if len(deleted):
[7738]177            self.flash(_('Successfully removed: ${a}',
178                mapping = {'a':', '.join(deleted)}))
[12892]179        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
180        self.context.logger.info(
181            '%s - removed: %s' % (ob_class, ', '.join(deleted)))
[11254]182        self.redirect(self.url(self.context, '@@manage')+'#tab2')
[6078]183        return
[5828]184
[7710]185    @action(_('Add applicants container'), validator=NullValidator)
[6069]186    def addApplicantsContainer(self, **data):
187        self.redirect(self.url(self.context, '@@add'))
[6078]188        return
189
[7710]190    @action(_('Add local role'), validator=NullValidator)
[6184]191    def addLocalRole(self, **data):
[7484]192        return add_local_role(self,3, **data)
[6184]193
[7710]194    @action(_('Remove selected local roles'))
[6184]195    def delLocalRoles(self, **data):
[7484]196        return del_local_roles(self,3,**data)
[6184]197
[8388]198    @action(_('Save'), style='primary')
199    def save(self, **data):
200        self.applyData(self.context, **data)
[12247]201        description = getattr(self.context, 'description', None)
202        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
203        self.context.description_dict = html2dict(description, portal_language)
[8390]204        self.flash(_('Form has been saved.'))
[8388]205        return
206
[7819]207class ApplicantsContainerAddFormPage(KofaAddFormPage):
[5822]208    grok.context(IApplicantsRoot)
[7136]209    grok.require('waeup.manageApplication')
[5822]210    grok.name('add')
[6107]211    grok.template('applicantscontaineraddpage')
[7710]212    label = _('Add applicants container')
[5843]213    pnav = 3
[6078]214
[6103]215    form_fields = grok.AutoFields(
[7903]216        IApplicantsContainerAdd).omit('code').omit('title')
[6078]217
[7710]218    @action(_('Add applicants container'))
[6069]219    def addApplicantsContainer(self, **data):
[6103]220        year = data['year']
221        code = u'%s%s' % (data['prefix'], year)
[9529]222        apptypes_dict = getUtility(IApplicantsUtils).APP_TYPES_DICT
223        title = apptypes_dict[data['prefix']][0]
[7685]224        title = u'%s %s/%s' % (title, year, year + 1)
[6087]225        if code in self.context.keys():
[6105]226            self.flash(
[11254]227              _('An applicants container for the same application '
228                'type and entrance year exists already in the database.'),
229                type='warning')
[5822]230            return
231        # Add new applicants container...
[8009]232        container = createObject(u'waeup.ApplicantsContainer')
[6069]233        self.applyData(container, **data)
[6087]234        container.code = code
235        container.title = title
236        self.context[code] = container
[7710]237        self.flash(_('Added:') + ' "%s".' % code)
[12892]238        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
239        self.context.logger.info('%s - added: %s' % (ob_class, code))
[7484]240        self.redirect(self.url(self.context, u'@@manage'))
[5822]241        return
[6078]242
[7710]243    @action(_('Cancel'), validator=NullValidator)
[6069]244    def cancel(self, **data):
[7484]245        self.redirect(self.url(self.context, '@@manage'))
[6078]246
[5845]247class ApplicantsRootBreadcrumb(Breadcrumb):
248    """A breadcrumb for applicantsroot.
249    """
250    grok.context(IApplicantsRoot)
[7710]251    title = _(u'Applicants')
[6078]252
[5845]253class ApplicantsContainerBreadcrumb(Breadcrumb):
254    """A breadcrumb for applicantscontainers.
255    """
256    grok.context(IApplicantsContainer)
[6319]257
[10655]258
259class ApplicantsExportsBreadcrumb(Breadcrumb):
260    """A breadcrumb for exports.
261    """
262    grok.context(VirtualApplicantsExportJobContainer)
263    title = _(u'Applicant Data Exports')
264    target = None
265
[6153]266class ApplicantBreadcrumb(Breadcrumb):
267    """A breadcrumb for applicants.
268    """
269    grok.context(IApplicant)
[6319]270
[6153]271    @property
272    def title(self):
273        """Get a title for a context.
274        """
[7240]275        return self.context.application_number
[5828]276
[7250]277class OnlinePaymentBreadcrumb(Breadcrumb):
278    """A breadcrumb for payments.
279    """
280    grok.context(IApplicantOnlinePayment)
281
282    @property
283    def title(self):
284        return self.context.p_id
285
[8563]286class ApplicantsStatisticsPage(KofaDisplayFormPage):
287    """Some statistics about applicants in a container.
288    """
289    grok.context(IApplicantsContainer)
290    grok.name('statistics')
[8565]291    grok.require('waeup.viewApplicationStatistics')
[8563]292    grok.template('applicantcontainerstatistics')
293
294    @property
295    def label(self):
296        return "%s" % self.context.title
297
[7819]298class ApplicantsContainerPage(KofaDisplayFormPage):
[5830]299    """The standard view for regular applicant containers.
300    """
301    grok.context(IApplicantsContainer)
302    grok.name('index')
[6153]303    grok.require('waeup.Public')
[6029]304    grok.template('applicantscontainerpage')
[5850]305    pnav = 3
[6053]306
[9078]307    @property
308    def form_fields(self):
[12247]309        form_fields = grok.AutoFields(IApplicantsContainer).omit(
310            'title', 'description')
[9078]311        form_fields[
312            'startdate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
313        form_fields[
314            'enddate'].custom_widget = FriendlyDatetimeDisplayWidget('le')
315        if self.request.principal.id == 'zope.anybody':
316            form_fields = form_fields.omit(
[10101]317                'code', 'prefix', 'year', 'mode', 'hidden',
[11870]318                'strict_deadline', 'application_category',
319                'application_slip_notice')
[9078]320        return form_fields
[6053]321
[5837]322    @property
[7708]323    def introduction(self):
[7833]324        # Here we know that the cookie has been set
325        lang = self.request.cookies.get('kofa.language')
[7708]326        html = self.context.description_dict.get(lang,'')
[8388]327        if html == '':
[7833]328            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7708]329            html = self.context.description_dict.get(portal_language,'')
[8388]330        return html
[7708]331
332    @property
[7467]333    def label(self):
[7493]334        return "%s" % self.context.title
[5837]335
[7819]336class ApplicantsContainerManageFormPage(KofaEditFormPage):
[5837]337    grok.context(IApplicantsContainer)
[5850]338    grok.name('manage')
[6107]339    grok.template('applicantscontainermanagepage')
[10625]340    form_fields = grok.AutoFields(IApplicantsContainer)
[7710]341    taboneactions = [_('Save'),_('Cancel')]
[8684]342    tabtwoactions = [_('Remove selected'),_('Cancel'),
[8314]343        _('Create students from selected')]
[7710]344    tabthreeactions1 = [_('Remove selected local roles')]
345    tabthreeactions2 = [_('Add local role')]
[5844]346    # Use friendlier date widget...
[7136]347    grok.require('waeup.manageApplication')
[13177]348    doclink = DOCLINK + '/applicants.html'
[5850]349
350    @property
351    def label(self):
[7710]352        return _('Manage applicants container')
[5850]353
[5845]354    pnav = 3
[5837]355
[8547]356    @property
357    def showApplicants(self):
[13217]358        if self.context.counts[1] < 1000:
[8547]359            return True
360        return False
361
[6184]362    def getLocalRoles(self):
363        roles = ILocalRolesAssignable(self.context)
364        return roles()
365
366    def getUsers(self):
367        """Get a list of all users.
368        """
369        for key, val in grok.getSite()['users'].items():
370            url = self.url(val)
371            yield(dict(url=url, name=key, val=val))
372
373    def getUsersWithLocalRoles(self):
374        return get_users_with_local_roles(self.context)
375
[7714]376    @action(_('Save'), style='primary')
[7489]377    def save(self, **data):
[9531]378        changed_fields = self.applyData(self.context, **data)
379        if changed_fields:
380            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
381        else:
382            changed_fields = []
[12247]383        description = getattr(self.context, 'description', None)
384        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
385        self.context.description_dict = html2dict(description, portal_language)
[7710]386        self.flash(_('Form has been saved.'))
[9531]387        fields_string = ' + '.join(changed_fields)
[12892]388        self.context.writeLogMessage(self, 'saved: %s' % fields_string)
[5837]389        return
[6078]390
[7710]391    @jsaction(_('Remove selected'))
[6105]392    def delApplicant(self, **data):
[6189]393        form = self.request.form
[9701]394        if 'val_id' in form:
[6189]395            child_id = form['val_id']
396        else:
[11254]397            self.flash(_('No applicant selected!'), type='warning')
398            self.redirect(self.url(self.context, '@@manage')+'#tab2')
[6189]399            return
400        if not isinstance(child_id, list):
401            child_id = [child_id]
402        deleted = []
403        for id in child_id:
404            try:
405                del self.context[id]
406                deleted.append(id)
407            except:
[7710]408                self.flash(_('Could not delete:') + ' %s: %s: %s' % (
[11254]409                    id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
[6189]410        if len(deleted):
[7741]411            self.flash(_('Successfully removed: ${a}',
[7738]412                mapping = {'a':', '.join(deleted)}))
[11254]413        self.redirect(self.url(self.context, u'@@manage')+'#tab2')
[6189]414        return
[6105]415
[8314]416    @action(_('Create students from selected'))
417    def createStudents(self, **data):
418        form = self.request.form
[9701]419        if 'val_id' in form:
[8314]420            child_id = form['val_id']
421        else:
[11254]422            self.flash(_('No applicant selected!'), type='warning')
423            self.redirect(self.url(self.context, '@@manage')+'#tab2')
[8314]424            return
425        if not isinstance(child_id, list):
426            child_id = [child_id]
427        created = []
428        for id in child_id:
429            success, msg = self.context[id].createStudent(view=self)
430            if success:
431                created.append(id)
432        if len(created):
433            self.flash(_('${a} students successfully created.',
434                mapping = {'a': len(created)}))
435        else:
[11254]436            self.flash(_('No student could be created.'), type='warning')
437        self.redirect(self.url(self.context, u'@@manage')+'#tab2')
[8314]438        return
439
[7710]440    @action(_('Cancel'), validator=NullValidator)
[5837]441    def cancel(self, **data):
442        self.redirect(self.url(self.context))
443        return
[5886]444
[7710]445    @action(_('Add local role'), validator=NullValidator)
[6184]446    def addLocalRole(self, **data):
447        return add_local_role(self,3, **data)
[6105]448
[7710]449    @action(_('Remove selected local roles'))
[6184]450    def delLocalRoles(self, **data):
451        return del_local_roles(self,3,**data)
452
[7819]453class ApplicantAddFormPage(KofaAddFormPage):
[6622]454    """Add-form to add an applicant.
[6327]455    """
456    grok.context(IApplicantsContainer)
[7136]457    grok.require('waeup.manageApplication')
[6327]458    grok.name('addapplicant')
[7240]459    #grok.template('applicantaddpage')
460    form_fields = grok.AutoFields(IApplicant).select(
[7356]461        'firstname', 'middlename', 'lastname',
[7240]462        'email', 'phone')
[7714]463    label = _('Add applicant')
[6327]464    pnav = 3
[13177]465    doclink = DOCLINK + '/applicants.html'
[6327]466
[7714]467    @action(_('Create application record'))
[6327]468    def addApplicant(self, **data):
[8008]469        applicant = createObject(u'waeup.Applicant')
[7240]470        self.applyData(applicant, **data)
471        self.context.addApplicant(applicant)
[13073]472        self.flash(_('Application record created.'))
[7363]473        self.redirect(
474            self.url(self.context[applicant.application_number], 'index'))
[6327]475        return
476
[13217]477class ApplicantsContainerPrefillFormPage(KofaAddFormPage):
[13218]478    """Form to pre-fill applicants containers.
[13217]479    """
480    grok.context(IApplicantsContainer)
481    grok.require('waeup.manageApplication')
482    grok.name('prefill')
[13218]483    grok.template('prefillcontainer')
[13217]484    label = _('Pre-fill container')
485    pnav = 3
[13232]486    doclink = DOCLINK + '/applicants/browser.html#preparation-and-maintenance-of-applicants-containers'
[13217]487
488    def update(self):
489        if self.context.mode == 'update':
490            self.flash(_('Container must be in create mode to be pre-filled.'),
491                type='danger')
492            self.redirect(self.url(self.context))
493            return
494        super(ApplicantsContainerPrefillFormPage, self).update()
495        return
496
497    @action(_('Pre-fill now'), style='primary')
[13218]498    def addApplicants(self):
[13217]499        form = self.request.form
500        if 'number' in form and form['number']:
501            number = int(form['number'])
502        for i in range(number):
503            applicant = createObject(u'waeup.Applicant')
504            self.context.addApplicant(applicant)
505        self.flash(_('%s application records created.' % number))
506        self.context.writeLogMessage(self, '%s applicants created' % (number))
507        self.redirect(self.url(self.context, 'index'))
508        return
509
510    @action(_('Cancel'), validator=NullValidator)
511    def cancel(self, **data):
512        self.redirect(self.url(self.context))
513        return
514
[13218]515class ApplicantsContainerPurgeFormPage(KofaEditFormPage):
516    """Form to pre-fill applicants containers.
517    """
518    grok.context(IApplicantsContainer)
519    grok.require('waeup.manageApplication')
520    grok.name('purge')
521    grok.template('purgecontainer')
522    label = _('Purge container')
523    pnav = 3
[13232]524    doclink = DOCLINK + '/applicants/browser.html#preparation-and-maintenance-of-applicants-containers'
[13218]525
[13232]526    @action(_('Remove initialized records'),
527              tooltip=_('Don\'t use if application is in progress!'),
528              warning=_('Are you really sure?'),
529              style='primary')
[13218]530    def purgeInitialized(self):
531        form = self.request.form
532        purged = 0
533        keys = [key for key in self.context.keys()]
534        for key in keys:
535            if self.context[key].state == 'initialized':
536                del self.context[key]
537                purged += 1
538        self.flash(_('%s application records purged.' % purged))
539        self.context.writeLogMessage(self, '%s applicants purged' % (purged))
540        self.redirect(self.url(self.context, 'index'))
541        return
542
543    @action(_('Cancel'), validator=NullValidator)
544    def cancel(self, **data):
545        self.redirect(self.url(self.context))
546        return
547
[7819]548class ApplicantDisplayFormPage(KofaDisplayFormPage):
[8014]549    """A display view for applicant data.
550    """
[5273]551    grok.context(IApplicant)
552    grok.name('index')
[7113]553    grok.require('waeup.viewApplication')
[7200]554    grok.template('applicantdisplaypage')
[7714]555    label = _('Applicant')
[5843]556    pnav = 3
[8922]557    hide_hint = False
[5273]558
[8046]559    @property
[13886]560    def display_payments(self):
561        if self.context.special:
562            return True
563        return getattr(self.context.__parent__, 'application_fee', None)
564
565    @property
[10831]566    def form_fields(self):
567        if self.context.special:
[11599]568            form_fields = grok.AutoFields(ISpecialApplicant).omit('locked')
[10831]569        else:
570            form_fields = grok.AutoFields(IApplicant).omit(
[10845]571                'locked', 'course_admitted', 'password', 'suspended')
[10831]572        return form_fields
573
574    @property
[10534]575    def target(self):
576        return getattr(self.context.__parent__, 'prefix', None)
577
578    @property
[8046]579    def separators(self):
580        return getUtility(IApplicantsUtils).SEPARATORS_DICT
581
[7063]582    def update(self):
583        self.passport_url = self.url(self.context, 'passport.jpg')
[7240]584        # Mark application as started if applicant logs in for the first time
[7272]585        usertype = getattr(self.request.principal, 'user_type', None)
586        if usertype == 'applicant' and \
587            IWorkflowState(self.context).getState() == INITIALIZED:
[7240]588            IWorkflowInfo(self.context).fireTransition('start')
[10895]589        if usertype == 'applicant' and self.context.state == 'created':
[10908]590            session = '%s/%s' % (self.context.__parent__.year,
591                                 self.context.__parent__.year+1)
592            title = getattr(grok.getSite()['configuration'], 'name', u'Sample University')
[10895]593            msg = _(
594                '\n <strong>Congratulations!</strong>' +
[10933]595                ' You have been offered provisional admission into the' +
[10908]596                ' ${c} Academic Session of ${d}.'
597                ' Your student record has been created for you.' +
598                ' Please, logout again and proceed to the' +
599                ' login page of the portal.'
[10895]600                ' Then enter your new student credentials:' +
601                ' user name= ${a}, password = ${b}.' +
602                ' Change your password when you have logged in.',
603                mapping = {
604                    'a':self.context.student_id,
[10908]605                    'b':self.context.application_number,
606                    'c':session,
607                    'd':title}
[10895]608                )
609            self.flash(msg)
[7063]610        return
611
[6196]612    @property
[7240]613    def hasPassword(self):
614        if self.context.password:
[7714]615            return _('set')
616        return _('unset')
[7240]617
618    @property
[6196]619    def label(self):
620        container_title = self.context.__parent__.title
[8096]621        return _('${a} <br /> Application Record ${b}', mapping = {
[7714]622            'a':container_title, 'b':self.context.application_number})
[6196]623
[7347]624    def getCourseAdmitted(self):
625        """Return link, title and code in html format to the certificate
626           admitted.
627        """
628        course_admitted = self.context.course_admitted
[7351]629        if getattr(course_admitted, '__parent__',None):
[7347]630            url = self.url(course_admitted)
631            title = course_admitted.title
632            code = course_admitted.code
633            return '<a href="%s">%s - %s</a>' %(url,code,title)
634        return ''
[6254]635
[7259]636class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
637    grok.context(IApplicant)
638    grok.name('base')
639    form_fields = grok.AutoFields(IApplicant).select(
[9141]640        'applicant_id','email', 'course1')
[7259]641
[7459]642class CreateStudentPage(UtilityView, grok.View):
[8636]643    """Create a student object from applicant data.
[7341]644    """
645    grok.context(IApplicant)
646    grok.name('createstudent')
647    grok.require('waeup.manageStudent')
648
649    def update(self):
[8314]650        msg = self.context.createStudent(view=self)[1]
[11254]651        self.flash(msg, type='warning')
[7341]652        self.redirect(self.url(self.context))
653        return
654
655    def render(self):
656        return
657
[8636]658class CreateAllStudentsPage(UtilityView, grok.View):
659    """Create all student objects from applicant data
[11874]660    in the root container or in a specific  applicants container only.
[11826]661    Only PortalManagers can do this.
[8636]662    """
[9900]663    #grok.context(IApplicantsContainer)
[8636]664    grok.name('createallstudents')
665    grok.require('waeup.managePortal')
666
667    def update(self):
668        cat = getUtility(ICatalog, name='applicants_catalog')
669        results = list(cat.searchResults(state=(ADMITTED, ADMITTED)))
670        created = []
[9900]671        container_only = False
672        applicants_root = grok.getSite()['applicants']
673        if isinstance(self.context, ApplicantsContainer):
674            container_only = True
[8636]675        for result in results:
[9900]676            if container_only and result.__parent__ is not self.context:
[8636]677                continue
678            success, msg = result.createStudent(view=self)
679            if success:
680                created.append(result.applicant_id)
681            else:
[12893]682                ob_class = self.__implemented__.__name__.replace(
683                    'waeup.kofa.','')
[9900]684                applicants_root.logger.info(
[8742]685                    '%s - %s - %s' % (ob_class, result.applicant_id, msg))
[8636]686        if len(created):
687            self.flash(_('${a} students successfully created.',
688                mapping = {'a': len(created)}))
689        else:
[11254]690            self.flash(_('No student could be created.'), type='warning')
[9900]691        self.redirect(self.url(self.context))
[8636]692        return
693
694    def render(self):
695        return
696
[8260]697class ApplicationFeePaymentAddPage(UtilityView, grok.View):
[7250]698    """ Page to add an online payment ticket
699    """
700    grok.context(IApplicant)
701    grok.name('addafp')
702    grok.require('waeup.payApplicant')
[8243]703    factory = u'waeup.ApplicantOnlinePayment'
[7250]704
[11726]705    @property
706    def custom_requirements(self):
707        return ''
708
[7250]709    def update(self):
[11726]710        # Additional requirements in custom packages.
711        if self.custom_requirements:
712            self.flash(
713                self.custom_requirements,
714                type='danger')
715            self.redirect(self.url(self.context))
[11727]716            return
[11575]717        if not self.context.special:
718            for key in self.context.keys():
719                ticket = self.context[key]
720                if ticket.p_state == 'paid':
721                      self.flash(
722                          _('This type of payment has already been made.'),
723                          type='warning')
724                      self.redirect(self.url(self.context))
725                      return
[8524]726        applicants_utils = getUtility(IApplicantsUtils)
727        container = self.context.__parent__
[8243]728        payment = createObject(self.factory)
[11254]729        failure = applicants_utils.setPaymentDetails(
[10831]730            container, payment, self.context)
[11254]731        if failure is not None:
[13123]732            self.flash(failure, type='danger')
[8524]733            self.redirect(self.url(self.context))
734            return
[7250]735        self.context[payment.p_id] = payment
[12893]736        self.context.writeLogMessage(self, 'added: %s' % payment.p_id)
[7714]737        self.flash(_('Payment ticket created.'))
[8280]738        self.redirect(self.url(payment))
[7250]739        return
740
741    def render(self):
742        return
743
744
[7819]745class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
[7250]746    """ Page to view an online payment ticket
747    """
748    grok.context(IApplicantOnlinePayment)
749    grok.name('index')
750    grok.require('waeup.viewApplication')
[9984]751    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
[8170]752    form_fields[
753        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
754    form_fields[
755        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[7250]756    pnav = 3
757
758    @property
759    def label(self):
[7714]760        return _('${a}: Online Payment Ticket ${b}', mapping = {
[8170]761            'a':self.context.__parent__.display_fullname,
762            'b':self.context.p_id})
[7250]763
[8420]764class OnlinePaymentApprovePage(UtilityView, grok.View):
765    """ Approval view
[7250]766    """
767    grok.context(IApplicantOnlinePayment)
[8420]768    grok.name('approve')
769    grok.require('waeup.managePortal')
[7250]770
771    def update(self):
[11580]772        flashtype, msg, log = self.context.approveApplicantPayment()
[8428]773        if log is not None:
[9771]774            applicant = self.context.__parent__
775            # Add log message to applicants.log
776            applicant.writeLogMessage(self, log)
777            # Add log message to payments.log
778            self.context.logger.info(
[9795]779                '%s,%s,%s,%s,%s,,,,,,' % (
[9771]780                applicant.applicant_id,
781                self.context.p_id, self.context.p_category,
782                self.context.amount_auth, self.context.r_code))
[11580]783        self.flash(msg, type=flashtype)
[7250]784        return
785
786    def render(self):
787        self.redirect(self.url(self.context, '@@index'))
788        return
789
[7459]790class ExportPDFPaymentSlipPage(UtilityView, grok.View):
[7250]791    """Deliver a PDF slip of the context.
792    """
793    grok.context(IApplicantOnlinePayment)
[8262]794    grok.name('payment_slip.pdf')
[7250]795    grok.require('waeup.viewApplication')
[9984]796    form_fields = grok.AutoFields(IApplicantOnlinePayment).omit('p_item')
[8173]797    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
798    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
[7250]799    prefix = 'form'
[8258]800    note = None
[7250]801
802    @property
[7714]803    def title(self):
[7819]804        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[7811]805        return translate(_('Payment Data'), 'waeup.kofa',
[7714]806            target_language=portal_language)
807
808    @property
[7250]809    def label(self):
[7819]810        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
[8262]811        return translate(_('Online Payment Slip'),
[7811]812            'waeup.kofa', target_language=portal_language) \
[7714]813            + ' %s' % self.context.p_id
[7250]814
[11754]815    @property
816    def payment_slip_download_warning(self):
817        if self.context.__parent__.state != SUBMITTED:
818            return _('Please submit the application form before '
819                     'trying to download payment slips.')
820        return ''
821
[7250]822    def render(self):
[11754]823        if self.payment_slip_download_warning:
824            self.flash(self.payment_slip_download_warning, type='danger')
[11599]825            self.redirect(self.url(self.context))
826            return
[7259]827        applicantview = ApplicantBaseDisplayFormPage(self.context.__parent__,
[7250]828            self.request)
829        students_utils = getUtility(IStudentsUtils)
[8262]830        return students_utils.renderPDF(self,'payment_slip.pdf',
[8258]831            self.context.__parent__, applicantview, note=self.note)
[7250]832
[10571]833class ExportPDFPageApplicationSlip(UtilityView, grok.View):
[6358]834    """Deliver a PDF slip of the context.
835    """
836    grok.context(IApplicant)
837    grok.name('application_slip.pdf')
[7136]838    grok.require('waeup.viewApplication')
[6358]839    prefix = 'form'
840
[8666]841    def update(self):
[9051]842        if self.context.state in ('initialized', 'started', 'paid'):
[8666]843            self.flash(
[11254]844                _('Please pay and submit before trying to download '
845                  'the application slip.'), type='warning')
[8666]846            return self.redirect(self.url(self.context))
847        return
848
[6358]849    def render(self):
[12395]850        try:
851            pdfstream = getAdapter(self.context, IPDF, name='application_slip')(
852                view=self)
853        except IOError:
854            self.flash(
855                _('Your image file is corrupted. '
856                  'Please replace.'), type='danger')
857            return self.redirect(self.url(self.context))
[6358]858        self.response.setHeader(
859            'Content-Type', 'application/pdf')
[7392]860        return pdfstream
[6358]861
[7081]862def handle_img_upload(upload, context, view):
[7063]863    """Handle upload of applicant image.
[7081]864
865    Returns `True` in case of success or `False`.
866
867    Please note that file pointer passed in (`upload`) most probably
868    points to end of file when leaving this function.
[7063]869    """
[7081]870    size = file_size(upload)
871    if size > MAX_UPLOAD_SIZE:
[11254]872        view.flash(_('Uploaded image is too big!'), type='danger')
[7081]873        return False
[7247]874    dummy, ext = os.path.splitext(upload.filename)
875    ext.lower()
876    if ext != '.jpg':
[11254]877        view.flash(_('jpg file extension expected.'), type='danger')
[7247]878        return False
[7081]879    upload.seek(0) # file pointer moved when determining size
[7063]880    store = getUtility(IExtFileStore)
881    file_id = IFileStoreNameChooser(context).chooseName()
882    store.createFile(file_id, upload)
[7081]883    return True
[7063]884
[7819]885class ApplicantManageFormPage(KofaEditFormPage):
[6196]886    """A full edit view for applicant data.
887    """
888    grok.context(IApplicant)
[7200]889    grok.name('manage')
[7136]890    grok.require('waeup.manageApplication')
[7200]891    grok.template('applicanteditpage')
[6322]892    manage_applications = True
[6196]893    pnav = 3
[12664]894    display_actions = [[_('Save'), _('Finally Submit')],
[7714]895        [_('Add online payment ticket'),_('Remove selected tickets')]]
[6196]896
[8046]897    @property
[13886]898    def display_payments(self):
899        if self.context.special:
900            return True
901        return getattr(self.context.__parent__, 'application_fee', None)
902
903    @property
[10831]904    def form_fields(self):
905        if self.context.special:
[10845]906            form_fields = grok.AutoFields(ISpecialApplicant)
907            form_fields['applicant_id'].for_display = True
[10831]908        else:
909            form_fields = grok.AutoFields(IApplicant)
910            form_fields['student_id'].for_display = True
911            form_fields['applicant_id'].for_display = True
912        return form_fields
913
914    @property
[10534]915    def target(self):
916        return getattr(self.context.__parent__, 'prefix', None)
917
918    @property
[8046]919    def separators(self):
920        return getUtility(IApplicantsUtils).SEPARATORS_DICT
921
[11733]922    @property
923    def custom_upload_requirements(self):
924        return ''
925
[6196]926    def update(self):
[7200]927        super(ApplicantManageFormPage, self).update()
[6353]928        self.wf_info = IWorkflowInfo(self.context)
[7081]929        self.max_upload_size = string_from_bytes(MAX_UPLOAD_SIZE)
[10090]930        self.upload_success = None
[6598]931        upload = self.request.form.get('form.passport', None)
932        if upload:
[11733]933            if self.custom_upload_requirements:
934                self.flash(
935                    self.custom_upload_requirements,
936                    type='danger')
937                self.redirect(self.url(self.context))
938                return
[10090]939            # We got a fresh upload, upload_success is
940            # either True or False
941            self.upload_success = handle_img_upload(
[7084]942                upload, self.context, self)
[10090]943            if self.upload_success:
[10095]944                self.context.writeLogMessage(self, 'saved: passport')
[6196]945        return
946
947    @property
948    def label(self):
949        container_title = self.context.__parent__.title
[8096]950        return _('${a} <br /> Application Form ${b}', mapping = {
[7714]951            'a':container_title, 'b':self.context.application_number})
[6196]952
[6303]953    def getTransitions(self):
[6351]954        """Return a list of dicts of allowed transition ids and titles.
[6353]955
956        Each list entry provides keys ``name`` and ``title`` for
957        internal name and (human readable) title of a single
958        transition.
[6349]959        """
[8434]960        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
[11482]961            if not t[0] in ('pay', 'create')]
[7687]962        return [dict(name='', title=_('No transition'))] +[
[6355]963            dict(name=x, title=y) for x, y in allowed_transitions]
[6303]964
[7714]965    @action(_('Save'), style='primary')
[6196]966    def save(self, **data):
[7240]967        form = self.request.form
968        password = form.get('password', None)
969        password_ctl = form.get('control_password', None)
970        if password:
971            validator = getUtility(IPasswordValidator)
972            errors = validator.validate_password(password, password_ctl)
973            if errors:
[11254]974                self.flash( ' '.join(errors), type='danger')
[7240]975                return
[10090]976        if self.upload_success is False:  # False is not None!
977            # Error during image upload. Ignore other values.
978            return
[6475]979        changed_fields = self.applyData(self.context, **data)
[7199]980        # Turn list of lists into single list
981        if changed_fields:
982            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
[7240]983        else:
984            changed_fields = []
985        if password:
986            # Now we know that the form has no errors and can set password ...
987            IUserAccount(self.context).setPassword(password)
988            changed_fields.append('password')
[7199]989        fields_string = ' + '.join(changed_fields)
[7085]990        trans_id = form.get('transition', None)
991        if trans_id:
992            self.wf_info.fireTransition(trans_id)
[7714]993        self.flash(_('Form has been saved.'))
[6644]994        if fields_string:
[12892]995            self.context.writeLogMessage(self, 'saved: %s' % fields_string)
[6196]996        return
997
[7250]998    def unremovable(self, ticket):
[7330]999        return False
[7250]1000
1001    # This method is also used by the ApplicantEditFormPage
1002    def delPaymentTickets(self, **data):
1003        form = self.request.form
[9701]1004        if 'val_id' in form:
[7250]1005            child_id = form['val_id']
1006        else:
[11254]1007            self.flash(_('No payment selected.'), type='warning')
[7250]1008            self.redirect(self.url(self.context))
1009            return
1010        if not isinstance(child_id, list):
1011            child_id = [child_id]
1012        deleted = []
1013        for id in child_id:
1014            # Applicants are not allowed to remove used payment tickets
1015            if not self.unremovable(self.context[id]):
1016                try:
1017                    del self.context[id]
1018                    deleted.append(id)
1019                except:
[7714]1020                    self.flash(_('Could not delete:') + ' %s: %s: %s' % (
[11254]1021                      id, sys.exc_info()[0], sys.exc_info()[1]), type='danger')
[7250]1022        if len(deleted):
[7741]1023            self.flash(_('Successfully removed: ${a}',
[7738]1024                mapping = {'a':', '.join(deleted)}))
[8742]1025            self.context.writeLogMessage(
1026                self, 'removed: % s' % ', '.join(deleted))
[7250]1027        return
1028
[7252]1029    # We explicitely want the forms to be validated before payment tickets
1030    # can be created. If no validation is requested, use
[7459]1031    # 'validator=NullValidator' in the action directive
[11578]1032    @action(_('Add online payment ticket'), style='primary')
[7250]1033    def addPaymentTicket(self, **data):
1034        self.redirect(self.url(self.context, '@@addafp'))
[7252]1035        return
[7250]1036
[7714]1037    @jsaction(_('Remove selected tickets'))
[7250]1038    def removePaymentTickets(self, **data):
1039        self.delPaymentTickets(**data)
1040        self.redirect(self.url(self.context) + '/@@manage')
1041        return
1042
[10094]1043    # Not used in base package
1044    def file_exists(self, attr):
1045        file = getUtility(IExtFileStore).getFileByContext(
1046            self.context, attr=attr)
1047        if file:
1048            return True
1049        else:
1050            return False
1051
[7200]1052class ApplicantEditFormPage(ApplicantManageFormPage):
[5982]1053    """An applicant-centered edit view for applicant data.
1054    """
[6196]1055    grok.context(IApplicantEdit)
[5273]1056    grok.name('edit')
[6198]1057    grok.require('waeup.handleApplication')
[7200]1058    grok.template('applicanteditpage')
[6322]1059    manage_applications = False
[10358]1060    submit_state = PAID
[5484]1061
[7250]1062    @property
[10831]1063    def form_fields(self):
1064        if self.context.special:
[11657]1065            form_fields = grok.AutoFields(ISpecialApplicant).omit(
1066                'locked', 'suspended')
[10845]1067            form_fields['applicant_id'].for_display = True
[10831]1068        else:
1069            form_fields = grok.AutoFields(IApplicantEdit).omit(
1070                'locked', 'course_admitted', 'student_id',
[10845]1071                'suspended'
[10831]1072                )
1073            form_fields['applicant_id'].for_display = True
1074            form_fields['reg_number'].for_display = True
1075        return form_fields
1076
1077    @property
[7250]1078    def display_actions(self):
[8286]1079        state = IWorkflowState(self.context).getState()
[13100]1080        # If the form is unlocked, applicants are allowed to save the form
1081        # and remove unused tickets.
1082        actions = [[_('Save')], [_('Remove selected tickets')]]
1083        # Only in state started they can also add tickets.
[10358]1084        if state == STARTED:
[7714]1085            actions = [[_('Save')],
1086                [_('Add online payment ticket'),_('Remove selected tickets')]]
[13100]1087        # In state paid, they can submit the data and further add tickets
1088        # if the application is special.
[11599]1089        elif self.context.special and state == PAID:
[12664]1090            actions = [[_('Save'), _('Finally Submit')],
[11599]1091                [_('Add online payment ticket'),_('Remove selected tickets')]]
[8286]1092        elif state == PAID:
[12664]1093            actions = [[_('Save'), _('Finally Submit')],
[7714]1094                [_('Remove selected tickets')]]
[7250]1095        return actions
1096
[7330]1097    def unremovable(self, ticket):
[13100]1098        return ticket.r_code
[7330]1099
[7145]1100    def emit_lock_message(self):
[11254]1101        self.flash(_('The requested form is locked (read-only).'),
1102                   type='warning')
[5941]1103        self.redirect(self.url(self.context))
1104        return
[6078]1105
[5686]1106    def update(self):
[8665]1107        if self.context.locked or (
1108            self.context.__parent__.expired and
1109            self.context.__parent__.strict_deadline):
[7145]1110            self.emit_lock_message()
[5941]1111            return
[7200]1112        super(ApplicantEditFormPage, self).update()
[5686]1113        return
[5952]1114
[6196]1115    def dataNotComplete(self):
[7252]1116        store = getUtility(IExtFileStore)
1117        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
[7714]1118            return _('No passport picture uploaded.')
[6322]1119        if not self.request.form.get('confirm_passport', False):
[7714]1120            return _('Passport picture confirmation box not ticked.')
[6196]1121        return False
[5952]1122
[7252]1123    # We explicitely want the forms to be validated before payment tickets
1124    # can be created. If no validation is requested, use
[7459]1125    # 'validator=NullValidator' in the action directive
[11578]1126    @action(_('Add online payment ticket'), style='primary')
[7250]1127    def addPaymentTicket(self, **data):
1128        self.redirect(self.url(self.context, '@@addafp'))
[7252]1129        return
[7250]1130
[7714]1131    @jsaction(_('Remove selected tickets'))
[7250]1132    def removePaymentTickets(self, **data):
1133        self.delPaymentTickets(**data)
1134        self.redirect(self.url(self.context) + '/@@edit')
1135        return
1136
[7996]1137    @action(_('Save'), style='primary')
[5273]1138    def save(self, **data):
[10090]1139        if self.upload_success is False:  # False is not None!
1140            # Error during image upload. Ignore other values.
1141            return
[5273]1142        self.applyData(self.context, **data)
[10210]1143        self.flash(_('Form has been saved.'))
[5273]1144        return
1145
[12664]1146    @action(_('Finally Submit'), warning=WARNING)
[5484]1147    def finalsubmit(self, **data):
[10090]1148        if self.upload_success is False:  # False is not None!
[7084]1149            return # error during image upload. Ignore other values
[6196]1150        if self.dataNotComplete():
[11254]1151            self.flash(self.dataNotComplete(), type='danger')
[5941]1152            return
[7252]1153        self.applyData(self.context, **data)
[8286]1154        state = IWorkflowState(self.context).getState()
[6322]1155        # This shouldn't happen, but the application officer
1156        # might have forgotten to lock the form after changing the state
[10358]1157        if state != self.submit_state:
[11254]1158            self.flash(_('The form cannot be submitted. Wrong state!'),
1159                       type='danger')
[6303]1160            return
1161        IWorkflowInfo(self.context).fireTransition('submit')
[8589]1162        # application_date is used in export files for sorting.
1163        # We can thus store utc.
[8194]1164        self.context.application_date = datetime.utcnow()
[7714]1165        self.flash(_('Form has been submitted.'))
[6196]1166        self.redirect(self.url(self.context))
[5273]1167        return
[5941]1168
[7063]1169class PassportImage(grok.View):
1170    """Renders the passport image for applicants.
1171    """
1172    grok.name('passport.jpg')
1173    grok.context(IApplicant)
[7113]1174    grok.require('waeup.viewApplication')
[7063]1175
1176    def render(self):
1177        # A filename chooser turns a context into a filename suitable
1178        # for file storage.
1179        image = getUtility(IExtFileStore).getFileByContext(self.context)
1180        self.response.setHeader(
1181            'Content-Type', 'image/jpeg')
1182        if image is None:
1183            # show placeholder image
[7089]1184            return open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb').read()
[7063]1185        return image
[7363]1186
[7819]1187class ApplicantRegistrationPage(KofaAddFormPage):
[7363]1188    """Captcha'd registration page for applicants.
1189    """
1190    grok.context(IApplicantsContainer)
1191    grok.name('register')
[7373]1192    grok.require('waeup.Anonymous')
[7363]1193    grok.template('applicantregister')
1194
[7368]1195    @property
[8033]1196    def form_fields(self):
1197        form_fields = None
[8128]1198        if self.context.mode == 'update':
1199            form_fields = grok.AutoFields(IApplicantRegisterUpdate).select(
[11738]1200                'lastname','reg_number','email')
[8128]1201        else: #if self.context.mode == 'create':
[8033]1202            form_fields = grok.AutoFields(IApplicantEdit).select(
1203                'firstname', 'middlename', 'lastname', 'email', 'phone')
1204        return form_fields
1205
1206    @property
[7368]1207    def label(self):
[8078]1208        return _('Apply for ${a}',
[7714]1209            mapping = {'a':self.context.title})
[7368]1210
[7363]1211    def update(self):
[8665]1212        if self.context.expired:
[11254]1213            self.flash(_('Outside application period.'), type='warning')
[7368]1214            self.redirect(self.url(self.context))
1215            return
[13394]1216        blocker = grok.getSite()['configuration'].maintmode_enabled_by
1217        if blocker:
1218            self.flash(_('The portal is in maintenance mode '
1219                        'and registration temporarily disabled.'),
1220                       type='warning')
1221            self.redirect(self.url(self.context))
1222            return
[7368]1223        # Handle captcha
[7363]1224        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1225        self.captcha_result = self.captcha.verify(self.request)
1226        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1227        return
1228
[8629]1229    def _redirect(self, email, password, applicant_id):
1230        # Forward only email to landing page in base package.
1231        self.redirect(self.url(self.context, 'registration_complete',
1232            data = dict(email=email)))
1233        return
1234
[9178]1235    @action(_('Send login credentials to email address'), style='primary')
[7363]1236    def register(self, **data):
1237        if not self.captcha_result.is_valid:
[8037]1238            # Captcha will display error messages automatically.
[7363]1239            # No need to flash something.
1240            return
[8033]1241        if self.context.mode == 'create':
[13217]1242            # Check if there are unused records in this container which
1243            # can be taken
1244            applicant = self.context.first_unused
1245            if applicant is None:
[13215]1246                # Add applicant
1247                applicant = createObject(u'waeup.Applicant')
1248                self.context.addApplicant(applicant)
[8033]1249            self.applyData(applicant, **data)
[8042]1250            applicant.reg_number = applicant.applicant_id
1251            notify(grok.ObjectModifiedEvent(applicant))
[8033]1252        elif self.context.mode == 'update':
1253            # Update applicant
[8037]1254            reg_number = data.get('reg_number','')
[11738]1255            lastname = data.get('lastname','')
[8033]1256            cat = getUtility(ICatalog, name='applicants_catalog')
1257            results = list(
1258                cat.searchResults(reg_number=(reg_number, reg_number)))
1259            if results:
1260                applicant = results[0]
[11738]1261                if getattr(applicant,'lastname',None) is None:
[11254]1262                    self.flash(_('An error occurred.'), type='danger')
[8037]1263                    return
[11738]1264                elif applicant.lastname.lower() != lastname.lower():
[8042]1265                    # Don't tell the truth here. Anonymous must not
[11738]1266                    # know that a record was found and only the lastname
[8042]1267                    # verification failed.
[13099]1268                    self.flash(
1269                        _('No application record found.'), type='warning')
[8037]1270                    return
[8627]1271                elif applicant.password is not None and \
1272                    applicant.state != INITIALIZED:
1273                    self.flash(_('Your password has already been set and used. '
[11254]1274                                 'Please proceed to the login page.'),
1275                               type='warning')
[8042]1276                    return
1277                # Store email address but nothing else.
[8033]1278                applicant.email = data['email']
[8042]1279                notify(grok.ObjectModifiedEvent(applicant))
[8033]1280            else:
[8042]1281                # No record found, this is the truth.
[11254]1282                self.flash(_('No application record found.'), type='warning')
[8033]1283                return
1284        else:
[8042]1285            # Does not happen but anyway ...
[8033]1286            return
[7819]1287        kofa_utils = getUtility(IKofaUtils)
[7811]1288        password = kofa_utils.genPassword()
[7380]1289        IUserAccount(applicant).setPassword(password)
[7365]1290        # Send email with credentials
[7399]1291        login_url = self.url(grok.getSite(), 'login')
[8853]1292        url_info = u'Login: %s' % login_url
[7714]1293        msg = _('You have successfully been registered for the')
[7811]1294        if kofa_utils.sendCredentials(IUserAccount(applicant),
[8853]1295            password, url_info, msg):
[8629]1296            email_sent = applicant.email
[7380]1297        else:
[8629]1298            email_sent = None
1299        self._redirect(email=email_sent, password=password,
1300            applicant_id=applicant.applicant_id)
[7380]1301        return
1302
[7819]1303class ApplicantRegistrationEmailSent(KofaPage):
[7380]1304    """Landing page after successful registration.
[8629]1305
[7380]1306    """
1307    grok.name('registration_complete')
1308    grok.require('waeup.Public')
1309    grok.template('applicantregemailsent')
[7714]1310    label = _('Your registration was successful.')
[7380]1311
[8629]1312    def update(self, email=None, applicant_id=None, password=None):
[7380]1313        self.email = email
[8629]1314        self.password = password
1315        self.applicant_id = applicant_id
[7380]1316        return
[10655]1317
[13254]1318class ApplicantCheckStatusPage(KofaPage):
1319    """Captcha'd status checking page for applicants.
1320    """
1321    grok.context(IApplicantsRoot)
1322    grok.name('checkstatus')
1323    grok.require('waeup.Anonymous')
1324    grok.template('applicantcheckstatus')
1325    buttonname = _('Submit')
1326
1327    def label(self):
1328        if self.result:
[13429]1329            return _('Admission status of ${a}',
[13254]1330                     mapping = {'a':self.applicant.applicant_id})
[13428]1331        return _('Check your admission status')
[13254]1332
1333    def update(self, SUBMIT=None):
1334        form = self.request.form
1335        self.result = False
1336        # Handle captcha
1337        self.captcha = getUtility(ICaptchaManager).getCaptcha()
1338        self.captcha_result = self.captcha.verify(self.request)
1339        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
1340        if SUBMIT:
1341            if not self.captcha_result.is_valid:
1342                # Captcha will display error messages automatically.
1343                # No need to flash something.
1344                return
[13363]1345            unique_id = form.get('unique_id', None)
[13254]1346            lastname = form.get('lastname', None)
[13363]1347            if not unique_id or not lastname:
[13254]1348                self.flash(
1349                    _('Required input missing.'), type='warning')
1350                return
1351            cat = getUtility(ICatalog, name='applicants_catalog')
1352            results = list(
[13363]1353                cat.searchResults(applicant_id=(unique_id, unique_id)))
1354            if not results:
1355                results = list(
1356                    cat.searchResults(reg_number=(unique_id, unique_id)))
[13254]1357            if results:
1358                applicant = results[0]
[13282]1359                if applicant.lastname.lower().strip() != lastname.lower():
[13254]1360                    # Don't tell the truth here. Anonymous must not
1361                    # know that a record was found and only the lastname
1362                    # verification failed.
1363                    self.flash(
1364                        _('No application record found.'), type='warning')
1365                    return
1366            else:
1367                self.flash(_('No application record found.'), type='warning')
1368                return
1369            self.applicant = applicant
1370            self.entry_session = "%s/%s" % (
1371                applicant.__parent__.year,
1372                applicant.__parent__.year+1)
1373            course_admitted = getattr(applicant, 'course_admitted', None)
1374            self.course_admitted = False
1375            if course_admitted is not None:
1376                self.course_admitted = True
1377                self.longtitle = course_admitted.longtitle
1378                self.department = course_admitted.__parent__.__parent__.longtitle
1379                self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
1380            self.result = True
1381            self.admitted = False
1382            self.not_admitted = False
1383            self.submitted = False
1384            self.not_submitted = False
[13356]1385            self.created = False
[13254]1386            if applicant.state in (ADMITTED, CREATED):
1387                self.admitted = True
[13356]1388            if applicant.state in (CREATED):
1389                self.created = True
[13365]1390                self.student_id = applicant.student_id
1391                self.password = applicant.application_number
[13254]1392            if applicant.state in (NOT_ADMITTED,):
1393                self.not_admitted = True
1394            if applicant.state in (SUBMITTED,):
1395                self.submitted = True
1396            if applicant.state in (INITIALIZED, STARTED, PAID):
1397                self.not_submitted = True
1398        return
1399
[10655]1400class ExportJobContainerOverview(KofaPage):
1401    """Page that lists active applicant data export jobs and provides links
1402    to discard or download CSV files.
1403
1404    """
1405    grok.context(VirtualApplicantsExportJobContainer)
1406    grok.require('waeup.manageApplication')
1407    grok.name('index.html')
1408    grok.template('exportjobsindex')
[11254]1409    label = _('Data Exports')
[10655]1410    pnav = 3
1411
1412    def update(self, CREATE=None, DISCARD=None, job_id=None):
1413        if CREATE:
1414            self.redirect(self.url('@@start_export'))
1415            return
1416        if DISCARD and job_id:
1417            entry = self.context.entry_from_job_id(job_id)
1418            self.context.delete_export_entry(entry)
1419            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1420            self.context.logger.info(
1421                '%s - discarded: job_id=%s' % (ob_class, job_id))
1422            self.flash(_('Discarded export') + ' %s' % job_id)
1423        self.entries = doll_up(self, user=self.request.principal.id)
1424        return
1425
[13950]1426class ExportJobContainerJobStart(UtilityView, grok.View):
1427    """View that starts two export jobs, one for applicants and a second
1428    one for applicant payments.
[10655]1429    """
1430    grok.context(VirtualApplicantsExportJobContainer)
1431    grok.require('waeup.manageApplication')
1432    grok.name('start_export')
1433
1434    def update(self):
[13152]1435        utils = queryUtility(IKofaUtils)
1436        if not utils.expensive_actions_allowed():
1437            self.flash(_(
1438                "Currently, exporters cannot be started due to high "
1439                "system load. Please try again later."), type='danger')
1440            self.entries = doll_up(self, user=None)
1441            return
[13950]1442
1443        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1444        container_code = self.context.__parent__.code
1445        # Start first exporter
[10655]1446        exporter = 'applicants'
1447        job_id = self.context.start_export_job(exporter,
1448                                      self.request.principal.id,
1449                                      container=container_code)
1450        self.context.logger.info(
1451            '%s - exported: %s (%s), job_id=%s'
1452            % (ob_class, exporter, container_code, job_id))
[13950]1453        # Commit transaction so that job is stored in the ZODB
1454        transaction.commit()
1455        # Start second exporter
1456        exporter = 'applicantpayments'
1457        job_id = self.context.start_export_job(exporter,
1458                                      self.request.principal.id,
1459                                      container=container_code)
1460        self.context.logger.info(
1461            '%s - exported: %s (%s), job_id=%s'
1462            % (ob_class, exporter, container_code, job_id))
1463
1464        self.flash(_('Exports started.'))
[10655]1465        self.redirect(self.url(self.context))
1466        return
1467
1468    def render(self):
1469        return
1470
1471class ExportJobContainerDownload(ExportCSVView):
1472    """Page that downloads a students export csv file.
1473
1474    """
1475    grok.context(VirtualApplicantsExportJobContainer)
[11253]1476    grok.require('waeup.manageApplication')
Note: See TracBrowser for help on using the repository browser.