## $Id: pages.py 7707 2012-02-27 08:38:38Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
""" Viewing components for SIRP objects.
"""
import copy
import csv
import grok
import os
import re
import sys
import time
import re
from zope import schema
from zope.authentication.interfaces import (
    IAuthentication, IUnauthenticatedPrincipal, ILogout)
from zope.catalog.interfaces import ICatalog
from zope.securitypolicy.interfaces import (
    IPrincipalRoleManager, IPrincipalRoleMap)
from zope.component import (
    getUtility, queryUtility, createObject, getAllUtilitiesRegisteredFor)
#from zope.component.interfaces import Invalid
from zope.event import notify
from zope.session.interfaces import ISession
from waeup.sirp.browser import (
    SIRPPage, SIRPForm, SIRPEditFormPage, SIRPAddFormPage,
    SIRPDisplayFormPage, NullValidator)
from waeup.sirp.browser.interfaces import (
    IUniversity, IFacultiesContainer, IFaculty, IFacultyAdd,
    IDepartment, IDepartmentAdd, ICourse, ICourseAdd, ICertificate,
    ICertificateAdd, ICertificateCourse, ICertificateCourseAdd)
from waeup.sirp.interfaces import MessageFactory as _
from waeup.sirp.browser.resources import warning, datepicker, tabs, datatable
from waeup.sirp.interfaces import(
    ISIRPObject, IUsersContainer, IUserAccount, IDataCenter,
    ISIRPXMLImporter, ISIRPXMLExporter, IBatchProcessor,
    ILocalRolesAssignable, DuplicationError, IConfigurationContainer,
    ISessionConfiguration, ISessionConfigurationAdd,
    IPasswordValidator, IContactForm, ISIRPUtils)
from waeup.sirp.permissions import get_users_with_local_roles, get_all_roles
from waeup.sirp.students.catalog import search as searchstudents
from waeup.sirp.university.catalog import search
from waeup.sirp.university.vocabularies import course_levels
from waeup.sirp.authentication import LocalRoleSetEvent
from waeup.sirp.widgets.restwidget import ReSTDisplayWidget
from waeup.sirp.authentication import get_principal_role_manager
from waeup.sirp.utils.helpers import get_user_account
from waeup.sirp.browser.layout import jsaction, action, UtilityView

grok.context(ISIRPObject)
grok.templatedir('templates')

def add_local_role(view, tab, **data):
    localrole = view.request.form.get('local_role', None)
    user = view.request.form.get('user', None)
    if user is None or localrole is None:
        view.flash('No user selected.')
        view.redirect(view.url(view.context, '@@manage')+'?tab%s' % tab)
        return
    role_manager = IPrincipalRoleManager(view.context)
    role_manager.assignRoleToPrincipal(localrole, user)
    notify(LocalRoleSetEvent(view.context, localrole, user, granted=True))
    view.redirect(view.url(view.context, u'@@manage')+'?tab%s' % tab)
    return

def del_local_roles(view, tab, **data):
    child_ids = view.request.form.get('role_id', None)
    if child_ids is None:
        view.flash(_('No local role selected.'))
        view.redirect(view.url(view.context, '@@manage')+'?tab%s' % tab)
        return
    if not isinstance(child_ids, list):
        child_ids = [child_ids]
    deleted = []
    role_manager = IPrincipalRoleManager(view.context)
    for child_id in child_ids:
        localrole = child_id.split('|')[1]
        user_name = child_id.split('|')[0]
        try:
            role_manager.unsetRoleForPrincipal(localrole, user_name)
            notify(LocalRoleSetEvent(
                    view.context, localrole, user_name, granted=False))
            deleted.append(child_id)
        except:
            view.flash('Could not remove %s: %s: %s' % (
                    child_id, sys.exc_info()[0], sys.exc_info()[1]))
    if len(deleted):
        view.flash(
            _('Local role successfully removed: ${a}',
            mapping = {'a':', '.join(deleted)}))
    view.redirect(view.url(view.context, u'@@manage')+'?tab%s' % tab)
    return

def delSubobjects(view, redirect, tab=None, subcontainer=None):
    form = view.request.form
    if form.has_key('val_id'):
        child_id = form['val_id']
    else:
        view.flash(_('No item selected.'))
        if tab:
            view.redirect(view.url(view.context, redirect)+'?tab%s' % tab)
        else:
            view.redirect(view.url(view.context, redirect))
        return
    if not isinstance(child_id, list):
        child_id = [child_id]
    deleted = []
    for id in child_id:
        try:
            if subcontainer:
                container = getattr(view.context, subcontainer, None)
                del container[id]
            else:
                del view.context[id]
            deleted.append(id)
        except:
            view.flash('Could not delete %s: %s: %s' % (
                    id, sys.exc_info()[0], sys.exc_info()[1]))
    if len(deleted):
        view.flash(_('Successfully removed: ${a}',
            mapping = {'a': ', '.join(deleted)}))
    if tab:
        view.redirect(view.url(view.context, redirect)+'?tab%s' % tab)
    else:
        view.redirect(view.url(view.context, redirect))
    return

#
# Login/logout and language switch pages...
#

class LoginPage(SIRPPage):
    """A login page, available for all objects.
    """
    grok.name('login')
    grok.context(ISIRPObject)
    grok.require('waeup.Public')
    label = _(u'Login')
    camefrom = None
    login_button = label

    def update(self, SUBMIT=None, camefrom=None):
        self.camefrom = camefrom
        if SUBMIT is not None:
            if self.request.principal.id != 'zope.anybody':
                self.flash(_('You logged in.'))
                if self.request.principal.user_type == 'student':
                    rel_link = '/students/%s' % self.request.principal.id
                    self.redirect(self.application_url() + rel_link)
                    return
                elif self.request.principal.user_type == 'applicant':
                    container, application_number = self.request.principal.id.split('_')
                    rel_link = '/applicants/%s/%s' % (
                        container, application_number)
                    self.redirect(self.application_url() + rel_link)
                    return
                if not self.camefrom:
                    # User might have entered the URL directly. Let's beam
                    # him back to our context.
                    self.redirect(self.url(self.context))
                    return
                self.redirect(self.camefrom)
                return
            self.flash(_('You entered wrong credentials.'))


class LogoutPage(SIRPPage):
    """A logout page. Calling this page will log the current user out.
    """
    grok.context(ISIRPObject)
    grok.require('waeup.Public')
    grok.name('logout')

    def update(self):
        if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
            auth = getUtility(IAuthentication)
            ILogout(auth).logout(self.request)
            self.flash(_("You have been logged out. Thanks for using WAeUP SIRP!"))
        self.redirect(self.application_url())


class LanguageChangePage(SIRPPage):
    """ Language switch
    """
    grok.context(ISIRPObject)
    grok.name('change_language')
    grok.require('waeup.Public')

    def update(self, lang='en', view_name='@@index'):
        self.response.setCookie('sirp.language', lang, path='/')
        self.redirect(self.url(self.context, view_name))
        return

    def render(self):
        return

