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 |