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

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

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

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

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