web/views/cwproperties.py
changeset 1808 aa09e20dd8c0
parent 1739 78b0819162a8
child 1865 62d3726ad8cb
equal deleted inserted replaced
1693:49075f57cf2c 1808:aa09e20dd8c0
       
     1 """Specific views for CWProperty
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 _ = unicode
       
     9 
       
    10 from logilab.mtconverter import html_escape
       
    11 
       
    12 from logilab.common.decorators import cached
       
    13 
       
    14 from cubicweb import UnknownProperty
       
    15 from cubicweb.selectors import (one_line_rset, none_rset, implements,
       
    16                                 match_user_groups)
       
    17 from cubicweb.view import StartupView
       
    18 from cubicweb.web import uicfg, stdmsgs
       
    19 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn
       
    20 from cubicweb.web.formfields import FIELDS, StringField
       
    21 from cubicweb.web.formwidgets import Select, Button, SubmitButton
       
    22 from cubicweb.web.views import primary
       
    23 
       
    24 
       
    25 # some string we want to be internationalizable for nicer display of eproperty
       
    26 # groups
       
    27 _('navigation')
       
    28 _('ui')
       
    29 _('actions')
       
    30 _('boxes')
       
    31 _('components')
       
    32 _('contentnavigation')
       
    33 _('navigation.combobox-limit')
       
    34 _('navigation.page-size')
       
    35 _('navigation.related-limit')
       
    36 _('navigation.short-line-size')
       
    37 _('ui.date-format')
       
    38 _('ui.datetime-format')
       
    39 _('ui.default-text-format')
       
    40 _('ui.fckeditor')
       
    41 _('ui.float-format')
       
    42 _('ui.language')
       
    43 _('ui.time-format')
       
    44 _('open all')
       
    45 _('ui.main-template')
       
    46 _('ui.site-title')
       
    47 _('ui.encoding')
       
    48 _('category')
       
    49 
       
    50 
       
    51 def make_togglable_link(nodeid, label, cookiename):
       
    52     """builds a HTML link that switches the visibility & remembers it"""
       
    53     action = u"javascript: toggleVisibility('%s', '%s')" % \
       
    54         (nodeid, cookiename)
       
    55     return u'<a href="%s">%s</a>' % (action, label)
       
    56 
       
    57 def css_class(someclass):
       
    58     return someclass and 'class="%s"' % someclass or ''
       
    59 
       
    60 
       
    61 class CWPropertyPrimaryView(primary.PrimaryView):
       
    62     __select__ = implements('CWProperty')
       
    63     skip_none = False
       
    64 
       
    65 
       
    66 class SystemEPropertiesForm(FormViewMixIn, StartupView):
       
    67     id = 'systemepropertiesform'
       
    68     __select__ = none_rset() & match_user_groups('managers')
       
    69 
       
    70     title = _('site configuration')
       
    71     category = 'startupview'
       
    72 
       
    73     def linkable(self):
       
    74         return True
       
    75 
       
    76     def url(self):
       
    77         """return the url associated with this view. We can omit rql here"""
       
    78         return self.build_url('view', vid=self.id)
       
    79 
       
    80     def _cookie_name(self, somestr):
       
    81         return str('%s_property_%s' % (self.config.appid, somestr))
       
    82 
       
    83     def _group_status(self, group, default=u'hidden'):
       
    84         """return css class name 'hidden' (collapsed), or '' (open)"""
       
    85         cookies = self.req.get_cookie()
       
    86         cookiename = self._cookie_name(group)
       
    87         cookie = cookies.get(cookiename)
       
    88         if cookie is None:
       
    89             cookies[cookiename] = default
       
    90             self.req.set_cookie(cookies, cookiename, maxage=None)
       
    91             status = default
       
    92         else:
       
    93             status = cookie.value
       
    94         return status
       
    95 
       
    96     def call(self, **kwargs):
       
    97         """The default view representing the application's index"""
       
    98         self.req.add_js('cubicweb.preferences.js')
       
    99         self.req.add_css('cubicweb.preferences.css')
       
   100         vreg = self.vreg
       
   101         values = self.defined_keys
       
   102         groupedopts = {}
       
   103         mainopts = {}
       
   104         # "self.id=='systemepropertiesform'" to skip site wide properties on
       
   105         # user's preference but not site's configuration
       
   106         for key in vreg.user_property_keys(self.id=='systemepropertiesform'):
       
   107             parts = key.split('.')
       
   108             if parts[0] in vreg:
       
   109                 # appobject configuration
       
   110                 reg, oid, propid = parts
       
   111                 groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key)
       
   112             else:
       
   113                 mainopts.setdefault(parts[0], []).append(key)
       
   114         # precompute form to consume error message
       
   115         for group, keys in mainopts.items():
       
   116             mainopts[group] = self.form(keys, True)
       
   117         for group, objects in groupedopts.items():
       
   118             for oid, keys in objects.items():
       
   119                 groupedopts[group][oid] = self.form(keys, True)
       
   120         w = self.w
       
   121         req = self.req
       
   122         _ = req._
       
   123         w(u'<h1>%s</h1>\n' % _(self.title))
       
   124         # we don't want this in each sub-forms
       
   125         w(u'<div id="progress">%s</div>' % self.req._('validating...'))
       
   126         for label, group, form in sorted((_(g), g, f)
       
   127                                          for g, f in mainopts.iteritems()):
       
   128             status = css_class(self._group_status(group))
       
   129             w(u'<h2 class="propertiesform">%s</h2>\n' %
       
   130               (make_togglable_link('fieldset_' + group, label,
       
   131                                    self._cookie_name(group))))
       
   132             w(u'<div id="fieldset_%s" %s>' % (group, status))
       
   133             w(form)
       
   134             w(u'</div>')
       
   135         for label, group, objects in sorted((_(g), g, o)
       
   136                                             for g, o in groupedopts.iteritems()):
       
   137             status = css_class(self._group_status(group))
       
   138             w(u'<h2 class="propertiesform">%s</h2>\n' %
       
   139               (make_togglable_link('fieldset_' + group, label,
       
   140                                    self._cookie_name(group))))
       
   141             w(u'<div id="fieldset_%s" %s>' % (group, status))
       
   142             for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f)
       
   143                                            for o, f in objects.iteritems()):
       
   144                 w(u'<fieldset class="subentity">')
       
   145                 w(u'<legend class="componentTitle">%s</legend>\n' % label)
       
   146                 docmsgid = '%s_%s_description' % (group, oid)
       
   147                 doc = _(docmsgid)
       
   148                 if doc != docmsgid:
       
   149                     w(u'<p class="description">%s</p>' % html_escape(doc))
       
   150                 w(form)
       
   151                 w(u'</fieldset>')
       
   152             w(u'</div>')
       
   153 
       
   154     @property
       
   155     @cached
       
   156     def eprops_rset(self):
       
   157         return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, '
       
   158                                 'P value V, NOT P for_user U')
       
   159 
       
   160     @property
       
   161     def defined_keys(self):
       
   162         values = {}
       
   163         for i, entity in enumerate(self.eprops_rset.entities()):
       
   164             values[entity.pkey] = i
       
   165         return values
       
   166 
       
   167     def entity_for_key(self, key):
       
   168         values = self.defined_keys
       
   169         if key in values:
       
   170             entity = self.eprops_rset.get_entity(values[key], 0)
       
   171         else:
       
   172             entity = self.vreg.etype_class('CWProperty')(self.req, None, None)
       
   173             entity.eid = self.req.varmaker.next()
       
   174             entity['pkey'] = key
       
   175             entity['value'] = self.vreg.property_value(key)
       
   176         return entity
       
   177 
       
   178     def form(self, keys, splitlabel=False):
       
   179         buttons = [SubmitButton(),
       
   180                    Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
       
   181         form = CompositeForm(self.req, domid=None, action=self.build_url(),
       
   182                              form_buttons=buttons,
       
   183                              submitmsg=self.req._('changes applied'))
       
   184         path = self.req.relative_path()
       
   185         if '?' in path:
       
   186             path, params = path.split('?', 1)
       
   187             form.form_add_hidden('__redirectparams', params)
       
   188         form.form_add_hidden('__redirectpath', path)
       
   189         for key in keys:
       
   190             self.form_row(form, key, splitlabel)
       
   191         return form.form_render(display_progress_div=False)
       
   192 
       
   193     def form_row(self, form, key, splitlabel):
       
   194         entity = self.entity_for_key(key)
       
   195         if splitlabel:
       
   196             label = key.split('.')[-1]
       
   197         else:
       
   198             label = key
       
   199         subform = EntityFieldsForm(self.req, entity=entity, set_error_url=False)
       
   200         subform.append_field(PropertyValueField(name='value', label=label,
       
   201                                                 eidparam=True))
       
   202         subform.vreg = self.vreg
       
   203         subform.form_add_hidden('pkey', key, eidparam=True)
       
   204         form.form_add_subform(subform)
       
   205         return subform
       
   206 
       
   207 
       
   208 def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs):
       
   209     return req.user.eid == rset[row or 0][col]
       
   210 
       
   211 
       
   212 class EPropertiesForm(SystemEPropertiesForm):
       
   213     id = 'epropertiesform'
       
   214     __select__ = (
       
   215         # we don't want guests to be able to come here
       
   216         match_user_groups('users', 'managers') &
       
   217         (none_rset() | ((one_line_rset() & is_user_prefs) &
       
   218                         (one_line_rset() & match_user_groups('managers'))))
       
   219         )
       
   220 
       
   221     title = _('preferences')
       
   222 
       
   223     @property
       
   224     def user(self):
       
   225         if self.rset is None:
       
   226             return self.req.user
       
   227         return self.rset.get_entity(self.row or 0, self.col or 0)
       
   228 
       
   229     @property
       
   230     @cached
       
   231     def eprops_rset(self):
       
   232         return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,'
       
   233                                 'P for_user U, U eid %(x)s', {'x': self.user.eid})
       
   234 
       
   235     def form_row(self, form, key, splitlabel):
       
   236         subform = super(EPropertiesForm, self).form_row(form, key, splitlabel)
       
   237         # if user is in the managers group and the property is being created,
       
   238         # we have to set for_user explicitly
       
   239         if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'):
       
   240             subform.form_add_hidden('for_user', self.user.eid, eidparam=True)
       
   241 
       
   242 
       
   243 # eproperty form objects ######################################################
       
   244 
       
   245 class PlaceHolderWidget(object):
       
   246 
       
   247     def render(self, form, field):
       
   248         domid = form.context[field]['id']
       
   249         # empty span as well else html validation fail (label is refering to
       
   250         # this id)
       
   251         return '<div id="div:%s"><span id="%s">%s</span></div>' % (
       
   252             domid, domid, form.req._('select a key first'))
       
   253 
       
   254 
       
   255 class NotEditableWidget(object):
       
   256     def __init__(self, value, msg=None):
       
   257         self.value = value
       
   258         self.msg = msg
       
   259 
       
   260     def render(self, form, field):
       
   261         domid = form.context[field]['id']
       
   262         value = '<span class="value" id="%s">%s</span>' % (domid, self.value)
       
   263         if self.msg:
       
   264             value + '<div class="helper">%s</div>' % self.msg
       
   265         return value
       
   266 
       
   267 
       
   268 class PropertyKeyField(StringField):
       
   269     """specific field for CWProperty.pkey to set the value widget according to
       
   270     the selected key
       
   271     """
       
   272     widget = Select
       
   273 
       
   274     def render(self, form, renderer):
       
   275         wdg = self.get_widget(form)
       
   276         wdg.attrs['tabindex'] = form.req.next_tabindex()
       
   277         wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
       
   278             form.edited_entity.eid, form.req.next_tabindex())
       
   279         return wdg.render(form, self)
       
   280 
       
   281     def vocabulary(self, form):
       
   282         entity = form.edited_entity
       
   283         _ = form.req._
       
   284         if entity.has_eid():
       
   285             return [(_(entity.pkey), entity.pkey)]
       
   286         # key beginning with 'system.' should usually not be edited by hand
       
   287         choices = entity.vreg.user_property_keys()
       
   288         return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices))
       
   289 
       
   290 
       
   291 class PropertyValueField(StringField):
       
   292     """specific field for CWProperty.value  which will be different according to
       
   293     the selected key type and vocabulary information
       
   294     """
       
   295     widget = PlaceHolderWidget
       
   296 
       
   297     def render(self, form, renderer=None, tabindex=None):
       
   298         wdg = self.get_widget(form)
       
   299         if tabindex is not None:
       
   300             wdg.attrs['tabindex'] = tabindex
       
   301         return wdg.render(form, self)
       
   302 
       
   303     def form_init(self, form):
       
   304         entity = form.edited_entity
       
   305         if not (entity.has_eid() or 'pkey' in entity):
       
   306             # no key set yet, just include an empty div which will be filled
       
   307             # on key selection
       
   308             return
       
   309         try:
       
   310             pdef = form.vreg.property_info(entity.pkey)
       
   311         except UnknownProperty, ex:
       
   312             self.warning('%s (you should probably delete that property '
       
   313                          'from the database)', ex)
       
   314             msg = form.req._('you should probably delete that property')
       
   315             self.widget = NotEditableWidget(entity.printable_value('value'),
       
   316                                             '%s (%s)' % (msg, ex))
       
   317         if entity.pkey.startswith('system.'):
       
   318             msg = form.req._('value associated to this key is not editable '
       
   319                              'manually')
       
   320             self.widget = NotEditableWidget(entity.printable_value('value'), msg)
       
   321         # XXX race condition when used from CWPropertyForm, should not rely on
       
   322         # instance attributes
       
   323         self.initial = pdef['default']
       
   324         self.help = pdef['help']
       
   325         vocab = pdef['vocabulary']
       
   326         if vocab is not None:
       
   327             if callable(vocab):
       
   328                 # list() just in case its a generator function
       
   329                 self.choices = list(vocab(form.req))
       
   330             else:
       
   331                 self.choices = vocab
       
   332             wdg = Select()
       
   333         else:
       
   334             wdg = FIELDS[pdef['type']].widget()
       
   335             if pdef['type'] == 'Boolean':
       
   336                 self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')]
       
   337             elif pdef['type'] in ('Float', 'Int'):
       
   338                 wdg.attrs.setdefault('size', 3)
       
   339         self.widget = wdg
       
   340 
       
   341 
       
   342 uicfg.autoform_field.tag_attribute(('CWProperty', 'pkey'), PropertyKeyField)
       
   343 uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField)