app/soc/logic/model.py
changeset 263 9b39d93b677f
parent 254 e88beba437a3
child 299 a1cc853a56e5
equal deleted inserted replaced
262:52a42831d9d6 263:9b39d93b677f
    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   '"Pawel Solyga" <pawel.solyga@gmail.com>',
    21   '"Pawel Solyga" <pawel.solyga@gmail.com>',
       
    22   '"Todd Larsen" <tlarsen@google.com>',
    22   ]
    23   ]
    23 
    24 
       
    25 
    24 from google.appengine.ext import db
    26 from google.appengine.ext import db
       
    27 
       
    28 
       
    29 def getFullClassName(cls):
       
    30   """Returns fully-qualified module.class name string.""" 
       
    31   return '%s.%s' % (cls.__module__, cls.__name__) 
       
    32 
       
    33 
       
    34 def buildTypedQueryString(base_class, derived_class=None):
       
    35   """Returns a GQL query string compatible with PolyModel.
       
    36   
       
    37   Args:
       
    38     base_class: Model class that inherits directly from
       
    39       polymodel.PolyModel, such as soc.models.work.Work
       
    40     derived_class: optional more-specific Model class that
       
    41       derives from base_class, such as soc.model.document.Document;
       
    42       default is None, in which case the inheritance_line
       
    43       property is *not* tested by the returned query string
       
    44   """
       
    45   query_str_parts = ['SELECT * FROM ', base_class.__name__]
       
    46 
       
    47   if derived_class:
       
    48     query_str_parts.extend(
       
    49       [" WHERE inheritance_line = '", getFullClassName(derived_class), "'"])
       
    50 
       
    51   return ''.join(query_str_parts)
       
    52 
       
    53 
       
    54 def buildOrderedQueryString(base_class, derived_class=None, order_by=None):
       
    55   """Returns a an ordered GQL query string compatible with PolyModel.
       
    56   
       
    57   Args:
       
    58     base_class, derived_class: see buildTypedQueryString()
       
    59     order_by: optional field name by which to order the query results;
       
    60       default is None, in which case no ORDER BY clause is placed in
       
    61       the query string
       
    62   """
       
    63   query_str_parts = [
       
    64     buildTypedQueryString(base_class, derived_class=derived_class)]
       
    65 
       
    66   if order_by:
       
    67     query_str_parts.extend([' ORDER BY ', order_by])
       
    68 
       
    69   return ''.join(query_str_parts)
       
    70 
       
    71 
       
    72 def getEntitiesForLimitAndOffset(base_class, limit, offset=0,
       
    73                                  derived_class=None, order_by=None):
       
    74   """Returns entities for given offset and limit or None if not found.
       
    75     
       
    76   Args:
       
    77     limit: max amount of entities to return
       
    78     offset: optional offset in entities list which defines first entity to
       
    79       return; default is zero (first entity)
       
    80   """
       
    81   query_string = buildOrderedQueryString(
       
    82       base_class, derived_class=derived_class, order_by=order_by)
       
    83 
       
    84   query = db.GqlQuery(query_string)
       
    85 
       
    86   return query.fetch(limit, offset)
       
    87 
       
    88 
       
    89 def getNearestEntities(base_class, fields_to_try, derived_class=None):
       
    90   """Get entities just before and just after the described entity.
       
    91     
       
    92   Args:
       
    93     fields_to_try: ordered list of key/value pairs that "describe"
       
    94       the desired entity (which may not necessarily exist), where key is
       
    95       the name of the field, and value is an instance of that field
       
    96       used in the comparison; if value is None, that field is skipped
       
    97 
       
    98   Returns:
       
    99     a two-tuple: ([nearest_entities], 'field_name')
       
   100     
       
   101     nearest_entities: list of entities being those just before and just
       
   102       after the (possibly non-existent) entity identified by the first
       
   103       of the supplied (non-None) fields
       
   104         OR
       
   105       possibly None if query had no results for the supplied field
       
   106       that was used.
       
   107   """
       
   108   # SELECT * FROM base_class WHERE inheritance_line = 'derived_class'
       
   109   typed_query_str = buildTypedQueryString(
       
   110     base_class, derived_class=derived_class)
       
   111   
       
   112   if derived_class:
       
   113     typed_query_str = typed_query_str + ' AND '
       
   114   else:
       
   115     typed_query_str = typed_query_str + ' WHERE '
       
   116     
       
   117   for field, value in fields_to_try:
       
   118     if value is None:
       
   119       # skip this not-supplied field
       
   120       continue
       
   121 
       
   122     query = db.GqlQuery('%s%s > :1' % (typed_query_str, field), value)
       
   123     return query.fetch(1), field
       
   124 
       
   125   # all fields exhausted, and all had None values
       
   126   return (None, None)
       
   127 
       
   128 
       
   129 def findNearestEntitiesOffset(width, base_class, fields_to_try,
       
   130                               derived_class=None):
       
   131   """Finds offset of beginning of a range of entities around the nearest.
       
   132   
       
   133   Args:
       
   134     width: the width of the "found" window around the nearest User found
       
   135     base_class, fields_to_try, derived_class:  see getNearestEntities()
       
   136     
       
   137   Returns:
       
   138     an offset into the list of entities that is width/2 less than the
       
   139     offset of the first entity returned by getNearestEntities(), or zero
       
   140     if that offset would be less than zero
       
   141       OR
       
   142     None if there are no nearest entities or the offset of the beginning of
       
   143     the range cannot be found for some reason 
       
   144   """
       
   145   # find entity "nearest" to supplied fields
       
   146   nearest_entities, field = getNearestEntities(
       
   147       base_class, fields_to_try, derived_class=derived_class)
       
   148   
       
   149   if not nearest_entities:
       
   150     # no "nearest" entity, so indicate that with None
       
   151     return None
       
   152 
       
   153   nearest_entity = nearest_entities[0]
       
   154 
       
   155   # start search for beginning of nearest Users range at offset zero
       
   156   offset = 0
       
   157   entities = getEntitiesForLimitAndOffset(
       
   158       base_class, width, offset=offset, derived_class=derived_class)
       
   159   
       
   160   while True:
       
   161     for entity in entities:
       
   162       if getattr(nearest_entity, field) == getattr(entity, field):
       
   163         # nearest User found in current search range, so return a range start
       
   164         return max(0, (offset - (width/2)))
       
   165 
       
   166       offset = offset + 1
       
   167 
       
   168     # nearest User was not in the current search range, so fetch the next set
       
   169     entities = getEntitiesForLimitAndOffset(
       
   170         base_class, width, offset=offset, derived_class=derived_class)
       
   171 
       
   172     if not entities:
       
   173       # nearest User never found, so indicate that with None
       
   174       break
       
   175 
       
   176   return None
    25 
   177 
    26 
   178 
    27 def updateModelProperties(model, **model_properties):
   179 def updateModelProperties(model, **model_properties):
    28   """Update existing model entity using supplied model properties.
   180   """Update existing model entity using supplied model properties.
    29 
   181