[views/reledit] refactor composite handling stable
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Wed, 15 Sep 2010 15:00:04 +0200
branchstable
changeset 6246 62e25fac41cd
parent 6245 e7e9d73d0c07
child 6247 f7cb092d2296
[views/reledit] refactor composite handling
doc/book/en/devweb/views/reledit.rst
rtags.py
web/data/cubicweb.css
web/data/cubicweb.old.css
web/test/data/schema.py
web/test/unittest_reledit.py
web/uicfg.py
web/views/forms.py
web/views/primary.py
web/views/reledit.py
--- a/doc/book/en/devweb/views/reledit.rst	Wed Sep 15 11:13:17 2010 +0200
+++ b/doc/book/en/devweb/views/reledit.rst	Wed Sep 15 15:00:04 2010 +0200
@@ -70,12 +70,23 @@
 controlled using the reledit_ctrl rtag, defined in
 :mod:`cubicweb.web.uicfg`.
 
-This rtag provides three control variables:
+This rtag provides four control variables:
 
-* ``default_value``
-* ``reload``, to specificy if edition of the relation entails a full page
-  reload, which defaults to False
-* ``noedit``, to explicitly inhibit edition
+* ``default_value``: alternative default value
+   The default value is what is shown when there is no value.
+* ``reload``: boolean, eid (to reload to) or function taking subject
+   and returning bool/eid This is useful when editing a relation (or
+   attribute) that impacts the url or another parts of the current
+   displayed page. Defaults to false.
+* ``rvid``: alternative view id (as str) for relation or composite
+   edition Default is 'incontext' or 'csv' depending on the
+   cardinality. They can also be statically changed by subclassing
+   ClickAndEditFormView and redefining _one_rvid (resp. _many_rvid).
+* ``edit_target``: 'rtype' (to edit the relation) or 'related' (to
+   edit the related entity) This controls whether to edit the relation
+   or the target entity of the relation.  Currently only one-to-one
+   relations support target entity edition. By default, the 'related'
+   option is taken whenever the relation is composite and one-to-one.
 
 Let's see how to use these controls.
 
@@ -86,15 +97,13 @@
     reledit_ctrl.tag_attribute(('Company', 'name'),
                                {'reload': lambda x:x.eid,
                                 'default_value': xml_escape(u'<logilab tastes better>')})
-    reledit_ctrl.tag_object_of(('*', 'boss', 'Person'), {'noedit': True})
+    reledit_ctrl.tag_object_of(('*', 'boss', 'Person'), {'edit_target': 'related'})
 
 The `default_value` needs to be an xml escaped unicode string.
 
-The `noedit` attribute is convenient to programmatically disable some
-relation edition on views that apply it systematically (the prime
-example being the primary view). Here we use it to forbid changing the
-`boss` relation from a `Person` side (as it could have unwanted
-effects).
+The `edit_target` tag on the `boss` relation being set to `related` will
+ensure edition of the `Person` entity instead (using a standard
+automatic form) of the association of Company and Person.
 
 Finally, the `reload` key accepts either a boolean, an eid or an
 unicode string representing an url. If an eid is provided, it will be
--- a/rtags.py	Wed Sep 15 11:13:17 2010 +0200
+++ b/rtags.py	Wed Sep 15 15:00:04 2010 +0200
@@ -210,4 +210,27 @@
     _allowed_values = frozenset((True, False))
 
 
+class NoTargetRelationTagsDict(RelationTagsDict):
+
+    @property
+    def name(self):
+        return self.__class__.name
+
+    def tag_subject_of(self, key, tag):
+        subj, rtype, obj = key
+        if obj != '*':
+            self.warning('using explict target type in %s.tag_subject_of() '
+                         'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)',
+                         self.name, subj, rtype, subj, rtype, obj)
+        super(NoTargetRelationTagsDict, self).tag_subject_of((subj, rtype, '*'), tag)
+
+    def tag_object_of(self, key, tag):
+        subj, rtype, obj = key
+        if subj != '*':
+            self.warning('using explict subject type in %s.tag_object_of() '
+                         'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)',
+                         self.name, rtype, obj, subj, rtype, obj)
+        super(NoTargetRelationTagsDict, self).tag_object_of(('*', rtype, obj), tag)
+
+
 set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags'))
--- a/web/data/cubicweb.css	Wed Sep 15 11:13:17 2010 +0200
+++ b/web/data/cubicweb.css	Wed Sep 15 15:00:04 2010 +0200
@@ -867,7 +867,7 @@
 }
 
 /********************************/
-/* rest releted classes         */
+/* rest related classes         */
 /********************************/
 
 img.align-right {
@@ -878,6 +878,18 @@
   margin-right: 1.5em;
 }
 
+/******************************/
+/* reledit                    */
+/******************************/
+
+.releditField {
+    display: inline;
+}
+
+.releditForm {
+ display:none;
+}
+
 /********************************/
 /* overwite other css here      */
 /********************************/
@@ -905,4 +917,3 @@
   margin-left: 0.5em;
   vertical-align: bottom;
 }
-
--- a/web/data/cubicweb.old.css	Wed Sep 15 11:13:17 2010 +0200
+++ b/web/data/cubicweb.old.css	Wed Sep 15 15:00:04 2010 +0200
@@ -860,3 +860,16 @@
 .otherView {
   float: right;
 }
+
+
+/******************************/
+/* reledit                    */
+/******************************/
+
+.releditField {
+    display: inline;
+}
+
+.releditForm {
+ display:none;
+}
--- a/web/test/data/schema.py	Wed Sep 15 11:13:17 2010 +0200
+++ b/web/test/data/schema.py	Wed Sep 15 15:00:04 2010 +0200
@@ -75,3 +75,19 @@
     cp   = String(maxsize=12)
     ville= String(maxsize=32)
 
