source: main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py @ 8457

Last change on this file since 8457 was 8457, checked in by Henrik Bettermann, 12 years ago

Grep strings.

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