app/soc/logic/model.py
changeset 299 a1cc853a56e5
parent 263 9b39d93b677f
child 302 3b9c52170f46
equal deleted inserted replaced
298:c76a366c7ab4 299:a1cc853a56e5
    16 
    16 
    17 """Helpers functions for updating different kinds of models in datastore.
    17 """Helpers functions for updating different kinds of models in datastore.
    18 """
    18 """
    19 
    19 
    20 __authors__ = [
    20 __authors__ = [
       
    21   '"Todd Larsen" <tlarsen@google.com>',
       
    22   '"Sverre Rabbelier" <sverer@rabbelier.nl>',
    21   '"Pawel Solyga" <pawel.solyga@gmail.com>',
    23   '"Pawel Solyga" <pawel.solyga@gmail.com>',
    22   '"Todd Larsen" <tlarsen@google.com>',
       
    23   ]
    24   ]
    24 
    25 
    25 
    26 
    26 from google.appengine.ext import db
    27 from google.appengine.ext import db
       
    28 
       
    29 from soc.logic import key_name
       
    30 from soc.logic import out_of_band
    27 
    31 
    28 
    32 
    29 def getFullClassName(cls):
    33 def getFullClassName(cls):
    30   """Returns fully-qualified module.class name string.""" 
    34   """Returns fully-qualified module.class name string.""" 
    31   return '%s.%s' % (cls.__module__, cls.__name__) 
    35   return '%s.%s' % (cls.__module__, cls.__name__) 
   174       break
   178       break
   175 
   179 
   176   return None
   180   return None
   177 
   181 
   178 
   182 
   179 def updateModelProperties(model, **model_properties):
   183 class BaseLogic():
   180   """Update existing model entity using supplied model properties.
   184   """Base logic for entity classes.
   181 
   185 
   182   Args:
   186   The BaseLogic class functions specific to Entity classes by relying
   183     model: a model entity
   187   on the the child-classes to implement _model, _name and _key_name
   184     **model_properties: keyword arguments that correspond to model entity
   188   """
   185       properties and their values
   189 
   186 
   190   def _updateField(self, model, name, value):
   187   Returns:
   191     """Hook called when a field is updated.
   188     the original model entity with any supplied properties changed 
   192 
   189   """
   193     Base classes should override if any special actions need to be
   190   def update():
   194     taken when a field is updated. The field is not updated if the
   191     return _unsafeUpdateModelProperties(model, **model_properties)
   195     method does not return a True value.
   192 
   196     """
   193   return db.run_in_transaction(update)
   197 
   194 
   198     return True
   195 
   199 
   196 def _unsafeUpdateModelProperties(model, **model_properties):
   200   def getFromKeyName(self, key_name):
   197   """(see updateModelProperties)
   201     """"Returns User entity for key_name or None if not found.
   198 
   202 -
   199   Like updateModelProperties(), but not run within a transaction. 
   203 -    Args:
   200   """
   204 -      key_name: key name of entity
   201   properties = model.properties()
   205     """
   202 
   206     return self._model.get_by_key_name(key_name)
   203   for prop in properties.values():
   207 
   204     if prop.name in model_properties:
   208   def getFromFields(self, **kwargs):
   205       value = model_properties[prop.name]
   209     """Returns the entity for a given link name, or None if not found.
   206       prop.__set__(model, value)
   210 
   207 
   211     Args:
   208   model.put()
   212       link_name: a link name of the entity that uniquely identifies it
   209   return model
   213     """
       
   214     # lookup by Sponsor key name
       
   215     key_name = self.getKeyNameForFields(**kwargs)
       
   216 
       
   217     if key_name:
       
   218       entity = self._model.get_by_key_name(key_name)
       
   219     else:
       
   220       entity = None
       
   221 
       
   222     return entity
       
   223 
       
   224   def getIfFields(self, **kwargs):
       
   225     """Returns Sponsor entity for supplied link name if one exists.
       
   226 
       
   227     Args:
       
   228       link_name: a link name of the Sponsor that uniquely identifies it
       
   229 
       
   230     Returns:
       
   231       * None if link name is false.
       
   232       * Sponsor entity for supplied link_name
       
   233 
       
   234     Raises:
       
   235       out_of_band.ErrorResponse if link name is not false, but no Sponsor entity
       
   236       with the supplied link name exists in the Datastore
       
   237     """
       
   238     if not all(kwargs.values()):
       
   239       # exit without error, to let view know that link_name was not supplied
       
   240       return None
       
   241 
       
   242     entity = self.getFromFields(**kwargs)
       
   243 
       
   244     if entity:
       
   245       # a Sponsor exist for this link_name, so return that Sponsor entity
       
   246       return entity
       
   247 
       
   248     fields = []
       
   249 
       
   250     for key, value in kwargs.iteritems():
       
   251       fields.extend('"%s" is "%s"' % (key, value))
       
   252 
       
   253     # else: fields were supplied, but there is no Entity that has it
       
   254     raise out_of_band.ErrorResponse(
       
   255         'There is no %s with %s.' % (self._name, ''.join(fields)), status=404)
       
   256 
       
   257   def getKeyNameForFields(self, **kwargs):
       
   258     """Return a Datastore key_name for a Sponsor from the link name.
       
   259 
       
   260     Args:
       
   261       link_name: a link name of the entity that uniquely identifies it
       
   262     """
       
   263     if not all(kwargs.values()):
       
   264       return None
       
   265 
       
   266     return self._keyName(**kwargs)
       
   267 
       
   268   def getForLimitAndOffset(self, limit, offset=0):
       
   269     """Returns entities for given offset and limit or None if not found.
       
   270 
       
   271     Args:
       
   272       limit: max amount of entities to return
       
   273       offset: optional offset in entities list which defines first entity to
       
   274         return; default is zero (first entity)
       
   275     """
       
   276     query = self._model.all()
       
   277     return query.fetch(limit, offset)
       
   278 
       
   279   def updateModelProperties(self, model, **model_properties):
       
   280     """Update existing model entity using supplied model properties.
       
   281 
       
   282     Args:
       
   283       model: a model entity
       
   284       **model_properties: keyword arguments that correspond to model entity
       
   285         properties and their values
       
   286 
       
   287     Returns:
       
   288       the original model entity with any supplied properties changed
       
   289     """
       
   290     def update():
       
   291       return self._unsafeUpdateModelProperties(model, **model_properties)
       
   292 
       
   293     return db.run_in_transaction(update)
       
   294 
       
   295   def _unsafeUpdateModelProperties(self, model, **model_properties):
       
   296     """(see updateModelProperties)
       
   297 
       
   298     Like updateModelProperties(), but not run within a transaction.
       
   299     """
       
   300     properties = model.properties()
       
   301 
       
   302     for prop in properties.values():
       
   303       name = prop.name
       
   304 
       
   305       if not name in self._skip_properties and name in model_properties:
       
   306         value = model_properties[prop.name]
       
   307 
       
   308         if self._updateField(model, name, value):
       
   309           prop.__set__(model, value)
       
   310 
       
   311     model.put()
       
   312     return model
       
   313 
       
   314   def updateOrCreateFromKeyName(self, properties, key_name):
       
   315     """Update existing entity, or create new one with supplied properties.
       
   316 
       
   317     Args:
       
   318       link_name: a link_name of the entity that uniquely identifies it
       
   319       **properties: keyword arguments that correspond to entity
       
   320         properties and their values
       
   321 
       
   322     Returns:
       
   323       the entity corresponding to the key_name, with any supplied
       
   324       properties changed, or a new entity now associated with the
       
   325       supplied key_name and properties.
       
   326     """
       
   327 
       
   328     entity = self.getFromKeyName(key_name)
       
   329 
       
   330     if not entity:
       
   331       # entity did not exist, so create one in a transaction
       
   332       entity = self._model.get_or_insert(key_name, **properties)
       
   333 
       
   334     # there is no way to be sure if get_or_insert() returned a new entity or
       
   335     # got an existing one due to a race, so update with sponsor_properties anyway,
       
   336     # in a transaction
       
   337     return self.updateModelProperties(entity, **properties)
       
   338 
       
   339   def updateOrCreateFromFields(self, properties, **kwargs):
       
   340     """Like updateOrCreateFromKeyName, but resolves **kwargs to a key_name first
       
   341     """
       
   342 
       
   343     # attempt to retrieve the existing entity
       
   344     key_name  = self.getKeyNameForFields(**kwargs)
       
   345 
       
   346     return self.updateOrCreateFromKeyName(properties, key_name)