make new editcontroller works, based on a _cw_edited_fields hidden input
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 21 Dec 2009 20:28:01 +0100
changeset 4171 f1b9f0ed1253
parent 4170 c325c62cec8e
child 4172 4d4cef034eec
make new editcontroller works, based on a _cw_edited_fields hidden input
web/formfields.py
web/views/editcontroller.py
--- a/web/formfields.py	Mon Dec 21 20:27:14 2009 +0100
+++ b/web/formfields.py	Mon Dec 21 20:28:01 2009 +0100
@@ -790,12 +790,22 @@
     def process_form_value(self, form):
         """process posted form and return correctly typed value"""
         widget = self.get_widget(form)
-        value = widget.process_field_data(form, self)
-        if value is None:
-            return ()
-        elif not isinstance(value, list):
-            return (value,)
-        return value
+        values = widget.process_field_data(form, self)
+        if values is None:
+            values = ()
+        elif not isinstance(values, list):
+            values = (values,)
+        eids = set()
+        for eid in values:
+            if not eid: # AutoCompletionWidget XXX deal with this in the widget
+                continue
+            typed_eid = form.actual_eid(eid)
+            if typed_eid is None:
+                form._cw.data['pendingfields'].append( (form, self) )
+                return None
+            eids.add(typed_eid)
+        return eids
+
 
 class CompoundField(Field):
     def __init__(self, fields, *args, **kwargs):
--- a/web/views/editcontroller.py	Mon Dec 21 20:27:14 2009 +0100
+++ b/web/views/editcontroller.py	Mon Dec 21 20:28:01 2009 +0100
@@ -72,7 +72,7 @@
             req.set_shared_data('__maineid', form['__maineid'], querydata=True)
         # no specific action, generic edition
         self._to_create = req.data['eidmap'] = {}
-        self._pending_relations = []
+        self._pending_fields = req.data['pendingfields'] = []
         todelete = self._cw.get_pending_deletes()
         toinsert = self._cw.get_pending_inserts()
         try:
@@ -85,19 +85,18 @@
                     method = getattr(entity, methodname)
                     method(formparams)
                 eid = self.edit_entity(formparams)
-        except (RequestError, NothingToEdit):
+        except (RequestError, NothingToEdit), ex:
             if '__linkto' in req.form and 'eid' in req.form:
                 self.execute_linkto()
             elif not ('__delete' in req.form or '__insert' in req.form or todelete or toinsert):
-                raise ValidationError(None, {None: req._('nothing to edit')})
+                raise ValidationError(None, {None: unicode(ex)})
+        # handle relations in newly created entities
+        if self._pending_fields:
+            for form, field in self._pending_fields:
+                self.handle_formfield(form, field)
+        # execute rql to set all relations
         for querydef in self.relations_rql:
             self._cw.execute(*querydef)
-        # handle relations in newly created entities
-        # XXX find a way to merge _pending_relations and relations_rql
-        if self._pending_relations:
-            for form, field, entity in self._pending_relations:
-                for querydef in self.handle_relation(form, field, entity, True):
-                    self._cw.execute(*querydef)
         # XXX this processes *all* pending operations of *all* entities
         if req.form.has_key('__delete'):
             todelete += req.list_form_param('__delete', req.form, pop=True)
@@ -108,6 +107,9 @@
         if toinsert:
             self.insert_relations(parse_relations_descr(toinsert))
         self._cw.remove_pending_operations()
+        if self.errors:
+            errors = dict((f.name, unicode(ex)) for f, ex in self.errors)
+            raise ValidationError(form.get('__maineid'), errors)
 
     def _insert_entity(self, etype, eid, rqlquery):
         rql = rqlquery.insert_query(etype)
@@ -125,17 +127,15 @@
         return neweid
 
     def _update_entity(self, eid, rqlquery):
-        rql = rqlquery.update_query(eid)
-        self._cw.execute(rql, rqlquery.kwargs)
+        self._cw.execute(rqlquery.update_query(eid), rqlquery.kwargs)
 
     def edit_entity(self, formparams, multiple=False):
         """edit / create / copy an entity and return its eid"""
         etype = formparams['__type']
         entity = self._cw.vreg['etypes'].etype_class(etype)(self._cw)
         entity.eid = formparams['eid']
