# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1240928514 -7200 # Node ID 525965b1c23a8eb5e263cf4da51e628f79434833 # Parent b38e888ee7e35e71d844706da075c4203c0e3268 rename diff -r b38e888ee7e3 -r 525965b1c23a web/views/cwproperties.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/cwproperties.py Tue Apr 28 16:21:54 2009 +0200 @@ -0,0 +1,329 @@ +"""Specific views for CWProperty + +:organization: Logilab +:copyright: 2007-2009 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 import UnknownProperty +from cubicweb.selectors import (one_line_rset, none_rset, implements, + match_user_groups, entity_implements) +from cubicweb.utils import UStringIO +from cubicweb.view import StartupView +from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, uicfg +from cubicweb.web.views import baseviews +from cubicweb.web import stdmsgs +from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn +from cubicweb.web.formfields import FIELDS, StringField +from cubicweb.web.formwidgets import Select, Button, SubmitButton + +_ = unicode + +# some string we want to be internationalizable for nicer display of eproperty +# groups +_('navigation') +_('ui') +_('actions') +_('boxes') +_('components') +_('contentnavigation') + + +def make_togglable_link(nodeid, label, cookiename): + """builds a HTML link that switches the visibility & remembers it""" + action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \ + (nodeid, cookiename) + return u'%s' % (action, label) + +def css_class(someclass): + return someclass and 'class="%s"' % someclass or '' + + +class CWPropertyPrimaryView(baseviews.PrimaryView): + __select__ = implements('CWProperty') + skip_none = False + + +class SystemEPropertiesForm(FormViewMixIn, StartupView): + id = 'systemepropertiesform' + __select__ = none_rset() & match_user_groups('managers') + + title = _('site configuration') + 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 _cookie_name(self, somestr): + return str('%s_property_%s' % (self.config.appid, somestr)) + + def _group_status(self, group, default=u'hidden'): + """return css class name 'hidden' (collapsed), or '' (open)""" + cookies = self.req.get_cookie() + cookiename = self._cookie_name(group) + cookie = cookies.get(cookiename) + if cookie is None: + cookies[cookiename] = default + self.req.set_cookie(cookies, cookiename, maxage=None) + status = default + else: + status = cookie.value + return status + + def call(self, **kwargs): + """The default view representing the application's index""" + self.req.add_js('cubicweb.preferences.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, True) + 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)) + # we don't want this in each sub-forms + w(u'
%s
' % self.req._('validating...')) + for label, group, form in sorted((_(g), g, f) + for g, f in mainopts.iteritems()): + status = css_class(self._group_status(group)) + w(u'

%s

\n' % + (make_togglable_link('fieldset_' + group, label, + self._cookie_name(group)))) + w(u'
' % (group, status)) + w(form) + w(u'
') + for label, group, objects in sorted((_(g), g, o) + for g, o in groupedopts.iteritems()): + status = css_class(self._group_status(group)) + w(u'

%s

\n' % + (make_togglable_link('fieldset_' + group, label, + self._cookie_name(group)))) + w(u'
' % (group, status)) + for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f) + for o, f in objects.iteritems()): + w(u'
') + w(u'%s\n' % label) + docmsgid = '%s_%s_description' % (group, oid) + doc = _(docmsgid) + if doc != docmsgid: + w(u'

%s

