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

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

Fix import of student/applicant interfaces in w.k.browser.interfaces.
w.k.browser interfaces should not rely on interfaces defined in other
submodules except they are core part.

Also move purely UI-related interface from w.k.interfaces to w.k.browser.

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