diff -r 9b07ddeb1412 -r 94834a1e6c01 app/soc/logic/models/base.py --- 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.