' % html_escape(doc)) + w(form) + w(u'
') + w(u'
') + + @property + @cached + def eprops_rset(self): + return self.req.execute('Any P,K,V WHERE P is CWProperty, 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('CWProperty')(self.req, None, None) + entity.eid = self.req.varmaker.next() + entity['pkey'] = key + entity['value'] = self.vreg.property_value(key) + return entity + + def form(self, keys, splitlabel=False): + buttons = [SubmitButton(), + Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] + form = CompositeForm(self.req, domid=None, action=self.build_url(), + form_buttons=buttons, + submitmsg=self.req._('changes applied')) + path = self.req.relative_path() + if '?' in path: + path, params = path.split('?', 1) + form.form_add_hidden('__redirectparams', params) + form.form_add_hidden('__redirectpath', path) + for key in keys: + self.form_row(form, key, splitlabel) + return form.form_render(display_progress_div=False) + + def form_row(self, form, key, splitlabel): + entity = self.entity_for_key(key) + if splitlabel: + label = key.split('.')[-1] + else: + label = key + subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False) + subform.append_field(PropertyValueField(name='value', label=label, + eidparam=True)) + subform.vreg = self.vreg + subform.form_add_hidden('pkey', key, eidparam=True) + form.form_add_subform(subform) + return subform + + +def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs): + return req.user.eid == rset[row or 0][col] + + +class EPropertiesForm(SystemEPropertiesForm): + id = 'epropertiesform' + __select__ = ( + # we don't want guests to be able to come here + match_user_groups('users', 'managers') & + (none_rset() | ((one_line_rset() & is_user_prefs) & + (one_line_rset() & match_user_groups('managers')))) + ) + + title = _('preferences') + + @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 CWProperty, P pkey K, P value V,' + 'P for_user U, U eid %(x)s', {'x': self.user.eid}) + + def form_row(self, form, key, splitlabel): + subform = super(EPropertiesForm, self).form_row(form, key, splitlabel) + # if user is in the managers group and the property is being created, + # we have to set for_user explicitly + if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'): + subform.form_add_hidden('for_user', self.user.eid, eidparam=True) + + +# eproperty form objects ###################################################### + +class PlaceHolderWidget(object): + + def render(self, form, field): + domid = form.context[field]['id'] + # empty span as well else html validation fail (label is refering to + # this id) + return '
%s
' % ( + domid, domid, form.req._('select a key first')) + + +class NotEditableWidget(object): + def __init__(self, value, msg=None): + self.value = value + self.msg = msg + + def render(self, form, field): + domid = form.context[field]['id'] + value = '%s' % (domid, self.value) + if self.msg: + value + '
%s
' % self.msg + return value + + +class PropertyKeyField(StringField): + """specific field for CWProperty.pkey to set the value widget according to + the selected key + """ + widget = Select + + def render(self, form, renderer): + wdg = self.get_widget(form) + wdg.attrs['tabindex'] = form.req.next_tabindex() + wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % ( + form.edited_entity.eid, form.req.next_tabindex()) + return wdg.render(form, self) + + def vocabulary(self, form): + entity = form.edited_entity + _ = form.req._ + if entity.has_eid(): + return [(_(entity.pkey), entity.pkey)] + # key beginning with 'system.' should usually not be edited by hand + choices = entity.vreg.user_property_keys() + return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices)) + + +class PropertyValueField(StringField): + """specific field for CWProperty.value which will be different according to + the selected key type and vocabulary information + """ + widget = PlaceHolderWidget + + def render(self, form, renderer=None, tabindex=None): + wdg = self.get_widget(form) + if tabindex is not None: + wdg.attrs['tabindex'] = tabindex + return wdg.render(form, self) + + def form_init(self, form): + entity = form.edited_entity + if not (entity.has_eid() or 'pkey' in entity): + # no key set yet, just include an empty div which will be filled + # on key selection + return + try: + pdef = form.vreg.property_info(entity.pkey) + except UnknownProperty, ex: + self.warning('%s (you should probably delete that property ' + 'from the database)', ex) + msg = form.req._('you should probably delete that property') + self.widget = NotEditableWidget(entity.printable_value('value'), + '%s (%s)' % (msg, ex)) + if entity.pkey.startswith('system.'): + msg = form.req._('value associated to this key is not editable ' + 'manually') + self.widget = NotEditableWidget(entity.printable_value('value'), msg) + # XXX race condition when used from CWPropertyForm, should not rely on + # instance attributes + self.initial = pdef['default'] + self.help = pdef['help'] + vocab = pdef['vocabulary'] + if vocab is not None: + if callable(vocab): + # list() just in case its a generator function + self.choices = list(vocab(form.req)) + else: + self.choices = vocab + wdg = Select() + else: + wdg = FIELDS[pdef['type']].widget() + if pdef['type'] == 'Boolean': + self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')] + elif pdef['type'] in ('Float', 'Int'): + wdg.attrs.setdefault('size', 3) + self.widget = wdg + +uicfg.rfields.set_rtag(PropertyKeyField, 'pkey', 'subject', 'CWProperty') +uicfg.rfields.set_rtag(PropertyValueField, 'value', 'subject', 'CWProperty') + diff -r b38e888ee7e3 -r 525965b1c23a web/views/eproperties.py --- a/web/views/eproperties.py Tue Apr 28 16:21:32 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,329 +0,0 @@ -"""Specific views for CWProperty - -:organization: Logilab -:copyright: 2007-2009 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 import UnknownProperty -from cubicweb.selectors import (one_line_rset, none_rset, implements, - match_user_groups, entity_implements) -from cubicweb.utils import UStringIO -from cubicweb.view import StartupView -from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, uicfg -from cubicweb.web.views import baseviews -from cubicweb.web import stdmsgs -from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn -from cubicweb.web.formfields import FIELDS, StringField -from cubicweb.web.formwidgets import Select, Button, SubmitButton - -_ = unicode - -# some string we want to be internationalizable for nicer display of eproperty -# groups -_('navigation') -_('ui') -_('actions') -_('boxes') -_('components') -_('contentnavigation') - - -def make_togglable_link(nodeid, label, cookiename): - """builds a HTML link that switches the visibility & remembers it""" - action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \ - (nodeid, cookiename) - return u'%s' % (action, label) - -def css_class(someclass): - return someclass and 'class="%s"' % someclass or '' - - -class CWPropertyPrimaryView(baseviews.PrimaryView): - __select__ = implements('CWProperty') - skip_none = False - - -class SystemEPropertiesForm(FormViewMixIn, StartupView): - id = 'systemepropertiesform' - __select__ = none_rset() & match_user_groups('managers') - - title = _('site configuration') - 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 _cookie_name(self, somestr): - return str('%s_property_%s' % (self.config.appid, somestr)) - - def _group_status(self, group, default=u'hidden'): - """return css class name 'hidden' (collapsed), or '' (open)""" - cookies = self.req.get_cookie() - cookiename = self._cookie_name(group) - cookie = cookies.get(cookiename) - if cookie is None: - cookies[cookiename] = default - self.req.set_cookie(cookies, cookiename, maxage=None) - status = default - else: - status = cookie.value - return status - - def call(self, **kwargs): - """The default view representing the application's index""" - self.req.add_js('cubicweb.preferences.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, True) - 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)) - # we don't want this in each sub-forms - w(u'
%s
' % self.req._('validating...')) - for label, group, form in sorted((_(g), g, f) - for g, f in mainopts.iteritems()): - status = css_class(self._group_status(group)) - w(u'