+# enough relations to cover most reledit use cases
+class Project(EntityType):
+    title = String(maxsize=32, required=True, fulltextindexed=True)
+    long_desc = SubjectRelation('Blog', composite='subject', cardinality='?*')
+    manager = SubjectRelation('Personne', cardinality='?*')
+
+class composite_card11_2ttypes(RelationDefinition):
+    subject = 'Project'
+    object = ('File', 'Blog')
+    composite = 'subject'
+    cardinality = '?*'
+
+class Ticket(EntityType):
+    title = String(maxsize=32, required=True, fulltextindexed=True)
+    concerns = SubjectRelation('Project', composite='object')
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_reledit.py	Wed Sep 15 15:00:04 2010 +0200
@@ -0,0 +1,223 @@
+# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""
+mainly regression-preventing tests for reledit/doreledit views
+"""
+
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.web.uicfg import reledit_ctrl
+
+class ReleditMixinTC(object):
+
+    def setup_database(self):
+        self.req = self.request()
+        self.proj = self.req.create_entity('Project', title=u'cubicweb-world-domination')
+        self.tick = self.req.create_entity('Ticket', title=u'write the code')
+        self.toto = self.req.create_entity('Personne', nom=u'Toto')
+
+class ClickAndEditFormTC(ReleditMixinTC, CubicWebTC):
+
+    def test_default_config(self):
+        reledit = {'title': """<div id="title-subject-917-reledit" onmouseout="jQuery('#title-subject-917').addClass('hidden')" onmouseover="jQuery('#title-subject-917').removeClass('hidden')" class="releditField"><div id="title-subject-917-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-917" class="editableField hidden"><div id="title-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-917&#39;, false, &#39;&#39;, &#39;&amp;lt;title not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+                   'long_desc': """<div id="long_desc-subject-917-reledit" onmouseout="jQuery('#long_desc-subject-917').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-917').removeClass('hidden')" class="releditField"><div id="long_desc-subject-917-value" class="editableFieldValue">&lt;long_desc not specified&gt;</div><div id="long_desc-subject-917" class="editableField hidden"><div id="long_desc-subject-917-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, 917, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;long_desc not specified&amp;gt;&#39;);" title="click to add a value"><img title="click to add a value" src="data/plus.png" alt="click to add a value"/></div></div></div>""",
+                   'manager': """<div id="manager-subject-917-reledit" onmouseout="jQuery('#manager-subject-917').addClass('hidden')" onmouseover="jQuery('#manager-subject-917').removeClass('hidden')" class="releditField"><div id="manager-subject-917-value" class="editableFieldValue">&lt;manager not specified&gt;</div><div id="manager-subject-917" class="editableField hidden"><div id="manager-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+                   'composite_card11_2ttypes': """&lt;composite_card11_2ttypes not specified&gt;""",
+                   'concerns': """&lt;concerns_object not specified&gt;"""}
+
+        for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
+            if rschema not in reledit:
+                continue
+            rtype = rschema.type
+            self.assertTextEquals(reledit[rtype], self.proj.view('reledit', rtype=rtype, role=role), rtype)
+
+    def test_default_forms(self):
+        doreledit = {'title': """<div id="title-subject-917-reledit" onmouseout="jQuery('#title-subject-917').addClass('hidden')" onmouseover="jQuery('#title-subject-917').removeClass('hidden')" class="releditField"><div id="title-subject-917-value" class="editableFieldValue">cubicweb-world-domination</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="title-subject-917-form" onsubmit="return freezeFormButtons(&#39;title-subject-917-form&#39;);" class="releditForm" cubicweb:target="eformframe">
+<fieldset>
+<input name="__form_id" type="hidden" value="base" />
+<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#title-subject-917-form" />
+<input name="__domid" type="hidden" value="title-subject-917-form" />
+<input name="__type:917" type="hidden" value="Project" />
+<input name="eid" type="hidden" value="917" />
+<input name="__maineid" type="hidden" value="917" />
+<input name="__reledit|default_value" type="hidden" value="&amp;lt;title not specified&amp;gt;" />
+<input name="__reledit|vid" type="hidden" value="" />
+<input name="__reledit|rtype" type="hidden" value="title" />
+<input name="__reledit|divid" type="hidden" value="title-subject-917" />
+<input name="__reledit|formid" type="hidden" value="base" />
+<input name="__reledit|reload" type="hidden" value="false" />
+<input name="__reledit|role" type="hidden" value="subject" />
+<input name="__reledit|eid" type="hidden" value="917" />
+<input name="_cw_edited_fields:917" type="hidden" value="title-subject,__type" />
+<fieldset class="default">
+<table class="">
+<tr class="title_subject_row">
+<td
+>
+<input id="title-subject:917" maxlength="32" name="title-subject:917" size="32" tabindex="1" type="text" value="cubicweb-world-domination" />
+</td></tr>
+</table></fieldset>
+<table class="buttonbar">
+<tr>
+
+<td><button class="validateButton" tabindex="2" type="submit" value="button_ok"><img alt="OK_ICON" src="http://crater:8080/data/ok.png" />button_ok</button></td>
+
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;title-subject-917&#39;)" tabindex="3" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://crater:8080/data/cancel.png" />button_cancel</button></td>
+
+</tr></table>
+</fieldset>
+</form><div id="title-subject-917" class="editableField hidden"><div id="title-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-917&#39;, false, &#39;&#39;, &#39;&amp;lt;title not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+
+                     'long_desc': """<div id="long_desc-subject-917-reledit" onmouseout="jQuery('#long_desc-subject-917').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-917').removeClass('hidden')" class="releditField"><div id="long_desc-subject-917-value" class="editableFieldValue">&lt;long_desc not specified&gt;</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="long_desc-subject-917-form" onsubmit="return freezeFormButtons(&#39;long_desc-subject-917-form&#39;);" class="releditForm" cubicweb:target="eformframe">
+<fieldset>
+<input name="__form_id" type="hidden" value="edition" />
+<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#long_desc-subject-917-form" />
+<input name="__domid" type="hidden" value="long_desc-subject-917-form" />
+<input name="__type:A" type="hidden" value="Blog" />
+<input name="eid" type="hidden" value="A" />
+<input name="__maineid" type="hidden" value="A" />
+<input name="__linkto" type="hidden" value="long_desc:917:object" />
+<input name="__message" type="hidden" value="entity linked" />
+<input name="__reledit|default_value" type="hidden" value="&amp;lt;long_desc not specified&amp;gt;" />
+<input name="__reledit|vid" type="hidden" value="incontext" />
+<input name="__reledit|rtype" type="hidden" value="long_desc" />
+<input name="__reledit|divid" type="hidden" value="long_desc-subject-917" />
+<input name="__reledit|formid" type="hidden" value="edition" />
+<input name="__reledit|reload" type="hidden" value="false" />
+<input name="__reledit|role" type="hidden" value="subject" />
+<input name="__reledit|eid" type="hidden" value="917" />
+<input name="_cw_edited_fields:A" type="hidden" value="title-subject,rss_url-subject,__type,description-subject" />
+<fieldset class="default">
+<table class="attributeForm">
+<tr class="title_subject_row">
+<th class="labelCol"><label class="required" for="title-subject:A">title</label></th>
+<td
+>
+<input id="title-subject:A" maxlength="50" name="title-subject:A" size="45" tabindex="4" type="text" value="" />
+</td></tr>
+<tr class="description_subject_row">
+<th class="labelCol"><label for="description-subject:A">description</label></th>
+<td
+>
+<input name="description_format-subject:A" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:A" name="description-subject:A" onkeyup="autogrow(this)" rows="2" tabindex="5"></textarea>
+</td></tr>
+<tr class="rss_url_subject_row">
+<th class="labelCol"><label for="rss_url-subject:A">rss_url</label></th>
+<td
+>
+<input id="rss_url-subject:A" maxlength="128" name="rss_url-subject:A" size="45" tabindex="6" type="text" value="" />
+</td></tr>
+</table></fieldset>
+<table class="buttonbar">
+<tr>
+
+<td><button class="validateButton" tabindex="7" type="submit" value="button_ok"><img alt="OK_ICON" src="http://crater:8080/data/ok.png" />button_ok</button></td>
+
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;long_desc-subject-917&#39;)" tabindex="8" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://crater:8080/data/cancel.png" />button_cancel</button></td>
+
+</tr></table>
+</fieldset>
+</form><div id="long_desc-subject-917" class="editableField hidden"><div id="long_desc-subject-917-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, 917, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;long_desc not specified&amp;gt;&#39;);" title="click to add a value"><img title="click to add a value" src="data/plus.png" alt="click to add a value"/></div></div></div>""",
+
+                     'manager': """<div id="manager-subject-917-reledit" onmouseout="jQuery('#manager-subject-917').addClass('hidden')" onmouseover="jQuery('#manager-subject-917').removeClass('hidden')" class="releditField"><div id="manager-subject-917-value" class="editableFieldValue">&lt;manager not specified&gt;</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="manager-subject-917-form" onsubmit="return freezeFormButtons(&#39;manager-subject-917-form&#39;);" class="releditForm" cubicweb:target="eformframe">
+<fieldset>
+<input name="__form_id" type="hidden" value="base" />
+<input name="__errorurl" type="hidden" value="http://testing.fr/cubicweb/view?rql=Blop&amp;vid=blop#manager-subject-917-form" />
+<input name="__domid" type="hidden" value="manager-subject-917-form" />
+<input name="__type:917" type="hidden" value="Project" />
+<input name="eid" type="hidden" value="917" />
+<input name="__maineid" type="hidden" value="917" />
+<input name="__linkto" type="hidden" value="long_desc:917:object" />
+<input name="__message" type="hidden" value="entity linked" />
+<input name="__reledit|default_value" type="hidden" value="&amp;lt;manager not specified&amp;gt;" />
+<input name="__reledit|vid" type="hidden" value="incontext" />
+<input name="__reledit|rtype" type="hidden" value="manager" />
+<input name="__reledit|divid" type="hidden" value="manager-subject-917" />
+<input name="__reledit|formid" type="hidden" value="base" />
+<input name="__reledit|reload" type="hidden" value="false" />
+<input name="__reledit|role" type="hidden" value="subject" />
+<input name="__reledit|eid" type="hidden" value="917" />
+<input name="_cw_edited_fields:917" type="hidden" value="manager-subject,__type" />
+<fieldset class="default">
+<table class="">
+<tr class="manager_subject_row">
+<td
+>
+<select id="manager-subject:917" name="manager-subject:917" size="1" tabindex="9">
+<option value="__cubicweb_internal_field__"></option>
+<option value="919">Toto</option>
+</select>
+</td></tr>
+</table></fieldset>
+<table class="buttonbar">
+<tr>
+
+<td><button class="validateButton" tabindex="10" type="submit" value="button_ok"><img alt="OK_ICON" src="http://crater:8080/data/ok.png" />button_ok</button></td>
+
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;manager-subject-917&#39;)" tabindex="11" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://crater:8080/data/cancel.png" />button_cancel</button></td>
+
+</tr></table>
+</fieldset>
+</form><div id="manager-subject-917" class="editableField hidden"><div id="manager-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+                     'composite_card11_2ttypes': """&lt;composite_card11_2ttypes not specified&gt;""",
+                     'concerns': """&lt;concerns_object not specified&gt;"""
+            }
+        for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
+            if rschema not in doreledit:
+                continue
+            rtype = rschema.type
+            self.assertTextEquals(doreledit[rtype],
+                                  self.proj.view('doreledit', rtype=rtype, role=role,
+                                                 formid='edition' if rtype == 'long_desc' else 'base'),
+                                  rtype)
+
+class ClickAndEditFormUICFGTC(ReleditMixinTC, CubicWebTC):
+
+    def setup_database(self):
+        super(ClickAndEditFormUICFGTC, self).setup_database()
+        self.tick.set_relations(concerns=self.proj)
+        self.proj.set_relations(manager=self.toto)
+
+    def test_with_uicfg(self):
+        old_rctl = reledit_ctrl._tagdefs.copy()
+        reledit_ctrl.tag_attribute(('Project', 'title'),
+                                   {'default_value': '<title is required>', 'reload': True})
+        reledit_ctrl.tag_subject_of(('Project', 'long_desc', '*'),
+                                    {'reload': True, 'edit_target': 'rtype',
+                                     'default_value': u'<long_desc is required>'})
+        reledit_ctrl.tag_subject_of(('Project', 'manager', '*'),
+                                   {'edit_target': 'related'})
+        reledit_ctrl.tag_subject_of(('Project', 'composite_card11_2ttypes', '*'),
+                                   {'edit_target': 'related'})
+        reledit_ctrl.tag_object_of(('Ticket', 'concerns', 'Project'),
+                                   {'edit_target': 'rtype'})
+        reledit = {
+            'title': """<div id="title-subject-917-reledit" onmouseout="jQuery('#title-subject-917').addClass('hidden')" onmouseover="jQuery('#title-subject-917').removeClass('hidden')" class="releditField"><div id="title-subject-917-value" class="editableFieldValue">cubicweb-world-domination</div><div id="title-subject-917" class="editableField hidden"><div id="title-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;title&#39;, &#39;subject&#39;, &#39;title-subject-917&#39;, true, &#39;&#39;, &#39;&lt;title is required&gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+            'long_desc': """<div id="long_desc-subject-917-reledit" onmouseout="jQuery('#long_desc-subject-917').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-917').removeClass('hidden')" class="releditField"><div id="long_desc-subject-917-value" class="editableFieldValue"><long_desc is required></div><div id="long_desc-subject-917" class="editableField hidden"><div id="long_desc-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;long_desc&#39;, &#39;subject&#39;, &#39;long_desc-subject-917&#39;, true, &#39;incontext&#39;, &#39;&lt;long_desc is required&gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+            'manager': """<div id="manager-subject-917-reledit" onmouseout="jQuery('#manager-subject-917').addClass('hidden')" onmouseover="jQuery('#manager-subject-917').removeClass('hidden')" class="releditField"><div id="manager-subject-917-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/personne/919" title="">Toto</a></div><div id="manager-subject-917" class="editableField hidden"><div id="manager-subject-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;edition&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div><div id="manager-subject-917-delete" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;deleteconf&#39;, 917, &#39;manager&#39;, &#39;subject&#39;, &#39;manager-subject-917&#39;, false, &#39;incontext&#39;, &#39;&amp;lt;manager not specified&amp;gt;&#39;);" title="click to delete this value"><img title="click to delete this value" src="data/cancel.png" alt="click to delete this value"/></div></div></div>""",
+            'composite_card11_2ttypes': """&lt;composite_card11_2ttypes not specified&gt;""",
+            'concerns': """<div id="concerns-object-917-reledit" onmouseout="jQuery('#concerns-object-917').addClass('hidden')" onmouseover="jQuery('#concerns-object-917').removeClass('hidden')" class="releditField"><div id="concerns-object-917-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/ticket/918" title="">write the code</a></div><div id="concerns-object-917" class="editableField hidden"><div id="concerns-object-917-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm(&#39;base&#39;, 917, &#39;concerns&#39;, &#39;object&#39;, &#39;concerns-object-917&#39;, false, &#39;csv&#39;, &#39;&amp;lt;concerns_object not specified&amp;gt;&#39;);" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>"""
+            }
+        for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
+            if rschema not in reledit:
+                continue
+            rtype = rschema.type
+            self.assertTextEquals(reledit[rtype],
+                                  self.proj.view('reledit', rtype=rtype, role=role),
+                                  rtype)
+        reledit_ctrl.clear()
+        reledit_ctrl._tagdefs.update(old_rctl)
--- a/web/uicfg.py	Wed Sep 15 11:13:17 2010 +0200
+++ b/web/uicfg.py	Wed Sep 15 15:00:04 2010 +0200
@@ -53,7 +53,8 @@
 
 from cubicweb import neg_role
 from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
-                            RelationTagsDict, register_rtag, _ensure_str_key)
+                            RelationTagsDict, NoTargetRelationTagsDict,
+                            register_rtag, _ensure_str_key)
 from cubicweb.schema import META_RTYPES
 
 
@@ -83,27 +84,11 @@
                                               'sideboxes', 'hidden')))
 
 
-class DisplayCtrlRelationTags(RelationTagsDict):
+class DisplayCtrlRelationTags(NoTargetRelationTagsDict):
     def __init__(self, *args, **kwargs):
         super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
         self.counter = 0
 
-    def tag_subject_of(self, key, tag):
-        subj, rtype, obj = key
-        if obj != '*':
-            self.warning('using explict target type in display_ctrl.tag_subject_of() '
-                         'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)',
-                         subj, rtype, subj, rtype, obj)
-        super(DisplayCtrlRelationTags, self).tag_subject_of((subj, rtype, '*'), tag)
-
-    def tag_object_of(self, key, tag):
-        subj, rtype, obj = key
-        if subj != '*':
-            self.warning('using explict subject type in display_ctrl.tag_object_of() '
-                         'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)',
-                         rtype, obj, subj, rtype, obj)
-        super(DisplayCtrlRelationTags, self).tag_object_of(('*', rtype, obj), tag)
-
 def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role):
     if role == 'subject':
         oschema = '*'
@@ -381,7 +366,7 @@
 autoform_field = RelationTags('autoform_field')
 
 # relations'field explicit kwargs (given to field's __init__)
-autoform_field_kwargs = RelationTagsDict()
+autoform_field_kwargs = RelationTagsDict('autoform_field_kwargs')
 
 
 # set of tags of the form <action>_on_new on relations. <action> is a
@@ -389,31 +374,49 @@
 # permissions checking is by-passed and supposed to be ok
 autoform_permissions_overrides = RelationTagsSet('autoform_permissions_overrides')
 
-class _ReleditTags(RelationTagsDict):
-    _keys = frozenset('reload default_value noedit'.split())
-
-    def tag_subject_of(self, key, *args, **kwargs):
-        subj, rtype, obj = key
-        if obj != '*':
-            self.warning('using explict target type in display_ctrl.tag_subject_of() '
-                         'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)',
-                         subj, rtype, subj, rtype, obj)
-        super(_ReleditTags, self).tag_subject_of(key, *args, **kwargs)
-
-    def tag_object_of(self, key, *args, **kwargs):
-        subj, rtype, obj = key
-        if subj != '*':
-            self.warning('using explict subject type in display_ctrl.tag_object_of() '
-                         'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)',
-                         rtype, obj, subj, rtype, obj)
-        super(_ReleditTags, self).tag_object_of(key, *args, **kwargs)
+class ReleditTags(NoTargetRelationTagsDict):
+    """
+    default_value: alternative default value
+      The default value is what is shown when there is no value.
+    reload: boolean, eid (to reload to) or function taking subject and returning bool/eid
+      This is useful when editing a relation (or attribute) that impacts the url
+      or another parts of the current displayed page. Defaults to False.
+    rvid: alternative view id (as str) for relation or composite edition
+      Default is 'incontext' or 'csv' depending on the cardinality. They can also be
+      statically changed by subclassing ClickAndEditFormView and redefining _one_rvid
+      (resp. _many_rvid).
+    edit_target: 'rtype' (to edit the relation) or 'related' (to edit the related entity)
+      This controls whether to edit the relation or the target entity of the relation.
+      Currently only one-to-one relations support target entity edition. By default,
+      the 'related' option is taken whenever the relation is composite and one-to-one.
+    """
+    _keys = frozenset('default_value reload rvid edit_target'.split())
 
     def tag_relation(self, key, tag):
         for tagkey in tag.iterkeys():
             assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys)
-        return super(_ReleditTags, self).tag_relation(key, tag)
+        return super(ReleditTags, self).tag_relation(key, tag)
 
-reledit_ctrl = _ReleditTags('reledit')
+def init_reledit_ctrl(rtag, sschema, rschema, oschema, role):
+    if rschema.final:
+        return
+    composite = rschema.rdef(sschema, oschema).composite == role
+    if role == 'subject':
+        oschema = '*'
+    else:
+        sschema = '*'
+    values = rtag.get(sschema, rschema, oschema, role)
+    edittarget = values.get('edit_target')
+    if edittarget not in (None, 'rtype', 'related'):
+        rtag.warning('reledit: wrong value for edit_target on relation %s: %s',
+                     rschema, edittarget)
+        edittarget = None
+    if not edittarget:
+        edittarget = 'related' if composite else 'rtype'
+        rtag.tag_relation((sschema, rschema, oschema, role),
+                          {'edit_target': edittarget})
+
+reledit_ctrl = ReleditTags('reledit', init_reledit_ctrl)
 
 # boxes.EditBox configuration #################################################
 
--- a/web/views/forms.py	Wed Sep 15 11:13:17 2010 +0200
+++ b/web/views/forms.py	Wed Sep 15 15:00:04 2010 +0200
@@ -188,7 +188,7 @@
         if self.formvalues is not None:
             return # already built
         self.formvalues = formvalues or {}
-        # use a copy in case fields are modified while context is build (eg
+        # use a copy in case fields are modified while context is built (eg
         # __linkto handling for instance)
         for field in self.fields[:]:
             for field in field.actual_fields(self):
--- a/web/views/primary.py	Wed Sep 15 11:13:17 2010 +0200
+++ b/web/views/primary.py	Wed Sep 15 15:00:04 2010 +0200
@@ -336,6 +336,28 @@
         if url:
             self.w(u'<a href="%s">%s</a>' % (url, url))
 
+class AttributeView(EntityView):
+    """use this view on an entity as an alternative to more sophisticated
+    views such as reledit.
+
+    Ex. usage:
+
+    uicfg.primaryview_display_ctrl.tag_attribute(('Foo', 'bar'), {'vid': 'attribute'})
+    """
+    __regid__ = 'attribute'
+    __select__ = EntityView.__select__ & match_kwargs('rtype')
+
+    def cell_call(self, row, col, rtype, **kwargs):
+        entity = self.cw_rset.get_entity(row, col)
+        if self._cw.vreg.schema.rschema(rtype).final:
+            self.w(entity.printable_value(rtype))
+        else:
+            dispctrl = uicfg.primaryview_display_ctrl.etype_get(
+                entity.e_schema, rtype, kwargs['role'], '*')
+            rset = entity.related(rtype, role)
+            if rset:
+                self.wview('autolimited', rset, initargs={'dispctrl': dispctrl})
+
 
 ## default primary ui configuration ###########################################
 
--- a/web/views/reledit.py	Wed Sep 15 11:13:17 2010 +0200
+++ b/web/views/reledit.py	Wed Sep 15 15:00:04 2010 +0200
@@ -21,6 +21,7 @@
 import copy
 
 from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import deprecated
 
 from cubicweb import neg_role
 from cubicweb.schema import display_name
@@ -39,8 +40,8 @@
         return u''
     def append_field(self, *args):
         pass
-    def field_by_name(self, rtype, role, eschema=None):
-        return None
+
+rctrl = uicfg.reledit_ctrl
 
 class ClickAndEditFormView(EntityView):
     __regid__ = 'doreledit'
@@ -60,8 +61,11 @@
     _editzonemsg = _('click to edit this field')
 
     # default relation vids according to cardinality
+    # can be changed per rtype using reledit_ctrl rtag
     _one_rvid = 'incontext'
     _many_rvid = 'csv'
+    # renderer
+    _form_renderer_id = 'base'
 
     def cell_call(self, row, col, rtype=None, role='subject',
                   reload=False, # controls reloading the whole page after change
@@ -69,89 +73,98 @@
                                 # function taking the subject entity & returning a boolean or an eid
                   rvid=None,    # vid to be applied to other side of rtype (non final relations only)
                   default_value=None,
-                  formid=None
+                  formid='base'
                   ):
         """display field to edit entity's `rtype` relation on click"""
         assert rtype
         assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
