app/soc/logic/models/base.py
changeset 481 94834a1e6c01
parent 459 2cfcedaf7c16
child 487 1a7591ff0051
--- a/app/soc/logic/models/base.py	Fri Nov 14 06:36:42 2008 +0000
+++ b/app/soc/logic/models/base.py	Sat Nov 15 03:12:33 2008 +0000
@@ -45,6 +45,23 @@
   on the the child-classes to implement _model, _name and _key_name
   """
 
+  def __init__(self, model, base_model=None,
+               name=None, skip_properties=None):
+    """Defines the name, key_name and model for this entity.
+    """
+    self._model = model
+    self._base_model = base_model
+    
+    if name:
+      self._name = name
+    else:
+      self._name = self.getModelClassName()
+    
+    if skip_properties:
+      self._skip_properties = skip_properties
+    else:
+      self._skip_properties = []
+
   def _updateField(self, model, name, value):
     """Hook called when a field is updated.
 
@@ -85,6 +102,17 @@
     # construct the KeyName in the appropriate format
     return ":".join([self._name] + keyvalues)
 
+  def getModelClassName(self):
+    """Returns model class name string.
+    """ 
+    return self._model.__name__ 
+
+  def getFullModelClassName(self):
+    """Returns fully-qualified model module.class name string.
+    """ 
+    return '%s.%s' % (self._model.__module__,
+                      self.getModelClassName()) 
+
   def getKeyValues(self, entity):
     """Exctracts the key values from entity and returns them
 
@@ -266,6 +294,135 @@
 
     return query.fetch(limit, offset)
 
+  def buildTypedQueryString(self):
+    """Returns a GQL query string compatible with PolyModel.
+    """
+    return ''.join(self._buildTypedQueryParts())
+
+  def _buildTypedQueryParts(self):
+    if self._base_model:
+      return [
+          'SELECT * FROM ', self._base_model.__name__,
+          " WHERE inheritance_line = '", self.getFullModelClassName(), "'"]
+    
+    return ['SELECT * FROM ', self._model.__name__]
+
+  def buildOrderedQueryString(self, order_by=None):
+    """Returns a an ordered GQL query string compatible with PolyModel.
+  
+    Args:
+      order_by: optional field name by which to order the query results;
+        default is None, in which case no ORDER BY clause is placed in
+        the query string
+    """
+    return ''.join(self._buildOrderedQueryParts(order_by=order_by))
+
+  def _buildOrderedQueryParts(self, order_by=None):
+    query_str_parts = self._buildTypedQueryParts()
+
+    if order_by:
+      query_str_parts.extend([' ORDER BY ', order_by])
+
+    return query_str_parts
+
+  def getEntitiesForLimitAndOffset(self, limit, offset=0, order_by=None):
+    """Returns entities for given offset and limit or None if not found.
+    
+    Args:
+      limit: max amount of entities to return
+      offset: optional offset in entities list which defines first entity to
+        return; default is zero (first entity)
+    """
+    query_string = self.buildOrderedQueryString(order_by=order_by)
+    query = db.GqlQuery(query_string)
+
+    return query.fetch(limit, offset)
+
+  def getNearestEntities(self, fields_to_try):
+    """Get entities just before and just after the described entity.
+    
+    Args:
+      fields_to_try: ordered list of key/value pairs that "describe"
+        the desired entity (which may not necessarily exist), where key is
+        the name of the field, and value is an instance of that field
+        used in the comparison; if value is None, that field is skipped
+
+    Returns:
+      a two-tuple: ([nearest_entities], 'field_name')
+    
+      nearest_entities: list of entities being those just before and just
+        after the (possibly non-existent) entity identified by the first
+        of the supplied (non-None) fields
+          OR
+        possibly None if query had no results for the supplied field
+        that was used.
+    """
+    # SELECT * FROM base_class WHERE inheritance_line = 'derived_class'
+    typed_query_parts = self._buildTypedQueryParts()
+  
+    if self._base_model:
+      typed_query_parts.append(' AND %s > :1')
+    else:
+      typed_query_parts.append(' WHERE %s > :1')
+
+    typed_query_fmt = ''.join(typed_query_parts)
+
+    for field, value in fields_to_try:
+      if value is None:
+        # skip this not-supplied field
+        continue
+
+      query = db.GqlQuery(typed_query_fmt % field, value)
+      return query.fetch(1), field
+
+    # all fields exhausted, and all had None values
+    return (None, None)
+
+  def findNearestEntitiesOffset(width, fields_to_try):
+    """Finds offset of beginning of a range of entities around the nearest.
+  
+    Args:
+      width: the width of the "found" window around the nearest User found
+      fields_to_try:  see getNearestEntities()
+    
+    Returns:
+      an offset into the list of entities that is width/2 less than the
+      offset of the first entity returned by getNearestEntities(), or zero
+      if that offset would be less than zero
+        OR
+      None if there are no nearest entities or the offset of the beginning of
+      the range cannot be found for some reason 
+    """
+    # find entity "nearest" to supplied fields
+    nearest_entities, field = self.getNearestEntities(fields_to_try)
+  
+    if not nearest_entities:
+      # no "nearest" entity, so indicate that with None
+      return None
+
+    nearest_entity = nearest_entities[0]
+
+    # start search for beginning of nearest Users range at offset zero
+    offset = 0
+    entities = self.getEntitiesForLimitAndOffset(width, offset=offset)
+  
+    while True:
+      for entity in entities:
+        if getattr(nearest_entity, field) == getattr(entity, field):
+          # nearest User found in current search range, so return a range start
+          return max(0, (offset - (width/2)))
+
+        offset = offset + 1
+
+      # nearest User was not in the current search range, so fetch the next set
+      entities = self.getEntitiesForLimitAndOffset(width, offset=offset)
+
+      if not entities:
+        # nearest User never found, so indicate that with None
+        break
+
+    return None
+
   def updateModelProperties(self, model, model_properties):
     """Update existing model entity using supplied model properties.