%s

\n' % - (make_togglable_link('fieldset_' + group, label, - self._cookie_name(group)))) - w(u'
' % (group, status)) - w(form) - w(u'
') - for label, group, objects in sorted((_(g), g, o) - for g, o in groupedopts.iteritems()): - status = css_class(self._group_status(group)) - w(u'

%s

\n' % - (make_togglable_link('fieldset_' + group, label, - self._cookie_name(group)))) - w(u'
' % (group, status)) - for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f) - for o, f in objects.iteritems()): - w(u'
') - w(u'%s\n' % label) - docmsgid = '%s_%s_description' % (group, oid) - doc = _(docmsgid) - if doc != docmsgid: - w(u'

%s

' % html_escape(doc)) - w(form) - w(u'
') - w(u'
') - - @property - @cached - def eprops_rset(self): - return self.req.execute('Any P,K,V WHERE P is CWProperty, 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('CWProperty')(self.req, None, None) - entity.eid = self.req.varmaker.next() - entity['pkey'] = key - entity['value'] = self.vreg.property_value(key) - return entity - - def form(self, keys, splitlabel=False): - buttons = [SubmitButton(), - Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] - form = CompositeForm(self.req, domid=None, action=self.build_url(), - form_buttons=buttons, - submitmsg=self.req._('changes applied')) - path = self.req.relative_path() - if '?' in path: - path, params = path.split('?', 1) - form.form_add_hidden('__redirectparams', params) - form.form_add_hidden('__redirectpath', path) - for key in keys: - self.form_row(form, key, splitlabel) - return form.form_render(display_progress_div=False) - - def form_row(self, form, key, splitlabel): - entity = self.entity_for_key(key) - if splitlabel: - label = key.split('.')[-1] - else: - label = key - subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False) - subform.append_field(PropertyValueField(name='value', label=label, - eidparam=True)) - subform.vreg = self.vreg - subform.form_add_hidden('pkey', key, eidparam=True) - form.form_add_subform(subform) - return subform - - -def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs): - return req.user.eid == rset[row or 0][col] - - -class EPropertiesForm(SystemEPropertiesForm): - id = 'epropertiesform' - __select__ = ( - # we don't want guests to be able to come here - match_user_groups('users', 'managers') & - (none_rset() | ((one_line_rset() & is_user_prefs) & - (one_line_rset() & match_user_groups('managers')))) - ) - - title = _('preferences') - - @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 CWProperty, P pkey K, P value V,' - 'P for_user U, U eid %(x)s', {'x': self.user.eid}) - - def form_row(self, form, key, splitlabel): - subform = super(EPropertiesForm, self).form_row(form, key, splitlabel) - # if user is in the managers group and the property is being created, - # we have to set for_user explicitly - if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'): - subform.form_add_hidden('for_user', self.user.eid, eidparam=True) - - -# eproperty form objects ###################################################### - -class PlaceHolderWidget(object): - - def render(self, form, field): - domid = form.context[field]['id'] - # empty span as well else html validation fail (label is refering to - # this id) - return '
%s
' % ( - domid, domid, form.req._('select a key first')) - - -class NotEditableWidget(object): - def __init__(self, value, msg=None): - self.value = value - self.msg = msg - - def render(self, form, field): - domid = form.context[field]['id'] - value = '%s' % (domid, self.value) - if self.msg: - value + '
%s
' % self.msg - return value - - -class PropertyKeyField(StringField): - """specific field for CWProperty.pkey to set the value widget according to - the selected key - """ - widget = Select - - def render(self, form, renderer): - wdg = self.get_widget(form) - wdg.attrs['tabindex'] = form.req.next_tabindex() - wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % ( - form.edited_entity.eid, form.req.next_tabindex()) - return wdg.render(form, self) - - def vocabulary(self, form): - entity = form.edited_entity - _ = form.req._ - if entity.has_eid(): - return [(_(entity.pkey), entity.pkey)] - # key beginning with 'system.' should usually not be edited by hand - choices = entity.vreg.user_property_keys() - return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices)) - - -class PropertyValueField(StringField): - """specific field for CWProperty.value which will be different according to - the selected key type and vocabulary information - """ - widget = PlaceHolderWidget - - def render(self, form, renderer=None, tabindex=None): - wdg = self.get_widget(form) - if tabindex is not None: - wdg.attrs['tabindex'] = tabindex - return wdg.render(form, self) - - def form_init(self, form): - entity = form.edited_entity - if not (entity.has_eid() or 'pkey' in entity): - # no key set yet, just include an empty div which will be filled - # on key selection - return - try: - pdef = form.vreg.property_info(entity.pkey) - except UnknownProperty, ex: - self.warning('%s (you should probably delete that property ' - 'from the database)', ex) - msg = form.req._('you should probably delete that property') - self.widget = NotEditableWidget(entity.printable_value('value'), - '%s (%s)' % (msg, ex)) - if entity.pkey.startswith('system.'): - msg = form.req._('value associated to this key is not editable ' - 'manually') - self.widget = NotEditableWidget(entity.printable_value('value'), msg) - # XXX race condition when used from CWPropertyForm, should not rely on - # instance attributes - self.initial = pdef['default'] - self.help = pdef['help'] - vocab = pdef['vocabulary'] - if vocab is not None: - if callable(vocab): - # list() just in case its a generator function - self.choices = list(vocab(form.req)) - else: - self.choices = vocab - wdg = Select() - else: - wdg = FIELDS[pdef['type']].widget() - if pdef['type'] == 'Boolean': - self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')] - elif pdef['type'] in ('Float', 'Int'): - wdg.attrs.setdefault('size', 3) - self.widget = wdg - -uicfg.rfields.set_rtag(PropertyKeyField, 'pkey', 'subject', 'CWProperty') -uicfg.rfields.set_rtag(PropertyValueField, 'value', 'subject', 'CWProperty') -