cubicweb/web/views/editcontroller.py
changeset 11870 3a84a79c4ed5
parent 11767 432f87a63057
child 11897 2ceb0bfa4b3f
equal deleted inserted replaced
11869:d8b66e3fd335 11870:3a84a79c4ed5
     1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    15 #
    15 #
    16 # You should have received a copy of the GNU Lesser General Public License along
    16 # You should have received a copy of the GNU Lesser General Public License along
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """The edit controller, automatically handling entity form submitting"""
    18 """The edit controller, automatically handling entity form submitting"""
    19 
    19 
    20 
       
    21 
       
    22 from warnings import warn
    20 from warnings import warn
    23 from collections import defaultdict
    21 from collections import defaultdict
    24 
    22 
    25 from datetime import datetime
    23 from datetime import datetime
    26 
    24 
    27 from six import text_type
    25 from six import text_type
    28 
    26 
    29 from logilab.common.deprecation import deprecated
       
    30 from logilab.common.graph import ordered_nodes
    27 from logilab.common.graph import ordered_nodes
    31 
    28 
    32 from rql.utils import rqlvar_maker
    29 from rql.utils import rqlvar_maker
    33 
    30 
    34 from cubicweb import _, Binary, ValidationError, UnknownEid
    31 from cubicweb import _, ValidationError, UnknownEid
    35 from cubicweb.view import EntityAdapter
    32 from cubicweb.view import EntityAdapter
    36 from cubicweb.predicates import is_instance
    33 from cubicweb.predicates import is_instance
    37 from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit,
    34 from cubicweb.web import RequestError, NothingToEdit, ProcessFormError
    38                           ProcessFormError)
       
    39 from cubicweb.web.views import basecontrollers, autoform
    35 from cubicweb.web.views import basecontrollers, autoform
    40 
    36 
    41 
    37 
    42 class IEditControlAdapter(EntityAdapter):
    38 class IEditControlAdapter(EntityAdapter):
    43     __regid__ = 'IEditControl'
    39     __regid__ = 'IEditControl'
    71 def valerror_eid(eid):
    67 def valerror_eid(eid):
    72     try:
    68     try:
    73         return int(eid)
    69         return int(eid)
    74     except (ValueError, TypeError):
    70     except (ValueError, TypeError):
    75         return eid
    71         return eid
       
    72 
    76 
    73 
    77 class RqlQuery(object):
    74 class RqlQuery(object):
    78     def __init__(self):
    75     def __init__(self):
    79         self.edited = []
    76         self.edited = []
    80         self.restrictions = []
    77         self.restrictions = []
   196         req.data['pending_inlined'] = defaultdict(set)
   193         req.data['pending_inlined'] = defaultdict(set)
   197         req.data['pending_others'] = set()
   194         req.data['pending_others'] = set()
   198         req.data['pending_composite_delete'] = set()
   195         req.data['pending_composite_delete'] = set()
   199         try:
   196         try:
   200             for formparams in self._ordered_formparams():
   197             for formparams in self._ordered_formparams():
   201                 eid = self.edit_entity(formparams)
   198                 self.edit_entity(formparams)
   202         except (RequestError, NothingToEdit) as ex:
   199         except (RequestError, NothingToEdit) as ex:
   203             if '__linkto' in req.form and 'eid' in req.form:
   200             if '__linkto' in req.form and 'eid' in req.form:
   204                 self.execute_linkto()
   201                 self.execute_linkto()
   205             elif '__delete' not in req.form:
   202             elif '__delete' not in req.form:
   206                 raise ValidationError(None, {None: text_type(ex)})
   203                 raise ValidationError(None, {None: text_type(ex)})
   234         try:
   231         try:
   235             entity = self._cw.execute(rql, rqlquery.kwargs).get_entity(0, 0)
   232             entity = self._cw.execute(rql, rqlquery.kwargs).get_entity(0, 0)
   236             neweid = entity.eid
   233             neweid = entity.eid
   237         except ValidationError as ex:
   234         except ValidationError as ex:
   238             self._to_create[eid] = ex.entity
   235             self._to_create[eid] = ex.entity
   239             if self._cw.ajax_request: # XXX (syt) why?
   236             if self._cw.ajax_request:  # XXX (syt) why?
   240                 ex.entity = eid
   237                 ex.entity = eid
   241             raise
   238             raise
   242         self._to_create[eid] = neweid
   239         self._to_create[eid] = neweid
   243         return neweid
   240         return neweid
   244 
   241 
   266             # inbetween, use cubicweb standard formid for inlined forms
   263             # inbetween, use cubicweb standard formid for inlined forms
   267             formid = 'edition'
   264             formid = 'edition'
   268         form = req.vreg['forms'].select(formid, req, entity=entity)
   265         form = req.vreg['forms'].select(formid, req, entity=entity)
   269         eid = form.actual_eid(entity.eid)
   266         eid = form.actual_eid(entity.eid)
   270         editedfields = formparams['_cw_entity_fields']
   267         editedfields = formparams['_cw_entity_fields']
   271         form.formvalues = {} # init fields value cache
   268         form.formvalues = {}  # init fields value cache
   272         for field in form.iter_modified_fields(editedfields, entity):
   269         for field in form.iter_modified_fields(editedfields, entity):
   273             self.handle_formfield(form, field, rqlquery)
   270             self.handle_formfield(form, field, rqlquery)
   274         # if there are some inlined field which were waiting for this entity's
   271         # if there are some inlined field which were waiting for this entity's
   275         # creation, add relevant data to the rqlquery
   272         # creation, add relevant data to the rqlquery
   276         for form_, field in req.data['pending_inlined'].pop(entity.eid, ()):
   273         for form_, field in req.data['pending_inlined'].pop(entity.eid, ()):
   277             rqlquery.set_inlined(field.name, form_.edited_entity.eid)
   274             rqlquery.set_inlined(field.name, form_.edited_entity.eid)
   278         if not rqlquery.canceled:
   275         if not rqlquery.canceled:
   279             if self.errors:
   276             if self.errors:
   280                 errors = dict((f.role_name(), text_type(ex)) for f, ex in self.errors)
   277                 errors = dict((f.role_name(), text_type(ex)) for f, ex in self.errors)
   281                 raise ValidationError(valerror_eid(entity.eid), errors)
   278                 raise ValidationError(valerror_eid(entity.eid), errors)
   282             if eid is None: # creation or copy
   279             if eid is None:  # creation or copy
   283                 entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery)
   280                 entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery)
   284             elif rqlquery.edited: # edition of an existant entity
   281             elif rqlquery.edited:  # edition of an existant entity
   285                 self.check_concurrent_edition(formparams, eid)
   282                 self.check_concurrent_edition(formparams, eid)
   286                 self._update_entity(eid, rqlquery)
   283                 self._update_entity(eid, rqlquery)
   287         else:
   284         else:
   288             self.errors = []
   285             self.errors = []
   289         if is_main_entity:
   286         if is_main_entity:
   292             # XXX deprecate?
   289             # XXX deprecate?
   293             todelete = req.list_form_param('__delete', formparams, pop=True)
   290             todelete = req.list_form_param('__delete', formparams, pop=True)
   294             autoform.delete_relations(req, todelete)
   291             autoform.delete_relations(req, todelete)
   295         if '__cloned_eid' in formparams:
   292         if '__cloned_eid' in formparams:
   296             entity.copy_relations(int(formparams['__cloned_eid']))
   293             entity.copy_relations(int(formparams['__cloned_eid']))
   297         if is_main_entity: # only execute linkto for the main entity
   294         if is_main_entity:  # only execute linkto for the main entity
   298             self.execute_linkto(entity.eid)
   295             self.execute_linkto(entity.eid)
   299         return eid
   296         return eid
   300 
   297 
   301     def handle_formfield(self, form, field, rqlquery=None):
   298     def handle_formfield(self, form, field, rqlquery=None):
   302         entity = form.edited_entity
   299         entity = form.edited_entity
   303         eschema = entity.e_schema
   300         eschema = entity.e_schema
   304         try:
   301         try:
   305             for field, value in field.process_posted(form):
   302             for field, value in field.process_posted(form):
   306                 if not (
   303                 if not ((field.role == 'subject' and field.name in eschema.subjrels)
   307                     (field.role == 'subject' and field.name in eschema.subjrels)
   304                         or
   308                     or
   305                         (field.role == 'object' and field.name in eschema.objrels)):
   309                     (field.role == 'object' and field.name in eschema.objrels)):
       
   310                     continue
   306                     continue
   311 
   307 
   312                 rschema = self._cw.vreg.schema.rschema(field.name)
   308                 rschema = self._cw.vreg.schema.rschema(field.name)
   313                 if rschema.final:
   309                 if rschema.final:
   314                     rqlquery.set_attribute(field.name, value)
   310                     rqlquery.set_attribute(field.name, value)
   315                     continue
   311                     continue
   316 
   312 
   317                 if entity.has_eid():
   313                 if entity.has_eid():
   318                     origvalues = set(data[0] for data in entity.related(field.name, field.role).rows)
   314                     origvalues = set(row[0] for row in entity.related(field.name, field.role).rows)
   319                 else:
   315                 else:
   320                     origvalues = set()
   316                     origvalues = set()
   321                 if value is None or value == origvalues:
   317                 if value is None or value == origvalues:
   322                     continue # not edited / not modified / to do later
   318                     continue  # not edited / not modified / to do later
   323 
   319 
   324                 unlinked_eids = origvalues - value
   320                 unlinked_eids = origvalues - value
   325 
   321 
   326                 if unlinked_eids:
   322                 if unlinked_eids:
   327                     # Special handling of composite relation removal
   323                     # Special handling of composite relation removal
   331                 if rschema.inlined and rqlquery is not None and field.role == 'subject':
   327                 if rschema.inlined and rqlquery is not None and field.role == 'subject':
   332                     self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
   328                     self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
   333                 elif form.edited_entity.has_eid():
   329                 elif form.edited_entity.has_eid():
   334                     self.handle_relation(form, field, value, origvalues)
   330                     self.handle_relation(form, field, value, origvalues)
   335                 else:
   331                 else:
   336                     form._cw.data['pending_others'].add( (form, field) )
   332                     form._cw.data['pending_others'].add((form, field))
   337 
   333 
   338         except ProcessFormError as exc:
   334         except ProcessFormError as exc:
   339             self.errors.append((field, exc))
   335             self.errors.append((field, exc))
   340 
   336 
   341     def handle_composite_removal(self, form, field,
   337     def handle_composite_removal(self, form, field,
   385             self.handle_relation(form, field, values, origvalues)
   381             self.handle_relation(form, field, values, origvalues)
   386 
   382 
   387     def handle_relation(self, form, field, values, origvalues):
   383     def handle_relation(self, form, field, values, origvalues):
   388         """handle edition for the (rschema, x) relation of the given entity
   384         """handle edition for the (rschema, x) relation of the given entity
   389         """
   385         """
   390         etype = form.edited_entity.e_schema
       
   391         rschema = self._cw.vreg.schema.rschema(field.name)
   386         rschema = self._cw.vreg.schema.rschema(field.name)
   392         if field.role == 'subject':
   387         if field.role == 'subject':
   393             desttype = rschema.objects(etype)[0]
       
   394             card = rschema.rdef(etype, desttype).cardinality[0]
       
   395             subjvar, objvar = 'X', 'Y'
   388             subjvar, objvar = 'X', 'Y'
   396         else:
   389         else:
   397             desttype = rschema.subjects(etype)[0]
       
   398             card = rschema.rdef(desttype, etype).cardinality[1]
       
   399             subjvar, objvar = 'Y', 'X'
   390             subjvar, objvar = 'Y', 'X'
   400         eid = form.edited_entity.eid
   391         eid = form.edited_entity.eid
   401         if field.role == 'object' or not rschema.inlined or not values:
   392         if field.role == 'object' or not rschema.inlined or not values:
   402             # this is not an inlined relation or no values specified,
   393             # this is not an inlined relation or no values specified,
   403             # explicty remove relations
   394             # explicty remove relations
   417         redirect_info = set()
   408         redirect_info = set()
   418         eidtypes = tuple(eidtypes)
   409         eidtypes = tuple(eidtypes)
   419         for eid, etype in eidtypes:
   410         for eid, etype in eidtypes:
   420             entity = self._cw.entity_from_eid(eid, etype)
   411             entity = self._cw.entity_from_eid(eid, etype)
   421             path, params = entity.cw_adapt_to('IEditControl').after_deletion_path()
   412             path, params = entity.cw_adapt_to('IEditControl').after_deletion_path()
   422             redirect_info.add( (path, tuple(params.items())) )
   413             redirect_info.add((path, tuple(params.items())))
   423             entity.cw_delete()
   414             entity.cw_delete()
   424         if len(redirect_info) > 1:
   415         if len(redirect_info) > 1:
   425             # In the face of ambiguity, refuse the temptation to guess.
   416             # In the face of ambiguity, refuse the temptation to guess.
   426             self._after_deletion_path = 'view', ()
   417             self._after_deletion_path = 'view', ()
   427         else:
   418         else:
   428             self._after_deletion_path = next(iter(redirect_info))
   419             self._after_deletion_path = next(iter(redirect_info))
   429         if len(eidtypes) > 1:
   420         if len(eidtypes) > 1:
   430             self._cw.set_message(self._cw._('entities deleted'))
   421             self._cw.set_message(self._cw._('entities deleted'))
   431         else:
   422         else:
   432             self._cw.set_message(self._cw._('entity deleted'))
   423             self._cw.set_message(self._cw._('entity deleted'))
   433 
       
   434 
   424 
   435     def check_concurrent_edition(self, formparams, eid):
   425     def check_concurrent_edition(self, formparams, eid):
   436         req = self._cw
   426         req = self._cw
   437         try:
   427         try:
   438             form_ts = datetime.utcfromtimestamp(float(formparams['__form_generation_time']))
   428             form_ts = datetime.utcfromtimestamp(float(formparams['__form_generation_time']))
   444             # We only mark the message for translation but the actual
   434             # We only mark the message for translation but the actual
   445             # translation will be handled by the Validation mechanism...
   435             # translation will be handled by the Validation mechanism...
   446             msg = _("Entity %(eid)s has changed since you started to edit it."
   436             msg = _("Entity %(eid)s has changed since you started to edit it."
   447                     " Reload the page and reapply your changes.")
   437                     " Reload the page and reapply your changes.")
   448             # ... this is why we pass the formats' dict as a third argument.
   438             # ... this is why we pass the formats' dict as a third argument.
   449             raise ValidationError(eid, {None: msg}, {'eid' : eid})
   439             raise ValidationError(eid, {None: msg}, {'eid': eid})
   450 
   440 
   451     def _action_apply(self):
   441     def _action_apply(self):
   452         self._default_publish()
   442         self._default_publish()
   453         self.reset()
   443         self.reset()
   454 
   444