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

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

Improve logging.

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