#
# Contact form...
#

class ContactAdminForm(SIRPForm):
    grok.name('contactadmin')
    #grok.context(IUniversity)
    grok.template('contactform')
    grok.require('waeup.Authenticated')
    pnav = 2
    form_fields = grok.AutoFields(IContactForm).select('body')

    @property
    def config(self):
        return grok.getSite()['configuration']

    def label(self):
        return _(u'Contact ${a}', mapping = {'a': self.config.name_admin})

    @property
    def get_user_account(self):
        return get_user_account(self.request)

    @action(_('Send message now'), style='primary')
    def send(self, *args, **data):
        fullname = self.request.principal.title
        try:
            email = self.request.principal.email
        except AttributeError:
            email = self.config.email_admin
        username = self.request.principal.id
        usertype = getattr(self.request.principal,
                           'user_type', 'system').title()
        sirp_utils = getUtility(ISIRPUtils)
        success = sirp_utils.sendContactForm(
                fullname,email,
                self.config.name_admin,self.config.email_admin,
                username,usertype,self.config.name,
                data['body'],self.config.email_subject)
        # Success is always True if sendContactForm didn't fail.
        # TODO: Catch exceptions.
        if success:
            self.flash(_('Your message has been sent.'))
        return

class EnquiriesForm(ContactAdminForm):
    grok.name('enquiries')
    grok.require('waeup.Public')
    pnav = 2
    form_fields = grok.AutoFields(IContactForm).select(
                          'fullname', 'email_from', 'body')

    @action(_('Send now'), style='primary')
    def send(self, *args, **data):
        sirp_utils = getUtility(ISIRPUtils)
        success = sirp_utils.sendContactForm(
                data['fullname'],data['email_from'],
                self.config.name_admin,self.config.email_admin,
                u'None',u'Anonymous',self.config.name,
                data['body'],self.config.email_subject)
        if success:
            self.flash(_('Your message has been sent.'))
        else:
            self.flash(_('A smtp server error occurred.'))
        return

#
# University related pages...
#

class UniversityPage(SIRPDisplayFormPage):
    """ The main university page.
    """
    grok.require('waeup.Public')
    grok.name('index')
    grok.context(IUniversity)
    pnav = 0
    label = ''

    @property
    def frontpage(self):
        portal_language = getUtility(ISIRPUtils).PORTAL_LANGUAGE
        lang = self.request.cookies.get('sirp.language', portal_language)
        html = self.context['configuration'].frontpage_dict.get(lang,'')
        if html =='':
            html = self.context[
                'configuration'].frontpage_dict.get(portal_language,'')
        if html =='':
            return _(u'<h1>Welcome to WAeUP.SIRP</h1>')
        else:
            return html

class AdministrationPage(SIRPPage):
    """ The administration overview page.
    """
    grok.name('administration')
    grok.context(IUniversity)
    grok.require('waeup.manageUniversity')
    label = _(u'Administration')
    pnav = 0

class RSS20Feed(grok.View):
    """An RSS 2.0 feed.
    """
    grok.name('feed.rss')
    grok.context(IUniversity)
    grok.require('waeup.Public')
    grok.template('universityrss20feed')

    name = 'General news feed'
    description = 'waeup.sirp now supports RSS 2.0 feeds :-)'
    language = None
    date = None
    buildDate = None
    editor = None
    webmaster = None

    @property
    def title(self):
        return getattr(grok.getSite(), 'name', u'Sample University')

    @property
    def contexttitle(self):
        return self.name

    @property
    def link(self):
        return self.url(grok.getSite())

    def update(self):
        self.response.setHeader('Content-Type', 'text/xml; charset=UTF-8')

    def entries(self):
        return ()

class ReindexPage(UtilityView, grok.View):
    """ Reindex view.

    Reindexes a catalog. For managers only.
    """
    grok.context(IUniversity)
    grok.name('reindex')
    grok.require('waeup.manageUniversity')

    def update(self,ctlg=None):
        if ctlg is None:
            self.flash('No catalog name provided.')
            return
        cat = queryUtility(ICatalog, name='%s_catalog' % ctlg)
        if cat is None:
            self.flash('%s_catalog does not exist' % ctlg)
            return
        self.context.logger.info(
            'Catalog `%s_catalog` re-indexing started.' % ctlg)
        cat.updateIndexes()
        no_of_entries = cat.values()[0].documentCount()
        self.flash('%d %s re-indexed.' % (no_of_entries,ctlg))
        self.context.logger.info(
            'Re-indexing of %d objects finished.' % no_of_entries)
        return

    def render(self):
        self.redirect(self.url(self.context, '@@index'))
        return

#
# User container pages...
#

class UsersContainerPage(SIRPPage):
    """Overview page for all local users.
    """
    grok.require('waeup.manageUsers')
    grok.context(IUsersContainer)
    grok.name('index')
    label = _('Portal Users')
    manage_button = _(u'Manage')
    delete_button = _(u'Remove')

    def update(self, userid=None, adduser=None, manage=None, delete=None):
        if manage is not None and userid is not None:
            self.redirect(self.url(userid) + '/@@manage')
        if delete is not None and userid is not None:
            self.context.delUser(userid)
            self.flash(_('User account ${a} successfully deleted.',
                mapping = {'a':  userid}))

    def getLocalRoles(self, account):
        local_roles = account.getLocalRoles()
        local_roles_string = ''
        site_url = self.url(grok.getSite())
        for local_role in local_roles.keys():
            role_title = dict(get_all_roles())[local_role].title
            objects_string = ''
            for object in local_roles[local_role]:
                objects_string += '<a href="%s">%s</a>, ' %(self.url(object),
                    self.url(object).replace(site_url,''))
            local_roles_string += '%s: <br />%s <br />' %(role_title,
                objects_string.rstrip(', '))
        return local_roles_string

    def getSiteRoles(self, account):
        site_roles = account.roles
        site_roles_string = ''
        for site_role in site_roles:
            role_title = dict(get_all_roles())[site_role].title
            site_roles_string += '%s <br />' % role_title
        return site_roles_string

class AddUserFormPage(SIRPAddFormPage):
    grok.require('waeup.manageUsers')
    grok.context(IUsersContainer)
    grok.name('add')
    grok.template('usereditformpage')
    form_fields = grok.AutoFields(IUserAccount)
    label = _('Add user')

    @action(_('Add user'), style='primary')
    def addUser(self, **data):
        name = data['name']
        title = data['title']
        email = data['email']
        phone = data['phone']
        description = data['description']
        #password = data['password']
        roles = data['roles']
        form = self.request.form
        password = form.get('password', None)
        password_ctl = form.get('control_password', None)
        if password:
            validator = getUtility(IPasswordValidator)
            errors = validator.validate_password(password, password_ctl)
            if errors:
                self.flash( ' '.join(errors))
                return
        try:
            self.context.addUser(name, password, title=title, email=email,
                                 phone=phone, description=description,
                                 roles=roles)
            self.flash(_('User account ${a} successfully added.',
                mapping = {'a': name}))
        except KeyError:
            self.status = self.flash('The userid chosen already exists '
                                  'in the database.')
            return
        self.redirect(self.url(self.context))