-        if self.__regid__ == 'doreledit':
-            assert formid
-        self._cw.add_js('cubicweb.reledit.js')
-        if formid:
-            self._cw.add_js('cubicweb.edition.js')
         self._cw.add_css('cubicweb.form.css')
+        self._cw.add_js('cubicweb.reledit.js', 'cubicweb.edition.js')
         entity = self.cw_rset.get_entity(row, col)
         rschema = self._cw.vreg.schema[rtype]
+        self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*')
         reload = self._compute_reload(entity, rschema, role, reload)
         default_value = self._compute_default_value(entity, rschema, role, default_value)
         divid = self._build_divid(rtype, role, entity.eid)
         if rschema.final:
-            self._handle_attributes(entity, rschema, role, divid, reload, default_value)
+            self._handle_attribute(entity, rschema, role, divid, reload, default_value)
         else:
-            self._handle_relations(entity, rschema, role, divid, reload, default_value, formid)
+            if self._is_composite():
+                self._handle_composite(entity, rschema, role, divid, reload, default_value, formid)
+            else:
+                self._handle_relation(entity, rschema, role, divid, reload, default_value, formid)
 
-    def _handle_attributes(self, entity, rschema, role, divid, reload, default_value):
+    def _handle_attribute(self, entity, rschema, role, divid, reload, default_value):
         rtype = rschema.type
         value = entity.printable_value(rtype)