-        eid = self._get_eid(entity.eid)
         is_main_entity = self._cw.form.get('__maineid') == formparams['eid']
-        # let a chance to do some entity specific stuff.tn
+        # let a chance to do some entity specific stuff
         entity.pre_web_edit()
         # create a rql query from parameters
         rqlquery = RqlQuery()
@@ -144,9 +144,26 @@
         # a few dark corners
         formid = self._cw.form.get('__form_id', 'edition')
         form = self._cw.vreg['forms'].select(formid, self._cw, entity=entity)
-        for field in form.fields:
-            if form.form_field_modified(field):
-                self.handle_formfield(form, field, entity, rqlquery)
+        eid = form.actual_eid(entity.eid)
+        try:
+            editedfields = formparams['_cw_edited_fields']
+        except KeyError:
+            raise RequestError(self._cw._('no edited fields specified for entity %s' % entity.eid))
+        for editedfield in splitstrip(editedfields):
+            try:
+                name, role = editedfield.split('-')
+            except:
+                name = editedfield
+                role = None
+            if form.field_by_name.im_func.func_code.co_argcount == 4:
+                field = form.field_by_name(name, role, eschema=entity.e_schema)
+            else:
+                field = form.field_by_name(name, role)
+            if field.has_been_modified(form):
+                self.handle_formfield(form, field, rqlquery)
+        if self.errors:
+            errors = dict((f.name, unicode(ex)) for f, ex in self.errors)
+            raise ValidationError(entity.eid, errors)
         if eid is None: # creation or copy
             entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
         elif rqlquery.edited: # edition of an existant entity
@@ -165,10 +182,10 @@
             self.execute_linkto(entity.eid)
         return eid
 
-    def handle_formfield(self, form, field, entity, rqlquery):
-        eschema = entity.e_schema
+    def handle_formfield(self, form, field, rqlquery=None):
+        eschema = form.edited_entity.e_schema
         try:
-            for attr, value in field.process_posted(form):
+            for field, value in field.process_posted(form):
                 if not (
                     (field.role == 'subject' and field.name in eschema.subjrels)
                     or
@@ -176,16 +193,64 @@
                     continue
                 rschema = self._cw.vreg.schema.rschema(field.name)
                 if rschema.final:
-                    rqlquery.kwargs[attr] = value
-                    rqlquery.edited.append('X %s %%(%s)s' % (attr, attr))
-                elif rschema.inlined:
-                    self.handle_inlined_relation(form, field, entity, rqlquery)
+                    rqlquery.kwargs[field.name] = value
+                    rqlquery.edited.append('X %s %%(%s)s' % (rschema, rschema))
                 else:
-                    self.relations_rql += self.handle_relation(
-                        form, field, entity)
+                    if form.edited_entity.has_eid():
+                        origvalues = set(entity.eid for entity in form.edited_entity.related(field.name, field.role, entities=True))
+                    else:
+                        origvalues = set()
+                    if value is None or value == origvalues:
+                        continue # not edited / not modified / to do later
+                    if rschema.inlined and rqlquery is not None:
+                        self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
+                    elif form.edited_entity.has_eid():
+                        self.handle_relation(form, field, value, origvalues)
+                    else:
+                        self._pending_fields.append( (form, field) )
+
         except ProcessFormError, exc:
             self.errors.append((field, exc))
 
+    def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
+        """handle edition for the (rschema, x) relation of the given entity
+        """
+        attr = field.name
+        if values:
+            rqlquery.kwargs[attr] = iter(values).next()
+            rqlquery.edited.append('X %s %s' % (attr, attr.upper()))
+            rqlquery.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
+        elif form.edited_entity.has_eid():
+            self.handle_relation(form, field, values, origvalues)
+
+    def handle_relation(self, form, field, values, origvalues):
+        """handle edition for the (rschema, x) relation of the given entity
+        """
+        etype = form.edited_entity.e_schema
+        rschema = self._cw.vreg.schema.rschema(field.name)
+        if field.role == 'subject':
+            desttype = rschema.objects(etype)[0]
+            card = rschema.rdef(etype, desttype).cardinality[0]
+            subjvar, objvar = 'X', 'Y'
+        else:
+            desttype = rschema.subjects(etype)[0]
+            card = rschema.rdef(desttype, etype).cardinality[1]
+            subjvar, objvar = 'Y', 'X'
+        eid = form.edited_entity.eid
+        if field.role == 'object' or not rschema.inlined or not values:
+            # this is not an inlined relation or no values specified,
+            # explicty remove relations
+            rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
+                subjvar, rschema, objvar)
+            for reid in origvalues.difference(values):
+                self.relations_rql.append((rql, {'x': eid, 'y': reid}, ('x', 'y')))
+        seteids = values.difference(origvalues)
+        if seteids:
+            rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
+                subjvar, rschema, objvar)
+            for reid in seteids:
+                self.relations_rql.append((rql, {'x': eid, 'y': reid}, ('x', 'y')))
+
     def _action_apply(self):
         self._default_publish()
         self.reset()