class UserManageFormPage(SIRPEditFormPage):
    """Manage a user account.
    """
    grok.context(IUserAccount)
    grok.name('manage')
    grok.template('usereditformpage')
    grok.require('waeup.manageUsers')

    form_fields = grok.AutoFields(IUserAccount).omit('name')

    def label(self):
        return _("Edit user ${a}", mapping = {'a':self.context.__name__})

    def setUpWidgets(self, ignore_request=False):
        super(UserManageFormPage,self).setUpWidgets(ignore_request)
        self.widgets['title'].displayWidth = 30
        self.widgets['description'].height = 3

    @action(_('Save'), style='primary')
    def save(self, **data):
        form = self.request.form
        password = form.get('password', None)
        password_ctl = form.get('control_password', None)
        if password:
            validator = getUtility(IPasswordValidator)
            errors = validator.validate_password(password, password_ctl)
            if errors:
                self.flash( ' '.join(errors))
                return
        changed_fields = self.applyData(self.context, **data)
        if changed_fields:
            changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
        else:
            changed_fields = []
        if password:
            # Now we know that the form has no errors and can set password ...
            self.context.setPassword(password)
            changed_fields.append('password')
        fields_string = ' + '.join(changed_fields)
        if fields_string:
            self.context.__parent__.logger.info(
                'User account %s edited: %s' % (self.context.name,fields_string))
        self.flash(_('User settings have been saved.'))
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context.__parent__))
        return

class ContactUserForm(ContactAdminForm):
    grok.name('contactuser')
    grok.context(IUserAccount)
    grok.template('contactform')
    grok.require('waeup.manageUsers')
    pnav = 0
    form_fields = grok.AutoFields(IContactForm).select('body')

    def label(self):
        return _(u'Send message to ${a}', mapping = {'a':self.context.title})

    @action(_('Send message now'), style='primary')
    def send(self, *args, **data):
        try:
            email = self.request.principal.email
        except AttributeError:
            email = self.config.email_admin
        usertype = getattr(self.request.principal,
                           'user_type', 'system').title()
        sirp_utils = getUtility(ISIRPUtils)
        success = sirp_utils.sendContactForm(
                self.request.principal.title,email,
                self.context.title,self.context.email,
                self.request.principal.id,usertype,self.config.name,
                data['body'],self.config.email_subject)
        # Success is always True if sendContactForm didn't fail.
        # TODO: Catch exceptions.
        if success:
            self.flash(_('Your message has been sent.'))
        return

class UserEditFormPage(UserManageFormPage):
    """Edit a user account by user
    """
    grok.name('index')
    grok.require('waeup.editUser')
    form_fields = grok.AutoFields(IUserAccount).omit(
        'name', 'description', 'roles')
    label = _(u"My Preferences")

    def setUpWidgets(self, ignore_request=False):
        super(UserManageFormPage,self).setUpWidgets(ignore_request)
        self.widgets['title'].displayWidth = 30

class MyRolesPage(SIRPPage):
    """Display site roles and local roles assigned to officers.
    """
    grok.name('my_roles')
    grok.require('waeup.editUser')
    grok.context(IUserAccount)
    grok.template('myrolespage')
    label = _(u"My Roles")

    @property
    def getLocalRoles(self):
        local_roles = get_user_account(self.request).getLocalRoles()
        local_roles_userfriendly = {}
        for local_role in local_roles:
            role_title = dict(get_all_roles())[local_role].title
            local_roles_userfriendly[role_title] = local_roles[local_role]
        return local_roles_userfriendly

    @property
    def getSiteRoles(self):
        site_roles = get_user_account(self.request).roles
        site_roles_userfriendly = []
        for site_role in site_roles:
            role_title = dict(get_all_roles())[site_role].title
            site_roles_userfriendly.append(role_title)
        return site_roles_userfriendly

#
# Search pages...
#

class SearchPage(SIRPPage):
    """General search page for the academics section.
    """
    grok.context(IFacultiesContainer)
    grok.name('search')
    grok.template('searchpage')
    grok.require('waeup.manageUniversity')
    label = _(u"Search Academic Section")
    pnav = 1
    search_button = _(u'Search')

    def update(self, *args, **kw):
        datatable.need()
        form = self.request.form
        self.hitlist = []
        self.query = ''
        if not 'query' in form:
            return
        query = form['query']
        self.query = query
        self.hitlist = search(query=self.query, view=self)
        return

#
# Configuration pages...
#

class ConfigurationContainerDisplayFormPage(SIRPDisplayFormPage):
    """View page of the configuration container.
    """
    grok.require('waeup.managePortalConfiguration')
    grok.name('view')
    grok.context(IConfigurationContainer)
    pnav = 0
    label = _(u'View portal configuration')
    form_fields = grok.AutoFields(IConfigurationContainer)
    form_fields['frontpage'].custom_widget = ReSTDisplayWidget

class ConfigurationContainerManageFormPage(SIRPEditFormPage):
    """Manage page of the configuration container. We always use the
    manage page in the UI not the view page, thus we use the index name here.
    """
    grok.require('waeup.managePortalConfiguration')
    grok.name('index')
    grok.context(IConfigurationContainer)
    grok.template('configurationmanagepage')
    pnav = 0
    label = _(u'Edit portal configuration')
    taboneactions = [_('Save'), _('Update plugins')]
    tabtwoactions = [
        _('Add session configuration'),
        _('Remove selected')]
    form_fields = grok.AutoFields(IConfigurationContainer).omit('frontpage_dict')

    def update(self):
        tabs.need()
        self.tab1 = self.tab2 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        datatable.need()
        warning.need()
        return super(ConfigurationContainerManageFormPage, self).update()

    def _frontpage(self):
        view = ConfigurationContainerDisplayFormPage(
            self.context,self.request)
        view.setUpWidgets()
        return view.widgets['frontpage']()

    @action(_('Save'), style='primary')
    def save(self, **data):
        self.applyData(self.context, **data)
        self.context.frontpage_dict = self._frontpage()
        self.flash(_('Settings have been saved.'))
        return

    @action(_('Add session configuration'), validator=NullValidator, style='primary')
    def addSubunit(self, **data):
        self.redirect(self.url(self.context, '@@add'))
        return

    def getSessionConfigurations(self):
        """Get a list of all stored session configuration objects.
        """
        for key, val in self.context.items():
            url = self.url(val)
            session_string = val.getSessionString()
            title = _('Session ${a} Configuration', mapping = {'a':session_string})
            yield(dict(url=url, name=key, title=title))

    @jsaction(_('Remove selected'))
    def delSessonConfigurations(self, **data):
        delSubobjects(self, redirect='@@index', tab='2')
        return

    @action(_('Update plugins'), validator=NullValidator)
    def updatePlugins(self, **data):
        grok.getSite().updatePlugins()
        self.flash(_('Plugins were updated. See log file for details.'))
        return