-        form, renderer = self._build_form(entity, rtype, role, divid, 'base',
-                                          default_value, reload)
-        if not self._should_edit_attribute(entity, rschema, form):
+        if not self._should_edit_attribute(entity, rschema):
             self.w(value)
             return
+
+        display_label, related_entity = self._prepare_form(entity, rtype, role)
+        form, renderer = self._build_form(entity, rtype, role, divid, 'base', default_value,
+                                          reload, display_label, related_entity)
         value = value or default_value
-        field = form.field_by_name(rtype, role, entity.e_schema)
-        form.append_field(field)
         self.view_form(divid, value, form, renderer)
 
-    def _handle_relations(self, entity, rschema, role, divid, reload, default_value, formid):
-        rtype = rschema.type
-        rvid = self._compute_best_vid(entity.e_schema, rschema, role)
-        related_rset = entity.related(rtype, role)
+    def _compute_formid_value(self, entity, rschema, role, default_value, rvid, formid):
+        related_rset = entity.related(rschema.type, role)
         if related_rset:
             value = self._cw.view(rvid, related_rset)
         else:
             value = default_value
-        ttypes = self._compute_ttypes(rschema, role)
+        if not self._should_edit_relation(entity, rschema, role):
+            return None, value
+        return formid, value
+
+    def _handle_relation(self, entity, rschema, role, divid, reload, default_value, formid):
+        rvid = self._compute_best_vid(entity.e_schema, rschema, role)
+        formid, value = self._compute_formid_value(entity, rschema, role, default_value, rvid, formid)
+        if formid is None:
+            return self.w(value)
 
