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 |
|