class SessionConfigurationAddFormPage(SIRPAddFormPage):
    """Add a session configuration object to configuration container.
    """
    grok.context(IConfigurationContainer)
    grok.name('add')
    grok.require('waeup.managePortalConfiguration')
    label = _('Add session configuration')
    form_fields = grok.AutoFields(ISessionConfigurationAdd)
    pnav = 0

    @action(_('Add Session Configuration'), style='primary')
    def addSessionConfiguration(self, **data):
        sessionconfiguration = createObject(u'waeup.SessionConfiguration')
        self.applyData(sessionconfiguration, **data)
        try:
            self.context.addSessionConfiguration(sessionconfiguration)
        except KeyError:
            self.flash(_('The session chosen already exists.'))
            return
        self.redirect(self.url(self.context, '@@index')+'?tab2')
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self):
        self.redirect(self.url(self.context, '@@index')+'?tab2')
        return

class SessionConfigurationManageFormPage(SIRPEditFormPage):
    """Manage session configuration object.
    """
    grok.context(ISessionConfiguration)
    grok.name('index')
    grok.require('waeup.managePortalConfiguration')
    form_fields = grok.AutoFields(ISessionConfiguration)
    pnav = 0

    @property
    def label(self):
        session_string = self.context.getSessionString()
        return _('Edit academic session ${a} configuration',
            mapping = {'a':session_string})

    @action(_('Save'), style='primary')
    def save(self, **data):
        self.applyData(self.context, **data)
        self.flash(_('Settings have been saved.'))
        self.redirect(self.url(self.context.__parent__, '@@index')+'?tab2')
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self):
        self.redirect(self.url(self.context.__parent__, '@@index')+'?tab2')
        return

#
# Datacenter pages...
#

class DatacenterPage(SIRPPage):
    grok.context(IDataCenter)
    grok.name('index')
    grok.require('waeup.manageUniversity')
    label = _(u'Data Center')
    pnav = 0

class DatacenterUploadPage(SIRPPage):
    grok.context(IDataCenter)
    grok.name('upload')
    grok.require('waeup.manageUniversity')
    label = _(u'Upload file')
    pnav = 0
    upload_button =_(u'Upload')
    cancel_button =_(u'Cancel')

    def update(self, uploadfile=None, CANCEL=None, SUBMIT=None):
        if CANCEL is not None:
            self.redirect(self.url(self.context))
            return
        if not uploadfile:
            return
        try:
            filename = uploadfile.filename
            target = os.path.join(self.context.storage,
                                  self.getNormalizedFileName(filename))
            open(target, 'wb').write(uploadfile.read())
            logger = self.context.logger
            logger.info('%s: Uploaded file "%s"' % (
                self.request.principal.id, target))
        except IOError:
            self.flash('Error while uploading file. Please retry.')
            self.flash('I/O error: %s' % sys.exc_info()[1])
            return
        self.redirect(self.url(self.context))

    def getNormalizedFileName(self, filename):
        """Build sane filename.

        An uploaded file foo.csv will be stored as foo_USERNAME.csv
        where username is the principal id of the currently logged in
        user.

        Spaces in filename are replaced by underscore.
        """
        username = self.request.principal.id
        filename = filename.replace(' ', '_')
        # Only accept typical filname chars...
        filtered_username = ''.join(re.findall('[a-zA-Z0-9_\.\-]', username))
        base, ext = os.path.splitext(filename)
        return '%s_%s%s' % (base, filtered_username, ext.lower())

class DatacenterImportStep1(SIRPPage):
    """Manual import step 1: choose file
    """
    grok.context(IDataCenter)
    grok.name('import1')
    grok.template('datacenterimport1page')
    grok.require('waeup.manageUniversity')
    label = _(u'Process CSV file')
    pnav = 0
    cancel_button =_(u'Cancel')

    def getFiles(self):
        files = self.context.getFiles(sort='date')
        for file in files:
            name = file.name
            if not name.endswith('.csv') and not name.endswith('.pending'):
                continue
            yield file

    def update(self, filename=None, select=None, cancel=None):
        if cancel is not None:
            self.flash(_('Import aborted.'))
            self.redirect(self.url(self.context))
            return
        if select is not None:
            # A filename was selected
            session = ISession(self.request)['waeup.sirp']
            session['import_filename'] = select
            self.redirect(self.url(self.context, '@@import2'))

class DatacenterImportStep2(SIRPPage):
    """Manual import step 2: choose importer
    """
    grok.context(IDataCenter)
    grok.name('import2')
    grok.template('datacenterimport2page')
    grok.require('waeup.manageUniversity')
    label = _(u'Process CSV file')
    pnav = 0
    cancel_button =_(u'Cancel')
    back_button =_(u'Back to step 1')
    proceed_button =_(u'Proceed to step 3')

    filename = None
    mode = 'create'
    importer = None
    mode_locked = False

    def getPreviewHeader(self):
        """Get the header fields of attached CSV file.
        """
        reader = csv.reader(open(self.fullpath, 'rb'))
        return reader.next()

    def getPreviewBody(self):
        """Get the first 5 rows of attached CSV file.
        """
        result = []
        num = 0
        if not self.reader:
            return
        for row in self.reader:
            if num > 4:
                break
            num += 1
            row = row.items()
            # Sort fields in headerfield order
            row = sorted(row,
                         key=lambda k: self.reader.fieldnames.index(k[0]))
            row = [x[1] for x in row]
            result.append(row)
        result.append(len(result[0]) * ['...'])
        return result

    def getImporters(self):
        importers = getAllUtilitiesRegisteredFor(IBatchProcessor)
        importers = [
            dict(title=x.name, name=x.util_name) for x in importers]
        return importers

    def getModeFromFilename(self, filename):
        """Lookup filename or path and return included mode name or None.
        """
        if not filename.endswith('.pending.csv'):
            return None
        base = os.path.basename(filename)
        parts = base.rsplit('.', 3)
        if len(parts) != 4:
            return None
        if parts[1] not in ['create', 'update', 'remove']:
            return None
        return parts[1]

    def getWarnings(self):
        import sys
        result = []
        try:
            headerfields = self.getPreviewHeader()
            headerfields_clean = list(set(headerfields))
            if len(headerfields) > len(headerfields_clean):
                result.append(
                    _("Double headers: each column name may only appear once. "))
        except:
            fatal = '%s' % sys.exc_info()[1]
            result.append(fatal)
        if result:
            warnings = ""
            for line in result:
                warnings += line + '<br />'
            warnings += _('Replace imported file!')
            return warnings
        return False

    def update(self, mode=None, importer=None,
               back1=None, cancel=None, proceed=None):
        session = ISession(self.request)['waeup.sirp']
        self.filename = session.get('import_filename', None)

        if self.filename is None or back1 is not None:
            self.redirect(self.url(self.context, '@@import1'))
            return
        if cancel is not None:
            self.flash(_('Import aborted.'))
            self.redirect(self.url(self.context))
            return
        self.mode = mode or session.get('import_mode', self.mode)
        filename_mode = self.getModeFromFilename(self.filename)
        if filename_mode is not None:
            self.mode = filename_mode
            self.mode_locked = True
        self.importer = importer or session.get('import_importer', None)
        session['import_importer'] = self.importer
        if self.importer and 'update' in self.importer:
            if self.mode != 'update':
                self.flash(_('Update mode only!'))
                self.mode_locked = True
                self.mode = 'update'
                proceed = None
        session['import_mode'] = self.mode
        if proceed is not None:
            self.redirect(self.url(self.context, '@@import3'))
            return
        self.fullpath = os.path.join(self.context.storage, self.filename)
        warnings = self.getWarnings()
        if not warnings:
            self.reader = csv.DictReader(open(self.fullpath, 'rb'))
        else:
            self.reader = ()
            self.flash(warnings)

