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

Last change on this file since 9166 was 9166, checked in by uli, 13 years ago

All remaining changes from last weeks. Sorry for the mess.

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