make ui configurations selectable (closes #2406609)

  • introduce a new 'uicfg' registry (storing instances)
  • use the relevant new APIs from lgc.registry to manage the new registrable uicfg objects
  • cw event manager useage is gone; instead thze standard registry reloading mechanism is used
  • ensure i18n commands still work (devctl)
  • introduce dynamic uicfgs use whenever possible (various views), even though sometimes the classic 'static' usage remains
authorFlorent Cayré <florent.cayre@logilab.fr>
changeset1dd655788ece
branchdefault
phasepublic
hiddenno
parent revision#e65af61bde7d [uicfg] uicfg.py moves from web/ to web/views/ (prepares #2406609)
child revision#5a394fc419b4 [web/uihelper,uicfg] transform uihelper functions into uicfg objects methods (closes #2543949)
files modified by this revision
cwvreg.py
devtools/devctl.py
rtags.py
server/hook.py
view.py
web/formfields.py
web/test/unittest_formfields.py
web/test/unittest_uicfg.py
web/views/actions.py
web/views/autoform.py
web/views/forms.py
web/views/management.py
web/views/primary.py
web/views/reledit.py
web/views/uicfg.py
# HG changeset patch
# User Florent Cayré <florent.cayre@logilab.fr>
# Date 1358782449 -3600
# Mon Jan 21 16:34:09 2013 +0100
# Node ID 1dd655788ece6018e3bf7d5486d3802e5a7b14d5
# Parent e65af61bde7d8056fa184693791bfd75be72cbca
make ui configurations selectable (closes #2406609)

* introduce a new 'uicfg' registry (storing instances)

* use the relevant new APIs from lgc.registry to manage the new
registrable uicfg objects

* cw event manager useage is gone; instead thze standard registry
reloading mechanism is used

* ensure i18n commands still work (devctl)

* introduce dynamic uicfgs use whenever possible (various views), even
though sometimes the classic 'static' usage remains

diff --git a/cwvreg.py b/cwvreg.py
@@ -195,30 +195,33 @@
1 
2  import sys
3  from os.path import join, dirname, realpath
4  from warnings import warn
5  from datetime import datetime, date, time, timedelta
6 +from functools import partial
7 
8  from logilab.common.decorators import cached, clear_cache
9  from logilab.common.deprecation import deprecated, class_deprecated
10  from logilab.common.modutils import cleanup_sys_modules
11  from logilab.common.registry import (
12 -    RegistryStore, Registry, classid,
13 +    RegistryStore, Registry, obj_registries,
14      ObjectNotFound, NoSelectableObject, RegistryNotFound)
15 
16  from rql import RQLHelper
17  from yams.constraints import BASE_CONVERTERS
18 
19  from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER,
20                        Binary, UnknownProperty, UnknownEid)
21 -from cubicweb.rtags import RTAGS
22  from cubicweb.predicates import (implements, appobject_selectable,
23                                   _reset_is_instance_cache)
24 
25 -def clear_rtag_objects():
26 -    for rtag in RTAGS:
27 -        rtag.clear()
28 +# backward compat: those modules are now refering to app objects in
29 +# cw.web.views.uicfg and import * from backward compat. On registry reload, we
30 +# should pop those modules from the cache so references are properly updated on
31 +# subsequent reload
32 +CW_EVENT_MANAGER.bind('before-registry-reload', partial(sys.modules.pop, 'cubicweb.web.uicfg', None))
33 +CW_EVENT_MANAGER.bind('before-registry-reload', partial(sys.modules.pop, 'cubicweb.web.uihelper', None))
34 
35  def use_interfaces(obj):
36      """return interfaces required by the given object by searching for
37      `implements` predicate
38      """
@@ -261,10 +264,19 @@
39      through the __appobject__ attribute
40      """
41      return getattr(obj, appobjectattr, obj)
42 
43 
44 +class InstancesRegistry(CWRegistry):
45 +
46 +    def selected(self, winner, args, kwargs):
47 +        """overriden to avoid the default 'instanciation' behaviour, ie
48 +        winner(*args, **kwargs)
49 +        """
50 +        return winner
51 +
52 +
53  class ETypeRegistry(CWRegistry):
54 
55      def clear_caches(self):
56          clear_cache(self, 'etype_class')
57          clear_cache(self, 'parent_classes')
@@ -495,10 +507,11 @@
58      REGISTRY_FACTORY = {None: CWRegistry,
59                          'etypes': ETypeRegistry,
60                          'views': ViewsRegistry,
61                          'actions': ActionsRegistry,
62                          'ctxcomponents': CtxComponentsRegistry,
63 +                        'uicfg': InstancesRegistry,
64                          }
65 
66      def __init__(self, config, initlog=True):
67          if initlog:
68              # first init log service
@@ -515,15 +528,10 @@
69                  sys.path.remove(webdir)
70          if CW_SOFTWARE_ROOT in sys.path:
71              sys.path.remove(CW_SOFTWARE_ROOT)
72          self.schema = None
73          self.initialized = False
74 -        # XXX give force_reload (or refactor [re]loading...)
75 -        if self.config.mode != 'test':
76 -            # don't clear rtags during test, this may cause breakage with
77 -            # manually imported appobject modules
78 -            CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects)
79          self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents')
80          self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents')
81 
82      def setdefault(self, regid):
83          try:
@@ -693,34 +701,36 @@
84                                     and iface in self.schema
85                                     and self['etypes'].etype_class(iface)
86                                     or iface
87                                     for iface in ifaces)
88                  if not ('Any' in ifaces or ifaces & implemented_interfaces):
89 +                    reg = self[obj_registries(obj)[0]]
90                      self.debug('unregister %s (no implemented '
91 -                               'interface among %s)', classid(obj), ifaces)
92 +                               'interface among %s)', reg.objid(obj), ifaces)
93                      self.unregister(obj)
94              # since 3.9: remove appobjects which depending on other, unexistant
95              # appobjects
96              for obj, (regname, regids) in self._needs_appobject.items():
97                  try:
98                      registry = self[regname]
99                  except RegistryNotFound:
100 -                    self.debug('unregister %s (no registry %s)', classid(obj),
101 -                               regname)
102 +                    self.debug('unregister %s (no registry %s)', obj, regname)
103                      self.unregister(obj)
104                      continue
105                  for regid in regids:
106                      if registry.get(regid):
107                          break
108                  else:
109                      self.debug('unregister %s (no %s object in registry %s)',
110 -                               classid(obj), ' or '.join(regids), regname)
111 +                               registry.objid(obj), ' or '.join(regids), regname)
112                      self.unregister(obj)
113          super(CWRegistryStore, self).initialization_completed()
114 -        for rtag in RTAGS:
115 -            # don't check rtags if we don't want to cleanup_interface_sobjects
116 -            rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
117 +        if 'uicfg' in self: # 'uicfg' is not loaded in a pure repository mode
118 +            for rtags in self['uicfg'].values():
119 +                for rtag in rtags:
120 +                    # don't check rtags if we don't want to cleanup_interface_sobjects
121 +                    rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
122 
123      # rql parsing utilities ####################################################
124 
125      @property
126      @cached
diff --git a/devtools/devctl.py b/devtools/devctl.py
@@ -93,15 +93,10 @@
127              continue
128          for path in config.appobjects_path():
129              if mod.__file__.startswith(path):
130                  del sys.modules[name]
131                  break
132 -    # fresh rtags
133 -    from cubicweb import rtags
134 -    from cubicweb.web.views import uicfg
135 -    rtags.RTAGS[:] = []
136 -    reload(uicfg)
137 
138  def generate_schema_pot(w, cubedir=None):
139      """generate a pot file with schema specific i18n messages
140 
141      notice that relation definitions description and static vocabulary
@@ -127,35 +122,33 @@
142 
143 
144  def _generate_schema_pot(w, vreg, schema, libconfig=None):
145      from copy import deepcopy
146      from cubicweb.i18n import add_msg
147 -    from cubicweb.web.views import uicfg
148      from cubicweb.schema import NO_I18NCONTEXT, CONSTRAINTS
149      w('# schema pot file, generated on %s\n'
150        % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
151      w('# \n')
152      w('# singular and plural forms for each entity type\n')
153      w('\n')
154      vregdone = set()
155      if libconfig is not None:
156 -        from cubicweb.cwvreg import CWRegistryStore, clear_rtag_objects
157 +        from cubicweb.cwvreg import CWRegistryStore
158          libschema = libconfig.load_schema(remove_unused_rtypes=False)
159 -        afs = deepcopy(uicfg.autoform_section)
160 -        appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
161 -        clear_rtag_objects()
162 +        afs = vreg['uicfg'].select('autoform_section')
163 +        appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
164          cleanup_sys_modules(libconfig)
165          libvreg = CWRegistryStore(libconfig)
166          libvreg.set_schema(libschema) # trigger objects registration
167 -        libafs = uicfg.autoform_section
168 -        libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
169 +        libafs = libvreg['uicfg'].select('autoform_section')
170 +        libappearsin_addmenu = libvreg['uicfg'].select('actionbox_appearsin_addmenu')
171          # prefill vregdone set
172          list(_iter_vreg_objids(libvreg, vregdone))
173      else:
174          libschema = {}
175 -        afs = uicfg.autoform_section
176 -        appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
177 +        afs = vreg['uicfg'].select('autoform_section')
178 +        appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
179          for cstrtype in CONSTRAINTS:
180              add_msg(w, cstrtype)
181      done = set()
182      for eschema in sorted(schema.entities()):
183          if eschema.type in libschema:
diff --git a/rtags.py b/rtags.py
@@ -36,42 +36,44 @@
184     * ``tag_attribute`` shortcut for tag_subject_of
185  """
186  __docformat__ = "restructuredtext en"
187 
188  import logging
189 +from warnings import warn
190 
191  from logilab.common.logging_ext import set_log_methods
192 -
193 -RTAGS = []
194 -def register_rtag(rtag):
195 -    RTAGS.append(rtag)
196 +from logilab.common.registry import RegistrableInstance, yes
197 
198  def _ensure_str_key(key):
199      return tuple(str(k) for k in key)
200 
201 -class RelationTags(object):
202 +class RegistrableRtags(RegistrableInstance):
203 +    __registry__ = 'uicfg'
204 +    __select__ = yes()
205 +
206 +
207 +class RelationTags(RegistrableRtags):
208      """a tag store for full relation definitions :
209 
210           (subject type, relation type, object type, tagged)
211 
212      allowing to set tags using wildcard (eg '*') as subject type / object type
213 
214      This class associates a single tag to each key.
215      """
216      _allowed_values = None
217 -    _initfunc = None
218 -    def __init__(self, name=None, initfunc=None, allowed_values=None):
219 -        self._name = name or '<unknown>'
220 +    # _init expected to be a method (introduced in 3.17), while _initfunc a
221 +    # function given as __init__ argument and kept for bw compat
222 +    _init = _initfunc = None
223 +
224 +    def __init__(self):
225          self._tagdefs = {}
226 -        if allowed_values is not None:
227 -            self._allowed_values = allowed_values
228 -        if initfunc is not None:
229 -            self._initfunc = initfunc
230 -        register_rtag(self)
231 
232      def __repr__(self):
233 -        return '%s: %s' % (self._name, repr(self._tagdefs))
234 +        # find a way to have more infos but keep it readable
235 +        # (in error messages in case of an ambiguity for instance)
236 +        return '%s (%s): %s' % (id(self), self.__regid__, self.__class__)
237 
238      # dict compat
239      def __getitem__(self, key):
240          return self.get(*key)
241      __contains__ = __getitem__
@@ -98,12 +100,12 @@
242                      if ertype != '*' and not ertype in schema:
243                          self.warning('removing rtag %s: %s, %s undefined in schema',
244                                       (stype, rtype, otype, tagged), value, ertype)
245                          self.del_rtag(stype, rtype, otype, tagged)
246                          break
247 -        if self._initfunc is not None:
248 -            self.apply(schema, self._initfunc)
249 +        if self._init is not None:
250 +            self.apply(schema, self._init)
251 
252      def apply(self, schema, func):
253          for eschema in schema.entities():
254              if eschema.final:
255                  continue
@@ -111,11 +113,11 @@
256                  for tschema in tschemas:
257                      if role == 'subject':
258                          sschema, oschema = eschema, tschema
259                      else:
260                          sschema, oschema = tschema, eschema
261 -                    func(self, sschema, rschema, oschema, role)
262 +                    func(sschema, rschema, oschema, role)
263 
264      # rtag declaration api ####################################################
265 
266      def tag_attribute(self, key, *args, **kwargs):
267          key = list(key)
@@ -248,6 +250,8 @@
268          elif key[-1] == 'object' and key[0] != '*':
269              if isinstance(key, tuple):
270                  key = list(key)
271              key[0] = '*'
272          super(NoTargetRelationTagsDict, self).tag_relation(key, tag)
273 +
274 +
275  set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags'))
diff --git a/server/hook.py b/server/hook.py
@@ -256,11 +256,11 @@
276 
277  from logilab.common.decorators import classproperty, cached
278  from logilab.common.deprecation import deprecated, class_renamed
279  from logilab.common.logging_ext import set_log_methods
280  from logilab.common.registry import (Predicate, NotPredicate, OrPredicate,
281 -                                     classid, objectify_predicate, yes)
282 +                                     objectify_predicate, yes)
283 
284  from cubicweb import RegistryNotFound, server
285  from cubicweb.cwvreg import CWRegistry, CWRegistryStore
286  from cubicweb.predicates import ExpectedValuePredicate, is_instance
287  from cubicweb.appobject import AppObject
@@ -766,11 +766,11 @@
288 
289      def handle_event(self, event):
290          """delegate event handling to the opertaion"""
291          if event == 'postcommit_event' and hasattr(self, 'commit_event'):
292              warn('[3.10] %s: commit_event method has been replaced by postcommit_event'
293 -                 % classid(self.__class__), DeprecationWarning)
294 +                 % self.__class__, DeprecationWarning)
295              self.commit_event() # pylint: disable=E1101
296          getattr(self, event)()
297 
298      def precommit_event(self):
299          """the observed connections set is preparing a commit"""
diff --git a/view.py b/view.py
@@ -24,11 +24,11 @@
300  from cStringIO import StringIO
301  from warnings import warn
302  from functools import partial
303 
304  from logilab.common.deprecation import deprecated
305 -from logilab.common.registry import classid, yes
306 +from logilab.common.registry import yes
307  from logilab.mtconverter import xml_escape
308 
309  from rql import nodes
310 
311  from cubicweb import NotAnEntity
@@ -606,11 +606,11 @@
312          def decorated(self, *args, **kwargs):
313              entity = self.entity
314              if hasattr(entity, func.__name__):
315                  warn('[3.9] %s method is deprecated, define it on a custom '
316                       '%s for %s instead' % (func.__name__, iface,
317 -                                            classid(entity.__class__)),
318 +                                            entity.__class__),
319                       DeprecationWarning)
320                  member = getattr(entity, func.__name__)
321                  if callable(member):
322                      return member(*args, **kwargs)
323                  return member
diff --git a/web/formfields.py b/web/formfields.py
@@ -80,11 +80,10 @@
324  from cubicweb.utils import support_args
325  from cubicweb.web import INTERNAL_FIELD_VALUE, ProcessFormError, eid_param, \
326       formwidgets as fw
327  from cubicweb.web.views import uicfg
328 
329 -
330  class UnmodifiedField(Exception):
331      """raise this when a field has not actually been edited and you want to skip
332      it
333      """
334 
@@ -464,12 +463,10 @@
335          except AttributeError:
336              # fields with eidparam=True but not corresponding to an actual
337              # attribute or relation
338              return True
339          # if it's a non final relation, we need the eids
340 -        # XXX underlying regression: getattr(ent, 'foo') used to return
341 -        #     a tuple, now we get a list
342          if isinstance(previous_value, (list, tuple)):
343              # widget should return a set of untyped eids
344              previous_value = set(e.eid for e in previous_value)
345          try:
346              new_value = self.process_form_value(form)
@@ -1162,11 +1159,11 @@
347          return eids
348 
349 
350  _AFF_KWARGS = uicfg.autoform_field_kwargs
351 
352 -def guess_field(eschema, rschema, role='subject', **kwargs):
353 +def guess_field(eschema, rschema, role='subject', req=None, **kwargs):
354      """This function return the most adapted field to edit the given relation
355      (`rschema`) where the given entity type (`eschema`) is the subject or object
356      (`role`).
357 
358      The field is initialized according to information found in the schema,
@@ -1210,16 +1207,20 @@
359              for cstr in rdef.constraints:
360                  if isinstance(cstr, SizeConstraint) and cstr.max is not None:
361                      kwargs['max_length'] = cstr.max
362              return StringField(**kwargs)
363          if fieldclass is FileField:
364 +            if req:
365 +                aff_kwargs = req.vreg['uicfg'].select('autoform_field_kwargs', req)
366 +            else:
367 +                aff_kwargs = _AFF_KWARGS
368              for metadata in KNOWN_METAATTRIBUTES:
369                  metaschema = eschema.has_metadata(rschema, metadata)
370                  if metaschema is not None:
371 -                    metakwargs = _AFF_KWARGS.etype_get(eschema, metaschema, 'subject')
372 +                    metakwargs = aff_kwargs.etype_get(eschema, metaschema, 'subject')
373                      kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
374 -                                                                **metakwargs)
375 +                                                                req=req, **metakwargs)
376          return fieldclass(**kwargs)
377      return RelationField.fromcardinality(card, **kwargs)
378 
379 
380  FIELDS = {
diff --git a/web/test/unittest_formfields.py b/web/test/unittest_formfields.py
@@ -33,57 +33,62 @@
381      global schema
382      config = TestServerConfiguration('data', apphome=GuessFieldTC.datadir)
383      config.bootstrap_cubes()
384      schema = config.load_schema()
385 
386 -class GuessFieldTC(TestCase):
387 +class GuessFieldTC(CubicWebTC):
388 +
389 +    def setUp(self):
390 +        super(GuessFieldTC, self).setUp()
391 +        self.req = self.request()
392 
393      def test_state_fields(self):
394 -        title_field = guess_field(schema['State'], schema['name'])
395 +        title_field = guess_field(schema['State'], schema['name'], req=self.req)
396          self.assertIsInstance(title_field, StringField)
397          self.assertEqual(title_field.required, True)
398 
399  #         synopsis_field = guess_field(schema['State'], schema['synopsis'])
400  #         self.assertIsInstance(synopsis_field, StringField)
401  #         self.assertIsInstance(synopsis_field.widget, TextArea)
402  #         self.assertEqual(synopsis_field.required, False)
403  #         self.assertEqual(synopsis_field.help, 'an abstract for this state')
404 
405 -        description_field = guess_field(schema['State'], schema['description'])
406 +        description_field = guess_field(schema['State'], schema['description'], req=self.req)
407          self.assertIsInstance(description_field, RichTextField)
408          self.assertEqual(description_field.required, False)
409          self.assertEqual(description_field.format_field, None)
410 
411          # description_format_field = guess_field(schema['State'], schema['description_format'])
412          # self.assertEqual(description_format_field, None)
413 
414 -        description_format_field = guess_field(schema['State'], schema['description_format'])
415 +        description_format_field = guess_field(schema['State'], schema['description_format'],
416 +                                               req=self.req)
417          self.assertEqual(description_format_field.internationalizable, True)
418          self.assertEqual(description_format_field.sort, True)
419 
420  #         wikiid_field = guess_field(schema['State'], schema['wikiid'])
421  #         self.assertIsInstance(wikiid_field, StringField)
422  #         self.assertEqual(wikiid_field.required, False)
423 
424 
425      def test_cwuser_fields(self):
426 -        upassword_field = guess_field(schema['CWUser'], schema['upassword'])
427 +        upassword_field = guess_field(schema['CWUser'], schema['upassword'], req=self.req)
428          self.assertIsInstance(upassword_field, StringField)
429          self.assertIsInstance(upassword_field.widget, PasswordInput)
430          self.assertEqual(upassword_field.required, True)
431 
432 -        last_login_time_field = guess_field(schema['CWUser'], schema['last_login_time'])
433 +        last_login_time_field = guess_field(schema['CWUser'], schema['last_login_time'], req=self.req)
434          self.assertIsInstance(last_login_time_field, DateTimeField)
435          self.assertEqual(last_login_time_field.required, False)
436 
437 -        in_group_field = guess_field(schema['CWUser'], schema['in_group'])
438 +        in_group_field = guess_field(schema['CWUser'], schema['in_group'], req=self.req)
439          self.assertIsInstance(in_group_field, RelationField)
440          self.assertEqual(in_group_field.required, True)
441          self.assertEqual(in_group_field.role, 'subject')
442          self.assertEqual(in_group_field.help, 'groups grant permissions to the user')
443 
444 -        owned_by_field = guess_field(schema['CWUser'], schema['owned_by'], 'object')
445 +        owned_by_field = guess_field(schema['CWUser'], schema['owned_by'], 'object', req=self.req)
446          self.assertIsInstance(owned_by_field, RelationField)
447          self.assertEqual(owned_by_field.required, False)
448          self.assertEqual(owned_by_field.role, 'object')
449 
450 
@@ -93,37 +98,37 @@
451          # data_encoding_field = guess_field(schema['File'], schema['data_encoding'])
452          # self.assertEqual(data_encoding_field, None)
453          # data_name_field = guess_field(schema['File'], schema['data_name'])
454          # self.assertEqual(data_name_field, None)
455 
456 -        data_field = guess_field(schema['File'], schema['data'])
457 +        data_field = guess_field(schema['File'], schema['data'], req=self.req)
458          self.assertIsInstance(data_field, FileField)
459          self.assertEqual(data_field.required, True)
460          self.assertIsInstance(data_field.format_field, StringField)
461          self.assertIsInstance(data_field.encoding_field, StringField)
462          self.assertIsInstance(data_field.name_field, StringField)
463 
464      def test_constraints_priority(self):
465 -        salesterm_field = guess_field(schema['Salesterm'], schema['reason'])
466 +        salesterm_field = guess_field(schema['Salesterm'], schema['reason'], req=self.req)
467          constraints = schema['reason'].rdef('Salesterm', 'String').constraints
468          self.assertEqual([c.__class__ for c in constraints],
469                            [SizeConstraint, StaticVocabularyConstraint])
470          self.assertIsInstance(salesterm_field, StringField)
471          self.assertIsInstance(salesterm_field.widget, Select)
472 
473 
474      def test_bool_field_base(self):
475 -        field = guess_field(schema['CWAttribute'], schema['indexed'])
476 +        field = guess_field(schema['CWAttribute'], schema['indexed'], req=self.req)
477          self.assertIsInstance(field, BooleanField)
478          self.assertEqual(field.required, False)
479          self.assertIsInstance(field.widget, Radio)
480          self.assertEqual(field.vocabulary(mock(_cw=mock(_=unicode))),
481                            [(u'yes', '1'), (u'no', '')])
482 
483      def test_bool_field_explicit_choices(self):
484          field = guess_field(schema['CWAttribute'], schema['indexed'],
485 -                            choices=[(u'maybe', '1'), (u'no', '')])
486 +                            choices=[(u'maybe', '1'), (u'no', '')], req=self.req)
487          self.assertIsInstance(field.widget, Radio)
488          self.assertEqual(field.vocabulary(mock(req=mock(_=unicode))),
489                            [(u'maybe', '1'), (u'no', '')])
490 
491 
diff --git a/web/test/unittest_uicfg.py b/web/test/unittest_uicfg.py
@@ -105,9 +105,27 @@
492          section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject')
493          self.assertItemsEqual(section_conf, ['main_hidden', 'muledit_attributes'])
494          self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'order': 1})
495 
496 
497 +class UicfgRegistryTC(CubicWebTC):
498 +
499 +    def test_default_uicfg_object(self):
500 +        'CW default ui config objects must be registered in uicfg registry'
501 +        onames = ('autoform_field', 'autoform_section', 'autoform_field_kwargs')
502 +        for oname in onames:
503 +            obj = self.vreg['uicfg'].select_or_none(oname)
504 +            self.assertTrue(obj is not None, '%s not found in uicfg registry'
505 +                            % oname)
506 +
507 +    def test_custom_uicfg(self):
508 +        ASRT = uicfg.AutoformSectionRelationTags
509 +        custom_afs = ASRT()
510 +        custom_afs.__select__ = ASRT.__select__ & ASRT.__select__
511 +        self.vreg['uicfg'].register(custom_afs)
512 +        obj = self.vreg['uicfg'].select_or_none('autoform_section')
513 +        self.assertTrue(obj is custom_afs)
514 +
515 
516  if __name__ == '__main__':
517      from logilab.common.testlib import unittest_main
518      unittest_main()
diff --git a/web/views/actions.py b/web/views/actions.py
@@ -289,11 +289,12 @@
519 
520          If you don't want any auto-generated actions, you should overrides this
521          method to return an empty list. If you only want some, you can configure
522          them by using uicfg.actionbox_appearsin_addmenu
523          """
524 -        appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
525 +        appearsin_addmenu = self._cw.vreg['uicfg'].select(
526 +            'actionbox_appearsin_addmenu', self._cw, entity=entity)
527          req = self._cw
528          eschema = entity.e_schema
529          for role, rschemas in (('subject', eschema.subject_relations()),
530                                 ('object', eschema.object_relations())):
531              for rschema in rschemas:
diff --git a/web/views/autoform.py b/web/views/autoform.py
@@ -1,6 +1,6 @@
532 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
533 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
534  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
535  #
536  # This file is part of CubicWeb.
537  #
538  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -124,11 +124,10 @@
539  from warnings import warn
540 
541  from logilab.mtconverter import xml_escape
542  from logilab.common.decorators import iclassmethod, cached
543  from logilab.common.deprecation import deprecated
544 -from logilab.common.registry import classid
545 
546  from cubicweb import typed_eid, neg_role, uilib
547  from cubicweb.schema import display_name
548  from cubicweb.view import EntityView
549  from cubicweb.predicates import (
@@ -138,13 +137,10 @@
550  from cubicweb.web import (stdmsgs, eid_param,
551                            form as f, formwidgets as fw, formfields as ff)
552  from cubicweb.web.views import uicfg, forms
553  from cubicweb.web.views.ajaxcontroller import ajaxfunc
554 
555 -_AFS = uicfg.autoform_section
556 -_AFFK = uicfg.autoform_field_kwargs
557 -
558 
559  # inlined form handling ########################################################
560 
561  class InlinedFormField(ff.Field):
562      def __init__(self, view=None, **kwargs):
@@ -753,10 +749,12 @@
563 
564      # base automatic entity form methods #######################################
565 
566      def __init__(self, *args, **kwargs):
567          super(AutomaticEntityForm, self).__init__(*args, **kwargs)
568 +        self.uicfg_afs = self._cw.vreg['uicfg'].select(
569 +            'autoform_section', self._cw, entity=self.edited_entity)
570          entity = self.edited_entity
571          if entity.has_eid():
572              entity.complete()
573          for rtype, role in self.editable_attributes():
574              try:
@@ -818,23 +816,23 @@
575          return GenericRelationsField(self.editable_relations(),
576                                       fieldset=fieldset, label=None)
577 
578      def _inlined_form_view_field(self, view):
579          # XXX allow more customization
580 -        kwargs = _AFFK.etype_get(self.edited_entity.e_schema, view.rtype,
581 -                                 view.role, view.etype)
582 +        kwargs = self.uicfg_affk.etype_get(self.edited_entity.e_schema,
583 +                                           view.rtype, view.role, view.etype)
584          if kwargs is None:
585              kwargs = {}
586          return InlinedFormField(view=view, **kwargs)
587 
588      # methods mapping edited entity relations to fields in the form ############
589 
590      def _relations_by_section(self, section, permission='add', strict=False):
591          """return a list of (relation schema, target schemas, role) matching
592          given category(ies) and permission
593          """
594 -        return _AFS.relations_by_section(
595 +        return self.uicfg_afs.relations_by_section(
596              self.edited_entity, self.formtype, section, permission, strict)
597 
598      def editable_attributes(self, strict=False):
599          """return a list of (relation schema, role) to edit for the entity"""
600          if self.display_fields is not None:
@@ -961,10 +959,11 @@
601                                              pform=self)
602 
603 
604  ## default form ui configuration ##############################################
605 
606 +_AFS = uicfg.autoform_section
607  # use primary and not generated for eid since it has to be an hidden
608  _AFS.tag_attribute(('*', 'eid'), 'main', 'attributes')
609  _AFS.tag_attribute(('*', 'eid'), 'muledit', 'attributes')
610  _AFS.tag_attribute(('*', 'description'), 'main', 'attributes')
611  _AFS.tag_attribute(('*', 'has_text'), 'main', 'hidden')
@@ -992,10 +991,11 @@
612  _AFS.tag_subject_of(('*', 'use_email', '*'), 'main', 'inlined')
613  _AFS.tag_subject_of(('CWRelation', 'relation_type', '*'), 'main', 'inlined')
614  _AFS.tag_subject_of(('CWRelation', 'from_entity', '*'), 'main', 'inlined')
615  _AFS.tag_subject_of(('CWRelation', 'to_entity', '*'), 'main', 'inlined')
616 
617 +_AFFK = uicfg.autoform_field_kwargs
618  _AFFK.tag_attribute(('RQLExpression', 'expression'),
619                      {'widget': fw.TextInput})
620  _AFFK.tag_subject_of(('TrInfo', 'wf_info_for', '*'),
621                       {'widget': fw.HiddenInput})
622 
diff --git a/web/views/forms.py b/web/views/forms.py
@@ -300,23 +300,22 @@
623                  errors = dict((f.role_name(), unicode(ex)) for f, ex in errors)
624                  raise ValidationError(None, errors)
625              return processed
626 
627 
628 -_AFF = uicfg.autoform_field
629 -_AFF_KWARGS = uicfg.autoform_field_kwargs
630 -
631  class EntityFieldsForm(FieldsForm):
632      """This class is designed for forms used to edit some entities. It should
633      handle for you all the underlying stuff necessary to properly work with the
634      generic :class:`~cubicweb.web.views.editcontroller.EditController`.
635      """
636 
637      __regid__ = 'base'
638      __select__ = (match_kwargs('entity')
639                    | (one_line_rset() & non_final_entity()))
640      domid = 'entityForm'
641 +    uicfg_aff = uicfg.autoform_field
642 +    uicfg_affk = uicfg.autoform_field_kwargs
643 
644      @iclassmethod
645      def field_by_name(cls_or_self, name, role=None, eschema=None):
646          """return field with the given name and role. If field is not explicitly
647          defined for the form but `eclass` is specified, guess_field will be
@@ -328,19 +327,25 @@
648              if eschema is None or role is None or not name in eschema.schema:
649                  raise
650              rschema = eschema.schema.rschema(name)
651              # XXX use a sample target type. Document this.
652              tschemas = rschema.targets(eschema, role)
653 -            fieldcls = _AFF.etype_get(eschema, rschema, role, tschemas[0])
654 -            kwargs = _AFF_KWARGS.etype_get(eschema, rschema, role, tschemas[0])
655 +            fieldcls = cls_or_self.uicfg_aff.etype_get(
656 +                eschema, rschema, role, tschemas[0])
657 +            kwargs = cls_or_self.uicfg_affk.etype_get(
658 +                eschema, rschema, role, tschemas[0])
659              if kwargs is None:
660                  kwargs = {}
661              if fieldcls:
662                  if not isinstance(fieldcls, type):
663                      return fieldcls # already and instance
664                  return fieldcls(name=name, role=role, eidparam=True, **kwargs)
665 -            field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
666 +            if isinstance(cls_or_self, type):
667 +                req = None
668 +            else:
669 +                req = cls_or_self._cw
670 +            field = guess_field(eschema, rschema, role, req=req, eidparam=True, **kwargs)
671              if field is None:
672                  raise
673              return field
674 
675      def __init__(self, _cw, rset=None, row=None, col=None, **kwargs):
@@ -348,10 +353,14 @@
676              self.edited_entity = kwargs.pop('entity')
677          except KeyError:
678              self.edited_entity = rset.complete_entity(row or 0, col or 0)
679          msg = kwargs.pop('submitmsg', None)
680          super(EntityFieldsForm, self).__init__(_cw, rset, row, col, **kwargs)
681 +        self.uicfg_aff = self._cw.vreg['uicfg'].select(
682 +            'autoform_field', self._cw, entity=self.edited_entity)
683 +        self.uicfg_affk = self._cw.vreg['uicfg'].select(
684 +            'autoform_field_kwargs', self._cw, entity=self.edited_entity)
685          self.add_hidden('__type', self.edited_entity.__regid__, eidparam=True)
686          self.add_hidden('eid', self.edited_entity.eid)
687          # mainform default to true in parent, hence default to True
688          if kwargs.get('mainform', True) or kwargs.get('mainentity', False):
689              self.add_hidden(u'__maineid', self.edited_entity.eid)
diff --git a/web/views/management.py b/web/views/management.py
@@ -74,11 +74,13 @@
690                                           form_renderer_id='onerowtable', submitmsg=msg,
691                                           form_buttons=[wdgs.SubmitButton()],
692                                           domid='ownership%s' % entity.eid,
693                                           __redirectvid='security',
694                                           __redirectpath=entity.rest_path())
695 -        field = guess_field(entity.e_schema, self._cw.vreg.schema.rschema('owned_by'))
696 +        field = guess_field(entity.e_schema,
697 +                            self._cw.vreg.schema['owned_by'],
698 +                            req=self._cw)
699          form.append_field(field)
700          form.render(w=self.w, display_progress_div=False)
701 
702      def owned_by_information(self, entity):
703          ownersrset = entity.related('owned_by')
diff --git a/web/views/primary.py b/web/views/primary.py
@@ -1,6 +1,6 @@
704 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
705 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
706  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
707  #
708  # This file is part of CubicWeb.
709  #
710  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -95,12 +95,12 @@
711 
712      __regid__ = 'primary'
713      title = _('primary')
714      show_attr_label = True
715      show_rel_label = True
716 -    rsection = uicfg.primaryview_section
717 -    display_ctrl = uicfg.primaryview_display_ctrl
718 +    rsection = None
719 +    display_ctrl = None
720      main_related_section = True
721 
722      def html_headers(self):
723          """return a list of html headers (eg something to be inserted between
724          <head> and </head> of the returned page
@@ -109,10 +109,17 @@
725          """
726          return []
727 
728      def entity_call(self, entity):
729          entity.complete()
730 +        uicfg_reg = self._cw.vreg['uicfg']
731 +        if self.rsection is None:
732 +            self.rsection = uicfg_reg.select('primaryview_section',
733 +                                             self._cw, entity=entity)
734 +        if self.display_ctrl is None:
735 +            self.display_ctrl = uicfg_reg.select('primaryview_display_ctrl',
736 +                                                 self._cw, entity=entity)
737          self.render_entity(entity)
738 
739      def render_entity(self, entity):
740          self.render_entity_toolbox(entity)
741          self.render_entity_title(entity)
diff --git a/web/views/reledit.py b/web/views/reledit.py
@@ -1,6 +1,6 @@
742 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
743 +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
744  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
745  #
746  # This file is part of CubicWeb.
747  #
748  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -49,12 +49,10 @@
749      def append_field(self, *args):
750          pass
751      def add_hidden(self, *args):
752          pass
753 
754 -rctrl = uicfg.reledit_ctrl
755 -
756  class AutoClickAndEditFormView(EntityView):
757      __regid__ = 'reledit'
758      __select__ = non_final_entity() & match_kwargs('rtype')
759 
760      # ui side continuations
@@ -89,10 +87,11 @@
761          assert rtype
762          self._cw.add_css('cubicweb.form.css')
763          self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js'))
764          self.entity = entity
765          rschema = self._cw.vreg.schema[rtype]
766 +        rctrl = self._cw.vreg['uicfg'].select('reledit', self._cw, entity=entity)
767          self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*')
768          if rvid is not None or default_value is not None:
769              warn('[3.9] specifying rvid/default_value on select is deprecated, '
770                   'reledit_ctrl rtag to control this' % self, DeprecationWarning)
771          reload = self._compute_reload(rschema, role, reload)
diff --git a/web/views/uicfg.py b/web/views/uicfg.py
@@ -1,6 +1,6 @@
772 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
773 +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
774  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
775  #
776  # This file is part of CubicWeb.
777  #
778  # CubicWeb is free software: you can redistribute it and/or modify it under the
@@ -13,11 +13,11 @@
779  # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
780  # details.
781  #
782  # You should have received a copy of the GNU Lesser General Public License along
783  # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
784 -"""This module (``cubicweb.web.uicfg``) regroups a set of structures that may be
785 +"""This module (``cubicweb.web.views.uicfg``) regroups a set of structures that may be
786  used to configure various options of the generated web interface.
787 
788  To configure the interface generation, we use ``RelationTag`` objects.
789 
790  Index view configuration
@@ -32,11 +32,11 @@
791 
792     By default only entities on the ``application`` category are shown.
793 
794  .. sourcecode:: python
795 
796 -    from cubicweb.web import uicfg
797 +    from cubicweb.web.views import uicfg
798      # force hiding
799      uicfg.indexview_etype_section['HideMe'] = 'subobject'
800      # force display
801      uicfg.indexview_etype_section['ShowMe'] = 'application'
802 
@@ -60,69 +60,76 @@
803  from logilab.common.compat import any
804 
805  from cubicweb import neg_role
806  from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
807                              RelationTagsDict, NoTargetRelationTagsDict,
808 -                            register_rtag, _ensure_str_key)
809 +                            _ensure_str_key)
810  from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES
811 
812 
813  # primary view configuration ##################################################
814 
815 -def init_primaryview_section(rtag, sschema, rschema, oschema, role):
816 -    if rtag.get(sschema, rschema, oschema, role) is None:
817 -        rdef = rschema.rdef(sschema, oschema)
818 -        if rschema.final:
819 -            if rschema.meta or sschema.is_metadata(rschema) \
820 -                    or oschema.type in ('Password', 'Bytes'):
821 -                section = 'hidden'
822 +class PrimaryViewSectionRelationTags(RelationTags):
823 +    """primary view section configuration"""
824 +    __regid__ = 'primaryview_section'
825 +
826 +    _allowed_values = frozenset(('attributes', 'relations',
827 +                                 'sideboxes', 'hidden'))
828 +
829 +    def _init(self, sschema, rschema, oschema, role):
830 +        if self.get(sschema, rschema, oschema, role) is None:
831 +            rdef = rschema.rdef(sschema, oschema)
832 +            if rschema.final:
833 +                if rschema.meta or sschema.is_metadata(rschema) \
834 +                        or oschema.type in ('Password', 'Bytes'):
835 +                    section = 'hidden'
836 +                else:
837 +                    section = 'attributes'
838              else:
839 -                section = 'attributes'
840 -        else:
841 -            if rdef.role_cardinality(role) in '1+':
842 -                section = 'attributes'
843 -            elif rdef.composite == neg_role(role):
844 -                section = 'relations'
845 -            else:
846 -                section = 'sideboxes'
847 -        rtag.tag_relation((sschema, rschema, oschema, role), section)
848 +                if rdef.role_cardinality(role) in '1+':
849 +                    section = 'attributes'
850 +                elif rdef.composite == neg_role(role):
851 +                    section = 'relations'
852 +                else:
853 +                    section = 'sideboxes'
854 +            self.tag_relation((sschema, rschema, oschema, role), section)
855 
856 -primaryview_section = RelationTags('primaryview_section',
857 -                                   init_primaryview_section,
858 -                                   frozenset(('attributes', 'relations',
859 -                                              'sideboxes', 'hidden')))
860 +primaryview_section = PrimaryViewSectionRelationTags()
861 
862 
863  class DisplayCtrlRelationTags(NoTargetRelationTagsDict):
864 +    """primary view display controller configuration"""
865 +    __regid__ = 'primaryview_display_ctrl'
866 +
867      def __init__(self, *args, **kwargs):
868          super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
869          self.counter = 0
870 
871 -def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role):
872 -    if role == 'subject':
873 -        oschema = '*'
874 -    else:
875 -        sschema = '*'
876 -    rtag.counter += 1
877 -    rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag.counter)
878 +    def _init(self, sschema, rschema, oschema, role):
879 +        if role == 'subject':
880 +            oschema = '*'
881 +        else:
882 +            sschema = '*'
883 +        self.counter += 1
884 +        self.setdefault((sschema, rschema, oschema, role),
885 +                        'order',
886 +                        self.counter)
887 
888 -primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
889 -                                                   init_primaryview_display_ctrl)
890 +primaryview_display_ctrl = DisplayCtrlRelationTags()
891 
892 
893  # index view configuration ####################################################
894  # entity type section in the index/manage page. May be one of
895  # * 'application'
896  # * 'system'
897  # * 'schema'
898  # * 'hidden'
899  # * 'subobject' (not displayed by default)
900 
901 -class InitializableDict(dict):
902 +class InitializableDict(dict): # XXX not a rtag. Turn into an appobject?
903      def __init__(self, *args, **kwargs):
904          super(InitializableDict, self).__init__(*args, **kwargs)
905 -        register_rtag(self)
906          self.__defaults = dict(self)
907 
908      def init(self, schema, check=True):
909          self.update(self.__defaults)
910          for eschema in schema.entities():
@@ -142,10 +149,11 @@
911      Bookmark='system',
912      # entity types in the 'system' table by default (managers only)
913      CWUser='system', CWGroup='system',
914      )
915 
916 +
917  # autoform.AutomaticEntityForm configuration ##################################
918 
919  def _formsections_as_dict(formsections):
920      result = {}
921      for formsection in formsections:
@@ -163,10 +171,11 @@
922          composed = not rschema.final and rdef.composite == 'subject'
923      return card, composed
924 
925  class AutoformSectionRelationTags(RelationTagsSet):
926      """autoform relations'section"""
927 +    __regid__ = 'autoform_section'
928 
929      _allowed_form_types = ('main', 'inlined', 'muledit')
930      _allowed_values = {'main': ('attributes', 'inlined', 'relations',
931                                  'metadata', 'hidden'),
932                         'inlined': ('attributes', 'inlined', 'hidden'),
@@ -175,12 +184,11 @@
933 
934      def init(self, schema, check=True):
935          super(AutoformSectionRelationTags, self).init(schema, check)
936          self.apply(schema, self._initfunc_step2)
937 
938 -    @staticmethod
939 -    def _initfunc(self, sschema, rschema, oschema, role):
940 +    def _init(self, sschema, rschema, oschema, role):
941          formsections = self.init_get(sschema, rschema, oschema, role)
942          if formsections is None:
943              formsections = self.tag_container_cls()
944          if not any(tag.startswith('inlined') for tag in formsections):
945              if not rschema.final:
@@ -188,11 +196,10 @@
946                  if 'main_inlined' in negsects:
947                      formsections.add('inlined_hidden')
948          key = _ensure_str_key( (sschema, rschema, oschema, role) )
949          self._tagdefs[key] = formsections
950 
951 -    @staticmethod
952      def _initfunc_step2(self, sschema, rschema, oschema, role):
953          formsections = self.get(sschema, rschema, oschema, role)
954          sectdict = _formsections_as_dict(formsections)
955          if rschema in META_RTYPES:
956              sectdict.setdefault('main', 'hidden')
@@ -264,14 +271,14 @@
957              for tag in tags:
958                  assert '_' in tag, (tag, tags)
959                  section, value = tag.split('_', 1)
960                  rtags[section] = value
961          cls = self.tag_container_cls
962 -        rtags = cls('_'.join([section,value]) for section,value in rtags.iteritems())
963 +        rtags = cls('_'.join([section,value])
964 +                    for section,value in rtags.iteritems())
965          return rtags
966 
967 -
968      def get(self, *key):
969          # overriden to avoid recomputing done in parent classes
970          return self._tagdefs.get(key, ())
971 
972      def relations_by_section(self, entity, formtype, section, permission,
@@ -282,11 +289,12 @@
973          `strict`:
974            bool telling if having local role is enough (strict = False) or not
975          """
976          tag = '%s_%s' % (formtype, section)
977          eschema  = entity.e_schema
978 -        permsoverrides = autoform_permissions_overrides
979 +        cw = entity._cw
980 +        permsoverrides = cw.vreg['uicfg'].select('autoform_permissions_overrides', cw, entity=entity)
981          if entity.has_eid():
982              eid = entity.eid
983          else:
984              eid = None
985              strict = False
@@ -294,11 +302,10 @@
986              assert section in ('attributes', 'metadata', 'hidden')
987              relpermission = 'add'
988          else:
989              assert section not in ('attributes', 'metadata', 'hidden')
990              relpermission = permission
991 -        cw = entity._cw
992          for rschema, targetschemas, role in eschema.relation_definitions(True):
993              _targetschemas = []
994              for tschema in targetschemas:
995                  # check section's tag first, potentially lower cost than
996                  # checking permission which may imply rql queries
@@ -345,23 +352,33 @@
997                      and not rdef.has_perm(cw, 'delete', toeid=eid,
998                                            fromeid=entity.related(rschema.type, role)[0][0])):
999                      continue
1000              yield (rschema, targetschemas, role)
1001 
1002 -autoform_section = AutoformSectionRelationTags('autoform_section')
1003 +autoform_section = AutoformSectionRelationTags()
1004 
1005  # relations'field class
1006 -autoform_field = RelationTags('autoform_field')
1007 +class AutoformFieldTags(RelationTags):
1008 +    __regid__ = 'autoform_field'
1009 +
1010 +autoform_field = AutoformFieldTags()
1011 
1012  # relations'field explicit kwargs (given to field's __init__)
1013 -autoform_field_kwargs = RelationTagsDict('autoform_field_kwargs')
1014 +class AutoformFieldKwargsTags(RelationTagsDict):
1015 +    __regid__ = 'autoform_field_kwargs'
1016 +
1017 +autoform_field_kwargs = AutoformFieldKwargsTags()
1018 
1019 
1020  # set of tags of the form <action>_on_new on relations. <action> is a
1021  # schema action (add/update/delete/read), and when such a tag is found
1022  # permissions checking is by-passed and supposed to be ok
1023 -autoform_permissions_overrides = RelationTagsSet('autoform_permissions_overrides')
1024 +class AutoFormPermissionsOverrides(RelationTagsSet):
1025 +    __regid__ = 'autoform_permissions_overrides'
1026 +
1027 +autoform_permissions_overrides = AutoFormPermissionsOverrides()
1028 +
1029 
1030  class ReleditTags(NoTargetRelationTagsDict):
1031      """Associate to relation a dictionary to control `reledit` (e.g. edition of
1032      attributes / relations from within views).
1033 
@@ -386,51 +403,61 @@
1034        (to edit the related entity).  This controls whether to edit the relation
1035        or the target entity of the relation.  Currently only one-to-one relations
1036        support target entity edition. By default, the 'related' option is taken
1037        whenever the relation is composite.
1038      """
1039 +    __regid__ = 'reledit'
1040      _keys = frozenset('novalue_label novalue_include_rtype reload rvid edit_target'.split())
1041 
1042      def tag_relation(self, key, tag):
1043          for tagkey in tag.iterkeys():
1044              assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys)
1045          return super(ReleditTags, self).tag_relation(key, tag)
1046 
1047 -def init_reledit_ctrl(rtag, sschema, rschema, oschema, role):
1048 -    values = rtag.get(sschema, rschema, oschema, role)
1049 -    if not rschema.final:
1050 -        composite = rschema.rdef(sschema, oschema).composite == role
1051 -        if role == 'subject':
1052 -            oschema = '*'
1053 -        else:
1054 -            sschema = '*'
1055 -        edittarget = values.get('edit_target')
1056 -        if edittarget not in (None, 'rtype', 'related'):
1057 -            rtag.warning('reledit: wrong value for edit_target on relation %s: %s',
1058 -                         rschema, edittarget)
1059 -            edittarget = None
1060 -        if not edittarget:
1061 -            edittarget = 'related' if composite else 'rtype'
1062 -            rtag.tag_relation((sschema, rschema, oschema, role),
1063 -                              {'edit_target': edittarget})
1064 -    if not 'novalue_include_rtype' in values:
1065 -        showlabel = primaryview_display_ctrl.get(
1066 -            sschema, rschema, oschema, role).get('showlabel', True)
1067 -        rtag.tag_relation((sschema, rschema, oschema, role),
1068 -                          {'novalue_include_rtype': not showlabel})
1069 +    def _init(self, sschema, rschema, oschema, role):
1070 +        values = self.get(sschema, rschema, oschema, role)
1071 +        if not rschema.final:
1072 +            composite = rschema.rdef(sschema, oschema).composite == role
1073 +            if role == 'subject':
1074 +                oschema = '*'
1075 +            else:
1076 +                sschema = '*'
1077 +            edittarget = values.get('edit_target')
1078 +            if edittarget not in (None, 'rtype', 'related'):
1079 +                self.warning('reledit: wrong value for edit_target on relation %s: %s',
1080 +                             rschema, edittarget)
1081 +                edittarget = None
1082 +            if not edittarget:
1083 +                edittarget = 'related' if composite else 'rtype'
1084 +                self.tag_relation((sschema, rschema, oschema, role),
1085 +                                  {'edit_target': edittarget})
1086 +        if not 'novalue_include_rtype' in values:
1087 +            showlabel = primaryview_display_ctrl.get(
1088 +                sschema, rschema, oschema, role).get('showlabel', True)
1089 +            self.tag_relation((sschema, rschema, oschema, role),
1090 +                              {'novalue_include_rtype': not showlabel})
1091 
1092 -reledit_ctrl = ReleditTags('reledit', init_reledit_ctrl)
1093 +reledit_ctrl = ReleditTags()
1094 +
1095 
1096  # boxes.EditBox configuration #################################################
1097 
1098  # 'link' / 'create' relation tags, used to control the "add entity" submenu
1099 -def init_actionbox_appearsin_addmenu(rtag, sschema, rschema, oschema, role):
1100 -    if rtag.get(sschema, rschema, oschema, role) is None:
1101 -        if rschema in META_RTYPES:
1102 -            rtag.tag_relation((sschema, rschema, oschema, role), False)
1103 -            return
1104 -        rdef = rschema.rdef(sschema, oschema)
1105 -        if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
1106 -            rtag.tag_relation((sschema, rschema, oschema, role), True)
1107 +class ActionBoxUicfg(RelationTagsBool):
1108 +    __regid__ = 'actionbox_appearsin_addmenu'
1109 
1110 -actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
1111 -                                               init_actionbox_appearsin_addmenu)
1112 +    def _init(self, sschema, rschema, oschema, role):
1113 +        if self.get(sschema, rschema, oschema, role) is None:
1114 +            if rschema in META_RTYPES:
1115 +                self.tag_relation((sschema, rschema, oschema, role), False)
1116 +                return
1117 +            rdef = rschema.rdef(sschema, oschema)
1118 +            if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
1119 +                self.tag_relation((sschema, rschema, oschema, role), True)
1120 +
1121 +actionbox_appearsin_addmenu = ActionBoxUicfg()
1122 +
1123 +
1124 +
1125 +def registration_callback(vreg):
1126 +    vreg.register_all(globals().values(), __name__)
1127 +    indexview_etype_section.init(vreg.schema)