web/views/reledit.py
branchstable
changeset 6246 62e25fac41cd
parent 6111 06c96a79c3c0
child 6264 0dcd19d89767
--- 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()