web/views/editcontroller.py
branchstable
changeset 9179 570208f74a84
parent 9178 b5762ac9a82e
child 9196 13461cb8ff40
equal deleted inserted replaced
9178:b5762ac9a82e 9179:570208f74a84
    18 """The edit controller, automatically handling entity form submitting"""
    18 """The edit controller, automatically handling entity form submitting"""
    19 
    19 
    20 __docformat__ = "restructuredtext en"
    20 __docformat__ = "restructuredtext en"
    21 
    21 
    22 from warnings import warn
    22 from warnings import warn
       
    23 from collections import defaultdict
    23 
    24 
    24 from logilab.common.deprecation import deprecated
    25 from logilab.common.deprecation import deprecated
       
    26 from logilab.common.graph import ordered_nodes
    25 
    27 
    26 from rql.utils import rqlvar_maker
    28 from rql.utils import rqlvar_maker
    27 
    29 
    28 from cubicweb import Binary, ValidationError
    30 from cubicweb import Binary, ValidationError
    29 from cubicweb.view import EntityAdapter, implements_adapter_compat
    31 from cubicweb.view import EntityAdapter, implements_adapter_compat
   127                 else:
   129                 else:
   128                     return callback()
   130                     return callback()
   129         self._default_publish()
   131         self._default_publish()
   130         self.reset()
   132         self.reset()
   131 
   133 
       
   134     def _ordered_formparams(self):
       
   135         """ Return form parameters dictionaries for each edited entity.
       
   136 
       
   137         We ensure that entities can be created in this order accounting for
       
   138         mandatory inlined relations.
       
   139         """
       
   140         req = self._cw
       
   141         graph = {}
       
   142         get_rschema = self._cw.vreg.schema.rschema
       
   143         # minparams = 2, because at least __type and eid are needed
       
   144         values_by_eid = dict((eid, req.extract_entity_params(eid, minparams=2))
       
   145                              for eid in req.edited_eids())
       
   146         # iterate over all the edited entities
       
   147         for eid, values in values_by_eid.iteritems():
       
   148             # add eid to the dependency graph
       
   149             graph.setdefault(eid, set())
       
   150             # search entity's edited fields for mandatory inlined relation
       
   151             for param in values['_cw_entity_fields'].split(','):
       
   152                 try:
       
   153                     rtype, role = param.split('-')
       
   154                 except ValueError:
       
   155                     # e.g. param='__type'
       
   156                     continue
       
   157                 rschema = get_rschema(rtype)
       
   158                 if rschema.inlined:
       
   159                     for target in rschema.targets(values['__type'], role):
       
   160                         rdef = rschema.role_rdef(values['__type'], target, role)
       
   161                         # if cardinality is 1 and if the target entity is being
       
   162                         # simultaneously edited, the current entity must be
       
   163                         # created before the target one
       
   164                         if rdef.cardinality[0] == '1':
       
   165                             target_eid = values[param]
       
   166                             if target_eid in values_by_eid:
       
   167                                 # add dependency from the target entity to the
       
   168                                 # current one
       
   169                                 graph.setdefault(target_eid, set()).add(eid)
       
   170                                 break
       
   171         for eid in reversed(ordered_nodes(graph)):
       
   172             yield values_by_eid[eid]
       
   173 
   132     def _default_publish(self):
   174     def _default_publish(self):
   133         req = self._cw
   175         req = self._cw
   134         self.errors = []
   176         self.errors = []
   135         self.relations_rql = []
   177         self.relations_rql = []
   136         form = req.form
   178         form = req.form
   137         # so we're able to know the main entity from the repository side
   179         # so we're able to know the main entity from the repository side
   138         if '__maineid' in form:
   180         if '__maineid' in form:
   139             req.set_shared_data('__maineid', form['__maineid'], txdata=True)
   181             req.set_shared_data('__maineid', form['__maineid'], txdata=True)
   140         # no specific action, generic edition
   182         # no specific action, generic edition
   141         self._to_create = req.data['eidmap'] = {}
   183         self._to_create = req.data['eidmap'] = {}
   142         self._pending_fields = req.data['pendingfields'] = set()
   184         # those two data variables are used to handle relation from/to entities
       
   185         # which doesn't exist at time where the entity is edited and that
       
   186         # deserves special treatment
       
   187         req.data['pending_inlined'] = defaultdict(set)
       
   188         req.data['pending_others'] = set()
   143         try:
   189         try:
   144             for eid in req.edited_eids():
   190             for formparams in self._ordered_formparams():
   145                 # __type and eid
       
   146                 formparams = req.extract_entity_params(eid, minparams=2)
       
   147                 eid = self.edit_entity(formparams)
   191                 eid = self.edit_entity(formparams)
   148         except (RequestError, NothingToEdit) as ex:
   192         except (RequestError, NothingToEdit) as ex:
   149             if '__linkto' in req.form and 'eid' in req.form:
   193             if '__linkto' in req.form and 'eid' in req.form:
   150                 self.execute_linkto()
   194                 self.execute_linkto()
   151             elif not ('__delete' in req.form or '__insert' in req.form):
   195             elif not ('__delete' in req.form or '__insert' in req.form):
   152                 raise ValidationError(None, {None: unicode(ex)})
   196                 raise ValidationError(None, {None: unicode(ex)})
   153         # handle relations in newly created entities
   197         # all pending inlined relations to newly created entities have been
   154         if self._pending_fields:
   198         # treated now (pop to ensure there are no attempt to add new ones)
   155             for form, field in self._pending_fields:
   199         pending_inlined = req.data.pop('pending_inlined')
   156                 self.handle_formfield(form, field)
   200         assert not pending_inlined, pending_inlined
   157         # execute rql to set all relations
   201         # handle all other remaining relations now
       
   202         for form_, field in req.data.pop('pending_others'):
       
   203             self.handle_formfield(form_, field)
       
   204         # then execute rql to set all relations
   158         for querydef in self.relations_rql:
   205         for querydef in self.relations_rql:
   159             self._cw.execute(*querydef)
   206             self._cw.execute(*querydef)
   160         # XXX this processes *all* pending operations of *all* entities
   207         # XXX this processes *all* pending operations of *all* entities
   161         if '__delete' in req.form:
   208         if '__delete' in req.form:
   162             todelete = req.list_form_param('__delete', req.form, pop=True)
   209             todelete = req.list_form_param('__delete', req.form, pop=True)
   215             except KeyError:
   262             except KeyError:
   216                 raise RequestError(req._('no edited fields specified for entity %s' % entity.eid))
   263                 raise RequestError(req._('no edited fields specified for entity %s' % entity.eid))
   217         form.formvalues = {} # init fields value cache
   264         form.formvalues = {} # init fields value cache
   218         for field in form.iter_modified_fields(editedfields, entity):
   265         for field in form.iter_modified_fields(editedfields, entity):
   219             self.handle_formfield(form, field, rqlquery)
   266             self.handle_formfield(form, field, rqlquery)
       
   267         # if there are some inlined field which were waiting for this entity's
       
   268         # creation, add relevant data to the rqlquery
       
   269         for form_, field in req.data['pending_inlined'].pop(entity.eid, ()):
       
   270             rqlquery.set_inlined(field.name, form_.edited_entity.eid)
   220         if self.errors:
   271         if self.errors:
   221             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
   272             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
   222             raise ValidationError(valerror_eid(entity.eid), errors)
   273             raise ValidationError(valerror_eid(entity.eid), errors)
   223         if eid is None: # creation or copy
   274         if eid is None: # creation or copy
   224             entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
   275             entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
   258                     if rschema.inlined and rqlquery is not None and field.role == 'subject':
   309                     if rschema.inlined and rqlquery is not None and field.role == 'subject':
   259                         self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
   310                         self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
   260                     elif form.edited_entity.has_eid():
   311                     elif form.edited_entity.has_eid():
   261                         self.handle_relation(form, field, value, origvalues)
   312                         self.handle_relation(form, field, value, origvalues)
   262                     else:
   313                     else:
   263                         self._pending_fields.add( (form, field) )
   314                         form._cw.data['pending_others'].add( (form, field) )
   264 
       
   265         except ProcessFormError as exc:
   315         except ProcessFormError as exc:
   266             self.errors.append((field, exc))
   316             self.errors.append((field, exc))
   267 
   317 
   268     def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
   318     def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
   269         """handle edition for the (rschema, x) relation of the given entity
   319         """handle edition for the (rschema, x) relation of the given entity