source: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/browser/pages.py @ 9209

Last change on this file since 9209 was 9209, checked in by uli, 12 years ago

Merge changes from update branch (includes trunk changes until r9107).

  • Property svn:keywords set to Id
File size: 76.7 KB
Line 
1## $Id: pages.py 9209 2012-09-20 08:49:37Z uli $
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""" Viewing components for Kofa objects.
19"""
20import csv
21import grok
22import os
23import re
24import sys
25from urllib import urlencode
26from zope import schema
27from zope.authentication.interfaces import (
28    IAuthentication, IUnauthenticatedPrincipal, ILogout)
29from zope.catalog.interfaces import ICatalog
30from zope.component import (
31    getUtility, queryUtility, createObject, getAllUtilitiesRegisteredFor,
32    getUtilitiesFor,
33    )
34from zope.event import notify
35from zope.securitypolicy.interfaces import IPrincipalRoleManager
36from zope.session.interfaces import ISession
37from zope.password.interfaces import IPasswordManager
38from waeup.kofa.browser.layout import (
39    KofaPage, KofaForm, KofaEditFormPage, KofaAddFormPage,
40    KofaDisplayFormPage, NullValidator)
41from waeup.kofa.browser.interfaces import (
42    IUniversity, IFacultiesContainer, IFaculty, IFacultyAdd,
43    IDepartment, IDepartmentAdd, ICourse, ICourseAdd, ICertificate,
44    ICertificateAdd, ICertificateCourse, ICertificateCourseAdd,
45    ICaptchaManager, IChangePassword)
46from waeup.kofa.browser.layout import jsaction, action, UtilityView
47from waeup.kofa.browser.resources import (
48    warning, tabs, datatable)
49from waeup.kofa.interfaces import MessageFactory as _
50from waeup.kofa.interfaces import(
51    IKofaObject, IUsersContainer, IUserAccount, IDataCenter,
52    IKofaXMLImporter, IKofaXMLExporter, IBatchProcessor,
53    ILocalRolesAssignable, DuplicationError, IConfigurationContainer,
54    ISessionConfiguration, ISessionConfigurationAdd, IJobManager,
55    IPasswordValidator, IContactForm, IKofaUtils, ICSVExporter,)
56from waeup.kofa.permissions import (
57    get_users_with_local_roles, get_all_roles, get_all_users)
58
59from waeup.kofa.students.catalog import search as searchstudents
60from waeup.kofa.university.catalog import search
61from waeup.kofa.university.vocabularies import course_levels
62from waeup.kofa.authentication import LocalRoleSetEvent
63from waeup.kofa.widgets.htmlwidget import HTMLDisplayWidget
64from waeup.kofa.utils.helpers import get_user_account
65from waeup.kofa.mandates.mandate import PasswordMandate
66
67grok.context(IKofaObject)
68grok.templatedir('templates')
69
70def add_local_role(view, tab, **data):
71    localrole = view.request.form.get('local_role', None)
72    user = view.request.form.get('user', None)
73    if user is None or localrole is None:
74        view.flash('No user selected.')
75        view.redirect(view.url(view.context, '@@manage')+'?tab%s' % tab)
76        return
77    role_manager = IPrincipalRoleManager(view.context)
78    role_manager.assignRoleToPrincipal(localrole, user)
79    notify(LocalRoleSetEvent(view.context, localrole, user, granted=True))
80    ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
81    grok.getSite().logger.info(
82        '%s - added: %s|%s' % (ob_class, user, localrole))
83    view.redirect(view.url(view.context, u'@@manage')+'?tab%s' % tab)
84    return
85
86def del_local_roles(view, tab, **data):
87    child_ids = view.request.form.get('role_id', None)
88    if child_ids is None:
89        view.flash(_('No local role selected.'))
90        view.redirect(view.url(view.context, '@@manage')+'?tab%s' % tab)
91        return
92    if not isinstance(child_ids, list):
93        child_ids = [child_ids]
94    deleted = []
95    role_manager = IPrincipalRoleManager(view.context)
96    for child_id in child_ids:
97        localrole = child_id.split('|')[1]
98        user_name = child_id.split('|')[0]
99        try:
100            role_manager.unsetRoleForPrincipal(localrole, user_name)
101            notify(LocalRoleSetEvent(
102                    view.context, localrole, user_name, granted=False))
103            deleted.append(child_id)
104        except:
105            view.flash('Could not remove %s: %s: %s' % (
106                    child_id, sys.exc_info()[0], sys.exc_info()[1]))
107    if len(deleted):
108        view.flash(
109            _('Local role successfully removed: ${a}',
110            mapping = {'a':', '.join(deleted)}))
111        ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
112        grok.getSite().logger.info(
113            '%s - removed: %s' % (ob_class, ', '.join(deleted)))
114    view.redirect(view.url(view.context, u'@@manage')+'?tab%s' % tab)
115    return
116
117def delSubobjects(view, redirect, tab=None, subcontainer=None):
118    form = view.request.form
119    if form.has_key('val_id'):
120        child_id = form['val_id']
121    else:
122        view.flash(_('No item selected.'))
123        if tab:
124            view.redirect(view.url(view.context, redirect)+'?tab%s' % tab)
125        else:
126            view.redirect(view.url(view.context, redirect))
127        return
128    if not isinstance(child_id, list):
129        child_id = [child_id]
130    deleted = []
131    for id in child_id:
132        try:
133            if subcontainer:
134                container = getattr(view.context, subcontainer, None)
135                del container[id]
136            else:
137                del view.context[id]
138            deleted.append(id)
139        except:
140            view.flash('Could not delete %s: %s: %s' % (
141                    id, sys.exc_info()[0], sys.exc_info()[1]))
142    if len(deleted):
143        view.flash(_('Successfully removed: ${a}',
144            mapping = {'a': ', '.join(deleted)}))
145        ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
146        grok.getSite().logger.info(
147            '%s - removed: %s' % (ob_class, ', '.join(deleted)))
148    if tab:
149        view.redirect(view.url(view.context, redirect)+'?tab%s' % tab)
150    else:
151        view.redirect(view.url(view.context, redirect))
152    return
153
154# Save function used for save methods in pages
155def msave(view, **data):
156    changed_fields = view.applyData(view.context, **data)
157    # Turn list of lists into single list
158    if changed_fields:
159        changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
160    fields_string = ' + '.join(changed_fields)
161    view.flash(_('Form has been saved.'))
162    ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
163    if fields_string:
164        grok.getSite().logger.info('%s - %s - saved: %s' % (ob_class, view.context.__name__, fields_string))
165    return
166
167#
168# Login/logout and language switch pages...
169#
170
171class LoginPage(KofaPage):
172    """A login page, available for all objects.
173    """
174    grok.name('login')
175    grok.context(IKofaObject)
176    grok.require('waeup.Public')
177    label = _(u'Login')
178    camefrom = None
179    login_button = label
180
181    def update(self, SUBMIT=None, camefrom=None):
182        self.camefrom = camefrom
183        if SUBMIT is not None:
184            if self.request.principal.id != 'zope.anybody':
185                self.flash(_('You logged in.'))
186                if self.request.principal.user_type == 'student':
187                    rel_link = '/students/%s' % self.request.principal.id
188                    self.redirect(self.application_url() + rel_link)
189                    return
190                elif self.request.principal.user_type == 'applicant':
191                    container, application_number = self.request.principal.id.split('_')
192                    rel_link = '/applicants/%s/%s' % (
193                        container, application_number)
194                    self.redirect(self.application_url() + rel_link)
195                    return
196                if not self.camefrom:
197                    # User might have entered the URL directly. Let's beam
198                    # him back to our context.
199                    self.redirect(self.url(self.context))
200                    return
201                self.redirect(self.camefrom)
202                return
203            # Display appropriate flash message if credentials are correct
204            # but student has been deactivated.
205            login = self.request.form['form.login']
206            if len(login) == 8 and grok.getSite()['students'].has_key(login):
207                student = grok.getSite()['students'][
208                    self.request.form['form.login']]
209                password = self.request.form['form.password']
210                passwordmanager = getUtility(IPasswordManager, 'SSHA')
211                if passwordmanager.checkPassword(student.password, password):
212                    self.flash(_('Your account has been deactivated.'))
213                    return
214            self.flash(_('You entered invalid credentials.'))
215
216
217class LogoutPage(KofaPage):
218    """A logout page. Calling this page will log the current user out.
219    """
220    grok.context(IKofaObject)
221    grok.require('waeup.Public')
222    grok.name('logout')
223
224    def update(self):
225        if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
226            auth = getUtility(IAuthentication)
227            ILogout(auth).logout(self.request)
228            self.flash(_("You have been logged out. Thanks for using WAeUP Kofa!"))
229        self.redirect(self.application_url())
230
231
232class LanguageChangePage(KofaPage):
233    """ Language switch
234    """
235    grok.context(IKofaObject)
236    grok.name('change_language')
237    grok.require('waeup.Public')
238
239    def update(self, lang='en', view_name='@@index'):
240        self.response.setCookie('kofa.language', lang, path='/')
241        self.redirect(self.url(self.context, view_name))
242        return
243
244    def render(self):
245        return
246
247#
248# Contact form...
249#
250
251class ContactAdminForm(KofaForm):
252    grok.name('contactadmin')
253    #grok.context(IUniversity)
254    grok.template('contactform')
255    grok.require('waeup.Authenticated')
256    pnav = 2
257    form_fields = grok.AutoFields(IContactForm).select('body')
258
259    @property
260    def config(self):
261        return grok.getSite()['configuration']
262
263    def label(self):
264        return _(u'Contact ${a}', mapping = {'a': self.config.name_admin})
265
266    @property
267    def get_user_account(self):
268        return get_user_account(self.request)
269
270    @action(_('Send message now'), style='primary')
271    def send(self, *args, **data):
272        fullname = self.request.principal.title
273        try:
274            email = self.request.principal.email
275        except AttributeError:
276            email = self.config.email_admin
277        username = self.request.principal.id
278        usertype = getattr(self.request.principal,
279                           'user_type', 'system').title()
280        kofa_utils = getUtility(IKofaUtils)
281        success = kofa_utils.sendContactForm(
282                fullname,email,
283                self.config.name_admin,self.config.email_admin,
284                username,usertype,self.config.name,
285                data['body'],self.config.email_subject)
286        # Success is always True if sendContactForm didn't fail.
287        # TODO: Catch exceptions.
288        if success:
289            self.flash(_('Your message has been sent.'))
290        return
291
292class EnquiriesForm(ContactAdminForm):
293    """Captcha'd page to let anonymous send emails to the administrator.
294    """
295    grok.name('enquiries')
296    grok.require('waeup.Public')
297    pnav = 2
298    form_fields = grok.AutoFields(IContactForm).select(
299                          'fullname', 'email_from', 'body')
300
301    def update(self):
302        # Handle captcha
303        self.captcha = getUtility(ICaptchaManager).getCaptcha()
304        self.captcha_result = self.captcha.verify(self.request)
305        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
306        return super(EnquiriesForm, self).update()
307
308    @action(_('Send now'), style='primary')
309    def send(self, *args, **data):
310        if not self.captcha_result.is_valid:
311            # Captcha will display error messages automatically.
312            # No need to flash something.
313            return
314        kofa_utils = getUtility(IKofaUtils)
315        success = kofa_utils.sendContactForm(
316                data['fullname'],data['email_from'],
317                self.config.name_admin,self.config.email_admin,
318                u'None',u'Anonymous',self.config.name,
319                data['body'],self.config.email_subject)
320        if success:
321            self.flash(_('Your message has been sent.'))
322        else:
323            self.flash(_('A smtp server error occurred.'))
324        return
325
326#
327# University related pages...
328#
329
330class UniversityPage(KofaDisplayFormPage):
331    """ The main university page.
332    """
333    grok.require('waeup.Public')
334    grok.name('index')
335    grok.context(IUniversity)
336    pnav = 0
337    label = ''
338
339    @property
340    def frontpage(self):
341        lang = self.request.cookies.get('kofa.language')
342        html = self.context['configuration'].frontpage_dict.get(lang,'')
343        if html =='':
344            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
345            html = self.context[
346                'configuration'].frontpage_dict.get(portal_language,'')
347        if html =='':
348            return _(u'<h1>Welcome to WAeUP.Kofa</h1>')
349        else:
350            return html
351
352class AdministrationPage(KofaPage):
353    """ The administration overview page.
354    """
355    grok.name('administration')
356    grok.context(IUniversity)
357    grok.require('waeup.managePortal')
358    label = _(u'Administration')
359    pnav = 0
360
361class RSS20Feed(grok.View):
362    """An RSS 2.0 feed.
363    """
364    grok.name('feed.rss')
365    grok.context(IUniversity)
366    grok.require('waeup.Public')
367    grok.template('universityrss20feed')
368
369    name = 'General news feed'
370    description = 'waeup.kofa now supports RSS 2.0 feeds :-)'
371    language = None
372    date = None
373    buildDate = None
374    editor = None
375    webmaster = None
376
377    @property
378    def title(self):
379        return getattr(grok.getSite(), 'name', u'Sample University')
380
381    @property
382    def contexttitle(self):
383        return self.name
384
385    @property
386    def link(self):
387        return self.url(grok.getSite())
388
389    def update(self):
390        self.response.setHeader('Content-Type', 'text/xml; charset=UTF-8')
391
392    def entries(self):
393        return ()
394
395#
396# User container pages...
397#
398
399class UsersContainerPage(KofaPage):
400    """Overview page for all local users.
401    """
402    grok.require('waeup.manageUsers')
403    grok.context(IUsersContainer)
404    grok.name('index')
405    label = _('Portal Users')
406    manage_button = _(u'Manage')
407    delete_button = _(u'Remove')
408
409    def update(self, userid=None, adduser=None, manage=None, delete=None):
410        datatable.need()
411        if manage is not None and userid is not None:
412            self.redirect(self.url(userid) + '/@@manage')
413        if delete is not None and userid is not None:
414            self.context.delUser(userid)
415            self.flash(_('User account ${a} successfully deleted.',
416                mapping = {'a':  userid}))
417            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
418            self.context.__parent__.logger.info(
419                '%s - removed: %s' % (ob_class, userid))
420
421    def getLocalRoles(self, account):
422        local_roles = account.getLocalRoles()
423        local_roles_string = ''
424        site_url = self.url(grok.getSite())
425        for local_role in local_roles.keys():
426            role_title = dict(get_all_roles())[local_role].title
427            objects_string = ''
428            for object in local_roles[local_role]:
429                objects_string += '<a href="%s">%s</a>, ' %(self.url(object),
430                    self.url(object).replace(site_url,''))
431            local_roles_string += '%s: <br />%s <br />' %(role_title,
432                objects_string.rstrip(', '))
433        return local_roles_string
434
435    def getSiteRoles(self, account):
436        site_roles = account.roles
437        site_roles_string = ''
438        for site_role in site_roles:
439            role_title = dict(get_all_roles())[site_role].title
440            site_roles_string += '%s <br />' % role_title
441        return site_roles_string
442
443class AddUserFormPage(KofaAddFormPage):
444    """Add a user account.
445    """
446    grok.require('waeup.manageUsers')
447    grok.context(IUsersContainer)
448    grok.name('add')
449    grok.template('usereditformpage')
450    form_fields = grok.AutoFields(IUserAccount)
451    label = _('Add user')
452
453    @action(_('Add user'), style='primary')
454    def addUser(self, **data):
455        name = data['name']
456        title = data['title']
457        email = data['email']
458        phone = data['phone']
459        description = data['description']
460        #password = data['password']
461        roles = data['roles']
462        form = self.request.form
463        password = form.get('password', None)
464        password_ctl = form.get('control_password', None)
465        if password:
466            validator = getUtility(IPasswordValidator)
467            errors = validator.validate_password(password, password_ctl)
468            if errors:
469                self.flash( ' '.join(errors))
470                return
471        try:
472            self.context.addUser(name, password, title=title, email=email,
473                                 phone=phone, description=description,
474                                 roles=roles)
475            self.flash(_('User account ${a} successfully added.',
476                mapping = {'a': name}))
477            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
478            self.context.__parent__.logger.info(
479                '%s - added: %s' % (ob_class, name))
480        except KeyError:
481            self.status = self.flash('The userid chosen already exists '
482                                  'in the database.')
483            return
484        self.redirect(self.url(self.context))
485
486class UserManageFormPage(KofaEditFormPage):
487    """Manage a user account.
488    """
489    grok.context(IUserAccount)
490    grok.name('manage')
491    grok.template('usereditformpage')
492    grok.require('waeup.manageUsers')
493    form_fields = grok.AutoFields(IUserAccount).omit('name')
494
495    def label(self):
496        return _("Edit user ${a}", mapping = {'a':self.context.__name__})
497
498    def setUpWidgets(self, ignore_request=False):
499        super(UserManageFormPage,self).setUpWidgets(ignore_request)
500        self.widgets['title'].displayWidth = 30
501        self.widgets['description'].height = 3
502        return
503
504    @action(_('Save'), style='primary')
505    def save(self, **data):
506        form = self.request.form
507        password = form.get('password', None)
508        password_ctl = form.get('control_password', None)
509        if password:
510            validator = getUtility(IPasswordValidator)
511            errors = validator.validate_password(password, password_ctl)
512            if errors:
513                self.flash( ' '.join(errors))
514                return
515        changed_fields = self.applyData(self.context, **data)
516        if changed_fields:
517            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
518        else:
519            changed_fields = []
520        if password:
521            # Now we know that the form has no errors and can set password ...
522            self.context.setPassword(password)
523            changed_fields.append('password')
524        fields_string = ' + '.join(changed_fields)
525        if fields_string:
526            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
527            self.context.__parent__.logger.info(
528                '%s - %s edited: %s' % (
529                ob_class, self.context.name, fields_string))
530        self.flash(_('User settings have been saved.'))
531        return
532
533    @action(_('Cancel'), validator=NullValidator)
534    def cancel(self, **data):
535        self.redirect(self.url(self.context.__parent__))
536        return
537
538class ContactUserForm(ContactAdminForm):
539    grok.name('contactuser')
540    grok.context(IUserAccount)
541    grok.template('contactform')
542    grok.require('waeup.manageUsers')
543    pnav = 0
544    form_fields = grok.AutoFields(IContactForm).select('body')
545
546    def label(self):
547        return _(u'Send message to ${a}', mapping = {'a':self.context.title})
548
549    @action(_('Send message now'), style='primary')
550    def send(self, *args, **data):
551        try:
552            email = self.request.principal.email
553        except AttributeError:
554            email = self.config.email_admin
555        usertype = getattr(self.request.principal,
556                           'user_type', 'system').title()
557        kofa_utils = getUtility(IKofaUtils)
558        success = kofa_utils.sendContactForm(
559                self.request.principal.title,email,
560                self.context.title,self.context.email,
561                self.request.principal.id,usertype,self.config.name,
562                data['body'],self.config.email_subject)
563        # Success is always True if sendContactForm didn't fail.
564        # TODO: Catch exceptions.
565        if success:
566            self.flash(_('Your message has been sent.'))
567        return
568
569class UserEditFormPage(UserManageFormPage):
570    """Edit a user account by user
571    """
572    grok.name('index')
573    grok.require('waeup.editUser')
574    form_fields = grok.AutoFields(IUserAccount).omit(
575        'name', 'description', 'roles')
576    label = _(u"My Preferences")
577
578    def setUpWidgets(self, ignore_request=False):
579        super(UserManageFormPage,self).setUpWidgets(ignore_request)
580        self.widgets['title'].displayWidth = 30
581
582class MyRolesPage(KofaPage):
583    """Display site roles and local roles assigned to officers.
584    """
585    grok.name('my_roles')
586    grok.require('waeup.editUser')
587    grok.context(IUserAccount)
588    grok.template('myrolespage')
589    label = _(u"My Roles")
590
591    @property
592    def getLocalRoles(self):
593        local_roles = get_user_account(self.request).getLocalRoles()
594        local_roles_userfriendly = {}
595        for local_role in local_roles:
596            role_title = dict(get_all_roles())[local_role].title
597            local_roles_userfriendly[role_title] = local_roles[local_role]
598        return local_roles_userfriendly
599
600    @property
601    def getSiteRoles(self):
602        site_roles = get_user_account(self.request).roles
603        site_roles_userfriendly = []
604        for site_role in site_roles:
605            role_title = dict(get_all_roles())[site_role].title
606            site_roles_userfriendly.append(role_title)
607        return site_roles_userfriendly
608
609#
610# Search pages...
611#
612
613class SearchPage(KofaPage):
614    """General search page for the academics section.
615    """
616    grok.context(IFacultiesContainer)
617    grok.name('search')
618    grok.template('searchpage')
619    grok.require('waeup.viewAcademics')
620    label = _(u"Search Academic Section")
621    pnav = 1
622    search_button = _(u'Search')
623
624    def update(self, *args, **kw):
625        datatable.need()
626        form = self.request.form
627        self.hitlist = []
628        self.query = ''
629        if not 'query' in form:
630            return
631        query = form['query']
632        self.query = query
633        self.hitlist = search(query=self.query, view=self)
634        return
635
636#
637# Configuration pages...
638#
639
640class ConfigurationContainerDisplayFormPage(KofaDisplayFormPage):
641    """View page of the configuration container.
642    """
643    grok.require('waeup.managePortalConfiguration')
644    grok.name('view')
645    grok.context(IConfigurationContainer)
646    pnav = 0
647    label = _(u'View portal configuration')
648    form_fields = grok.AutoFields(IConfigurationContainer)
649    form_fields['frontpage'].custom_widget = HTMLDisplayWidget
650
651class ConfigurationContainerManageFormPage(KofaEditFormPage):
652    """Manage page of the configuration container. We always use the
653    manage page in the UI not the view page, thus we use the index name here.
654    """
655    grok.require('waeup.managePortalConfiguration')
656    grok.name('index')
657    grok.context(IConfigurationContainer)
658    grok.template('configurationmanagepage')
659    pnav = 0
660    label = _(u'Edit portal configuration')
661    taboneactions = [_('Save'), _('Update plugins')]
662    tabtwoactions = [
663        _('Add session configuration'),
664        _('Remove selected')]
665    form_fields = grok.AutoFields(IConfigurationContainer).omit('frontpage_dict')
666
667    def update(self):
668        tabs.need()
669        self.tab1 = self.tab2 = ''
670        qs = self.request.get('QUERY_STRING', '')
671        if not qs:
672            qs = 'tab1'
673        setattr(self, qs, 'active')
674        datatable.need()
675        warning.need()
676        return super(ConfigurationContainerManageFormPage, self).update()
677
678    def _frontpage(self):
679        view = ConfigurationContainerDisplayFormPage(
680            self.context,self.request)
681        view.setUpWidgets()
682        return view.widgets['frontpage']()
683
684    @action(_('Save'), style='primary')
685    def save(self, **data):
686        msave(self, **data)
687        self.context.frontpage_dict = self._frontpage()
688        return
689
690    @action(_('Add session configuration'), validator=NullValidator,
691            style='primary')
692    def addSubunit(self, **data):
693        self.redirect(self.url(self.context, '@@add'))
694        return
695
696    def getSessionConfigurations(self):
697        """Get a list of all stored session configuration objects.
698        """
699        for key, val in self.context.items():
700            url = self.url(val)
701            session_string = val.getSessionString()
702            title = _('Session ${a} Configuration',
703                      mapping = {'a':session_string})
704            yield(dict(url=url, name=key, title=title))
705
706    @jsaction(_('Remove selected'))
707    def delSessonConfigurations(self, **data):
708        delSubobjects(self, redirect='@@index', tab='2')
709        return
710
711    @action(_('Update plugins'), validator=NullValidator)
712    def updatePlugins(self, **data):
713        grok.getSite().updatePlugins()
714        self.flash(_('Plugins were updated. See log file for details.'))
715        return
716
717class SessionConfigurationAddFormPage(KofaAddFormPage):
718    """Add a session configuration object to configuration container.
719    """
720    grok.context(IConfigurationContainer)
721    grok.name('add')
722    grok.require('waeup.managePortalConfiguration')
723    label = _('Add session configuration')
724    form_fields = grok.AutoFields(ISessionConfigurationAdd)
725    pnav = 0
726
727    @action(_('Add Session Configuration'), style='primary')
728    def addSessionConfiguration(self, **data):
729        sessionconfiguration = createObject(u'waeup.SessionConfiguration')
730        self.applyData(sessionconfiguration, **data)
731        try:
732            self.context.addSessionConfiguration(sessionconfiguration)
733            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
734            self.context.__parent__.logger.info(
735                '%s - added: %s' % (
736                ob_class, sessionconfiguration.academic_session))
737        except KeyError:
738            self.flash(_('The session chosen already exists.'))
739            return
740        self.redirect(self.url(self.context, '@@index')+'?tab2')
741        return
742
743    @action(_('Cancel'), validator=NullValidator)
744    def cancel(self):
745        self.redirect(self.url(self.context, '@@index')+'?tab2')
746        return
747
748class SessionConfigurationManageFormPage(KofaEditFormPage):
749    """Manage session configuration object.
750    """
751    grok.context(ISessionConfiguration)
752    grok.name('index')
753    grok.require('waeup.managePortalConfiguration')
754    form_fields = grok.AutoFields(ISessionConfiguration)
755    pnav = 0
756
757    @property
758    def label(self):
759        session_string = self.context.getSessionString()
760        return _('Edit academic session ${a} configuration',
761            mapping = {'a':session_string})
762
763    @action(_('Save'), style='primary')
764    def save(self, **data):
765        msave(self, **data)
766        self.redirect(self.url(self.context.__parent__, '@@index')+'?tab2')
767        return
768
769    @action(_('Cancel'), validator=NullValidator)
770    def cancel(self):
771        self.redirect(self.url(self.context.__parent__, '@@index')+'?tab2')
772        return
773
774#
775# Datacenter pages...
776#
777
778class DatacenterPage(KofaEditFormPage):
779    grok.context(IDataCenter)
780    grok.name('index')
781    grok.require('waeup.manageDataCenter')
782    label = _(u'Data Center')
783    pnav = 0
784
785    def update(self):
786        datatable.need()
787        warning.need()
788        return super(DatacenterPage, self).update()
789
790    @jsaction(_('Remove selected'))
791    def delFiles(self, **data):
792        form = self.request.form
793        if form.has_key('val_id'):
794            child_id = form['val_id']
795        else:
796            self.flash(_('No item selected.'))
797            return
798        if not isinstance(child_id, list):
799            child_id = [child_id]
800        deleted = []
801        for id in child_id:
802            fullpath = os.path.join(self.context.storage, id)
803            try:
804                os.remove(fullpath)
805                deleted.append(id)
806            except OSError:
807                self.flash(_('OSError: The file could not be deleted.'))
808                return
809        if len(deleted):
810            self.flash(_('Successfully deleted: ${a}',
811                mapping = {'a': ', '.join(deleted)}))
812            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
813            self.context.logger.info(
814                '%s - deleted: %s' % (ob_class, ', '.join(deleted)))
815        return
816
817class DatacenterFinishedPage(KofaEditFormPage):
818    grok.context(IDataCenter)
819    grok.name('processed')
820    grok.require('waeup.manageDataCenter')
821    label = _(u'Processed Files')
822    pnav = 0
823
824    def update(self):
825        datatable.need()
826        return super(DatacenterFinishedPage, self).update()
827
828class DatacenterUploadPage(KofaPage):
829    grok.context(IDataCenter)
830    grok.name('upload')
831    grok.require('waeup.manageDataCenter')
832    label = _(u'Upload portal data as CSV file')
833    pnav = 0
834    upload_button =_(u'Upload')
835    cancel_button =_(u'Cancel')
836
837    def update(self, uploadfile=None, CANCEL=None, SUBMIT=None):
838        if CANCEL is not None:
839            self.redirect(self.url(self.context))
840            return
841        if not uploadfile:
842            return
843        try:
844            filename = uploadfile.filename
845            #if 'pending' in filename:
846            #    self.flash(_("You can't re-upload pending data files."))
847            #    return
848            if not filename.endswith('.csv'):
849                self.flash(_("Only csv files are allowed."))
850                return
851            normalized_filename = self.getNormalizedFileName(filename)
852            finished_file = os.path.join(
853                self.context.storage, 'finished', normalized_filename)
854            unfinished_file = os.path.join(
855                self.context.storage, 'unfinished', normalized_filename)
856            if os.path.exists(finished_file) or os.path.exists(unfinished_file):
857                self.flash(_("File with same name was uploaded earlier."))
858                return
859            target = os.path.join(self.context.storage, normalized_filename)
860            open(target, 'wb').write(uploadfile.read())
861            os.chmod(target, 0664)
862            logger = self.context.logger
863            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
864            logger.info('%s - uploaded: %s' % (ob_class, target))
865        except IOError:
866            self.flash('Error while uploading file. Please retry.')
867            self.flash('I/O error: %s' % sys.exc_info()[1])
868            return
869        self.redirect(self.url(self.context))
870
871    def getNormalizedFileName(self, filename):
872        """Build sane filename.
873
874        An uploaded file foo.csv will be stored as foo_USERNAME.csv
875        where username is the principal id of the currently logged in
876        user.
877
878        Spaces in filename are replaced by underscore.
879        Pending data filenames remain unchanged.
880        """
881        if filename.endswith('.pending.csv'):
882            return filename
883        username = self.request.principal.id
884        filename = filename.replace(' ', '_')
885        # Only accept typical filname chars...
886        filtered_username = ''.join(re.findall('[a-zA-Z0-9_\.\-]', username))
887        base, ext = os.path.splitext(filename)
888        return '%s_%s%s' % (base, filtered_username, ext.lower())
889
890    def getImporters(self):
891        importers = getAllUtilitiesRegisteredFor(IBatchProcessor)
892        importer_props = []
893        for x in importers:
894            iface_fields = schema.getFields(x.iface)
895            available_fields = []
896            for key in iface_fields.keys():
897                iface_fields[key] = (iface_fields[key].__class__.__name__,
898                    iface_fields[key].required)
899            for value in x.available_fields:
900                available_fields.append(
901                    dict(f_name=value,
902                         f_type=iface_fields.get(value, (None, False))[0],
903                         f_required=iface_fields.get(value, (None, False))[1]
904                         )
905                    )
906            available_fields = sorted(available_fields, key=lambda k: k['f_name'])
907            importer_props.append(
908                dict(title=x.name, name=x.util_name, fields=available_fields))
909        return sorted(importer_props, key=lambda k: k['title'])
910
911class FileDownloadView(UtilityView, grok.View):
912    grok.context(IDataCenter)
913    grok.name('download')
914    grok.require('waeup.manageDataCenter')
915
916    def update(self, filename=None):
917        self.filename = self.request.form['filename']
918        return
919
920    def render(self):
921        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
922        self.context.logger.info(
923            '%s - downloaded: %s' % (ob_class, self.filename))
924        self.response.setHeader(
925            'Content-Type', 'text/csv; charset=UTF-8')
926        self.response.setHeader(
927            'Content-Disposition:', 'attachment; filename="%s' %
928            self.filename.replace('finished/',''))
929        fullpath = os.path.join(self.context.storage, self.filename)
930        return open(fullpath, 'rb').read()
931
932class SkeletonDownloadView(UtilityView, grok.View):
933    grok.context(IDataCenter)
934    grok.name('skeleton')
935    grok.require('waeup.manageDataCenter')
936
937    def update(self, processorname=None):
938        self.processorname = self.request.form['name']
939        self.filename = ('%s_000.csv' %
940            self.processorname.replace('processor','import'))
941        return
942
943    def render(self):
944        #ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
945        #self.context.logger.info(
946        #    '%s - skeleton downloaded: %s' % (ob_class, self.filename))
947        self.response.setHeader(
948            'Content-Type', 'text/csv; charset=UTF-8')
949        self.response.setHeader(
950            'Content-Disposition:', 'attachment; filename="%s' % self.filename)
951        processor = getUtility(IBatchProcessor, name=self.processorname)
952        csv_data = processor.get_csv_skeleton()
953        return csv_data
954
955class DatacenterImportStep1(KofaPage):
956    """Manual import step 1: choose file
957    """
958    grok.context(IDataCenter)
959    grok.name('import1')
960    grok.template('datacenterimport1page')
961    grok.require('waeup.importData')
962    label = _(u'Process CSV file')
963    pnav = 0
964    cancel_button =_(u'Cancel')
965
966    def getFiles(self):
967        files = self.context.getPendingFiles(sort='date')
968        for file in files:
969            name = file.name
970            if not name.endswith('.csv') and not name.endswith('.pending'):
971                continue
972            yield file
973
974    def update(self, filename=None, select=None, cancel=None):
975        if cancel is not None:
976            self.flash(_('Import aborted.'))
977            self.redirect(self.url(self.context))
978            return
979        if select is not None:
980            # A filename was selected
981            session = ISession(self.request)['waeup.kofa']
982            session['import_filename'] = select
983            self.redirect(self.url(self.context, '@@import2'))
984
985class DatacenterImportStep2(KofaPage):
986    """Manual import step 2: choose processor
987    """
988    grok.context(IDataCenter)
989    grok.name('import2')
990    grok.template('datacenterimport2page')
991    grok.require('waeup.importData')
992    label = _(u'Process CSV file')
993    pnav = 0
994    cancel_button =_(u'Cancel')
995    back_button =_(u'Back to step 1')
996    proceed_button =_(u'Proceed to step 3')
997
998    filename = None
999    mode = 'create'
1000    importer = None
1001    mode_locked = False
1002
1003    def getPreviewHeader(self):
1004        """Get the header fields of attached CSV file.
1005        """
1006        reader = csv.reader(open(self.fullpath, 'rb'))
1007        return reader.next()
1008
1009    def getPreviewTable(self):
1010        """Get transposed table with 3 sample records.
1011
1012        The first column contains the headers.
1013        """
1014        if not self.reader:
1015            return
1016        header = self.getPreviewHeader()
1017        num = 0
1018        data = []
1019        for line in self.reader:
1020            if num > 2:
1021                break
1022            num += 1
1023            data.append(line)
1024        result = []
1025        for name in header:
1026            result_line = []
1027            result_line.append(name)
1028            for d in data:
1029                result_line.append(d[name])
1030            result.append(result_line)
1031        return result
1032
1033    def getImporters(self):
1034        importers = getAllUtilitiesRegisteredFor(IBatchProcessor)
1035        importers = sorted(
1036            [dict(title=x.name, name=x.util_name) for x in importers])
1037        return importers
1038
1039    def getModeFromFilename(self, filename):
1040        """Lookup filename or path and return included mode name or None.
1041        """
1042        if not filename.endswith('.pending.csv'):
1043            return None
1044        base = os.path.basename(filename)
1045        parts = base.rsplit('.', 3)
1046        if len(parts) != 4:
1047            return None
1048        if parts[1] not in ['create', 'update', 'remove']:
1049            return None
1050        return parts[1]
1051
1052    def getWarnings(self):
1053        import sys
1054        result = []
1055        try:
1056            headerfields = self.getPreviewHeader()
1057            headerfields_clean = list(set(headerfields))
1058            if len(headerfields) > len(headerfields_clean):
1059                result.append(
1060                    _("Double headers: each column name may only appear once. "))
1061        except:
1062            fatal = '%s' % sys.exc_info()[1]
1063            result.append(fatal)
1064        if result:
1065            warnings = ""
1066            for line in result:
1067                warnings += line + '<br />'
1068            warnings += _('Replace imported file!')
1069            return warnings
1070        return False
1071
1072    def update(self, mode=None, importer=None,
1073               back1=None, cancel=None, proceed=None):
1074        session = ISession(self.request)['waeup.kofa']
1075        self.filename = session.get('import_filename', None)
1076
1077        if self.filename is None or back1 is not None:
1078            self.redirect(self.url(self.context, '@@import1'))
1079            return
1080        if cancel is not None:
1081            self.flash(_('Import aborted.'))
1082            self.redirect(self.url(self.context))
1083            return
1084        self.mode = mode or session.get('import_mode', self.mode)
1085        filename_mode = self.getModeFromFilename(self.filename)
1086        if filename_mode is not None:
1087            self.mode = filename_mode
1088            self.mode_locked = True
1089        self.importer = importer or session.get('import_importer', None)
1090        session['import_importer'] = self.importer
1091        if self.importer and 'update' in self.importer:
1092            if self.mode != 'update':
1093                self.flash(_('Update mode only!'))
1094                self.mode_locked = True
1095                self.mode = 'update'
1096                proceed = None
1097        session['import_mode'] = self.mode
1098        if proceed is not None:
1099            self.redirect(self.url(self.context, '@@import3'))
1100            return
1101        self.fullpath = os.path.join(self.context.storage, self.filename)
1102        warnings = self.getWarnings()
1103        if not warnings:
1104            self.reader = csv.DictReader(open(self.fullpath, 'rb'))
1105        else:
1106            self.reader = ()
1107            self.flash(warnings)
1108
1109class DatacenterImportStep3(KofaPage):
1110    """Manual import step 3: modify header
1111    """
1112    grok.context(IDataCenter)
1113    grok.name('import3')
1114    grok.template('datacenterimport3page')
1115    grok.require('waeup.importData')
1116    label = _(u'Process CSV file')
1117    pnav = 0
1118    cancel_button =_(u'Cancel')
1119    reset_button =_(u'Reset')
1120    update_button =_(u'Set headerfields')
1121    back_button =_(u'Back to step 2')
1122    proceed_button =_(u'Perform import')
1123
1124    filename = None
1125    mode = None
1126    importername = None
1127
1128    @property
1129    def nextstep(self):
1130        return self.url(self.context, '@@import4')
1131
1132    def getPreviewHeader(self):
1133        """Get the header fields of attached CSV file.
1134        """
1135        reader = csv.reader(open(self.fullpath, 'rb'))
1136        return reader.next()
1137
1138    def getPreviewTable(self):
1139        """Get transposed table with 1 sample record.
1140
1141        The first column contains the headers.
1142        """
1143        if not self.reader:
1144            return
1145        headers = self.getPreviewHeader()
1146        num = 0
1147        data = []
1148        for line in self.reader:
1149            if num > 0:
1150                break
1151            num += 1
1152            data.append(line)
1153        result = []
1154        field_num = 0
1155        for name in headers:
1156            result_line = []
1157            result_line.append(field_num)
1158            field_num += 1
1159            for d in data:
1160                result_line.append(d[name])
1161            result.append(result_line)
1162        return result
1163
1164    def getPossibleHeaders(self):
1165        """Get the possible headers.
1166
1167        The headers are described as dicts {value:internal_name,
1168        title:displayed_name}
1169        """
1170        result = [dict(title='<IGNORE COL>', value='--IGNORE--')]
1171        headers = self.importer.getHeaders()
1172        result.extend([dict(title=x, value=x) for x in headers])
1173        return result
1174
1175    def getWarnings(self):
1176        import sys
1177        result = []
1178        try:
1179            self.importer.checkHeaders(self.headerfields, mode=self.mode)
1180        except:
1181            fatal = '%s' % sys.exc_info()[1]
1182            result.append(fatal)
1183        if result:
1184            warnings = ""
1185            for line in result:
1186                warnings += line + '<br />'
1187            warnings += _('Edit headers or replace imported file!')
1188            return warnings
1189        return False
1190
1191    def update(self, headerfield=None, back2=None, cancel=None, proceed=None):
1192        datatable.need()
1193        session = ISession(self.request)['waeup.kofa']
1194        self.filename = session.get('import_filename', None)
1195        self.mode = session.get('import_mode', None)
1196        self.importername = session.get('import_importer', None)
1197
1198        if None in (self.filename, self.mode, self.importername):
1199            self.redirect(self.url(self.context, '@@import2'))
1200            return
1201        if back2 is not None:
1202            self.redirect(self.url(self.context ,'@@import2'))
1203            return
1204        if cancel is not None:
1205            self.flash(_('Import aborted.'))
1206            self.redirect(self.url(self.context))
1207            return
1208
1209        self.fullpath = os.path.join(self.context.storage, self.filename)
1210        self.headerfields = headerfield or self.getPreviewHeader()
1211        session['import_headerfields'] = self.headerfields
1212
1213        if proceed is not None:
1214            self.redirect(self.url(self.context, '@@import4'))
1215            return
1216        self.importer = getUtility(IBatchProcessor, name=self.importername)
1217        self.reader = csv.DictReader(open(self.fullpath, 'rb'))
1218        warnings = self.getWarnings()
1219        if warnings:
1220            self.flash(warnings)
1221
1222class DatacenterImportStep4(KofaPage):
1223    """Manual import step 4: do actual import
1224    """
1225    grok.context(IDataCenter)
1226    grok.name('import4')
1227    grok.template('datacenterimport4page')
1228    grok.require('waeup.importData')
1229    label = _(u'Process CSV file')
1230    pnav = 0
1231    show_button =_(u'View processing log')
1232    back_button =_(u'Back to data center')
1233
1234    filename = None
1235    mode = None
1236    importername = None
1237    headerfields = None
1238    warnnum = None
1239
1240    def update(self, back=None, finish=None, showlog=None):
1241        if finish is not None:
1242            self.redirect(self.url(self.context))
1243            return
1244        session = ISession(self.request)['waeup.kofa']
1245        self.filename = session.get('import_filename', None)
1246        self.mode = session.get('import_mode', None)
1247        self.importername = session.get('import_importer', None)
1248        self.headerfields = session.get('import_headerfields', None)
1249
1250        if None in (self.filename, self.mode, self.importername,
1251                    self.headerfields):
1252            self.redirect(self.url(self.context, '@@import3'))
1253            return
1254
1255        if showlog is not None:
1256            logfilename = "datacenter.log"
1257            session['logname'] = logfilename
1258            self.redirect(self.url(self.context, '@@show'))
1259            return
1260
1261        self.fullpath = os.path.join(self.context.storage, self.filename)
1262        self.importer = getUtility(IBatchProcessor, name=self.importername)
1263
1264        # Perform batch processing...
1265        # XXX: This might be better placed in datacenter module.
1266        (linenum, self.warn_num,
1267         fin_path, pending_path) = self.importer.doImport(
1268            self.fullpath, self.headerfields, self.mode,
1269            self.request.principal.id, logger=self.context.logger)
1270        # Put result files in desired locations...
1271        self.context.distProcessedFiles(
1272            self.warn_num == 0, self.fullpath, fin_path, pending_path,
1273            self.mode)
1274
1275        if self.warn_num:
1276            self.flash(_('Processing of ${a} rows failed.',
1277                mapping = {'a':self.warn_num}))
1278        self.flash(_('Successfully processed ${a} rows.',
1279            mapping = {'a':linenum - self.warn_num}))
1280
1281class DatacenterLogsOverview(KofaPage):
1282    grok.context(IDataCenter)
1283    grok.name('logs')
1284    grok.template('datacenterlogspage')
1285    grok.require('waeup.manageDataCenter')
1286    label = _(u'Show logfiles')
1287    pnav = 0
1288    back_button = _(u'Back to Data Center')
1289    show_button = _(u'Show')
1290
1291    def update(self, back=None):
1292        if back is not None:
1293            self.redirect(self.url(self.context))
1294            return
1295        self.files = self.context.getLogFiles()
1296
1297class DatacenterLogsFileview(KofaPage):
1298    grok.context(IDataCenter)
1299    grok.name('show')
1300    grok.template('datacenterlogsshowfilepage')
1301    grok.require('waeup.manageDataCenter')
1302    title = _(u'Data Center')
1303    pnav = 0
1304    search_button = _('Search')
1305    back_button = _('Back')
1306    placeholder = _('Enter a regular expression here...')
1307
1308    def label(self):
1309        return "Logfile %s" % self.filename
1310
1311    def update(self, back=None, query=None, logname=None):
1312        if os.name != 'posix':
1313            self.flash(
1314                _('Log files can only be searched ' +
1315                  'on Unix-based operating systems.'))
1316            self.redirect(self.url(self.context, '@@logs'))
1317            return
1318        if back is not None or logname is None:
1319            self.redirect(self.url(self.context, '@@logs'))
1320            return
1321        self.filename = logname
1322        self.query = query
1323        if search is None or not query:
1324            return
1325        try:
1326            self.result = ''.join(
1327                self.context.queryLogfiles(logname, query))
1328        except ValueError:
1329            self.flash(_('Invalid search expression.'))
1330            return
1331        if not self.result:
1332            self.flash(_('No search results found.'))
1333        return
1334
1335class DatacenterSettings(KofaPage):
1336    grok.context(IDataCenter)
1337    grok.name('manage')
1338    grok.template('datacentermanagepage')
1339    grok.require('waeup.managePortal')
1340    label = _('Edit data center settings')
1341    pnav = 0
1342    save_button =_(u'Save')
1343    reset_button =_(u'Reset')
1344    cancel_button =_(u'Cancel')
1345
1346    def update(self, newpath=None, move=False, overwrite=False,
1347               save=None, cancel=None):
1348        if move:
1349            move = True
1350        if overwrite:
1351            overwrite = True
1352        if newpath is None:
1353            return
1354        if cancel is not None:
1355            self.redirect(self.url(self.context))
1356            return
1357        try:
1358            not_copied = self.context.setStoragePath(newpath, move=move)
1359            for name in not_copied:
1360                self.flash(_('File already existed (not copied): ${a}',
1361                    mapping = {'a':name}))
1362        except:
1363            self.flash(_('Given storage path cannot be used.'))
1364            self.flash(_('Error: ${a}', mapping = {'a':sys.exc_info()[1]}))
1365            return
1366        if newpath:
1367            self.flash(_('New storage path succefully set.'))
1368            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1369            self.context.logger.info(
1370                '%s - storage path set: %s' % (ob_class, newpath))
1371            self.redirect(self.url(self.context))
1372        return
1373
1374class ExportCSVPage(KofaPage):
1375    grok.context(IDataCenter)
1376    grok.name('export')
1377    grok.template('datacenterexportpage')
1378    grok.require('waeup.manageDataCenter')
1379    label = _('Download portal data as CSV file')
1380    pnav = 0
1381    export_button = _(u'Create CSV file')
1382    _running_exports = None
1383
1384    def getExporters(self):
1385        utils = getUtilitiesFor(ICSVExporter)
1386        title_name_tuples = [
1387            (util.title, name) for name, util in utils]
1388        return sorted(title_name_tuples)
1389
1390    def job_finished(self, status):
1391        return status == 'completed'
1392
1393    def getRunningExports(self):
1394        """Returns running exports as list of tuples.
1395
1396        Only exports triggered by the current user (identified by
1397        principal.id) are returned.
1398
1399        Each tuple has the form (<STATUS>, <STATUS_TITLE>, <EXPORTER_NAME>).
1400
1401        ``STATUS``:
1402           the status as machine readable string (something like
1403           ``'completed'``)
1404
1405        ``STATUS_TITLE``:
1406           status of export as translated string.
1407
1408        ``EXPORTER_NAME``:
1409           string representing the exporter title used when triggering
1410           the export job.
1411        """
1412        if self._running_exports is None:
1413            self._running_exports = self._getRunningExports()
1414        return self._running_exports
1415
1416    def _getRunningExports(self):
1417        result = self.context.get_export_jobs_status(self.user_id)
1418        return result
1419
1420    def update(self, export=None, start_export=None, exporter=None,
1421               discard=None, download=None):
1422        self.user_id = self.request.principal.id
1423        if discard:
1424            myjobs = self.context.get_running_export_jobs(self.user_id)
1425            for entry in myjobs:
1426                self.context.delete_export_entry(entry)
1427                self.flash(_('Discarded export result'))
1428            return
1429        if download:
1430            myjobs = self.context.get_running_export_jobs(self.user_id)
1431            if not len(myjobs):
1432                self.flash(_('This export was already discarded.'))
1433                return
1434            job_id = myjobs[0][0]
1435            self.redirect(
1436                self.url(self.context, 'export.csv') + '?job_id=%s' % job_id)
1437            return
1438        if None in (start_export, exporter):
1439            return
1440        job_id = self.context.start_export_job(
1441            exporter, self.request.principal.id)
1442        self.redirect(self.url(self.context, 'export'))
1443        return
1444
1445class ExportCSVView(grok.View):
1446    grok.context(IDataCenter)
1447    grok.name('export.csv')
1448    grok.require('waeup.manageDataCenter')
1449
1450    def render(self, exporter=None, job_id=None):
1451
1452        manager = getUtility(IJobManager)
1453        job = manager.get(job_id)
1454        if job is None:
1455            return
1456        if hasattr(job.result, 'traceback'):
1457            # XXX: Some error happened. Do something more approriate here...
1458            return
1459        path = job.result
1460        if not os.path.exists(path):
1461            # XXX: Do something more appropriate here...
1462            return
1463        result = open(path, 'rb').read()
1464        acronym = grok.getSite()['configuration'].acronym.replace(' ','')
1465        filename = "%s_%s" % (acronym, os.path.basename(path))
1466        self.response.setHeader(
1467            'Content-Type', 'text/csv; charset=UTF-8')
1468        self.response.setHeader(
1469            'Content-Disposition', 'attachment; filename="%s' % filename)
1470        # remove job and running_exports entry from context
1471        self.context.delete_export_entry(
1472            self.context.entry_from_job_id(job_id))
1473        return result
1474
1475class ExportXMLPage(grok.View):
1476    """Deliver an XML representation of the context.
1477    """
1478    grok.name('export.xml')
1479    grok.require('waeup.managePortal')
1480
1481    def render(self):
1482        exporter = IKofaXMLExporter(self.context)
1483        xml = exporter.export().read()
1484        self.response.setHeader(
1485            'Content-Type', 'text/xml; charset=UTF-8')
1486        return xml
1487
1488class ImportXMLPage(KofaPage):
1489    """Replace the context object by an object created from an XML
1490       representation.
1491
1492       XXX: This does not work for ISite objects, i.e. for instance
1493            for complete University objects.
1494    """
1495    grok.name('importxml')
1496    grok.require('waeup.managePortal')
1497
1498    def update(self, xmlfile=None, CANCEL=None, SUBMIT=None):
1499        if CANCEL is not None:
1500            self.redirect(self.url(self.context))
1501            return
1502        if not xmlfile:
1503            return
1504        importer = IKofaXMLImporter(self.context)
1505        obj = importer.doImport(xmlfile)
1506        if type(obj) != type(self.context):
1507            return
1508        parent = self.context.__parent__
1509        name = self.context.__name__
1510        self.context = obj
1511        if hasattr(parent, name):
1512            setattr(parent, name, obj)
1513        else:
1514            del parent[name]
1515            parent[name] = obj
1516            pass
1517        return
1518
1519
1520#
1521# FacultiesContainer pages...
1522#
1523
1524class FacultiesContainerPage(KofaPage):
1525    """ Index page for faculty containers.
1526    """
1527    grok.context(IFacultiesContainer)
1528    grok.require('waeup.viewAcademics')
1529    grok.name('index')
1530    label = _('Academic Section')
1531    pnav = 1
1532    grok.template('facultypage')
1533
1534class FacultiesContainerManageFormPage(KofaEditFormPage):
1535    """Manage the basic properties of a `Faculty` instance.
1536    """
1537    grok.context(IFacultiesContainer)
1538    grok.name('manage')
1539    grok.require('waeup.manageAcademics')
1540    grok.template('facultiescontainermanagepage')
1541    pnav = 1
1542    taboneactions = [_('Add faculty'), _('Remove selected'),_('Cancel')]
1543    subunits = _('Faculties')
1544
1545    @property
1546    def label(self):
1547        return _('Manage academic section')
1548
1549    def update(self):
1550        warning.need()
1551        return super(FacultiesContainerManageFormPage, self).update()
1552
1553    @jsaction(_('Remove selected'))
1554    def delFaculties(self, **data):
1555        delSubobjects(self, redirect='@@manage', tab='1')
1556        return
1557
1558    @action(_('Add faculty'), validator=NullValidator)
1559    def addFaculty(self, **data):
1560        self.redirect(self.url(self.context, '@@add'))
1561        return
1562
1563    @action(_('Cancel'), validator=NullValidator)
1564    def cancel(self, **data):
1565        self.redirect(self.url(self.context))
1566        return
1567
1568
1569class FacultyAddFormPage(KofaAddFormPage):
1570    """ Page form to add a new faculty to a faculty container.
1571    """
1572    grok.context(IFacultiesContainer)
1573    grok.require('waeup.manageAcademics')
1574    grok.name('add')
1575    label = _('Add faculty')
1576    form_fields = grok.AutoFields(IFacultyAdd)
1577    pnav = 1
1578
1579    @action(_('Add faculty'), style='primary')
1580    def addFaculty(self, **data):
1581        faculty = createObject(u'waeup.Faculty')
1582        self.applyData(faculty, **data)
1583        try:
1584            self.context.addFaculty(faculty)
1585        except KeyError:
1586            self.flash(_('The faculty code chosen already exists.'))
1587            return
1588        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1589        self.context.__parent__.logger.info(
1590            '%s - added: %s' % (ob_class, faculty.code))
1591        self.redirect(self.url(self.context, u'@@manage')+'?tab1')
1592        return
1593
1594    @action(_('Cancel'), validator=NullValidator)
1595    def cancel(self, **data):
1596        self.redirect(self.url(self.context))
1597
1598#
1599# Faculty pages
1600#
1601class FacultyPage(KofaPage):
1602    """Index page of faculties.
1603    """
1604    grok.context(IFaculty)
1605    grok.require('waeup.viewAcademics')
1606    grok.name('index')
1607    pnav = 1
1608
1609    @property
1610    def label(self):
1611        return _('Departments')
1612
1613class FacultyManageFormPage(KofaEditFormPage):
1614    """Manage the basic properties of a `Faculty` instance.
1615    """
1616    grok.context(IFaculty)
1617    grok.name('manage')
1618    grok.require('waeup.manageAcademics')
1619    grok.template('facultymanagepage')
1620    pnav = 1
1621    subunits = _('Departments')
1622    taboneactions = [_('Save'),_('Cancel')]
1623    tabtwoactions = [_('Add department'), _('Remove selected'),_('Cancel')]
1624    tabthreeactions1 = [_('Remove selected local roles')]
1625    tabthreeactions2 = [_('Add local role')]
1626
1627    form_fields = grok.AutoFields(IFaculty)
1628
1629    @property
1630    def label(self):
1631        return _('Manage faculty')
1632
1633    def update(self):
1634        tabs.need()
1635        self.tab1 = self.tab2 = self.tab3 = ''
1636        qs = self.request.get('QUERY_STRING', '')
1637        if not qs:
1638            qs = 'tab1'
1639        setattr(self, qs, 'active')
1640        warning.need()
1641        datatable.need()
1642        return super(FacultyManageFormPage, self).update()
1643
1644    def getLocalRoles(self):
1645        roles = ILocalRolesAssignable(self.context)
1646        return roles()
1647
1648    def getUsers(self):
1649        return get_all_users()
1650
1651    def getUsersWithLocalRoles(self):
1652        return get_users_with_local_roles(self.context)
1653
1654    @jsaction(_('Remove selected'))
1655    def delDepartments(self, **data):
1656        delSubobjects(self, redirect='@@manage', tab='2')
1657        return
1658
1659    @action(_('Save'), style='primary')
1660    def save(self, **data):
1661        return msave(self, **data)
1662
1663    @action(_('Cancel'), validator=NullValidator)
1664    def cancel(self, **data):
1665        self.redirect(self.url(self.context))
1666        return
1667
1668    @action(_('Add department'), validator=NullValidator)
1669    def addSubunit(self, **data):
1670        self.redirect(self.url(self.context, '@@add'))
1671        return
1672
1673    @action(_('Add local role'), validator=NullValidator)
1674    def addLocalRole(self, **data):
1675        return add_local_role(self, '3', **data)
1676
1677    @action(_('Remove selected local roles'))
1678    def delLocalRoles(self, **data):
1679        return del_local_roles(self,3,**data)
1680
1681class DepartmentAddFormPage(KofaAddFormPage):
1682    """Add a department to a faculty.
1683    """
1684    grok.context(IFaculty)
1685    grok.name('add')
1686    grok.require('waeup.manageAcademics')
1687    label = _('Add department')
1688    form_fields = grok.AutoFields(IDepartmentAdd)
1689    pnav = 1
1690
1691    @action(_('Add department'), style='primary')
1692    def addDepartment(self, **data):
1693        department = createObject(u'waeup.Department')
1694        self.applyData(department, **data)
1695        try:
1696            self.context.addDepartment(department)
1697        except KeyError:
1698            self.flash(_('The code chosen already exists in this faculty.'))
1699            return
1700        self.status = self.flash(
1701            _("Department ${a} added.", mapping = {'a':data['code']}))
1702        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1703        self.context.__parent__.__parent__.logger.info(
1704            '%s - added: %s' % (ob_class, data['code']))
1705        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
1706        return
1707
1708    @action(_('Cancel'), validator=NullValidator)
1709    def cancel(self, **data):
1710        self.redirect(self.url(self.context))
1711        return
1712
1713#
1714# Department pages
1715#
1716class DepartmentPage(KofaPage):
1717    """Department index page.
1718    """
1719    grok.context(IDepartment)
1720    grok.require('waeup.viewAcademics')
1721    grok.name('index')
1722    pnav = 1
1723    label = _('Courses and Certificates')
1724
1725    def update(self):
1726        tabs.need()
1727        datatable.need()
1728        super(DepartmentPage, self).update()
1729        return
1730
1731    def getCourses(self):
1732        """Get a list of all stored courses.
1733        """
1734        for key, val in self.context.courses.items():
1735            url = self.url(val)
1736            yield(dict(url=url, name=key, container=val))
1737
1738    def getCertificates(self):
1739        """Get a list of all stored certificates.
1740        """
1741        for key, val in self.context.certificates.items():
1742            url = self.url(val)
1743            yield(dict(url=url, name=key, container=val))
1744
1745class ShowStudentsInDepartmentPage(KofaPage):
1746    """Page that lists all students in the department.
1747    """
1748    grok.context(IDepartment)
1749    grok.require('waeup.showStudents')
1750    grok.name('showdepstudents')
1751    grok.template('showstudentspage')
1752    pnav = 1
1753    label = _('Students')
1754
1755    @property
1756    def getStudents(self):
1757        hitlist = searchstudents(query=self.context.code,
1758            searchtype='depcode', view=self)
1759        return hitlist
1760
1761    def update(self, *args, **kw):
1762        datatable.need()
1763        return
1764
1765class ShowStudentsInCertificatePage(ShowStudentsInDepartmentPage):
1766    """Page that lists all students studying a certificate.
1767    """
1768    grok.context(ICertificate)
1769    grok.require('waeup.showStudents')
1770    grok.name('showcertstudents')
1771    pnav = 1
1772    label = _('Students')
1773
1774    @property
1775    def getStudents(self):
1776        hitlist = searchstudents(query=self.context.code,
1777            searchtype='certcode', view=self)
1778        return hitlist
1779
1780class DepartmentManageFormPage(KofaEditFormPage):
1781    """Manage the basic properties of a `Department` instance.
1782    """
1783    grok.context(IDepartment)
1784    grok.name('manage')
1785    grok.require('waeup.manageAcademics')
1786    pnav = 1
1787    grok.template('departmentmanagepage')
1788    taboneactions = [_('Save'),_('Cancel')]
1789    tabtwoactions = [_('Add course'), _('Remove selected courses'),_('Cancel')]
1790    tabthreeactions = [_('Add certificate'), _('Remove selected certificates'),
1791                       _('Cancel')]
1792    tabfouractions1 = [_('Remove selected local roles')]
1793    tabfouractions2 = [_('Add local role')]
1794
1795    form_fields = grok.AutoFields(IDepartment)
1796
1797    @property
1798    def label(self):
1799        return _('Manage department')
1800
1801    def getCourses(self):
1802        """Get a list of all stored courses.
1803        """
1804        for key, val in self.context.courses.items():
1805            url = self.url(val)
1806            yield(dict(url=url, name=key, container=val))
1807
1808    def getCertificates(self):
1809        """Get a list of all stored certificates.
1810        """
1811        for key, val in self.context.certificates.items():
1812            url = self.url(val)
1813            yield(dict(url=url, name=key, container=val))
1814
1815    def update(self):
1816        tabs.need()
1817        self.tab1 = self.tab2 = self.tab3 = self.tab4 = ''
1818        qs = self.request.get('QUERY_STRING', '')
1819        if not qs:
1820            qs = 'tab1'
1821        setattr(self, qs, 'active')
1822        warning.need()
1823        datatable.need()
1824        super(DepartmentManageFormPage, self).update()
1825        return
1826
1827    def getLocalRoles(self):
1828        roles = ILocalRolesAssignable(self.context)
1829        return roles()
1830
1831    def getUsers(self):
1832        return get_all_users()
1833
1834    def getUsersWithLocalRoles(self):
1835        return get_users_with_local_roles(self.context)
1836
1837    @action(_('Save'), style='primary')
1838    def save(self, **data):
1839        return msave(self, **data)
1840
1841    @jsaction(_('Remove selected courses'))
1842    def delCourses(self, **data):
1843        delSubobjects(
1844            self, redirect='@@manage', tab='2', subcontainer='courses')
1845        return
1846
1847    @jsaction(_('Remove selected certificates'))
1848    def delCertificates(self, **data):
1849        delSubobjects(
1850            self, redirect='@@manage', tab='3', subcontainer='certificates')
1851        return
1852
1853    @action(_('Add course'), validator=NullValidator)
1854    def addCourse(self, **data):
1855        self.redirect(self.url(self.context, 'addcourse'))
1856        return
1857
1858    @action(_('Add certificate'), validator=NullValidator)
1859    def addCertificate(self, **data):
1860        self.redirect(self.url(self.context, 'addcertificate'))
1861        return
1862
1863    @action(_('Cancel'), validator=NullValidator)
1864    def cancel(self, **data):
1865        self.redirect(self.url(self.context))
1866        return
1867
1868    @action(_('Add local role'), validator=NullValidator)
1869    def addLocalRole(self, **data):
1870        return add_local_role(self, 4, **data)
1871
1872    @action(_('Remove selected local roles'))
1873    def delLocalRoles(self, **data):
1874        return del_local_roles(self,4,**data)
1875
1876class CourseAddFormPage(KofaAddFormPage):
1877    """Add-form to add course to a department.
1878    """
1879    grok.context(IDepartment)
1880    grok.name('addcourse')
1881    grok.require('waeup.manageAcademics')
1882    label = _(u'Add course')
1883    form_fields = grok.AutoFields(ICourseAdd)
1884    pnav = 1
1885
1886    @action(_('Add course'))
1887    def addCourse(self, **data):
1888        course = createObject(u'waeup.Course')
1889        self.applyData(course, **data)
1890        try:
1891            self.context.courses.addCourse(course)
1892        except DuplicationError, error:
1893            # signals duplication error
1894            entries = error.entries
1895            for entry in entries:
1896                # do not use error.msg but render a more detailed message instead
1897                # and show links to all certs with same code
1898                message = _('A course with same code already exists: ')
1899                message += '<a href="%s">%s</a>' % (
1900                    self.url(entry), getattr(entry, '__name__', u'Unnamed'))
1901                self.flash(message)
1902            self.redirect(self.url(self.context, u'@@addcourse'))
1903            return
1904        message = _(u'Course ${a} successfully created.', mapping = {'a':course.code})
1905        self.flash(message)
1906        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1907        self.context.__parent__.__parent__.__parent__.logger.info(
1908            '%s - added: %s' % (ob_class, data['code']))
1909        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
1910        return
1911
1912    @action(_('Cancel'), validator=NullValidator)
1913    def cancel(self, **data):
1914        self.redirect(self.url(self.context))
1915        return
1916
1917class CertificateAddFormPage(KofaAddFormPage):
1918    """Add-form to add certificate to a department.
1919    """
1920    grok.context(IDepartment)
1921    grok.name('addcertificate')
1922    grok.require('waeup.manageAcademics')
1923    label = _(u'Add certificate')
1924    form_fields = grok.AutoFields(ICertificateAdd)
1925    pnav = 1
1926
1927    @action(_('Add certificate'))
1928    def addCertificate(self, **data):
1929        certificate = createObject(u'waeup.Certificate')
1930        self.applyData(certificate, **data)
1931        try:
1932            self.context.certificates.addCertificate(certificate)
1933        except DuplicationError, error:
1934            # signals duplication error
1935            entries = error.entries
1936            for entry in entries:
1937                # do not use error.msg but render a more detailed message instead
1938                # and show links to all certs with same code
1939                message = _('A certificate with same code already exists: ')
1940                message += '<a href="%s">%s</a>' % (
1941                    self.url(entry), getattr(entry, '__name__', u'Unnamed'))
1942                self.flash(message)
1943            self.redirect(self.url(self.context, u'@@addcertificate'))
1944            return
1945        message = _(u'Certificate ${a} successfully created.', mapping = {'a':certificate.code})
1946        self.flash(message)
1947        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
1948        self.context.__parent__.__parent__.__parent__.logger.info(
1949            '%s - added: %s' % (ob_class, data['code']))
1950        self.redirect(self.url(self.context, u'@@manage')+'?tab3')
1951        return
1952
1953    @action(_('Cancel'), validator=NullValidator)
1954    def cancel(self): #, **data):
1955        self.redirect(self.url(self.context))
1956        return
1957
1958#
1959# Courses pages
1960#
1961class CoursePage(KofaDisplayFormPage):
1962    """Course index page.
1963    """
1964    grok.context(ICourse)
1965    grok.name('index')
1966    grok.require('waeup.viewAcademics')
1967    pnav = 1
1968    form_fields = grok.AutoFields(ICourse)
1969
1970    @property
1971    def label(self):
1972        return '%s (%s)' % (self.context.title, self.context.code)
1973
1974class CourseManageFormPage(KofaEditFormPage):
1975    """Edit form page for courses.
1976    """
1977    grok.context(ICourse)
1978    grok.name('manage')
1979    grok.require('waeup.manageAcademics')
1980    label = _(u'Edit course')
1981    pnav = 1
1982
1983    form_fields = grok.AutoFields(ICourse)
1984
1985    @action(_('Save'), style='primary')
1986    def save(self, **data):
1987        return msave(self, **data)
1988
1989    @action(_('Cancel'), validator=NullValidator)
1990    def cancel(self, **data):
1991        self.redirect(self.url(self.context))
1992        return
1993
1994#
1995# Certificate pages
1996#
1997class CertificatePage(KofaDisplayFormPage):
1998    """Index page for certificates.
1999    """
2000    grok.context(ICertificate)
2001    grok.name('index')
2002    grok.require('waeup.viewAcademics')
2003    pnav = 1
2004    form_fields = grok.AutoFields(ICertificate)
2005    grok.template('certificatepage')
2006
2007    @property
2008    def label(self):
2009        return "%s (%s)" % (self.context.title, self.context.code)
2010
2011    def update(self):
2012        datatable.need()
2013        return super(CertificatePage, self).update()
2014
2015class CertificateManageFormPage(KofaEditFormPage):
2016    """Manage the properties of a `Certificate` instance.
2017    """
2018    grok.context(ICertificate)
2019    grok.name('manage')
2020    grok.require('waeup.manageAcademics')
2021    pnav = 1
2022    label = _('Edit certificate')
2023
2024    form_fields = grok.AutoFields(ICertificate)
2025
2026    pnav = 1
2027    grok.template('certificatemanagepage')
2028    taboneactions = [_('Save'),_('Cancel')]
2029    tabtwoactions = [_('Add certificate course'),
2030                     _('Remove selected certificate courses'),_('Cancel')]
2031    tabthreeactions1 = [_('Remove selected local roles')]
2032    tabthreeactions2 = [_('Add local role')]
2033
2034    @property
2035    def label(self):
2036        return _('Manage certificate')
2037
2038    def update(self):
2039        tabs.need()
2040        self.tab1 = self.tab2 = self.tab3 = ''
2041        qs = self.request.get('QUERY_STRING', '')
2042        if not qs:
2043            qs = 'tab1'
2044        setattr(self, qs, 'active')
2045        warning.need()
2046        datatable.need()
2047        return super(CertificateManageFormPage, self).update()
2048
2049    @action(_('Save'), style='primary')
2050    def save(self, **data):
2051        return msave(self, **data)
2052
2053    @jsaction(_('Remove selected certificate courses'))
2054    def delCertificateCourses(self, **data):
2055        delSubobjects(self, redirect='@@manage', tab='2')
2056        return
2057
2058    @action(_('Add certificate course'), validator=NullValidator)
2059    def addCertificateCourse(self, **data):
2060        self.redirect(self.url(self.context, 'addcertificatecourse'))
2061        return
2062
2063    @action(_('Cancel'), validator=NullValidator)
2064    def cancel(self, **data):
2065        self.redirect(self.url(self.context))
2066        return
2067
2068    def getLocalRoles(self):
2069        roles = ILocalRolesAssignable(self.context)
2070        return roles()
2071
2072    def getUsers(self):
2073        return get_all_users()
2074
2075    def getUsersWithLocalRoles(self):
2076        return get_users_with_local_roles(self.context)
2077
2078    @action(_('Add local role'), validator=NullValidator)
2079    def addLocalRole(self, **data):
2080        return add_local_role(self, 3, **data)
2081
2082    @action(_('Remove selected local roles'))
2083    def delLocalRoles(self, **data):
2084        return del_local_roles(self,3,**data)
2085
2086
2087class CertificateCourseAddFormPage(KofaAddFormPage):
2088    """Add-page to add a course ref to a certificate
2089    """
2090    grok.context(ICertificate)
2091    grok.name('addcertificatecourse')
2092    grok.require('waeup.manageAcademics')
2093    form_fields = grok.AutoFields(ICertificateCourseAdd)
2094    pnav = 1
2095    label = _('Add certificate course')
2096
2097    @action(_('Add certificate course'))
2098    def addCertcourse(self, **data):
2099        try:
2100            self.context.addCertCourse(**data)
2101        except KeyError:
2102            self.status = self.flash(_('The chosen certificate course is already '
2103                                  'part of this certificate.'))
2104            return
2105        self.status = self.flash(
2106            _("certificate course ${a}_${b} added.",
2107            mapping = {'a': data['course'].code, 'b': data['level']}))
2108        code = "%s_%s" % (data['course'].code, data['level'])
2109        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
2110        grok.getSite().logger.info('%s - added: %s' % (ob_class, code))
2111        self.redirect(self.url(self.context, u'@@manage')+'?tab2')
2112        return
2113
2114    @action(_('Cancel'), validator=NullValidator)
2115    def cancel(self, **data):
2116        self.redirect(self.url(self.context))
2117        return
2118
2119
2120#
2121# Certificate course pages...
2122#
2123class CertificateCoursePage(KofaPage):
2124    """CertificateCourse index page.
2125    """
2126    grok.context(ICertificateCourse)
2127    grok.name('index')
2128    grok.require('waeup.viewAcademics')
2129    pnav = 1
2130    #form_fields = grok.AutoFields(ICertificateCourse)
2131
2132    @property
2133    def label(self):
2134        return '%s' % (self.context.longtitle())
2135
2136    @property
2137    def leveltitle(self):
2138        return course_levels.getTerm(self.context.level).title
2139
2140class CertificateCourseManageFormPage(KofaEditFormPage):
2141    """Manage the basic properties of a `CertificateCourse` instance.
2142    """
2143    grok.context(ICertificateCourse)
2144    grok.name('manage')
2145    grok.require('waeup.manageAcademics')
2146    form_fields = grok.AutoFields(ICertificateCourse)
2147    label = _('Edit certificate course')
2148    pnav = 1
2149
2150    @action(_('Save and return'), style='primary')
2151    def saveAndReturn(self, **data):
2152        parent = self.context.__parent__
2153        if self.context.level == data['level']:
2154            msave(self, **data)
2155        else:
2156            # The level changed. We have to create a new certcourse as
2157            # the key of the entry will change as well...
2158            old_level = self.context.level
2159            data['course'] = self.context.course
2160            parent.addCertCourse(**data)
2161            parent.delCertCourse(data['course'].code, level=old_level)
2162            self.flash(_('Form has been saved.'))
2163            old_code = "%s_%s" % (data['course'].code, old_level)
2164            new_code = "%s_%s" % (data['course'].code, data['level'])
2165            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
2166            grok.getSite().logger.info(
2167                '%s - renamed: %s to %s' % (ob_class, old_code, new_code))
2168        self.redirect(self.url(parent))
2169        return
2170
2171    @action(_('Cancel'), validator=NullValidator)
2172    def cancel(self, **data):
2173        self.redirect(self.url(self.context))
2174        return
2175
2176class ChangePasswordRequestPage(KofaForm):
2177    """Captcha'd page for all kind of users to request a password change.
2178    """
2179    grok.context(IUniversity)
2180    grok.name('changepw')
2181    grok.require('waeup.Anonymous')
2182    grok.template('changepw')
2183    label = _('Send me a new password')
2184    form_fields = grok.AutoFields(IChangePassword)
2185
2186    def update(self):
2187        # Handle captcha
2188        self.captcha = getUtility(ICaptchaManager).getCaptcha()
2189        self.captcha_result = self.captcha.verify(self.request)
2190        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
2191        return
2192
2193    def _searchUser(self, identifier, email):
2194        # Search student
2195        cat = queryUtility(ICatalog, name='students_catalog')
2196        results = cat.searchResults(
2197            #reg_number=(identifier, identifier),
2198            email=(email,email))
2199        for result in results:
2200            if result.student_id == identifier or \
2201                result.reg_number == identifier or \
2202                result.matric_number == identifier:
2203                return result
2204        # Search applicant
2205        cat = queryUtility(ICatalog, name='applicants_catalog')
2206        if cat is not None:
2207            results = cat.searchResults(
2208                #reg_number=(identifier, identifier),
2209                email=(email,email))
2210            for result in results:
2211                if result.applicant_id == identifier or \
2212                    result.reg_number == identifier:
2213                    return result
2214        # Search portal user
2215        user = grok.getSite()['users'].get(identifier, None)
2216        if user is not None and user.email == email:
2217            return user
2218        return None
2219
2220    @action(_('Send login credentials to email address'), style='primary')
2221    def request(self, **data):
2222        if not self.captcha_result.is_valid:
2223            # Captcha will display error messages automatically.
2224            # No need to flash something.
2225            return
2226        # Search student or applicant
2227        identifier = data['identifier']
2228        email = data['email']
2229        user = self._searchUser(identifier, email)
2230        if user is None:
2231            self.flash(_('No record found.'))
2232            return
2233        # Change password
2234        kofa_utils = getUtility(IKofaUtils)
2235        password = kofa_utils.genPassword()
2236        mandate = PasswordMandate()
2237        mandate.params['password'] = password
2238        mandate.params['user'] = user
2239        site = grok.getSite()
2240        site['mandates'].addMandate(mandate)
2241        # Send email with credentials
2242        args = {'mandate_id':mandate.mandate_id}
2243        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
2244        url_info = u'Confirmation link: %s' % mandate_url
2245        msg = _('You have successfully requested a password for the')
2246        success = kofa_utils.sendCredentials(
2247            IUserAccount(user),password,url_info,msg)
2248        if success:
2249            self.flash(_('An email with your user name and password ' +
2250                'has been sent to ${a}.', mapping = {'a':email}))
2251        else:
2252            self.flash(_('An smtp server error occurred.'))
2253        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
2254        self.context.logger.info(
2255            '%s - %s - %s' % (ob_class, data['identifier'], data['email']))
2256        return
Note: See TracBrowser for help on using the repository browser.