@@ -201,92 +266,4 @@
         self.delete_entities(self._cw.edited_eids(withtype=True))
         return self.reset()
 
-    def _relation_values(self, form, field, entity, late=False):
-        """handle edition for the (rschema, x) relation of the given entity
-        """
-        values = set()
-        for eid in field.process_form_value(form):
-            if not eid: # AutoCompletionWidget
-                continue
-            typed_eid = self._get_eid(eid)
-            if typed_eid is None:
-                if late:
-                    # eid is still None while it's already a late call
-                    # this mean that the associated entity has not been created
-                    raise Exception("eid %s is still not created" % eid)
-                self._pending_relations.append( (form, field, entity) )
-                return None
-            values.add(typed_eid)
-        return values
 
-    def handle_inlined_relation(self, form, field, entity, rqlquery):
-        """handle edition for the (rschema, x) relation of the given entity
-        """
-        if entity.has_eid():
-            origvalues = set(row[0] for row in entity.related(field.name, field.role))
-        else:
-            origvalues = set()
-        values  = self._relation_values(form, field, entity)
-        if values is None or values == origvalues:
-            return # not edited / not modified / to do later
-        attr = field.name
-        if values:
-            rqlquery.kwargs[attr] = iter(values).next()
-            rqlquery.edited.append('X %s %s' % (attr, attr.upper()))
-            rqlquery.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
-        elif entity.has_eid():
-            self.relations_rql += self.handle_relation(form, field, entity)
-
-    def handle_relation(self, form, field, entity, late=False):
-        """handle edition for the (rschema, x) relation of the given entity
-        """
-        if entity.has_eid():
-            origvalues = set(row[0] for row in entity.related(field.name, field.role))
-        else:
-            origvalues = set()
-        values  = self._relation_values(form, field, entity, late)
-        if values is None or values == origvalues:
-            return # not edited / not modified / to do later
-        etype = entity.e_schema
-        rschema = self._cw.vreg.schema.rschema(field.name)
-        if field.role == 'subject':
-            desttype = rschema.objects(etype)[0]
-            card = rschema.rproperty(etype, desttype, 'cardinality')[0]
-            subjvar, objvar = 'X', 'Y'
-        else:
-            desttype = rschema.subjects(etype)[0]
-            card = rschema.rproperty(desttype, etype, 'cardinality')[1]
-            subjvar, objvar = 'Y', 'X'
-        eid = entity.eid
-        if field.role == 'object' or not rschema.inlined or not values:
-            # this is not an inlined relation or no values specified,
-            # explicty remove relations
-            rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
-                subjvar, rschema, objvar)
-            for reid in origvalues.difference(values):
-                yield (rql, {'x': eid, 'y': reid}, ('x', 'y'))
-        seteids = values.difference(origvalues)
-        if seteids:
-            rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
-                subjvar, rschema, objvar)
-            for reid in seteids:
-                yield (rql, {'x': eid, 'y': reid}, ('x', 'y'))
-
-    def _get_eid(self, eid):
-        # should be either an int (existant entity) or a variable (to be
-        # created entity)
-        assert eid or eid == 0, repr(eid) # 0 is a valid eid
-        try:
-            return typed_eid(eid)
-        except ValueError:
-            try:
-                return self._to_create[eid]
-            except KeyError:
-                self._to_create[eid] = None
-                return None
-
-    def _linked_eids(self, eids, late=False):
-        """return a list of eids if they are all known, else raise ToDoLater
-        """
-
-