-        if not self._should_edit_relation(entity, rschema, role):
-            self.w(value)
-            return
-        # this is for attribute-like composites (1 target type, 1 related entity at most)
+        rtype = rschema.type
+        display_label, related_entity = self._prepare_form(entity, rtype, role)
+        form, renderer = self._build_form(entity, rtype, role, divid, formid, default_value, reload,
+                                          display_label, related_entity, dict(vid=rvid))
+        self.view_form(divid, value, form, renderer)
+
+    def _handle_composite(self, entity, rschema, role, divid, reload, default_value, formid):
+        # this is for attribute-like composites (1 target type, 1 related entity at most, for now)
+        ttypes = self._compute_ttypes(rschema, role)
+        related_rset = entity.related(rschema.type, role)
         add_related = self._may_add_related(related_rset, entity, rschema, role, ttypes)
         edit_related = self._may_edit_related_entity(related_rset, entity, rschema, role, ttypes)
         delete_related = edit_related and self._may_delete_related(related_rset, entity, rschema, role)
-        # compute formid
-        if len(ttypes) > 1: # redundant safety belt
-            formid = 'base'
-        else:
-            afs = uicfg.autoform_section.etype_get(entity.e_schema, rschema, role, ttypes[0])
-            # is there an afs spec that says we should edit
-            # the rschema as an attribute ?
-            if afs and 'main_attributes' in afs:
-                formid = 'base'
+
+        rvid = self._compute_best_vid(entity.e_schema, rschema, role)
+        formid, value = self._compute_formid_value(entity, rschema, role, default_value, rvid, formid)
+        if formid is None or not (edit_related or add_related):
+            # till we learn to handle cases where not (edit_related or add_related)
+            self.w(value)
+            return
 
