diff -r 000000000000 -r b97547f5f1fa web/views/management.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/management.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,535 @@ +"""management and error screens + + +:organization: Logilab +:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from logilab.mtconverter import html_escape + +from logilab.common.decorators import cached + +from cubicweb.common.utils import UStringIO +from cubicweb.common.view import AnyRsetView, StartupView, EntityView +from cubicweb.common.uilib import (html_traceback, rest_traceback, html_escape, + toggle_link) +from cubicweb.common.selectors import (yes_selector, onelinerset_selector, + accept_rset_selector, norset_selector, + chainfirst, chainall) +from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs +from cubicweb.web.widgets import StaticComboBoxWidget +from cubicweb.web.form import FormMixIn + +_ = unicode + +def begin_form(w, entity, redirectvid, redirectpath=None, msg=None): + w(u'
\n' % entity.req.build_url('edit')) + w(u'
\n') + w(u'\n' % redirectvid) + w(u'\n' % ( + html_escape(redirectpath or entity.rest_path()))) + w(u'\n' % entity.eid) + w(u'\n' % ( + eid_param('__type', entity.eid), entity.e_schema)) + if msg: + w(u'\n' + % html_escape(msg)) + + +class SecurityManagementView(EntityView): + """display security information for a given entity""" + id = 'security' + title = _('security') + + def cell_call(self, row, col): + self.req.add_js('cubicweb.edition.js') + self.req.add_css('cubicweb.acl.css') + entity = self.entity(row, col) + w = self.w + _ = self.req._ + w(u'

%s %s

' + % (entity.dc_type().capitalize(), + html_escape(entity.absolute_url()), + html_escape(entity.dc_title()))) + # first show permissions defined by the schema + self.w('

%s

' % _('schema\'s permissions definitions')) + self.schema_definition(entity) + self.w('

%s

' % _('manage security')) + # ownership information + if self.schema.rschema('owned_by').has_perm(self.req, 'add', + fromeid=entity.eid): + self.owned_by_edit_form(entity) + else: + self.owned_by_information(entity) + # epermissions + if 'require_permission' in entity.e_schema.subject_relations(): + w('

%s

' % _('permissions for this entity')) + reqpermschema = self.schema.rschema('require_permission') + self.require_permission_information(entity, reqpermschema) + if reqpermschema.has_perm(self.req, 'add', fromeid=entity.eid): + self.require_permission_edit_form(entity) + + def schema_definition(self, entity): + w = self.w + _ = self.req._ + w(u'') + w(u'' % ( + _("access type"), _('granted to groups'), _('rql expressions'))) + for access_type in ('read', 'add', 'update', 'delete'): + w(u'') + w(u'' % self.req.__('%s_permission' % access_type)) + groups = entity.e_schema.get_groups(access_type) + l = [] + for group in groups: + l.append(u'%s' % ( + self.build_url('egroup/%s' % group), _(group))) + w(u'' % u', '.join(l)) + rqlexprs = entity.e_schema.get_rqlexprs(access_type) + w(u'' % u'
'.join(expr.expression for expr in rqlexprs)) + w(u'\n') + w(u'
%s%s%s
%s%s%s
') + + def owned_by_edit_form(self, entity): + self.w('

%s

' % self.req._('ownership')) + begin_form(self.w, entity, 'security', msg= _('ownerships have been changed')) + self.w(u'\n') + self.w(u'\n
\n') + wdg = entity.get_widget('owned_by') + self.w(wdg.edit_render(entity)) + self.w(u'\n') + self.w(self.button_ok()) + self.w(u'
\n') + self.w(u'
\n') + + def owned_by_information(self, entity): + ownersrset = entity.related('owned_by') + if ownersrset: + self.w('

%s

' % self.req._('ownership')) + self.w(u'
') + self.w(self.req._('this entity is currently owned by') + ' ') + self.wview('csv', entity.related('owned_by'), 'null') + self.w(u'
') + # else we don't know if this is because entity has no owner or becayse + # user as no access to owner users entities + + def require_permission_information(self, entity, reqpermschema): + if entity.require_permission: + w = self.w + _ = self.req._ + if reqpermschema.has_perm(self.req, 'delete', fromeid=entity.eid): + delurl = self.build_url('edit', __redirectvid='security', + __redirectpath=entity.rest_path()) + delurl = delurl.replace('%', '%%') + # don't give __delete value to build_url else it will be urlquoted + # and this will replace %s by %25s + delurl += '&__delete=%s:require_permission:%%s' % entity.eid + dellinktempl = u'[-] ' % ( + html_escape(delurl), _('delete this permission')) + else: + dellinktempl = None + w(u'') + w(u'' % (_("permission"), + _('granted to groups'))) + for eperm in entity.require_permission: + w(u'') + if dellinktempl: + w(u'' % (dellinktempl % eperm.eid, + eperm.view('oneline'))) + else: + w(u'' % eperm.view('oneline')) + w(u'' % self.view('csv', eperm.related('require_group'), 'null')) + w(u'\n') + w(u'
%s%s
%s%s%s%s
') + else: + self.w(self.req._('no associated epermissions')) + + def require_permission_edit_form(self, entity): + w = self.w + _ = self.req._ + newperm = self.vreg.etype_class('EPermission')(self.req, None) + newperm.eid = self.req.varmaker.next() + w(u'

%s

' % _('add a new permission')) + begin_form(w, newperm, 'security', entity.rest_path()) + w(u'' + % eid_param('edito-require_permission', newperm.eid)) + w(u'' + % (eid_param('require_permission', newperm.eid), entity.eid)) + w(u'\n') + w(u'\n' + % (_("name"), _("label"), _('granted to groups'))) + if getattr(entity, '__permissions__', None): + wdg = StaticComboBoxWidget(self.vreg, self.schema['EPermission'], + self.schema['name'], self.schema['String'], + vocabfunc=lambda x: entity.__permissions__) + else: + wdg = newperm.get_widget('name') + w(u'\n' % wdg.edit_render(newperm)) + wdg = newperm.get_widget('label') + w(u'\n' % wdg.edit_render(newperm)) + wdg = newperm.get_widget('require_group') + w(u'\n' % wdg.edit_render(newperm)) + w(u'\n' % self.button_ok()) + w(u'
%s%s%s 
%s%s%s%s
') + w(u'\n') + + def button_ok(self): + return (u'' + % self.req._(stdmsgs.BUTTON_OK)) + + +class ErrorView(AnyRsetView): + """default view when no result has been found""" + __selectors__ = (yes_selector,) + id = 'error' + + def page_title(self): + """returns a title according to the result set - used for the + title in the HTML header + """ + return self.req._('an error occured') + + def call(self): + req = self.req.reset_headers() + _ = req._ + ex = req.data.get('ex')#_("unable to find exception information")) + excinfo = req.data.get('excinfo') + title = _('an error occured') + self.w(u'

%s

' % title) + if 'errmsg' in req.data: + ex = req.data['errmsg'] + else: + ex = exc_message(ex, req.encoding) + if excinfo is not None and self.config['print-traceback']: + exclass = ex.__class__.__name__ + self.w(u'
%s: %s
' % (exclass, html_escape(ex).replace("\n","
"))) + self.w(u'
') + self.w(u'
%s
' % html_traceback(excinfo, ex, '')) + else: + self.w(u'
%s
' % (html_escape(ex).replace("\n","
"))) + # if excinfo is not None, it's probably not a bug + if excinfo is None: + return + vcconf = self.config.vc_config() + self.w(u"
") + eversion = vcconf.get('cubicweb', _('no version information')) + # NOTE: tuple wrapping needed since eversion is itself a tuple + self.w(u"CubicWeb version: %s
\n" % (eversion,)) + for pkg in self.config.cubes(): + pkgversion = vcconf.get(pkg, _('no version information')) + self.w(u"Package %s version: %s
\n" % (pkg, pkgversion)) + self.w(u"
") + # creates a bug submission link if SUBMIT_URL is set + submiturl = self.config['submit-url'] + if submiturl: + binfo = text_error_description(ex, excinfo, req, eversion, + [(pkg, vcconf.get(pkg, _('no version information'))) + for pkg in self.config.cubes()]) + self.w(u'
\n' % html_escape(submiturl)) + self.w(u'
\n') + self.w(u'' % html_escape(binfo)) + self.w(u'') + self.w(u'') + self.w(u'' % _('Submit bug report')) + self.w(u'
\n') + self.w(u'
\n') + submitmail = self.config['submit-mail'] + if submitmail: + binfo = text_error_description(ex, excinfo, req, eversion, + [(pkg, vcconf.get(pkg, _('no version information'))) + for pkg in self.config.cubes()]) + self.w(u'
\n' % req.build_url('reportbug')) + self.w(u'
\n') + self.w(u'' % html_escape(binfo)) + self.w(u'') + self.w(u'' % _('Submit bug report by mail')) + self.w(u'
\n') + self.w(u'
\n') + + +def exc_message(ex, encoding): + try: + return unicode(ex) + except: + try: + return unicode(str(ex), encoding, 'replace') + except: + return unicode(repr(ex), encoding, 'replace') + +def text_error_description(ex, excinfo, req, eversion, cubes): + binfo = rest_traceback(excinfo, html_escape(ex)) + binfo += u'\n\n:URL: %s\n' % req.url() + if not '__bugreporting' in req.form: + binfo += u'\n:form params:\n' + binfo += u'\n'.join(u' * %s = %s' % (k, v) for k, v in req.form.iteritems()) + binfo += u'\n\n:CubicWeb version: %s\n' % (eversion,) + for pkg, pkgversion in cubes: + binfo += u":Package %s version: %s\n" % (pkg, pkgversion) + binfo += '\n' + return binfo + +# some string we want to be internationalizable for nicer display of eproperty +# groups +_('navigation') +_('ui') +_('actions') +_('boxes') +_('components') +_('contentnavigation') + +class SystemEpropertiesForm(FormMixIn, StartupView): + controller = 'edit' + id = 'systemepropertiesform' + title = _('site configuration') + require_groups = ('managers',) + category = 'startupview' + + def linkable(self): + return True + + def url(self): + """return the url associated with this view. We can omit rql here""" + return self.build_url('view', vid=self.id) + + def call(self, **kwargs): + """The default view representing the application's index""" + self.req.add_js('cubicweb.edition.js') + self.req.add_css('cubicweb.preferences.css') + vreg = self.vreg + values = self.defined_keys + groupedopts = {} + mainopts = {} + # "self.id=='systemepropertiesform'" to skip site wide properties on + # user's preference but not site's configuration + for key in vreg.user_property_keys(self.id=='systemepropertiesform'): + parts = key.split('.') + if parts[0] in vreg: + # appobject configuration + reg, oid, propid = parts + groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key) + else: + mainopts.setdefault(parts[0], []).append(key) + # precompute form to consume error message + for group, keys in mainopts.items(): + mainopts[group] = self.form(keys, False) + for group, objects in groupedopts.items(): + for oid, keys in objects.items(): + groupedopts[group][oid] = self.form(keys, True) + + w = self.w + req = self.req + _ = req._ + w(u'

