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

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

All remaining changes from last weeks. Sorry for the mess.

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