43 |
43 |
44 The BaseLogic class functions specific to Entity classes by relying |
44 The BaseLogic class functions specific to Entity classes by relying |
45 on the the child-classes to implement _model, _name and _key_name |
45 on the the child-classes to implement _model, _name and _key_name |
46 """ |
46 """ |
47 |
47 |
|
48 def __init__(self, model, base_model=None, |
|
49 name=None, skip_properties=None): |
|
50 """Defines the name, key_name and model for this entity. |
|
51 """ |
|
52 self._model = model |
|
53 self._base_model = base_model |
|
54 |
|
55 if name: |
|
56 self._name = name |
|
57 else: |
|
58 self._name = self.getModelClassName() |
|
59 |
|
60 if skip_properties: |
|
61 self._skip_properties = skip_properties |
|
62 else: |
|
63 self._skip_properties = [] |
|
64 |
48 def _updateField(self, model, name, value): |
65 def _updateField(self, model, name, value): |
49 """Hook called when a field is updated. |
66 """Hook called when a field is updated. |
50 |
67 |
51 Base classes should override if any special actions need to be |
68 Base classes should override if any special actions need to be |
52 taken when a field is updated. The field is not updated if the |
69 taken when a field is updated. The field is not updated if the |
82 for key_field_name in key_field_names: |
99 for key_field_name in key_field_names: |
83 keyvalues.append(kwargs[key_field_name]) |
100 keyvalues.append(kwargs[key_field_name]) |
84 |
101 |
85 # construct the KeyName in the appropriate format |
102 # construct the KeyName in the appropriate format |
86 return ":".join([self._name] + keyvalues) |
103 return ":".join([self._name] + keyvalues) |
|
104 |
|
105 def getModelClassName(self): |
|
106 """Returns model class name string. |
|
107 """ |
|
108 return self._model.__name__ |
|
109 |
|
110 def getFullModelClassName(self): |
|
111 """Returns fully-qualified model module.class name string. |
|
112 """ |
|
113 return '%s.%s' % (self._model.__module__, |
|
114 self.getModelClassName()) |
87 |
115 |
88 def getKeyValues(self, entity): |
116 def getKeyValues(self, entity): |
89 """Exctracts the key values from entity and returns them |
117 """Exctracts the key values from entity and returns them |
90 |
118 |
91 Args: |
119 Args: |
264 if unique: |
292 if unique: |
265 return query.get() |
293 return query.get() |
266 |
294 |
267 return query.fetch(limit, offset) |
295 return query.fetch(limit, offset) |
268 |
296 |
|
297 def buildTypedQueryString(self): |
|
298 """Returns a GQL query string compatible with PolyModel. |
|
299 """ |
|
300 return ''.join(self._buildTypedQueryParts()) |
|
301 |
|
302 def _buildTypedQueryParts(self): |
|
303 if self._base_model: |
|
304 return [ |
|
305 'SELECT * FROM ', self._base_model.__name__, |
|
306 " WHERE inheritance_line = '", self.getFullModelClassName(), "'"] |
|
307 |
|
308 return ['SELECT * FROM ', self._model.__name__] |
|
309 |
|
310 def buildOrderedQueryString(self, order_by=None): |
|
311 """Returns a an ordered GQL query string compatible with PolyModel. |
|
312 |
|
313 Args: |
|
314 order_by: optional field name by which to order the query results; |
|
315 default is None, in which case no ORDER BY clause is placed in |
|
316 the query string |
|
317 """ |
|
318 return ''.join(self._buildOrderedQueryParts(order_by=order_by)) |
|
319 |
|
320 def _buildOrderedQueryParts(self, order_by=None): |
|
321 query_str_parts = self._buildTypedQueryParts() |
|
322 |
|
323 if order_by: |
|
324 query_str_parts.extend([' ORDER BY ', order_by]) |
|
325 |
|
326 return query_str_parts |
|
327 |
|
328 def getEntitiesForLimitAndOffset(self, limit, offset=0, order_by=None): |
|
329 """Returns entities for given offset and limit or None if not found. |
|
330 |
|
331 Args: |
|
332 limit: max amount of entities to return |
|
333 offset: optional offset in entities list which defines first entity to |
|
334 return; default is zero (first entity) |
|
335 """ |
|
336 query_string = self.buildOrderedQueryString(order_by=order_by) |
|
337 query = db.GqlQuery(query_string) |
|
338 |
|
339 return query.fetch(limit, offset) |
|
340 |
|
341 def getNearestEntities(self, fields_to_try): |
|
342 """Get entities just before and just after the described entity. |
|
343 |
|
344 Args: |
|
345 fields_to_try: ordered list of key/value pairs that "describe" |
|
346 the desired entity (which may not necessarily exist), where key is |
|
347 the name of the field, and value is an instance of that field |
|
348 used in the comparison; if value is None, that field is skipped |
|
349 |
|
350 Returns: |
|
351 a two-tuple: ([nearest_entities], 'field_name') |
|
352 |
|
353 nearest_entities: list of entities being those just before and just |
|
354 after the (possibly non-existent) entity identified by the first |
|
355 of the supplied (non-None) fields |
|
356 OR |
|
357 possibly None if query had no results for the supplied field |
|
358 that was used. |
|
359 """ |
|
360 # SELECT * FROM base_class WHERE inheritance_line = 'derived_class' |
|
361 typed_query_parts = self._buildTypedQueryParts() |
|
362 |
|
363 if self._base_model: |
|
364 typed_query_parts.append(' AND %s > :1') |
|
365 else: |
|
366 typed_query_parts.append(' WHERE %s > :1') |
|
367 |
|
368 typed_query_fmt = ''.join(typed_query_parts) |
|
369 |
|
370 for field, value in fields_to_try: |
|
371 if value is None: |
|
372 # skip this not-supplied field |
|
373 continue |
|
374 |
|
375 query = db.GqlQuery(typed_query_fmt % field, value) |
|
376 return query.fetch(1), field |
|
377 |
|
378 # all fields exhausted, and all had None values |
|
379 return (None, None) |
|
380 |
|
381 def findNearestEntitiesOffset(width, fields_to_try): |
|
382 """Finds offset of beginning of a range of entities around the nearest. |
|
383 |
|
384 Args: |
|
385 width: the width of the "found" window around the nearest User found |
|
386 fields_to_try: see getNearestEntities() |
|
387 |
|
388 Returns: |
|
389 an offset into the list of entities that is width/2 less than the |
|
390 offset of the first entity returned by getNearestEntities(), or zero |
|
391 if that offset would be less than zero |
|
392 OR |
|
393 None if there are no nearest entities or the offset of the beginning of |
|
394 the range cannot be found for some reason |
|
395 """ |
|
396 # find entity "nearest" to supplied fields |
|
397 nearest_entities, field = self.getNearestEntities(fields_to_try) |
|
398 |
|
399 if not nearest_entities: |
|
400 # no "nearest" entity, so indicate that with None |
|
401 return None |
|
402 |
|
403 nearest_entity = nearest_entities[0] |
|
404 |
|
405 # start search for beginning of nearest Users range at offset zero |
|
406 offset = 0 |
|
407 entities = self.getEntitiesForLimitAndOffset(width, offset=offset) |
|
408 |
|
409 while True: |
|
410 for entity in entities: |
|
411 if getattr(nearest_entity, field) == getattr(entity, field): |
|
412 # nearest User found in current search range, so return a range start |
|
413 return max(0, (offset - (width/2))) |
|
414 |
|
415 offset = offset + 1 |
|
416 |
|
417 # nearest User was not in the current search range, so fetch the next set |
|
418 entities = self.getEntitiesForLimitAndOffset(width, offset=offset) |
|
419 |
|
420 if not entities: |
|
421 # nearest User never found, so indicate that with None |
|
422 break |
|
423 |
|
424 return None |
|
425 |
269 def updateModelProperties(self, model, model_properties): |
426 def updateModelProperties(self, model, model_properties): |
270 """Update existing model entity using supplied model properties. |
427 """Update existing model entity using supplied model properties. |
271 |
428 |
272 Args: |
429 Args: |
273 model: a model entity |
430 model: a model entity |