app/soc/logic/model.py
changeset 481 94834a1e6c01
parent 480 9b07ddeb1412
child 482 839740b061ad
equal deleted inserted replaced
480:9b07ddeb1412 481:94834a1e6c01
     1 #!/usr/bin/python2.5
       
     2 #
       
     3 # Copyright 2008 the Melange authors.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #   http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 
       
    17 """Helpers functions for updating different kinds of models in datastore.
       
    18 """
       
    19 
       
    20 __authors__ = [
       
    21   '"Todd Larsen" <tlarsen@google.com>',
       
    22   '"Sverre Rabbelier" <sverre@rabbelier.nl>',
       
    23   '"Pawel Solyga" <pawel.solyga@gmail.com>',
       
    24   ]
       
    25 
       
    26 
       
    27 from google.appengine.ext import db
       
    28 
       
    29 
       
    30 def getFullClassName(cls):
       
    31   """Returns fully-qualified module.class name string.""" 
       
    32 
       
    33   return '%s.%s' % (cls.__module__, cls.__name__) 
       
    34 
       
    35 
       
    36 def buildTypedQueryString(base_class, derived_class=None):
       
    37   """Returns a GQL query string compatible with PolyModel.
       
    38   
       
    39   Args:
       
    40     base_class: Model class that inherits directly from
       
    41       polymodel.PolyModel, such as soc.models.work.Work
       
    42     derived_class: optional more-specific Model class that
       
    43       derives from base_class, such as soc.models.document.Document;
       
    44       default is None, in which case the inheritance_line
       
    45       property is *not* tested by the returned query string
       
    46   """
       
    47 
       
    48   query_str_parts = ['SELECT * FROM ', base_class.__name__]
       
    49 
       
    50   if derived_class:
       
    51     query_str_parts.extend(
       
    52       [" WHERE inheritance_line = '", getFullClassName(derived_class), "'"])
       
    53 
       
    54   return ''.join(query_str_parts)
       
    55 
       
    56 
       
    57 def buildOrderedQueryString(base_class, derived_class=None, order_by=None):
       
    58   """Returns a an ordered GQL query string compatible with PolyModel.
       
    59   
       
    60   Args:
       
    61     base_class, derived_class: see buildTypedQueryString()
       
    62     order_by: optional field name by which to order the query results;
       
    63       default is None, in which case no ORDER BY clause is placed in
       
    64       the query string
       
    65   """
       
    66 
       
    67   query_str_parts = [
       
    68     buildTypedQueryString(base_class, derived_class=derived_class)]
       
    69 
       
    70   if order_by:
       
    71     query_str_parts.extend([' ORDER BY ', order_by])
       
    72 
       
    73   return ''.join(query_str_parts)
       
    74 
       
    75 
       
    76 def getEntitiesForLimitAndOffset(base_class, limit, offset=0,
       
    77                                  derived_class=None, order_by=None):
       
    78   """Returns entities for given offset and limit or None if not found.
       
    79     
       
    80   Args:
       
    81     limit: max amount of entities to return
       
    82     offset: optional offset in entities list which defines first entity to
       
    83       return; default is zero (first entity)
       
    84   """
       
    85 
       
    86   query_string = buildOrderedQueryString(
       
    87       base_class, derived_class=derived_class, order_by=order_by)
       
    88 
       
    89   query = db.GqlQuery(query_string)
       
    90 
       
    91   return query.fetch(limit, offset)
       
    92 
       
    93 
       
    94 def getNearestEntities(base_class, fields_to_try, derived_class=None):
       
    95   """Get entities just before and just after the described entity.
       
    96     
       
    97   Args:
       
    98     fields_to_try: ordered list of key/value pairs that "describe"
       
    99       the desired entity (which may not necessarily exist), where key is
       
   100       the name of the field, and value is an instance of that field
       
   101       used in the comparison; if value is None, that field is skipped
       
   102 
       
   103   Returns:
       
   104     a two-tuple: ([nearest_entities], 'field_name')
       
   105     
       
   106     nearest_entities: list of entities being those just before and just
       
   107       after the (possibly non-existent) entity identified by the first
       
   108       of the supplied (non-None) fields
       
   109         OR
       
   110       possibly None if query had no results for the supplied field
       
   111       that was used.
       
   112   """
       
   113 
       
   114   # SELECT * FROM base_class WHERE inheritance_line = 'derived_class'
       
   115   typed_query_str = buildTypedQueryString(
       
   116     base_class, derived_class=derived_class)
       
   117   
       
   118   if derived_class:
       
   119     typed_query_str = typed_query_str + ' AND '
       
   120   else:
       
   121     typed_query_str = typed_query_str + ' WHERE '
       
   122     
       
   123   for field, value in fields_to_try:
       
   124     if value is None:
       
   125       # skip this not-supplied field
       
   126       continue
       
   127 
       
   128     query = db.GqlQuery('%s%s > :1' % (typed_query_str, field), value)
       
   129     return query.fetch(1), field
       
   130 
       
   131   # all fields exhausted, and all had None values
       
   132   return (None, None)
       
   133 
       
   134 
       
   135 def findNearestEntitiesOffset(width, base_class, fields_to_try,
       
   136                               derived_class=None):
       
   137   """Finds offset of beginning of a range of entities around the nearest.
       
   138   
       
   139   Args:
       
   140     width: the width of the "found" window around the nearest User found
       
   141     base_class, fields_to_try, derived_class:  see getNearestEntities()
       
   142     
       
   143   Returns:
       
   144     an offset into the list of entities that is width/2 less than the
       
   145     offset of the first entity returned by getNearestEntities(), or zero
       
   146     if that offset would be less than zero
       
   147       OR
       
   148     None if there are no nearest entities or the offset of the beginning of
       
   149     the range cannot be found for some reason 
       
   150   """
       
   151 
       
   152   # find entity "nearest" to supplied fields
       
   153   nearest_entities, field = getNearestEntities(
       
   154       base_class, fields_to_try, derived_class=derived_class)
       
   155   
       
   156   if not nearest_entities:
       
   157     # no "nearest" entity, so indicate that with None
       
   158     return None
       
   159 
       
   160   nearest_entity = nearest_entities[0]
       
   161 
       
   162   # start search for beginning of nearest Users range at offset zero
       
   163   offset = 0
       
   164   entities = getEntitiesForLimitAndOffset(
       
   165       base_class, width, offset=offset, derived_class=derived_class)
       
   166   
       
   167   while True:
       
   168     for entity in entities:
       
   169       if getattr(nearest_entity, field) == getattr(entity, field):
       
   170         # nearest User found in current search range, so return a range start
       
   171         return max(0, (offset - (width/2)))
       
   172 
       
   173       offset = offset + 1
       
   174 
       
   175     # nearest User was not in the current search range, so fetch the next set
       
   176     entities = getEntitiesForLimitAndOffset(
       
   177         base_class, width, offset=offset, derived_class=derived_class)
       
   178 
       
   179     if not entities:
       
   180       # nearest User never found, so indicate that with None
       
   181       break
       
   182 
       
   183   return None