%s

\n' % _(self.title)) + w(self.error_message()) + for label, group, form in sorted((_(g), g, f) + for g, f in mainopts.iteritems()): + w(u'

%s

\n' % + (toggle_link('fieldset_' + group, label))) + w(u'') + for label, group, objects in sorted((_(g), g, o) + for g, o in groupedopts.iteritems()): + w(u'

%s

\n' % + (toggle_link('fieldset_' + group, label))) + w(u'') + + + + @property + @cached + def eprops_rset(self): + return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U') + + @property + def defined_keys(self): + values = {} + for i, entity in enumerate(self.eprops_rset.entities()): + values[entity.pkey] = i + return values + + def entity_for_key(self, key): + values = self.defined_keys + if key in values: + entity = self.eprops_rset.get_entity(values[key], 0) + else: + entity = self.vreg.etype_class('EProperty')(self.req, None, None) + entity.eid = self.req.varmaker.next() + entity['value'] = self.vreg.property_value(key) + return entity + + def form(self, keys, splitlabel=False): + stream = UStringIO() + w = stream.write + w(u'
\n' % self.build_url()) + w(u'
\n') + w(u'\n' + % html_escape(self.req.url())) + w(u'\n' % self.id) + path = self.req.relative_path() + if '?' in path: + path, params = path.split('?', 1) + w(u'\n' + % html_escape(params)) + w(u'\n' % path) + #w(u'\n') + w(u'\n' + % self.req._('changes applied')) + w(u'
\n') + + w(u'\n') + for key in keys: + w(u'\n') + self.form_row(w, key, splitlabel) + w(u'\n') + w(u'
\n') + w(u'
\n') + w(self.button_ok()) + w(self.button_cancel()) + w(u'
\n') + w(u'
\n') + w(u'
\n') + return stream.getvalue() + + def form_row(self, w, key, splitlabel): + entity = self.entity_for_key(key) + eid = entity.eid + if splitlabel: + w(u'%s' % self.req._(key.split('.')[-1])) + else: + w(u'%s' % self.req._(key)) + wdg = self.vreg.property_value_widget(key, req=self.req) + error = wdg.render_error(entity) + w(u'' % (error and 'error' or '')) + w(error) + self.form_row_hiddens(w, entity, key) + w(wdg.edit_render(entity)) + w(u'\n') + w(u'%s' % wdg.render_help(entity)) + return entity + + def form_row_hiddens(self, w, entity, key): + eid = entity.eid + w(u'' % eid) + w(u'' % eid_param('__type', eid)) + w(u'' % (eid_param('pkey', eid), key)) + w(u'' % (eid_param('edits-pkey', eid), '')) + + +class EpropertiesForm(SystemEpropertiesForm): + id = 'epropertiesform' + title = _('preferences') + require_groups = ('users', 'managers') # we don't want guests to be able to come here + __selectors__ = chainfirst(norset_selector, + chainall(onelinerset_selector, accept_rset_selector)), + accepts = ('EUser',) + + @classmethod + def accept_rset(cls, req, rset, row, col): + if row is None: + row = 0 + score = super(EpropertiesForm, cls).accept_rset(req, rset, row, col) + # check current user is the rset user or he is in the managers group + if score and (req.user.eid == rset[row][col or 0] + or req.user.matching_groups('managers')): + return score + return 0 + + @property + def user(self): + if self.rset is None: + return self.req.user + return self.rset.get_entity(self.row or 0, self.col or 0) + + @property + @cached + def eprops_rset(self): + return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V,' + 'P for_user U, U eid %(x)s', {'x': self.user.eid}) + + def form_row_hiddens(self, w, entity, key): + super(EpropertiesForm, self).form_row_hiddens(w, entity, key) + # if user is in the managers group and the property is being created, + # we have to set for_user explicitly + if not entity.has_eid() and self.user.matching_groups('managers'): + eid = entity.eid + w(u'' + % (eid_param('edits-for_user', eid), INTERNAL_FIELD_VALUE)) + w(u'' + % (eid_param('for_user', eid), self.user.eid)) + + + + +class ProcessInformationView(StartupView): + id = 'info' + title = _('server information') + require_groups = ('managers',) + + def call(self, **kwargs): + """display server information""" + vcconf = self.config.vc_config() + req = self.req + _ = req._ + # display main information + self.w(u'

%s

' % _('Application')) + self.w(u'') + self.w(u'' % ( + 'CubicWeb', vcconf.get('cubicweb', _('no version information')))) + for pkg in self.config.cubes(): + pkgversion = vcconf.get(pkg, _('no version information')) + self.w(u'' % ( + pkg, pkgversion)) + self.w(u'' % ( + _('home'), self.config.apphome)) + self.w(u'' % ( + _('base url'), req.base_url())) + self.w(u'' % ( + _('data directory url'), req.datadir_url)) + self.w(u'
%s%s
%s%s
%s%s
%s%s
%s%s
') + self.w(u'
') + # environment and request and server information + try: + # need to remove our adapter and then modpython-apache wrapper... + env = req._areq._req.subprocess_env + except AttributeError: + return + self.w(u'

%s

' % _('Environment')) + self.w(u'') + for attr in env.keys(): + self.w(u'' + % (attr, html_escape(env[attr]))) + self.w(u'
%s%s
') + self.w(u'

%s

' % _('Request')) + self.w(u'') + for attr in ('filename', 'form', 'hostname', 'main', 'method', + 'path_info', 'protocol', + 'search_state', 'the_request', 'unparsed_uri', 'uri'): + val = getattr(req, attr) + self.w(u'' + % (attr, html_escape(val))) + self.w(u'
%s%s
') + server = req.server + self.w(u'

%s

' % _('Server')) + self.w(u'') + for attr in dir(server): + val = getattr(server, attr) + if attr.startswith('_') or callable(val): + continue + self.w(u'' + % (attr, html_escape(val))) + self.w(u'
%s%s
') +