class DatacenterImportStep3(SIRPPage):
    """Manual import step 3: modify header
    """
    grok.context(IDataCenter)
    grok.name('import3')
    grok.template('datacenterimport3page')
    grok.require('waeup.manageUniversity')
    label = _(u'Process CSV file')
    pnav = 0
    cancel_button =_(u'Cancel')
    reset_button =_(u'Reset')
    update_button =_(u'Set headerfields')
    back_button =_(u'Back to step 2')
    proceed_button =_(u'Perform import')

    filename = None
    mode = None
    importername = None

    @property
    def nextstep(self):
        return self.url(self.context, '@@import4')

    def getPreviewHeader(self):
        """Get the header fields of attached CSV file.
        """
        reader = csv.reader(open(self.fullpath, 'rb'))
        return reader.next()

    def getPreviewBody(self):
        """Get the first 5 rows of attached CSV file.
        """
        result = []
        num = 0
        for row in self.reader:
            if num > 4:
                break
            num += 1
            row = row.items()
            # Sort fields in headerfield order
            row = sorted(row,
                         key=lambda k: self.reader.fieldnames.index(k[0]))
            row = [x[1] for x in row]
            result.append(row)
        result.append(len(result[0]) * ['...'])
        return result

    def getPossibleHeaders(self):
        """Get the possible headers.

        The headers are described as dicts {value:internal_name,
        title:displayed_name}
        """
        result = [dict(title='<IGNORE COL>', value='--IGNORE--')]
        headers = self.importer.getHeaders()
        result.extend([dict(title=x, value=x) for x in headers])
        return result

    def getWarnings(self):
        import sys
        result = []
        try:
            self.importer.checkHeaders(self.headerfields, mode=self.mode)
        except:
            fatal = '%s' % sys.exc_info()[1]
            result.append(fatal)
        if result:
            warnings = ""
            for line in result:
                warnings += line + '<br />'
            warnings += _('Edit headers or replace imported file!')
            return warnings
        return False

    @property
    def nextstep(self):
        return self.url(self.context, '@@import4')

    def update(self, headerfield=None, back2=None, cancel=None, proceed=None):
        session = ISession(self.request)['waeup.sirp']
        self.filename = session.get('import_filename', None)
        self.mode = session.get('import_mode', None)
        self.importername = session.get('import_importer', None)

        if None in (self.filename, self.mode, self.importername):
            self.redirect(self.url(self.context, '@@import2'))
            return
        if back2 is not None:
            self.redirect(self.url(self.context ,'@@import2'))
            return
        if cancel is not None:
            self.flash(_('Import aborted.'))
            self.redirect(self.url(self.context))
            return

        self.fullpath = os.path.join(self.context.storage, self.filename)
        self.headerfields = headerfield or self.getPreviewHeader()
        session['import_headerfields'] = self.headerfields

        if proceed is not None:
            self.redirect(self.url(self.context, '@@import4'))
            return
        self.importer = getUtility(IBatchProcessor, name=self.importername)
        self.reader = csv.DictReader(open(self.fullpath, 'rb'))
        warnings = self.getWarnings()
        if warnings:
            self.flash(warnings)

class DatacenterImportStep4(SIRPPage):
    """Manual import step 4: do actual import
    """
    grok.context(IDataCenter)
    grok.name('import4')
    grok.template('datacenterimport4page')
    grok.require('waeup.manageUniversity')
    label = _(u'Process CSV file')
    pnav = 0
    show_button =_(u'View processing log')
    back_button =_(u'Back to data center')

    filename = None
    mode = None
    importername = None
    headerfields = None
    warnnum = None

    def update(self, back=None, finish=None, showlog=None):
        if finish is not None:
            self.redirect(self.url(self.context))
            return
        session = ISession(self.request)['waeup.sirp']
        self.filename = session.get('import_filename', None)
        self.mode = session.get('import_mode', None)
        self.importername = session.get('import_importer', None)
        self.headerfields = session.get('import_headerfields', None)

        if None in (self.filename, self.mode, self.importername,
                    self.headerfields):
            self.redirect(self.url(self.context, '@@import3'))
            return

        if showlog is not None:
            logfilename = "datacenter.log"
            session['logname'] = logfilename
            self.redirect(self.url(self.context, '@@show'))
            return

        self.fullpath = os.path.join(self.context.storage, self.filename)
        self.importer = getUtility(IBatchProcessor, name=self.importername)

        # Perform batch processing...
        # XXX: This might be better placed in datacenter module.
        (linenum, self.warn_num,
         fin_path, pending_path) = self.importer.doImport(
            self.fullpath, self.headerfields, self.mode,
            self.request.principal.id, logger=self.context.logger)
        # Put result files in desired locations...
        self.context.distProcessedFiles(
            self.warn_num == 0, self.fullpath, fin_path, pending_path,
            self.mode)

        if self.warn_num:
            self.flash(_('Processing of ${a} rows failed.',
                mapping = {'a':self.warn_num}))
        self.flash(_('Successfully processed ${a} rows.',
            mapping = {'a':linenum - self.warn_num}))

class DatacenterLogsOverview(SIRPPage):
    grok.context(IDataCenter)
    grok.name('logs')
    grok.template('datacenterlogspage')
    grok.require('waeup.manageUniversity')
    label = _(u'Show logfiles')
    pnav = 0
    back_button = _(u'Back to Data Center')
    show_button = _(u'Show')

    def update(self, show=None, logname=None, back=None):
        session = ISession(self.request)['waeup.sirp']
        if back is not None:
            self.redirect(self.url(self.context))
            return
        if logname is not None:
            session['logname'] = logname
        if show is not None:
            self.redirect(self.url(self.context, '@@show'))
            return
        self.files = self.context.getLogFiles()

