web/views/editcontroller.py
changeset 11118 0c645f09d96a
parent 10385 cb3a48780615
parent 11114 468b91aabd9d
child 11127 6464edfa95bb
equal deleted inserted replaced
11097:900c27ea30e9 11118:0c645f09d96a
    27 from logilab.common.deprecation import deprecated
    27 from logilab.common.deprecation import deprecated
    28 from logilab.common.graph import ordered_nodes
    28 from logilab.common.graph import ordered_nodes
    29 
    29 
    30 from rql.utils import rqlvar_maker
    30 from rql.utils import rqlvar_maker
    31 
    31 
    32 from cubicweb import Binary, ValidationError
    32 from cubicweb import Binary, ValidationError, UnknownEid
    33 from cubicweb.view import EntityAdapter
    33 from cubicweb.view import EntityAdapter
    34 from cubicweb.predicates import is_instance
    34 from cubicweb.predicates import is_instance
    35 from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit,
    35 from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit,
    36                           ProcessFormError)
    36                           ProcessFormError)
    37 from cubicweb.web.views import basecontrollers, autoform
    37 from cubicweb.web.views import basecontrollers, autoform
    75 class RqlQuery(object):
    75 class RqlQuery(object):
    76     def __init__(self):
    76     def __init__(self):
    77         self.edited = []
    77         self.edited = []
    78         self.restrictions = []
    78         self.restrictions = []
    79         self.kwargs = {}
    79         self.kwargs = {}
       
    80         self.canceled = False
    80 
    81 
    81     def __repr__(self):
    82     def __repr__(self):
    82         return ('Query <edited=%r restrictions=%r kwargs=%r>' % (
    83         return ('Query <edited=%r restrictions=%r kwargs=%r>' % (
    83             self.edited, self.restrictions, self.kwargs))
    84             self.edited, self.restrictions, self.kwargs))
    84 
    85 
    85     def insert_query(self, etype):
    86     def insert_query(self, etype):
       
    87         assert not self.canceled
    86         if self.edited:
    88         if self.edited:
    87             rql = 'INSERT %s X: %s' % (etype, ','.join(self.edited))
    89             rql = 'INSERT %s X: %s' % (etype, ','.join(self.edited))
    88         else:
    90         else:
    89             rql = 'INSERT %s X' % etype
    91             rql = 'INSERT %s X' % etype
    90         if self.restrictions:
    92         if self.restrictions:
    91             rql += ' WHERE %s' % ','.join(self.restrictions)
    93             rql += ' WHERE %s' % ','.join(self.restrictions)
    92         return rql
    94         return rql
    93 
    95 
    94     def update_query(self, eid):
    96     def update_query(self, eid):
       
    97         assert not self.canceled
    95         varmaker = rqlvar_maker()
    98         varmaker = rqlvar_maker()
    96         var = varmaker.next()
    99         var = varmaker.next()
    97         while var in self.kwargs:
   100         while var in self.kwargs:
    98             var = varmaker.next()
   101             var = varmaker.next()
    99         rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.edited), var)
   102         rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.edited), var)
   188         # those two data variables are used to handle relation from/to entities
   191         # those two data variables are used to handle relation from/to entities
   189         # which doesn't exist at time where the entity is edited and that
   192         # which doesn't exist at time where the entity is edited and that
   190         # deserves special treatment
   193         # deserves special treatment
   191         req.data['pending_inlined'] = defaultdict(set)
   194         req.data['pending_inlined'] = defaultdict(set)
   192         req.data['pending_others'] = set()
   195         req.data['pending_others'] = set()
       
   196         req.data['pending_composite_delete'] = set()
   193         try:
   197         try:
   194             for formparams in self._ordered_formparams():
   198             for formparams in self._ordered_formparams():
   195                 eid = self.edit_entity(formparams)
   199                 eid = self.edit_entity(formparams)
   196         except (RequestError, NothingToEdit) as ex:
   200         except (RequestError, NothingToEdit) as ex:
   197             if '__linkto' in req.form and 'eid' in req.form:
   201             if '__linkto' in req.form and 'eid' in req.form:
   206         for form_, field in req.data.pop('pending_others'):
   210         for form_, field in req.data.pop('pending_others'):
   207             self.handle_formfield(form_, field)
   211             self.handle_formfield(form_, field)
   208         # then execute rql to set all relations
   212         # then execute rql to set all relations
   209         for querydef in self.relations_rql:
   213         for querydef in self.relations_rql:
   210             self._cw.execute(*querydef)
   214             self._cw.execute(*querydef)
       
   215         # delete pending composite
       
   216         for entity in req.data['pending_composite_delete']:
       
   217             entity.cw_delete()
   211         # XXX this processes *all* pending operations of *all* entities
   218         # XXX this processes *all* pending operations of *all* entities
   212         if '__delete' in req.form:
   219         if '__delete' in req.form:
   213             todelete = req.list_form_param('__delete', req.form, pop=True)
   220             todelete = req.list_form_param('__delete', req.form, pop=True)
   214             if todelete:
   221             if todelete:
   215                 autoform.delete_relations(self._cw, todelete)
   222                 autoform.delete_relations(self._cw, todelete)
   262             self.handle_formfield(form, field, rqlquery)
   269             self.handle_formfield(form, field, rqlquery)
   263         # if there are some inlined field which were waiting for this entity's
   270         # if there are some inlined field which were waiting for this entity's
   264         # creation, add relevant data to the rqlquery
   271         # creation, add relevant data to the rqlquery
   265         for form_, field in req.data['pending_inlined'].pop(entity.eid, ()):
   272         for form_, field in req.data['pending_inlined'].pop(entity.eid, ()):
   266             rqlquery.set_inlined(field.name, form_.edited_entity.eid)
   273             rqlquery.set_inlined(field.name, form_.edited_entity.eid)
   267         if self.errors:
   274         if not rqlquery.canceled:
   268             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
   275             if self.errors:
   269             raise ValidationError(valerror_eid(entity.eid), errors)
   276                 errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
   270         if eid is None: # creation or copy
   277                 raise ValidationError(valerror_eid(entity.eid), errors)
   271             entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery)
   278             if eid is None: # creation or copy
   272         elif rqlquery.edited: # edition of an existant entity
   279                 entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery)
   273             self.check_concurrent_edition(formparams, eid)
   280             elif rqlquery.edited: # edition of an existant entity
   274             self._update_entity(eid, rqlquery)
   281                 self.check_concurrent_edition(formparams, eid)
       
   282                 self._update_entity(eid, rqlquery)
       
   283         else:
       
   284             self.errors = []
   275         if is_main_entity:
   285         if is_main_entity:
   276             self.notify_edited(entity)
   286             self.notify_edited(entity)
   277         if '__delete' in formparams:
   287         if '__delete' in formparams:
   278             # XXX deprecate?
   288             # XXX deprecate?
   279             todelete = req.list_form_param('__delete', formparams, pop=True)
   289             todelete = req.list_form_param('__delete', formparams, pop=True)
   283         if is_main_entity: # only execute linkto for the main entity
   293         if is_main_entity: # only execute linkto for the main entity
   284             self.execute_linkto(entity.eid)
   294             self.execute_linkto(entity.eid)
   285         return eid
   295         return eid
   286 
   296 
   287     def handle_formfield(self, form, field, rqlquery=None):
   297     def handle_formfield(self, form, field, rqlquery=None):
   288         eschema = form.edited_entity.e_schema
   298         entity = form.edited_entity
       
   299         eschema = entity.e_schema
   289         try:
   300         try:
   290             for field, value in field.process_posted(form):
   301             for field, value in field.process_posted(form):
   291                 if not (
   302                 if not (
   292                     (field.role == 'subject' and field.name in eschema.subjrels)
   303                     (field.role == 'subject' and field.name in eschema.subjrels)
   293                     or
   304                     or
   294                     (field.role == 'object' and field.name in eschema.objrels)):
   305                     (field.role == 'object' and field.name in eschema.objrels)):
   295                     continue
   306                     continue
       
   307 
   296                 rschema = self._cw.vreg.schema.rschema(field.name)
   308                 rschema = self._cw.vreg.schema.rschema(field.name)
   297                 if rschema.final:
   309                 if rschema.final:
   298                     rqlquery.set_attribute(field.name, value)
   310                     rqlquery.set_attribute(field.name, value)
       
   311                     continue
       
   312 
       
   313                 if entity.has_eid():
       
   314                     origvalues = set(data[0] for data in entity.related(field.name, field.role).rows)
   299                 else:
   315                 else:
   300                     if form.edited_entity.has_eid():
   316                     origvalues = set()
   301                         origvalues = set(entity.eid for entity in form.edited_entity.related(field.name, field.role, entities=True))
   317                 if value is None or value == origvalues:
   302                     else:
   318                     continue # not edited / not modified / to do later
   303                         origvalues = set()
   319 
   304                     if value is None or value == origvalues:
   320                 unlinked_eids = origvalues - value
   305                         continue # not edited / not modified / to do later
   321 
   306                     if rschema.inlined and rqlquery is not None and field.role == 'subject':
   322                 if unlinked_eids:
   307                         self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
   323                     # Special handling of composite relation removal
   308                     elif form.edited_entity.has_eid():
   324                     self.handle_composite_removal(
   309                         self.handle_relation(form, field, value, origvalues)
   325                         form, field, unlinked_eids, value, rqlquery)
   310                     else:
   326 
   311                         form._cw.data['pending_others'].add( (form, field) )
   327                 if rschema.inlined and rqlquery is not None and field.role == 'subject':
       
   328                     self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
       
   329                 elif form.edited_entity.has_eid():
       
   330                     self.handle_relation(form, field, value, origvalues)
       
   331                 else:
       
   332                     form._cw.data['pending_others'].add( (form, field) )
       
   333 
   312         except ProcessFormError as exc:
   334         except ProcessFormError as exc:
   313             self.errors.append((field, exc))
   335             self.errors.append((field, exc))
       
   336 
       
   337     def handle_composite_removal(self, form, field,
       
   338                                  removed_values, new_values, rqlquery):
       
   339         """
       
   340         In EditController-handled forms, when the user removes a composite
       
   341         relation, it triggers the removal of the related entity in the
       
   342         composite. This is where this happens.
       
   343 
       
   344         See for instance test_subject_subentity_removal in
       
   345         web/test/unittest_application.py.
       
   346         """
       
   347         rschema = self._cw.vreg.schema.rschema(field.name)
       
   348         new_value_etypes = set()
       
   349         # the user could have included nonexisting eids in the POST; don't crash.
       
   350         for eid in new_values:
       
   351             try:
       
   352                 new_value_etypes.add(self._cw.entity_from_eid(eid).cw_etype)
       
   353             except UnknownEid:
       
   354                 continue
       
   355         for unlinked_eid in removed_values:
       
   356             unlinked_entity = self._cw.entity_from_eid(unlinked_eid)
       
   357             rdef = rschema.role_rdef(form.edited_entity.cw_etype,
       
   358                                      unlinked_entity.cw_etype,
       
   359                                      field.role)
       
   360             if rdef.composite is not None:
       
   361                 if rdef.composite == field.role:
       
   362                     to_be_removed = unlinked_entity
       
   363                 else:
       
   364                     if unlinked_entity.cw_etype in new_value_etypes:
       
   365                         # This is a same-rdef re-parenting: do not remove the entity
       
   366                         continue
       
   367                     to_be_removed = form.edited_entity
       
   368                     self.info('Edition of %s is cancelled (deletion requested)',
       
   369                               to_be_removed)
       
   370                     rqlquery.canceled = True
       
   371                 self.info('Scheduling removal of %s as composite relation '
       
   372                           '%s was removed', to_be_removed, rdef)
       
   373                 form._cw.data['pending_composite_delete'].add(to_be_removed)
   314 
   374 
   315     def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
   375     def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
   316         """handle edition for the (rschema, x) relation of the given entity
   376         """handle edition for the (rschema, x) relation of the given entity
   317         """
   377         """
   318         if values:
   378         if values: