|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
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 |
|
18 """Simple datastore view and interactive console, for use in dev_appserver.""" |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 import cgi |
|
25 import csv |
|
26 import cStringIO |
|
27 import datetime |
|
28 import logging |
|
29 import math |
|
30 import mimetypes |
|
31 import os.path |
|
32 import random |
|
33 import sys |
|
34 import time |
|
35 import traceback |
|
36 import types |
|
37 import urllib |
|
38 import urlparse |
|
39 import wsgiref.handlers |
|
40 |
|
41 from google.appengine.api import datastore |
|
42 from google.appengine.api import datastore_types |
|
43 from google.appengine.api import datastore_errors |
|
44 from google.appengine.api import users |
|
45 from google.appengine.ext import db |
|
46 from google.appengine.ext import webapp |
|
47 from google.appengine.ext.webapp import template |
|
48 |
|
49 _DEBUG = True |
|
50 |
|
51 |
|
52 class ImageHandler(webapp.RequestHandler): |
|
53 """Serves a static image. |
|
54 |
|
55 This exists because we don't want to burden the user with specifying |
|
56 a static file handler for the image resources used by the admin tool. |
|
57 """ |
|
58 |
|
59 PATH = '/images/.*' |
|
60 |
|
61 def get(self): |
|
62 image_name = os.path.basename(self.request.path) |
|
63 content_type, encoding = mimetypes.guess_type(image_name) |
|
64 if not content_type or not content_type.startswith('image/'): |
|
65 logging.debug('image_name=%r, content_type=%r, encoding=%r', |
|
66 image_name, content_type, encoding) |
|
67 self.error(404) |
|
68 return |
|
69 directory = os.path.dirname(__file__) |
|
70 path = os.path.join(directory, 'templates', 'images', image_name) |
|
71 try: |
|
72 image_stream = open(path, 'rb') |
|
73 except IOError, e: |
|
74 logging.error('Cannot open image %s: %s', image_name, e) |
|
75 self.error(404) |
|
76 return |
|
77 try: |
|
78 image_data = image_stream.read() |
|
79 finally: |
|
80 image_stream.close() |
|
81 self.response.headers['Content-Type'] = content_type |
|
82 self.response.out.write(image_data) |
|
83 |
|
84 |
|
85 class BaseRequestHandler(webapp.RequestHandler): |
|
86 """Supplies a common template generation function. |
|
87 |
|
88 When you call generate(), we augment the template variables supplied with |
|
89 the current user in the 'user' variable and the current webapp request |
|
90 in the 'request' variable. |
|
91 """ |
|
92 |
|
93 def generate(self, template_name, template_values={}): |
|
94 base_path = self.base_path() |
|
95 values = { |
|
96 'user': users.get_current_user(), |
|
97 'request': self.request, |
|
98 'home_path': base_path + DefaultPageHandler.PATH, |
|
99 'datastore_path': base_path + DatastoreQueryHandler.PATH, |
|
100 'datastore_edit_path': base_path + DatastoreEditHandler.PATH, |
|
101 'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH, |
|
102 'interactive_path': base_path + InteractivePageHandler.PATH, |
|
103 'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH, |
|
104 } |
|
105 values.update(template_values) |
|
106 directory = os.path.dirname(__file__) |
|
107 path = os.path.join(directory, os.path.join('templates', template_name)) |
|
108 self.response.out.write(template.render(path, values, debug=_DEBUG)) |
|
109 |
|
110 def base_path(self): |
|
111 """Returns the base path of this admin app, which is chosen by the user. |
|
112 |
|
113 The user specifies which paths map to this application in their app.cfg. |
|
114 You can get that base path with this method. Combine with the constant |
|
115 paths specified by the classes to construct URLs. |
|
116 """ |
|
117 path = self.__class__.PATH |
|
118 return self.request.path[:-len(path)] |
|
119 |
|
120 def filter_url(self, args): |
|
121 """Filters the current URL to only have the given list of arguments. |
|
122 |
|
123 For example, if your URL is /search?q=foo&num=100&start=10, then |
|
124 |
|
125 self.filter_url(['start', 'num']) => /search?num=100&start=10 |
|
126 self.filter_url(['q']) => /search?q=10 |
|
127 self.filter_url(['random']) => /search? |
|
128 |
|
129 """ |
|
130 queries = [] |
|
131 for arg in args: |
|
132 value = self.request.get(arg) |
|
133 if value: |
|
134 queries.append(arg + '=' + urllib.quote_plus(self.request.get(arg))) |
|
135 return self.request.path + '?' + '&'.join(queries) |
|
136 |
|
137 |
|
138 class DefaultPageHandler(BaseRequestHandler): |
|
139 """Redirects to the Datastore application by default.""" |
|
140 |
|
141 PATH = '/' |
|
142 |
|
143 def get(self): |
|
144 if self.request.path.endswith('/'): |
|
145 base = self.request.path[:-1] |
|
146 else: |
|
147 base = self.request.path |
|
148 self.redirect(base + DatastoreQueryHandler.PATH) |
|
149 |
|
150 |
|
151 class InteractivePageHandler(BaseRequestHandler): |
|
152 """Shows our interactive console HTML.""" |
|
153 PATH = '/interactive' |
|
154 |
|
155 def get(self): |
|
156 self.generate('interactive.html') |
|
157 |
|
158 |
|
159 class InteractiveExecuteHandler(BaseRequestHandler): |
|
160 """Executes the Python code submitted in a POST within this context. |
|
161 |
|
162 For obvious reasons, this should only be available to administrators |
|
163 of the applications. |
|
164 """ |
|
165 |
|
166 PATH = InteractivePageHandler.PATH + '/execute' |
|
167 |
|
168 def post(self): |
|
169 self.response.headers['Content-Type'] = 'text/plain' |
|
170 |
|
171 save_stdout = sys.stdout |
|
172 try: |
|
173 sys.stdout = self.response.out |
|
174 |
|
175 code = self.request.get('code') |
|
176 code = code.replace("\r\n", "\n") |
|
177 |
|
178 try: |
|
179 compiled_code = compile(code, '<string>', 'exec') |
|
180 exec(compiled_code, globals()) |
|
181 except Exception, e: |
|
182 lines = traceback.format_exception(*sys.exc_info()) |
|
183 self.response.out.write(''.join(lines)) |
|
184 finally: |
|
185 sys.stdout = save_stdout |
|
186 |
|
187 |
|
188 class DatastoreRequestHandler(BaseRequestHandler): |
|
189 """The base request handler for our datastore admin pages. |
|
190 |
|
191 We provide utility functions for quering the datastore and infering the |
|
192 types of entity properties. |
|
193 """ |
|
194 |
|
195 def start(self): |
|
196 """Returns the santized "start" argument from the URL.""" |
|
197 return self.request.get_range('start', min_value=0, default=0) |
|
198 |
|
199 def num(self): |
|
200 """Returns the sanitized "num" argument from the URL.""" |
|
201 return self.request.get_range('num', min_value=1, max_value=100, |
|
202 default=10) |
|
203 |
|
204 def execute_query(self, start=0, num=0, no_order=False): |
|
205 """Parses the URL arguments and executes the query. |
|
206 |
|
207 We return a tuple (list of entities, total entity count). |
|
208 |
|
209 If the appropriate URL arguments are not given, we return an empty |
|
210 set of results and 0 for the entity count. |
|
211 """ |
|
212 kind = self.request.get('kind') |
|
213 if not kind: |
|
214 return ([], 0) |
|
215 query = datastore.Query(kind) |
|
216 |
|
217 order = self.request.get('order') |
|
218 order_type = self.request.get('order_type') |
|
219 if order and order_type: |
|
220 order_type = DataType.get_by_name(order_type).python_type() |
|
221 if order.startswith('-'): |
|
222 direction = datastore.Query.DESCENDING |
|
223 order = order[1:] |
|
224 else: |
|
225 direction = datastore.Query.ASCENDING |
|
226 try: |
|
227 query.Order((order, order_type, direction)) |
|
228 except datastore_errors.BadArgumentError: |
|
229 pass |
|
230 |
|
231 if not start: |
|
232 start = self.start() |
|
233 if not num: |
|
234 num = self.num() |
|
235 total = query.Count() |
|
236 entities = query.Get(start + num)[start:] |
|
237 return (entities, total) |
|
238 |
|
239 def get_key_values(self, entities): |
|
240 """Returns the union of key names used by the given list of entities. |
|
241 |
|
242 We return the union as a dictionary mapping the key names to a sample |
|
243 value from one of the entities for the key name. |
|
244 """ |
|
245 key_dict = {} |
|
246 for entity in entities: |
|
247 for key, value in entity.iteritems(): |
|
248 if key_dict.has_key(key): |
|
249 key_dict[key].append(value) |
|
250 else: |
|
251 key_dict[key] = [value] |
|
252 return key_dict |
|
253 |
|
254 |
|
255 class DatastoreQueryHandler(DatastoreRequestHandler): |
|
256 """Our main request handler that executes queries and lists entities. |
|
257 |
|
258 We use execute_query() in our base request handler to parse URL arguments |
|
259 and execute the datastore query. |
|
260 """ |
|
261 |
|
262 PATH = '/datastore' |
|
263 |
|
264 def get(self): |
|
265 """Formats the results from execute_query() for datastore.html. |
|
266 |
|
267 The only complex part of that process is calculating the pager variables |
|
268 to generate the Gooooogle pager at the bottom of the page. |
|
269 """ |
|
270 result_set, total = self.execute_query() |
|
271 key_values = self.get_key_values(result_set) |
|
272 keys = key_values.keys() |
|
273 keys.sort() |
|
274 |
|
275 headers = [] |
|
276 for key in keys: |
|
277 sample_value = key_values[key][0] |
|
278 headers.append({ |
|
279 'name': key, |
|
280 'type': DataType.get(sample_value).name(), |
|
281 }) |
|
282 |
|
283 entities = [] |
|
284 edit_path = self.base_path() + DatastoreEditHandler.PATH |
|
285 for entity in result_set: |
|
286 attributes = [] |
|
287 for key in keys: |
|
288 if entity.has_key(key): |
|
289 raw_value = entity[key] |
|
290 value = DataType.get(raw_value).format(raw_value) |
|
291 short_value = DataType.get(raw_value).short_format(raw_value) |
|
292 else: |
|
293 value = '' |
|
294 short_value = '' |
|
295 attributes.append({ |
|
296 'name': key, |
|
297 'value': value, |
|
298 'short_value': short_value, |
|
299 }) |
|
300 entities.append({ |
|
301 'key': str(entity.key()), |
|
302 'key_name': entity.key().name(), |
|
303 'key_id': entity.key().id(), |
|
304 'shortened_key': str(entity.key())[:8] + '...', |
|
305 'attributes': attributes, |
|
306 'edit_uri': edit_path + '?key=' + str(entity.key()) + '&kind=' + urllib.quote(self.request.get('kind')) + '&next=' + urllib.quote(self.request.uri), |
|
307 }) |
|
308 |
|
309 start = self.start() |
|
310 num = self.num() |
|
311 max_pager_links = 8 |
|
312 current_page = start / num |
|
313 num_pages = int(math.ceil(total * 1.0 / num)) |
|
314 page_start = max(math.floor(current_page - max_pager_links / 2), 0) |
|
315 page_end = min(page_start + max_pager_links, num_pages) |
|
316 |
|
317 pages = [] |
|
318 for page in range(page_start + 1, page_end + 1): |
|
319 pages.append({ |
|
320 'number': page, |
|
321 'start': (page - 1) * num, |
|
322 }) |
|
323 current_page += 1 |
|
324 |
|
325 values = { |
|
326 'request': self.request, |
|
327 'kind': self.request.get('kind'), |
|
328 'order': self.request.get('order'), |
|
329 'headers': headers, |
|
330 'entities': entities, |
|
331 'message': self.request.get('msg'), |
|
332 'pages': pages, |
|
333 'current_page': current_page, |
|
334 'num': num, |
|
335 'next_start': -1, |
|
336 'prev_start': -1, |
|
337 'start': start, |
|
338 'total': total, |
|
339 'start_base_url': self.filter_url(['kind', 'order', 'order_type', |
|
340 'num']), |
|
341 'order_base_url': self.filter_url(['kind', 'num']), |
|
342 } |
|
343 if current_page > 1: |
|
344 values['prev_start'] = int((current_page - 2) * num) |
|
345 if current_page < num_pages: |
|
346 values['next_start'] = int(current_page * num) |
|
347 |
|
348 self.generate('datastore.html', values) |
|
349 |
|
350 |
|
351 class DatastoreBatchEditHandler(DatastoreRequestHandler): |
|
352 """Request handler for a batch operation on entities. |
|
353 |
|
354 Supports deleting multiple entities by key, then redirecting to another url. |
|
355 """ |
|
356 |
|
357 PATH = DatastoreQueryHandler.PATH + '/batchedit' |
|
358 |
|
359 def post(self): |
|
360 kind = self.request.get('kind') |
|
361 |
|
362 keys = [] |
|
363 index = 0 |
|
364 num_keys = int(self.request.get('numkeys')) |
|
365 for i in xrange(1, num_keys+1): |
|
366 key = self.request.get('key%d' % i) |
|
367 if key: |
|
368 keys.append(key) |
|
369 |
|
370 if self.request.get('action') == 'Delete': |
|
371 num_deleted = 0 |
|
372 for key in keys: |
|
373 datastore.Delete(datastore.Key(key)) |
|
374 num_deleted = num_deleted + 1 |
|
375 message = '%d entit%s deleted.' % ( |
|
376 num_deleted, ('ies', 'y')[num_deleted == 1]) |
|
377 self.redirect( |
|
378 '%s&msg=%s' % (self.request.get('next'), urllib.quote_plus(message))) |
|
379 return |
|
380 |
|
381 self.error(404) |
|
382 |
|
383 |
|
384 class DatastoreEditHandler(DatastoreRequestHandler): |
|
385 """Request handler for the entity create/edit form. |
|
386 |
|
387 We determine how to generate a form to edit an entity by doing a query |
|
388 on the entity kind and looking at the set of keys and their types in |
|
389 the result set. We use the DataType subclasses for those introspected types |
|
390 to generate the form and parse the form results. |
|
391 """ |
|
392 |
|
393 PATH = DatastoreQueryHandler.PATH + '/edit' |
|
394 |
|
395 def get(self): |
|
396 kind = self.request.get('kind') |
|
397 sample_entities = self.execute_query()[0] |
|
398 if len(sample_entities) < 1: |
|
399 next_uri = self.request.get('next') |
|
400 kind_param = 'kind=%s' % kind |
|
401 if not kind_param in next_uri: |
|
402 if '?' in next_uri: |
|
403 next_uri += '&' + kind_param |
|
404 else: |
|
405 next_uri += '?' + kind_param |
|
406 self.redirect(next_uri) |
|
407 return |
|
408 |
|
409 entity_key = self.request.get('key') |
|
410 if entity_key: |
|
411 key_instance = datastore.Key(entity_key) |
|
412 entity_key_name = key_instance.name() |
|
413 entity_key_id = key_instance.id() |
|
414 parent_key = key_instance.parent() |
|
415 entity = datastore.Get(key_instance) |
|
416 else: |
|
417 key_instance = None |
|
418 entity_key_name = None |
|
419 entity_key_id = None |
|
420 parent_key = None |
|
421 entity = None |
|
422 |
|
423 if parent_key: |
|
424 parent_kind = parent_key.kind() |
|
425 else: |
|
426 parent_kind = None |
|
427 |
|
428 fields = [] |
|
429 key_values = self.get_key_values(sample_entities) |
|
430 for key, sample_values in key_values.iteritems(): |
|
431 if entity and entity.has_key(key): |
|
432 data_type = DataType.get(entity[key]) |
|
433 else: |
|
434 data_type = DataType.get(sample_values[0]) |
|
435 name = data_type.name() + "|" + key |
|
436 if entity and entity.has_key(key): |
|
437 value = entity[key] |
|
438 else: |
|
439 value = None |
|
440 field = data_type.input_field(name, value, sample_values) |
|
441 fields.append((key, data_type.name(), field)) |
|
442 |
|
443 self.generate('datastore_edit.html', { |
|
444 'kind': kind, |
|
445 'key': entity_key, |
|
446 'key_name': entity_key_name, |
|
447 'key_id': entity_key_id, |
|
448 'fields': fields, |
|
449 'focus': self.request.get('focus'), |
|
450 'next': self.request.get('next'), |
|
451 'parent_key': parent_key, |
|
452 'parent_kind': parent_kind, |
|
453 }) |
|
454 |
|
455 def post(self): |
|
456 kind = self.request.get('kind') |
|
457 entity_key = self.request.get('key') |
|
458 if entity_key: |
|
459 if self.request.get('action') == 'Delete': |
|
460 datastore.Delete(datastore.Key(entity_key)) |
|
461 self.redirect(self.request.get('next')) |
|
462 return |
|
463 entity = datastore.Get(datastore.Key(entity_key)) |
|
464 else: |
|
465 entity = datastore.Entity(kind) |
|
466 |
|
467 args = self.request.arguments() |
|
468 for arg in args: |
|
469 bar = arg.find('|') |
|
470 if bar > 0: |
|
471 data_type_name = arg[:bar] |
|
472 field_name = arg[bar + 1:] |
|
473 form_value = self.request.get(arg) |
|
474 data_type = DataType.get_by_name(data_type_name) |
|
475 if entity and entity.has_key(field_name): |
|
476 old_formatted_value = data_type.format(entity[field_name]) |
|
477 if old_formatted_value == form_value: |
|
478 continue |
|
479 |
|
480 if len(form_value) > 0: |
|
481 value = data_type.parse(form_value) |
|
482 entity[field_name] = value |
|
483 elif entity.has_key(field_name): |
|
484 del entity[field_name] |
|
485 |
|
486 datastore.Put(entity) |
|
487 |
|
488 self.redirect(self.request.get('next')) |
|
489 |
|
490 |
|
491 class DataType(object): |
|
492 """A DataType represents a data type in the datastore. |
|
493 |
|
494 Each DataType subtype defines four methods: |
|
495 |
|
496 format: returns a formatted string for a datastore value |
|
497 input_field: returns a string HTML <input> element for this DataType |
|
498 name: the friendly string name of this DataType |
|
499 parse: parses the formatted string representation of this DataType |
|
500 python_type: the canonical Python type for this datastore type |
|
501 |
|
502 We use DataType instances to display formatted values in our result lists, |
|
503 and we uses input_field/format/parse to generate forms and parse the results |
|
504 from those forms to allow editing of entities. |
|
505 """ |
|
506 @staticmethod |
|
507 def get(value): |
|
508 return _DATA_TYPES[value.__class__] |
|
509 |
|
510 @staticmethod |
|
511 def get_by_name(name): |
|
512 return _NAMED_DATA_TYPES[name] |
|
513 |
|
514 def format(self, value): |
|
515 return str(value) |
|
516 |
|
517 def short_format(self, value): |
|
518 return self.format(value) |
|
519 |
|
520 def input_field(self, name, value, sample_values): |
|
521 if value is not None: |
|
522 string_value = self.format(value) |
|
523 else: |
|
524 string_value = '' |
|
525 return '<input class="%s" name="%s" type="text" size="%d" value="%s"/>' % (cgi.escape(self.name()), cgi.escape(name), self.input_field_size(), |
|
526 cgi.escape(string_value)) |
|
527 |
|
528 def input_field_size(self): |
|
529 return 30 |
|
530 |
|
531 |
|
532 class StringType(DataType): |
|
533 def format(self, value): |
|
534 return value |
|
535 |
|
536 def input_field(self, name, value, sample_values): |
|
537 multiline = False |
|
538 if value: |
|
539 multiline = len(value) > 255 or value.find('\n') >= 0 |
|
540 if not multiline: |
|
541 for sample_value in sample_values: |
|
542 if len(sample_value) > 255 or sample_value.find('\n') >= 0: |
|
543 multiline = True |
|
544 break |
|
545 if multiline: |
|
546 if not value: |
|
547 value = '' |
|
548 return '<textarea name="%s" rows="5" cols="50">%s</textarea>' % (cgi.escape(name), cgi.escape(value)) |
|
549 else: |
|
550 return DataType.input_field(self, name, value, sample_values) |
|
551 |
|
552 def name(self): |
|
553 return 'string' |
|
554 |
|
555 def parse(self, value): |
|
556 return value |
|
557 |
|
558 def python_type(self): |
|
559 return str |
|
560 |
|
561 def input_field_size(self): |
|
562 return 50 |
|
563 |
|
564 |
|
565 class TextType(StringType): |
|
566 def name(self): |
|
567 return 'Text' |
|
568 |
|
569 def input_field(self, name, value, sample_values): |
|
570 return '<textarea name="%s" rows="5" cols="50">%s</textarea>' % (cgi.escape(name), cgi.escape(str(value))) |
|
571 |
|
572 def parse(self, value): |
|
573 return datastore_types.Text(value) |
|
574 |
|
575 def python_type(self): |
|
576 return datastore_types.Text |
|
577 |
|
578 |
|
579 class BlobType(StringType): |
|
580 def name(self): |
|
581 return 'Blob' |
|
582 |
|
583 def input_field(self, name, value, sample_values): |
|
584 return '<binary>' |
|
585 |
|
586 def format(self, value): |
|
587 return '<binary>' |
|
588 |
|
589 def python_type(self): |
|
590 return datastore_types.Blob |
|
591 |
|
592 |
|
593 class TimeType(DataType): |
|
594 _FORMAT = '%Y-%m-%d %H:%M:%S' |
|
595 |
|
596 def format(self, value): |
|
597 return value.strftime(TimeType._FORMAT) |
|
598 |
|
599 def name(self): |
|
600 return 'datetime' |
|
601 |
|
602 def parse(self, value): |
|
603 return datetime.datetime(*(time.strptime(value, TimeType._FORMAT)[0:6])) |
|
604 |
|
605 def python_type(self): |
|
606 return datetime.datetime |
|
607 |
|
608 |
|
609 class ListType(DataType): |
|
610 def format(self, value): |
|
611 value_file = cStringIO.StringIO() |
|
612 try: |
|
613 writer = csv.writer(value_file) |
|
614 writer.writerow(value) |
|
615 return value_file.getvalue() |
|
616 finally: |
|
617 value_file.close() |
|
618 |
|
619 def name(self): |
|
620 return 'list' |
|
621 |
|
622 def parse(self, value): |
|
623 value_file = cStringIO.StringIO(value) |
|
624 try: |
|
625 reader = csv.reader(value_file) |
|
626 return reader.next() |
|
627 finally: |
|
628 value_file.close() |
|
629 |
|
630 def python_type(self): |
|
631 return list |
|
632 |
|
633 |
|
634 class BoolType(DataType): |
|
635 def name(self): |
|
636 return 'bool' |
|
637 |
|
638 def input_field(self, name, value, sample_values): |
|
639 selected = { None: '', False: '', True: '' }; |
|
640 selected[value] = "selected" |
|
641 return """<select class="%s" name="%s"> |
|
642 <option %s value=''></option> |
|
643 <option %s value='0'>False</option> |
|
644 <option %s value='1'>True</option></select>""" % (cgi.escape(self.name()), cgi.escape(name), selected[None], |
|
645 selected[False], selected[True]) |
|
646 |
|
647 def parse(self, value): |
|
648 if value.lower() is 'true': |
|
649 return True |
|
650 if value.lower() is 'false': |
|
651 return False |
|
652 return bool(int(value)) |
|
653 |
|
654 def python_type(self): |
|
655 return bool |
|
656 |
|
657 |
|
658 class NumberType(DataType): |
|
659 def input_field_size(self): |
|
660 return 10 |
|
661 |
|
662 |
|
663 class IntType(NumberType): |
|
664 def name(self): |
|
665 return 'int' |
|
666 |
|
667 def parse(self, value): |
|
668 return int(value) |
|
669 |
|
670 def python_type(self): |
|
671 return int |
|
672 |
|
673 |
|
674 class LongType(NumberType): |
|
675 def name(self): |
|
676 return 'long' |
|
677 |
|
678 def parse(self, value): |
|
679 return long(value) |
|
680 |
|
681 def python_type(self): |
|
682 return long |
|
683 |
|
684 |
|
685 class FloatType(NumberType): |
|
686 def name(self): |
|
687 return 'float' |
|
688 |
|
689 def parse(self, value): |
|
690 return float(value) |
|
691 |
|
692 def python_type(self): |
|
693 return float |
|
694 |
|
695 |
|
696 class UserType(DataType): |
|
697 def name(self): |
|
698 return 'User' |
|
699 |
|
700 def parse(self, value): |
|
701 return users.User(value) |
|
702 |
|
703 def python_type(self): |
|
704 return users.User |
|
705 |
|
706 def input_field_size(self): |
|
707 return 15 |
|
708 |
|
709 class ReferenceType(DataType): |
|
710 def name(self): |
|
711 return 'Key' |
|
712 |
|
713 def short_format(self, value): |
|
714 return str(value)[:8] + '...' |
|
715 |
|
716 def parse(self, value): |
|
717 return datastore_types.Key(value) |
|
718 |
|
719 def python_type(self): |
|
720 return datastore_types.Key |
|
721 |
|
722 def input_field_size(self): |
|
723 return 85 |
|
724 |
|
725 |
|
726 class EmailType(StringType): |
|
727 def name(self): |
|
728 return 'Email' |
|
729 |
|
730 def parse(self, value): |
|
731 return datastore_types.Email(value) |
|
732 |
|
733 def python_type(self): |
|
734 return datastore_types.Email |
|
735 |
|
736 |
|
737 class CategoryType(StringType): |
|
738 def name(self): |
|
739 return 'Category' |
|
740 |
|
741 def parse(self, value): |
|
742 return datastore_types.Category(value) |
|
743 |
|
744 def python_type(self): |
|
745 return datastore_types.Category |
|
746 |
|
747 |
|
748 class LinkType(StringType): |
|
749 def name(self): |
|
750 return 'Link' |
|
751 |
|
752 def parse(self, value): |
|
753 return datastore_types.Link(value) |
|
754 |
|
755 def python_type(self): |
|
756 return datastore_types.Link |
|
757 |
|
758 |
|
759 class GeoPtType(DataType): |
|
760 def name(self): |
|
761 return 'GeoPt' |
|
762 |
|
763 def parse(self, value): |
|
764 return datastore_types.GeoPt(value) |
|
765 |
|
766 def python_type(self): |
|
767 return datastore_types.GeoPt |
|
768 |
|
769 |
|
770 class ImType(DataType): |
|
771 def name(self): |
|
772 return 'IM' |
|
773 |
|
774 def parse(self, value): |
|
775 return datastore_types.IM(value) |
|
776 |
|
777 def python_type(self): |
|
778 return datastore_types.IM |
|
779 |
|
780 |
|
781 class PhoneNumberType(StringType): |
|
782 def name(self): |
|
783 return 'PhoneNumber' |
|
784 |
|
785 def parse(self, value): |
|
786 return datastore_types.PhoneNumber(value) |
|
787 |
|
788 def python_type(self): |
|
789 return datastore_types.PhoneNumber |
|
790 |
|
791 |
|
792 class PostalAddressType(StringType): |
|
793 def name(self): |
|
794 return 'PostalAddress' |
|
795 |
|
796 def parse(self, value): |
|
797 return datastore_types.PostalAddress(value) |
|
798 |
|
799 def python_type(self): |
|
800 return datastore_types.PostalAddress |
|
801 |
|
802 |
|
803 class RatingType(NumberType): |
|
804 def name(self): |
|
805 return 'Rating' |
|
806 |
|
807 def parse(self, value): |
|
808 return datastore_types.Rating(value) |
|
809 |
|
810 def python_type(self): |
|
811 return datastore_types.Rating |
|
812 |
|
813 |
|
814 class NoneType(DataType): |
|
815 def name(self): |
|
816 return 'None' |
|
817 |
|
818 def parse(self, value): |
|
819 return None |
|
820 |
|
821 def format(self, value): |
|
822 return 'None' |
|
823 |
|
824 _DATA_TYPES = { |
|
825 types.NoneType: NoneType(), |
|
826 types.StringType: StringType(), |
|
827 types.UnicodeType: StringType(), |
|
828 datastore_types.Text: TextType(), |
|
829 datastore_types.Blob: BlobType(), |
|
830 types.BooleanType: BoolType(), |
|
831 types.IntType: IntType(), |
|
832 types.LongType: LongType(), |
|
833 types.FloatType: FloatType(), |
|
834 datetime.datetime: TimeType(), |
|
835 users.User: UserType(), |
|
836 datastore_types.Key: ReferenceType(), |
|
837 types.ListType: ListType(), |
|
838 datastore_types.Email: EmailType(), |
|
839 datastore_types.Category: CategoryType(), |
|
840 datastore_types.Link: LinkType(), |
|
841 datastore_types.GeoPt: GeoPtType(), |
|
842 datastore_types.IM: ImType(), |
|
843 datastore_types.PhoneNumber: PhoneNumberType(), |
|
844 datastore_types.PostalAddress: PostalAddressType(), |
|
845 datastore_types.Rating: RatingType(), |
|
846 } |
|
847 |
|
848 _NAMED_DATA_TYPES = {} |
|
849 for data_type in _DATA_TYPES.values(): |
|
850 _NAMED_DATA_TYPES[data_type.name()] = data_type |
|
851 |
|
852 |
|
853 def main(): |
|
854 application = webapp.WSGIApplication([ |
|
855 ('.*' + DatastoreQueryHandler.PATH, DatastoreQueryHandler), |
|
856 ('.*' + DatastoreEditHandler.PATH, DatastoreEditHandler), |
|
857 ('.*' + DatastoreBatchEditHandler.PATH, DatastoreBatchEditHandler), |
|
858 ('.*' + InteractivePageHandler.PATH, InteractivePageHandler), |
|
859 ('.*' + InteractiveExecuteHandler.PATH, InteractiveExecuteHandler), |
|
860 ('.*' + ImageHandler.PATH, ImageHandler), |
|
861 ('.*', DefaultPageHandler), |
|
862 ], debug=_DEBUG) |
|
863 wsgiref.handlers.CGIHandler().run(application) |
|
864 |
|
865 |
|
866 import django |
|
867 if django.VERSION[:2] < (0, 97): |
|
868 from django.template import defaultfilters |
|
869 def safe(text, dummy=None): |
|
870 return text |
|
871 defaultfilters.register.filter("safe", safe) |
|
872 |
|
873 |
|
874 if __name__ == '__main__': |
|
875 main() |