-        form, renderer = self._build_form(entity, rtype, role, divid, formid, default_value,
-                                          reload, dict(vid=rvid),
-                                          edit_related, add_related and ttypes[0])
-        if formid == 'base':
-            field = form.field_by_name(rtype, role, entity.e_schema)
-            form.append_field(field)
-        self.view_form(divid, value, form, renderer, edit_related,
-                       delete_related, add_related)
-
+        rtype = rschema.type
+        ttype = ttypes[0]
+        _fdata = self._prepare_composite_form(entity, rtype, role, edit_related, add_related and ttype)
+        display_label, related_entity = _fdata
+        form, renderer = self._build_form(entity, rtype, role, divid, formid, default_value, reload,
+                                          display_label, related_entity, dict(vid=rvid))
+        self.view_form(divid, value, form, renderer,
+                       edit_related, add_related, delete_related)
 
     def _compute_best_vid(self, eschema, rschema, role):
+        rvid = self._one_rvid
         if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
-            return self._many_rvid
-        return self._one_rvid
+            rvid = self._many_rvid
+        return self._rules.get('rvid', rvid)
 
     def _compute_ttypes(self, rschema, role):
         dual_role = neg_role(role)
         return getattr(rschema, '%ss' % dual_role)()
 
     def _compute_reload(self, entity, rschema, role, reload):