class DatacenterLogsFileview(SIRPPage):
    grok.context(IDataCenter)
    grok.name('show')
    grok.template('datacenterlogsshowfilepage')
    grok.require('waeup.manageUniversity')
    title = _(u'Data Center')
    pnav = 0

    def label(self):
        return "Logfile %s" % self.filename

    def update(self, show=None, remove=None, back=None):
        session = ISession(self.request)['waeup.sirp']
        logname = session.get('logname', None)
        if back is not None or logname is None:
            self.redirect(self.url(self.context, '@@logs'))
            return
        self.filename = logname
        self.files = self.context.getLogFiles()
        fullpath = os.path.join(self.context.storage, 'logs', logname)
        self.filecontents = open(fullpath, 'rb').read()

class DatacenterSettings(SIRPPage):
    grok.context(IDataCenter)
    grok.name('manage')
    grok.template('datacentermanagepage')
    grok.require('waeup.manageUniversity')
    label = _('Edit data center settings')
    pnav = 0
    save_button =_(u'Save')
    reset_button =_(u'Reset')
    cancel_button =_(u'Cancel')

    def update(self, newpath=None, move=False, overwrite=False,
               save=None, cancel=None):
        if move:
            move = True
        if overwrite:
            overwrite = True
        if newpath is None:
            return
        if cancel is not None:
            self.redirect(self.url(self.context))
            return
        try:
            not_copied = self.context.setStoragePath(newpath, move=move)
            for name in not_copied:
                self.flash(_('File already existed (not copied):') + ' %s' % name)
        except:
            self.flash(_('Given storage path cannot be used.'))
            self.flash(_('Error:') + '%s' %sys.exc_info()[1])
            return
        if newpath:
            self.flash(_('New storage path succefully set.'))
            self.redirect(self.url(self.context))
        return

class ExportXMLPage(grok.View):
    """Deliver an XML representation of the context.
    """
    grok.name('export.xml')
    grok.require('waeup.manageUniversity')

    def render(self):
        exporter = ISIRPXMLExporter(self.context)
        xml = exporter.export().read()
        self.response.setHeader(
            'Content-Type', 'text/xml; charset=UTF-8')
        return xml

class ImportXMLPage(SIRPPage):
    """Replace the context object by an object created from an XML
       representation.

       XXX: This does not work for ISite objects, i.e. for instance
            for complete University objects.
    """
    grok.name('importxml')
    grok.require('waeup.manageUniversity')

    def update(self, xmlfile=None, CANCEL=None, SUBMIT=None):
        if CANCEL is not None:
            self.redirect(self.url(self.context))
            return
        if not xmlfile:
            return
        importer = ISIRPXMLImporter(self.context)
        obj = importer.doImport(xmlfile)
        if type(obj) != type(self.context):
            return
        parent = self.context.__parent__
        name = self.context.__name__
        self.context = obj
        if hasattr(parent, name):
            setattr(parent, name, obj)
        else:
            del parent[name]
            parent[name] = obj
            pass
        return



#
# FacultiesContainer pages...
#

class FacultiesContainerPage(SIRPPage):
    """ Index page for faculty containers.
    """
    grok.context(IFacultiesContainer)
    grok.require('waeup.viewAcademics')
    grok.name('index')
    label = _('Academic Section')
    pnav = 1
    grok.template('facultypage')

class FacultiesContainerManageFormPage(SIRPEditFormPage):
    """Manage the basic properties of a `Faculty` instance.
    """
    grok.context(IFacultiesContainer)
    grok.name('manage')
    grok.require('waeup.manageUniversity')
    grok.template('facultiescontainermanagepage')
    pnav = 1
    taboneactions = [_('Add faculty'), _('Remove selected'),_('Cancel')]
    subunits = _('Faculties')

    @property
    def label(self):
        return _('Manage academic section')

    def update(self):
        warning.need()
        return super(FacultiesContainerManageFormPage, self).update()

    @jsaction(_('Remove selected'))
    def delFaculties(self, **data):
        delSubobjects(self, redirect='@@manage', tab='1')
        return

    @action(_('Add faculty'), validator=NullValidator)
    def addFaculty(self, **data):
        self.redirect(self.url(self.context, '@@add'))
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return


class FacultyAddFormPage(SIRPAddFormPage):
    """ Page form to add a new faculty to a faculty container.
    """
    grok.context(IFacultiesContainer)
    grok.require('waeup.manageUniversity')
    grok.name('add')
    label = _('Add faculty')
    form_fields = grok.AutoFields(IFacultyAdd)
    pnav = 1

    @action(_('Add faculty'), style='primary')
    def addFaculty(self, **data):
        faculty = createObject(u'waeup.Faculty')
        self.applyData(faculty, **data)
        try:
            self.context.addFaculty(faculty)
        except KeyError:
            self.flash(_('The faculty code chosen already exists.'))
            return
        self.redirect(self.url(self.context, u'@@manage')+'?tab1')

    @action(_('Cancel'))
    def cancel(self, **data):
        self.redirect(self.url(self.context))

#
# Faculty pages
#
class FacultyPage(SIRPPage):
    """Index page of faculties.
    """
    grok.context(IFaculty)
    grok.require('waeup.viewAcademics')
    grok.name('index')
    pnav = 1

    @property
    def label(self):
        return _('Departments')

class FacultyManageFormPage(SIRPEditFormPage):
    """Manage the basic properties of a `Faculty` instance.
    """
    grok.context(IFaculty)
    grok.name('manage')
    grok.require('waeup.manageUniversity')
    grok.template('facultymanagepage')
    pnav = 1
    subunits = _('Departments')
    taboneactions = [_('Save'),_('Cancel')]
    tabtwoactions = [_('Add department'), _('Remove selected'),_('Cancel')]
    tabthreeactions1 = [_('Remove selected local roles')]
    tabthreeactions2 = [_('Add local role')]

    form_fields = grok.AutoFields(IFaculty)

    @property
    def label(self):
        return _('Manage faculty')

    def update(self):
        tabs.need()
        self.tab1 = self.tab2 = self.tab3 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        warning.need()
        datatable.need()
        return super(FacultyManageFormPage, self).update()

    def getLocalRoles(self):
        roles = ILocalRolesAssignable(self.context)
        return roles()

    def getUsers(self):
        """Get a list of all users.
        """
        for key, val in grok.getSite()['users'].items():
            url = self.url(val)
            yield(dict(url=url, name=key, val=val))

    def getUsersWithLocalRoles(self):
        return get_users_with_local_roles(self.context)

    @jsaction(_('Remove selected'))
    def delDepartments(self, **data):
        delSubobjects(self, redirect='@@manage', tab='2')
        return

    @action(_('Save'), style='primary')
    def save(self, **data):
        self.applyData(self.context, **data)
        self.flash(_('Form has been saved.'))
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return

    @action(_('Add department'), validator=NullValidator)
    def addSubunit(self, **data):
        self.redirect(self.url(self.context, '@@add'))
        return

    @action(_('Add local role'), validator=NullValidator)
    def addLocalRole(self, **data):
        return add_local_role(self, '3', **data)

    @action(_('Remove selected local roles'))
    def delLocalRoles(self, **data):
        return del_local_roles(self,3,**data)

class DepartmentAddFormPage(SIRPAddFormPage):
    """Add a department to a faculty.
    """
    grok.context(IFaculty)
    grok.name('add')
    grok.require('waeup.manageUniversity')
    label = _('Add department')
    form_fields = grok.AutoFields(IDepartmentAdd)
    pnav = 1

    @action(_('Add department'), style='primary')
    def addDepartment(self, **data):
        department = createObject(u'waeup.Department')
        self.applyData(department, **data)
        try:
            self.context.addDepartment(department)
        except KeyError:
            self.flash(_('The code chosen already exists in this faculty.'))
            return
        self.status = self.flash(
            _("Department ${a} added.", mapping = {'a':data['code']}))
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')

    @action(_('Cancel'))
    def cancel(self, **data):
        self.redirect(self.url(self.context))

#
# Department pages
#
class DepartmentPage(SIRPPage):
    """Department index page.
    """
    grok.context(IDepartment)
    grok.require('waeup.viewAcademics')
    grok.name('index')
    pnav = 1
    label = _('Courses and Certificates')

    def update(self):
        tabs.need()
        datatable.need()
        super(DepartmentPage, self).update()
        return

    def getCourses(self):
        """Get a list of all stored courses.
        """
        for key, val in self.context.courses.items():
            url = self.url(val)
            yield(dict(url=url, name=key, container=val))

    def getCertificates(self):
        """Get a list of all stored certificates.
        """
        for key, val in self.context.certificates.items():
            url = self.url(val)
            yield(dict(url=url, name=key, container=val))

class ShowStudentsInDepartmentPage(SIRPPage):
    """Page that lists all students in the department.
    """
    grok.context(IDepartment)
    grok.require('waeup.showStudents')
    grok.name('showdepstudents')
    grok.template('showstudentspage')
    pnav = 1
    label = _('Students')

    @property
    def getStudents(self):
        hitlist = searchstudents(query=self.context.code,
            searchtype='depcode', view=self)
        return hitlist

    def update(self, *args, **kw):
        datatable.need()
        return

class ShowStudentsInCertificatePage(ShowStudentsInDepartmentPage):
    """Page that lists all students studying a certificate.
    """
    grok.context(ICertificate)
    grok.require('waeup.showStudents')
    grok.name('showcertstudents')
    pnav = 1
    label = _('Students')

    @property
    def getStudents(self):
        hitlist = searchstudents(query=self.context.code,
            searchtype='certcode', view=self)
        return hitlist

class DepartmentManageFormPage(SIRPEditFormPage):
    """Manage the basic properties of a `Department` instance.
    """
    grok.context(IDepartment)
    grok.name('manage')
    grok.require('waeup.manageUniversity')
    pnav = 1
    grok.template('departmentmanagepage')
    taboneactions = [_('Save'),_('Cancel')]
    tabtwoactions = [_('Add course'), _('Remove selected courses'),_('Cancel')]
    tabthreeactions = [_('Add certificate'), _('Remove selected certificates'),
                       _('Cancel')]
    tabfouractions1 = [_('Remove selected local roles')]
    tabfouractions2 = [_('Add local role')]

    form_fields = grok.AutoFields(IDepartment)

    @property
    def label(self):
        return _('Manage department')

    def getCourses(self):
        """Get a list of all stored courses.
        """
        for key, val in self.context.courses.items():
            url = self.url(val)
            yield(dict(url=url, name=key, container=val))

    def getCertificates(self):
        """Get a list of all stored certificates.
        """
        for key, val in self.context.certificates.items():
            url = self.url(val)
            yield(dict(url=url, name=key, container=val))

    def update(self):
        tabs.need()
        self.tab1 = self.tab2 = self.tab3 = self.tab4 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        warning.need()
        datatable.need()
        super(DepartmentManageFormPage, self).update()
        return

    def getLocalRoles(self):
        roles = ILocalRolesAssignable(self.context)
        return roles()

    def getUsers(self):
        """Get a list of all users.
        """
        for key, val in grok.getSite()['users'].items():
            url = self.url(val)
            yield(dict(url=url, name=key, val=val))

    def getUsersWithLocalRoles(self):
        return get_users_with_local_roles(self.context)

    @action(_('Save'), style='primary')
    def save(self, **data):
        self.applyData(self.context, **data)
        self.flash('Form has been saved.')
        return

    @jsaction(_('Remove selected courses'))
    def delCourses(self, **data):
        delSubobjects(
            self, redirect='@@manage', tab='2', subcontainer='courses')
        return

    @jsaction(_('Remove selected certificates'))
    def delCertificates(self, **data):
        delSubobjects(
            self, redirect='@@manage', tab='3', subcontainer='certificates')
        return

    @action(_('Add course'), validator=NullValidator)
    def addCourse(self, **data):
        self.redirect(self.url(self.context, 'addcourse'))
        return

    @action(_('Add certificate'), validator=NullValidator)
    def addCertificate(self, **data):
        self.redirect(self.url(self.context, 'addcertificate'))
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return

    @action(_('Add local role'), validator=NullValidator)
    def addLocalRole(self, **data):
        return add_local_role(self, 4, **data)

    @action(_('Remove selected local roles'))
    def delLocalRoles(self, **data):
        return del_local_roles(self,4,**data)

class CourseAddFormPage(SIRPAddFormPage):
    """Add-form to add course to a department.
    """
    grok.context(IDepartment)
    grok.name('addcourse')
    grok.require('waeup.manageUniversity')
    label = _(u'Add course')
    form_fields = grok.AutoFields(ICourseAdd)
    pnav = 1

    @action(_('Add course'))
    def addCourse(self, **data):
        course = createObject(u'waeup.Course')
        self.applyData(course, **data)
        try:
            self.context.courses.addCourse(course)
        except DuplicationError, error:
            # signals duplication error
            entries = error.entries
            for entry in entries:
                # do not use error.msg but render a more detailed message instead
                # and show links to all certs with same code
                message = _('A course with same code already exists: ')
                message += '<a href="%s">%s</a>' % (
                    self.url(entry), getattr(entry, '__name__', u'Unnamed'))
                self.flash(message)
            self.redirect(self.url(self.context, u'@@addcourse'))
            return
        message = _(u'Course ${a} successfully created.', mapping = {'a':course.code})
        self.flash(message)
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return

