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

Last change on this file since 13991 was 13991, checked in by Henrik Bettermann, 8 years ago

Improve RefereeReportAddFormPage? and add tests.

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