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