class CertificateAddFormPage(SIRPAddFormPage):
    """Add-form to add certificate to a department.
    """
    grok.context(IDepartment)
    grok.name('addcertificate')
    grok.require('waeup.manageUniversity')
    label = _(u'Add certificate')
    form_fields = grok.AutoFields(ICertificateAdd)
    pnav = 1

    @action(_('Add certificate'))
    def addCertificate(self, **data):
        certificate = createObject(u'waeup.Certificate')
        self.applyData(certificate, **data)
        try:
            self.context.certificates.addCertificate(certificate)
        except DuplicationError, error:
            # signals duplication error
            entries = error.entries
            for entry in entries:
                # do not use error.msg but render a more detailed message instead
                # and show links to all certs with same code
                message = _('A certificate with same code already exists: ')
                message += '<a href="%s">%s</a>' % (
                    self.url(entry), getattr(entry, '__name__', u'Unnamed'))
                self.flash(message)
            self.redirect(self.url(self.context, u'@@addcertificate'))
            return
        message = _(u'Certificate ${a} successfully created.', mapping = {'a':certificate.code})
        self.flash(message)
        self.redirect(self.url(self.context, u'@@manage')+'?tab3')
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self): #, **data):
        self.redirect(self.url(self.context))
        return

#
# Courses pages
#
class CoursePage(SIRPDisplayFormPage):
    """Course index page.
    """
    grok.context(ICourse)
    grok.name('index')
    grok.require('waeup.viewAcademics')
    pnav = 1
    form_fields = grok.AutoFields(ICourse)

    @property
    def label(self):
        return '%s (%s)' % (self.context.title, self.context.code)

class CourseManageFormPage(SIRPEditFormPage):
    """Edit form page for courses.
    """
    grok.context(ICourse)
    grok.name('manage')
    grok.require('waeup.manageUniversity')
    label = _(u'Edit course')
    pnav = 1

    form_fields = grok.AutoFields(ICourse)

    @action(_('Save'), style='primary')
    def save(self, **data):
        self.applyData(self.context, **data)
        self.flash(_('Form has been saved.'))
        return

    #@action('Save and return')
    #def saveAndReturn(self, **data):
    #    self.applyData(self.context, **data)
    #    self.redirect(self.url(self.context))
    #    return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return

#
# Certificate pages
#
class CertificatePage(SIRPDisplayFormPage):
    """Index page for certificates.
    """
    grok.context(ICertificate)
    grok.name('index')
    grok.require('waeup.viewAcademics')
    pnav = 1
    form_fields = grok.AutoFields(ICertificate)
    grok.template('certificatepage')

    @property
    def label(self):
        return "%s (%s)" % (self.context.title, self.context.code)

    def update(self):
        datatable.need()
        return super(CertificatePage, self).update()

class CertificateManageFormPage(SIRPEditFormPage):
    """Manage the properties of a `Certificate` instance.
    """
    grok.context(ICertificate)
    grok.name('manage')
    grok.require('waeup.manageUniversity')
    pnav = 1
    label = _('Edit certificate')

    form_fields = grok.AutoFields(ICertificate)

    pnav = 1
    grok.template('certificatemanagepage')
    taboneactions = [_('Save'),_('Cancel')]
    tabtwoactions = [_('Add course referrer'),
                     _('Remove selected course referrers'),_('Cancel')]
    tabthreeactions1 = [_('Remove selected local roles')]
    tabthreeactions2 = [_('Add local role')]

    @property
    def label(self):
        return _('Manage certificate')

    def update(self):
        tabs.need()
        self.tab1 = self.tab2 = self.tab3 = ''
        qs = self.request.get('QUERY_STRING', '')
        if not qs:
            qs = 'tab1'
        setattr(self, qs, 'active')
        warning.need()
        datatable.need()
        return super(CertificateManageFormPage, self).update()

    @action(_('Save'), style='primary')
    def save(self, **data):
        self.applyData(self.context, **data)
        self.flash(_('Form has been saved.'))
        return

    @jsaction(_('Remove selected course referrers'))
    def delCertificateCourses(self, **data):
        delSubobjects(self, redirect='@@manage', tab='2')
        return

    @action(_('Add course referrer'), validator=NullValidator)
    def addCertificateCourse(self, **data):
        self.redirect(self.url(self.context, 'addcertificatecourse'))
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return

    def getLocalRoles(self):
        roles = ILocalRolesAssignable(self.context)
        return roles()

    def getUsers(self):
        """Get a list of all users.
        """
        for key, val in grok.getSite()['users'].items():
            url = self.url(val)
            yield(dict(url=url, name=key, val=val))

    def getUsersWithLocalRoles(self):
        return get_users_with_local_roles(self.context)

    @action(_('Add local role'), validator=NullValidator)
    def addLocalRole(self, **data):
        return add_local_role(self, 3, **data)

    @action(_('Remove selected local roles'))
    def delLocalRoles(self, **data):
        return del_local_roles(self,3,**data)


class CertificateCourseAddFormPage(SIRPAddFormPage):
    """Add-page to add a course ref to a certificate
    """
    grok.context(ICertificate)
    grok.name('addcertificatecourse')
    grok.require('waeup.manageUniversity')
    form_fields = grok.AutoFields(ICertificateCourseAdd)
    pnav = 1
    label = _('Add course referrer')

    @action(_('Add course referrer'))
    def addCertcourse(self, **data):
        try:
            self.context.addCourseRef(**data)
        except KeyError:
            self.status = self.flash(_('The chosen course referrer is already '
                                  'part of this certificate.'))
            return
        self.status = self.flash(
            _("Course referrer ${a}_${b} added.",
            mapping = {'a': data['course'].code, 'b': data['level']}))
        self.redirect(self.url(self.context, u'@@manage')+'?tab2')

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return


#
# Certificate course pages...
#
class CertificateCoursePage(SIRPPage):
    """CertificateCourse index page.
    """
    grok.context(ICertificateCourse)
    grok.name('index')
    grok.require('waeup.viewAcademics')
    pnav = 1
    #form_fields = grok.AutoFields(ICertificateCourse)

    @property
    def label(self):
        return '%s' % (self.context.longtitle())

    @property
    def leveltitle(self):
        return course_levels.getTerm(self.context.level).title

class CertificateCourseManageFormPage(SIRPEditFormPage):
    """Manage the basic properties of a `CertificateCourse` instance.
    """
    grok.context(ICertificateCourse)
    grok.name('manage')
    grok.require('waeup.manageUniversity')
    form_fields = grok.AutoFields(ICertificateCourse)
    label = _('Edit course referrer')
    pnav = 1

    @action(_('Save and return'), style='primary')
    def saveAndReturn(self, **data):
        parent = self.context.__parent__
        if self.context.level == data['level']:
            self.applyData(self.context, **data)
        else:
            # The level changed. We have to create a new certcourse as
            # the key of the entry will change as well...
            old_level = self.context.level
            data['course'] = self.context.course
            parent.addCourseRef(**data)
            parent.delCourseRef(data['course'].code, level=old_level)
        self.flash(_('Form has been saved.'))
        self.redirect(self.url(parent))
        return

    @action(_('Cancel'), validator=NullValidator)
    def cancel(self, **data):
        self.redirect(self.url(self.context))
        return

