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

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

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

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