cubicweb/server/edition.py
changeset 11057 0b59724cb3f2
parent 10997 da712d3f0601
child 11767 432f87a63057
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    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/>.
       
    18 """helper classes to handle server-side edition of entities"""
       
    19 __docformat__ = "restructuredtext en"
       
    20 
       
    21 from copy import copy
       
    22 from yams import ValidationError
       
    23 
       
    24 
       
    25 _MARKER = object()
       
    26 
       
    27 class dict_protocol_catcher(object):
       
    28     def __init__(self, entity):
       
    29         self.__entity = entity
       
    30     def __getitem__(self, attr):
       
    31         return self.__entity.cw_edited[attr]
       
    32     def __setitem__(self, attr, value):
       
    33         self.__entity.cw_edited[attr] = value
       
    34     def __getattr__(self, attr):
       
    35         return getattr(self.__entity, attr)
       
    36 
       
    37 
       
    38 class EditedEntity(dict):
       
    39     """encapsulate entities attributes being written by an RQL query"""
       
    40     def __init__(self, entity, **kwargs):
       
    41         super(EditedEntity, self).__init__(**kwargs)
       
    42         self.entity = entity
       
    43         self.skip_security = set()
       
    44         self.querier_pending_relations = {}
       
    45         self.saved = False
       
    46 
       
    47     def __hash__(self):
       
    48         # dict|set keyable
       
    49         return hash(id(self))
       
    50 
       
    51     def __lt__(self, other):
       
    52         # we don't want comparison by value inherited from dict
       
    53         raise NotImplementedError
       
    54 
       
    55     def __eq__(self, other):
       
    56         return self is other
       
    57 
       
    58     def __ne__(self, other):
       
    59         return not (self == other)
       
    60 
       
    61     def __setitem__(self, attr, value):
       
    62         assert attr != 'eid'
       
    63         # don't add attribute into skip_security if already in edited
       
    64         # attributes, else we may accidentally skip a desired security check
       
    65         if attr not in self:
       
    66             self.skip_security.add(attr)
       
    67         self.edited_attribute(attr, value)
       
    68 
       
    69     def __delitem__(self, attr):
       
    70         assert not self.saved, 'too late to modify edited attributes'
       
    71         super(EditedEntity, self).__delitem__(attr)
       
    72         self.entity.cw_attr_cache.pop(attr, None)
       
    73 
       
    74     def __copy__(self):
       
    75         # default copy protocol fails in EditedEntity.__setitem__ because
       
    76         # copied entity has no skip_security attribute at this point
       
    77         return EditedEntity(self.entity, **self)
       
    78 
       
    79     def pop(self, attr, *args):
       
    80         # don't update skip_security by design (think to storage api)
       
    81         assert not self.saved, 'too late to modify edited attributes'
       
    82         value = super(EditedEntity, self).pop(attr, *args)
       
    83         self.entity.cw_attr_cache.pop(attr, *args)
       
    84         return value
       
    85 
       
    86     def setdefault(self, attr, default):
       
    87         assert attr != 'eid'
       
    88         # don't add attribute into skip_security if already in edited
       
    89         # attributes, else we may accidentally skip a desired security check
       
    90         if attr not in self:
       
    91             self[attr] = default
       
    92         return self[attr]
       
    93 
       
    94     def update(self, values, skipsec=True):
       
    95         if skipsec:
       
    96             setitem = self.__setitem__
       
    97         else:
       
    98             setitem = self.edited_attribute
       
    99         for attr, value in values.items():
       
   100             setitem(attr, value)
       
   101 
       
   102     def edited_attribute(self, attr, value):
       
   103         """attribute being edited by a rql query: should'nt be added to
       
   104         skip_security
       
   105         """
       
   106         assert not self.saved, 'too late to modify edited attributes'
       
   107         super(EditedEntity, self).__setitem__(attr, value)
       
   108         self.entity.cw_attr_cache[attr] = value
       
   109         if self.entity._cw.vreg.schema.rschema(attr).final:
       
   110             self.entity._cw_dont_cache_attribute(attr)
       
   111 
       
   112     def oldnewvalue(self, attr):
       
   113         """returns the couple (old attr value, new attr value)
       
   114 
       
   115         NOTE: will only work in a before_update_entity hook
       
   116         """
       
   117         assert not self.saved, 'too late to get the old value'
       
   118         # get new value and remove from local dict to force a db query to
       
   119         # fetch old value
       
   120         newvalue = self.entity.cw_attr_cache.pop(attr, _MARKER)
       
   121         oldvalue = getattr(self.entity, attr)
       
   122         if newvalue is not _MARKER:
       
   123             self.entity.cw_attr_cache[attr] = newvalue
       
   124         else:
       
   125             newvalue = oldvalue
       
   126         return oldvalue, newvalue
       
   127 
       
   128     def set_defaults(self):
       
   129         """set default values according to the schema"""
       
   130         for attr, value in self.entity.e_schema.defaults():
       
   131             if not attr in self:
       
   132                 self[str(attr)] = value
       
   133 
       
   134     def check(self, creation=False):
       
   135         """check the entity edition against its schema. Only final relation
       
   136         are checked here, constraint on actual relations are checked in hooks
       
   137         """
       
   138         entity = self.entity
       
   139         if creation:
       
   140             # on creations, we want to check all relations, especially
       
   141             # required attributes
       
   142             relations = [rschema for rschema in entity.e_schema.subject_relations()
       
   143                          if rschema.final and rschema.type != 'eid']
       
   144         else:
       
   145             relations = [entity._cw.vreg.schema.rschema(rtype)
       
   146                          for rtype in self]
       
   147         try:
       
   148             entity.e_schema.check(dict_protocol_catcher(entity),
       
   149                                   creation=creation, relations=relations)
       
   150         except ValidationError as ex:
       
   151             ex.entity = self.entity.eid
       
   152             raise
       
   153 
       
   154     def clone(self):
       
   155         thecopy = EditedEntity(copy(self.entity))
       
   156         thecopy.entity.cw_attr_cache = copy(self.entity.cw_attr_cache)
       
   157         thecopy.entity._cw_related_cache = {}
       
   158         thecopy.update(self, skipsec=False)
       
   159         return thecopy