-        rule = uicfg.reledit_ctrl.etype_get(entity.e_schema.type, rschema.type, role, '*')
-        ctrl_reload = rule.get('reload', reload)
+        ctrl_reload = self._rules.get('reload', reload)
         if callable(ctrl_reload):
             ctrl_reload = ctrl_reload(entity)
         if isinstance(ctrl_reload, int) and ctrl_reload > 1: # not True/False
@@ -160,8 +173,7 @@
 
     def _compute_default_value(self, entity, rschema, role, default_value):
         etype = entity.e_schema.type
-        rule = uicfg.reledit_ctrl.etype_get(etype, rschema.type, role, '*')
-        ctrl_default = rule.get('default_value', default_value)
+        ctrl_default = self._rules.get('default_value', default_value)
         if ctrl_default:
             return ctrl_default
         if default_value is None:
@@ -169,47 +181,42 @@
                               display_name(self._cw, rschema.type, role))
         return default_value
 
-    def _is_composite(self, eschema, rschema, role):
-        return eschema.rdef(rschema, role).composite == role
+    def _is_composite(self):
+        return self._rules.get('edit_target') == 'related'
 
     def _may_add_related(self, related_rset, entity, rschema, role, ttypes):
         """ ok for attribute-like composite entities """
-        if self._is_composite(entity.e_schema, rschema, role):
-            if len(ttypes) > 1: # wrong cardinality: do not handle
-                return False
-            rdef = rschema.role_rdef(entity.e_schema, ttypes[0], role)
-            card = rdef.role_cardinality(role)
-            if related_rset and card in '?1':
-                return False
-            if role == 'subject':
-                kwargs = {'fromeid': entity.eid}
-            else:
-                kwargs = {'toeid': entity.eid}
-            if rdef.has_perm(self._cw, 'add', **kwargs):
-                return True
-        return False
+        if len(ttypes) > 1: # many etypes: learn how to do it
+            return False
+        rdef = rschema.role_rdef(entity.e_schema, ttypes[0], role)
+        card = rdef.role_cardinality(role)
+        if related_rset or card not in '?1':
+            return False
+        if role == 'subject':
+            kwargs = {'fromeid': entity.eid}
+        else:
+            kwargs = {'toeid': entity.eid}
+        return rdef.has_perm(self._cw, 'add', **kwargs)
 
     def _may_edit_related_entity(self, related_rset, entity, rschema, role, ttypes):
         """ controls the edition of the related entity """
-        if entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1':
-            return False
-        if len(related_rset.rows) != 1:
+        if len(ttypes) > 1 or len(related_rset.rows) != 1:
             return False
-        if len(ttypes) > 1:
-            return False
-        if not self._is_composite(entity.e_schema, rschema, role):
+        if entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1':
             return False
         return related_rset.get_entity(0, 0).cw_has_perm('update')
 
     def _may_delete_related(self, related_rset, entity, rschema, role):
-        # we assume may_edit_related
-        kwargs = {'fromeid': entity.eid} if role == 'subject' else {'toeid': entity.eid}
-        if not rschema.has_perm(self._cw, 'delete', **kwargs):
+        # we assume may_edit_related, only 1 related entity
+        if not related_rset:
             return False
-        for related_entity in related_rset.entities():
-            if not related_entity.cw_has_perm('delete'):
-                return False
-        return True
+        rentity = related_rset.get_entity(0, 0)
+        if role == 'subject':
+            kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid}
+        else:
+            kwargs = {'fromeid': rentity.eid, 'toeid': entity.eid}
+        # NOTE: should be sufficient given a well built schema/security
+        return rschema.has_perm(self._cw, 'delete', **kwargs)
 
     def _build_edit_zone(self):
         return self._editzone % {'msg' : xml_escape(_(self._cw._(self._editzonemsg)))}
@@ -234,32 +241,43 @@
             event_args.update(extradata)
         return event_args
 
-    def _build_form(self, entity, rtype, role, divid, formid, default_value, reload,
-                    extradata=None, edit_related=False, add_related=False, **formargs):
-        event_args = self._build_args(entity, rtype, role, formid, default_value,
-                                      reload, extradata)
-        cancelclick = self._cancelclick % divid
+    def _prepare_form(self, entity, _rtype, role):
+        display_label = False
+        related_entity = entity
+        return display_label, related_entity
+
+    def _prepare_composite_form(self, entity, rtype, role, edit_related, add_related):
         if edit_related and not add_related:
-            display_fields = None
             display_label = True
             related_entity = entity.related(rtype, role).get_entity(0, 0)
-            self._cw.form['eid'] = related_entity.eid
         elif add_related:
-            display_fields = None
             display_label = True
             _new_entity = self._cw.vreg['etypes'].etype_class(add_related)(self._cw)
             _new_entity.eid = self._cw.varmaker.next()
             related_entity = _new_entity
+            # XXX see forms.py ~ 276 and entities.linked_to method
+            #     is there another way ?
             self._cw.form['__linkto'] = '%s:%s:%s' % (rtype, entity.eid, neg_role(role))
-        else: # base case: edition/attribute relation
-            display_fields = [(rtype, role)]
-            display_label = False
-            related_entity = entity
+        return display_label, related_entity
+
+    def _build_renderer(self, related_entity, display_label):
+        return self._cw.vreg['formrenderers'].select(
+            self._form_renderer_id, self._cw, entity=related_entity,
+            display_label=display_label,
+            table_class='attributeForm' if display_label else '',
+            display_help=False, button_bar_class='buttonbar',
+            display_progress_div=False)
+
+    def _build_form(self, entity, rtype, role, divid, formid, default_value, reload,
+                    display_label, related_entity, extradata=None, **formargs):
+        event_args = self._build_args(entity, rtype, role, formid, default_value,
+                                      reload, extradata)
+        cancelclick = self._cancelclick % divid
         form = self._cw.vreg['forms'].select(
-            formid, self._cw, rset=related_entity.as_rset(), entity=related_entity, domid='%s-form' % divid,
-            display_fields=display_fields, formtype='inlined',
-            action=self._cw.build_url('validateform?__onsuccess=window.parent.cw.reledit.onSuccess'),
-            cwtarget='eformframe', cssstyle='display: none',
+            formid, self._cw, rset=related_entity.as_rset(), entity=related_entity,
+            domid='%s-form' % divid, formtype='inlined',
+            action=self._cw.build_url('validateform', __onsuccess='window.parent.cw.reledit.onSuccess'),
+            cwtarget='eformframe', cssclass='releditForm',
             **formargs)
         # pass reledit arguments
         for pname, pvalue in event_args.iteritems():
@@ -279,53 +297,38 @@
             form.form_buttons = [SubmitButton(),
                                  Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)]
         form.event_args = event_args
-        renderer = self._cw.vreg['formrenderers'].select(
-            'base', self._cw, entity=related_entity, display_label=display_label,
-            display_help=False, table_class='',
-            button_bar_class='buttonbar', display_progress_div=False)
-        return form, renderer
+        if formid == 'base':
+            field = form.field_by_name(rtype, role, entity.e_schema)
+            form.append_field(field)
+        return form, self._build_renderer(related_entity, display_label)
 
-    def _should_edit_attribute(self, entity, rschema, form):
-        # examine rtags
-        noedit = uicfg.reledit_ctrl.etype_get(entity.e_schema, rschema.type, 'subject').get('noedit', False)
-        if noedit:
-            return False
+    def _should_edit_attribute(self, entity, rschema):
         rdef = entity.e_schema.rdef(rschema)
-        afs = uicfg.autoform_section.etype_get(entity.__regid__, rschema, 'subject', rdef.object)
-        if 'main_hidden' in  afs:
-            return False
         # check permissions
         if not entity.cw_has_perm('update'):
             return False
         rdef = entity.e_schema.rdef(rschema)
-        if not rdef.has_perm(self._cw, 'update', eid=entity.eid):
-            return False
-        # XXX ?
-        try:
-            form.field_by_name(str(rschema), 'subject', entity.e_schema)
-        except FieldNotFound:
-            return False
-        return True
+        return rdef.has_perm(self._cw, 'update', eid=entity.eid)
+
+    should_edit_attributes = deprecated('[3.9] should_edit_attributes is deprecated,'
+                                        ' use _should_edit_attribute instead',
+                                        _should_edit_attribute)
 
     def _should_edit_relation(self, entity, rschema, role):
         # examine rtags
         rtype = rschema.type
-        noedit = uicfg.reledit_ctrl.etype_get(entity.e_schema, rtype, role).get('noedit', False)
-        if noedit:
-            return False
         rdef = entity.e_schema.rdef(rschema, role)
-        afs = uicfg.autoform_section.etype_get(
-            entity.__regid__, rschema, role, rdef.object)
-        if 'main_hidden' in afs:
-            return False
         perm_args = {'fromeid': entity.eid} if role == 'subject' else {'toeid': entity.eid}
         return rschema.has_perm(self._cw, 'add', **perm_args)
 
-    def view_form(self, divid, value, form=None, renderer=None,
-                  edit_related=False, delete_related=False, add_related=False):
+    should_edit_relations = deprecated('[3.9] should_edit_relations is deprecated,'
+                                       ' use _should_edit_relation instead',
+                                       _should_edit_relation)
+
+    def _open_form_wrapper(self, divid, value, form, renderer):
         w = self.w
-        w(u'<div id="%(id)s-reledit" onmouseout="%(out)s" onmouseover="%(over)s">' %
-          {'id': divid,
+        w(u'<div id="%(id)s-reledit" onmouseout="%(out)s" onmouseover="%(over)s" class="%(css)s">' %
+          {'id': divid, 'css': 'releditField',
            'out': "jQuery('#%s').addClass('hidden')" % divid,
            'over': "jQuery('#%s').removeClass('hidden')" % divid})
         w(u'<div id="%s-value" class="editableFieldValue">' % divid)
@@ -333,15 +336,24 @@
         w(u'</div>')
         w(form.render(renderer=renderer))
         w(u'<div id="%s" class="editableField hidden">' % divid)
+
+    def _close_form_wrapper(self):
+        self.w(u'</div>')
+        self.w(u'</div>')
+
+    def view_form(self, divid, value, form=None, renderer=None,
+                  edit_related=False, add_related=False, delete_related=False):
+        self._open_form_wrapper(divid, value, form, renderer)
+        w = self.w
         args = form.event_args.copy()
-        if not add_related: # excludes edition
-            args['formid'] = 'edition'
+        if not add_related: # currently, excludes edition
+            args['formid'] = 'edition' if edit_related else 'base'
             w(u'<div id="%s-update" class="editableField" onclick="%s" title="%s">' %
               (divid, xml_escape(self._onclick % args), self._cw._(self._editzonemsg)))
             w(self._build_edit_zone())
             w(u'</div>')
         else:
-            args['formid'] = 'edition'
+            args['formid'] = 'edition' if add_related else 'base'
             w(u'<div id="%s-add" class="editableField" onclick="%s" title="%s">' %
               (divid, xml_escape(self._onclick % args), self._cw._(self._addmsg)))
             w(self._build_add_zone())
@@ -352,14 +364,14 @@
               (divid, xml_escape(self._onclick % args), self._cw._(self._deletemsg)))
             w(self._build_delete_zone())
             w(u'</div>')
-        w(u'</div>')
-        w(u'</div>')
+        self._close_form_wrapper()
+
 
 class AutoClickAndEditFormView(ClickAndEditFormView):
     __regid__ = 'reledit'
 
     def _build_form(self, entity, rtype, role, divid, formid, default_value, reload,
-                  extradata=None, edit_related=False, add_related=False, **formargs):
+                    display_label, related_entity, extradata=None, **formargs):
         event_args = self._build_args(entity, rtype, role, 'base', default_value,
                                       reload, extradata)
         form = _DummyForm()