Load /Users/solydzajs/Downloads/google_appengine into
[Melange.git] / thirdparty / google_appengine / google / appengine / ext / admin / __init__.py
blob2930d8c54dc28fac9e53ed61bc188ca1f31220ff
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 """Simple datastore view and interactive console, for use in dev_appserver."""
24 import cgi
25 import csv
26 import cStringIO
27 import datetime
28 import logging
29 import math
30 import mimetypes
31 import os
32 import os.path
33 import pickle
34 import pprint
35 import random
36 import sys
37 import time
38 import traceback
39 import types
40 import urllib
41 import urlparse
42 import wsgiref.handlers
44 try:
45 from google.appengine.cron import groctimespecification
46 from google.appengine.api import croninfo
47 except ImportError:
48 HAVE_CRON = False
49 else:
50 HAVE_CRON = True
52 from google.appengine.api import datastore
53 from google.appengine.api import datastore_admin
54 from google.appengine.api import datastore_types
55 from google.appengine.api import datastore_errors
56 from google.appengine.api import memcache
57 from google.appengine.api import users
58 from google.appengine.ext import db
59 from google.appengine.ext import webapp
60 from google.appengine.ext.webapp import template
62 _DEBUG = True
65 class ImageHandler(webapp.RequestHandler):
66 """Serves a static image.
68 This exists because we don't want to burden the user with specifying
69 a static file handler for the image resources used by the admin tool.
70 """
72 PATH = '/images/.*'
74 def get(self):
75 image_name = os.path.basename(self.request.path)
76 content_type, encoding = mimetypes.guess_type(image_name)
77 if not content_type or not content_type.startswith('image/'):
78 logging.debug('image_name=%r, content_type=%r, encoding=%r',
79 image_name, content_type, encoding)
80 self.error(404)
81 return
82 directory = os.path.dirname(__file__)
83 path = os.path.join(directory, 'templates', 'images', image_name)
84 try:
85 image_stream = open(path, 'rb')
86 except IOError, e:
87 logging.error('Cannot open image %s: %s', image_name, e)
88 self.error(404)
89 return
90 try:
91 image_data = image_stream.read()
92 finally:
93 image_stream.close()
94 self.response.headers['Content-Type'] = content_type
95 self.response.out.write(image_data)
98 class BaseRequestHandler(webapp.RequestHandler):
99 """Supplies a common template generation function.
101 When you call generate(), we augment the template variables supplied with
102 the current user in the 'user' variable and the current webapp request
103 in the 'request' variable.
106 def generate(self, template_name, template_values={}):
107 base_path = self.base_path()
108 values = {
109 'application_name': self.request.environ['APPLICATION_ID'],
110 'user': users.get_current_user(),
111 'request': self.request,
112 'home_path': base_path + DefaultPageHandler.PATH,
113 'datastore_path': base_path + DatastoreQueryHandler.PATH,
114 'datastore_edit_path': base_path + DatastoreEditHandler.PATH,
115 'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH,
116 'interactive_path': base_path + InteractivePageHandler.PATH,
117 'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH,
118 'memcache_path': base_path + MemcachePageHandler.PATH,
120 if HAVE_CRON:
121 values['cron_path'] = base_path + CronPageHandler.PATH
123 values.update(template_values)
124 directory = os.path.dirname(__file__)
125 path = os.path.join(directory, os.path.join('templates', template_name))
126 self.response.out.write(template.render(path, values, debug=_DEBUG))
128 def base_path(self):
129 """Returns the base path of this admin app, which is chosen by the user.
131 The user specifies which paths map to this application in their app.cfg.
132 You can get that base path with this method. Combine with the constant
133 paths specified by the classes to construct URLs.
135 path = self.__class__.PATH
136 return self.request.path[:-len(path)]
138 def filter_url(self, args):
139 """Filters the current URL to only have the given list of arguments.
141 For example, if your URL is /search?q=foo&num=100&start=10, then
143 self.filter_url(['start', 'num']) => /search?num=100&start=10
144 self.filter_url(['q']) => /search?q=10
145 self.filter_url(['random']) => /search?
148 queries = []
149 for arg in args:
150 value = self.request.get(arg)
151 if value:
152 queries.append(arg + '=' + urllib.quote_plus(self.request.get(arg)))
153 return self.request.path + '?' + '&'.join(queries)
155 def in_production(self):
156 """Detects if app is running in production.
158 Returns a boolean.
160 server_software = os.environ['SERVER_SOFTWARE']
161 return not server_software.startswith('Development')
164 class DefaultPageHandler(BaseRequestHandler):
165 """Redirects to the Datastore application by default."""
167 PATH = '/'
169 def get(self):
170 if self.request.path.endswith('/'):
171 base = self.request.path[:-1]
172 else:
173 base = self.request.path
174 self.redirect(base + DatastoreQueryHandler.PATH)
177 class InteractivePageHandler(BaseRequestHandler):
178 """Shows our interactive console HTML."""
179 PATH = '/interactive'
181 def get(self):
182 self.generate('interactive.html')
185 class InteractiveExecuteHandler(BaseRequestHandler):
186 """Executes the Python code submitted in a POST within this context.
188 For obvious reasons, this should only be available to administrators
189 of the applications.
192 PATH = InteractivePageHandler.PATH + '/execute'
194 def post(self):
195 save_stdout = sys.stdout
196 results_io = cStringIO.StringIO()
197 try:
198 sys.stdout = results_io
200 code = self.request.get('code')
201 code = code.replace("\r\n", "\n")
203 try:
204 compiled_code = compile(code, '<string>', 'exec')
205 exec(compiled_code, globals())
206 except Exception, e:
207 traceback.print_exc(file=results_io)
208 finally:
209 sys.stdout = save_stdout
211 results = results_io.getvalue()
212 self.generate('interactive-output.html', {'output': results})
215 class CronPageHandler(BaseRequestHandler):
216 """Shows information about configured cron jobs in this application."""
217 PATH = '/cron'
219 def get(self, now=None):
220 """Shows template displaying the configured cron jobs."""
221 if not now:
222 now = datetime.datetime.now()
223 values = {'request': self.request}
224 cron_info = _ParseCronYaml()
225 values['cronjobs'] = []
226 values['now'] = str(now)
227 if cron_info:
228 for entry in cron_info.cron:
229 job = {}
230 values['cronjobs'].append(job)
231 if entry.description:
232 job['description'] = entry.description
233 else:
234 job['description'] = '(no description)'
235 if entry.timezone:
236 job['timezone'] = entry.timezone
237 job['url'] = entry.url
238 job['schedule'] = entry.schedule
239 schedule = groctimespecification.GrocTimeSpecification(entry.schedule)
240 matches = schedule.GetMatches(now, 3)
241 job['times'] = []
242 for match in matches:
243 job['times'].append({'runtime': match.strftime("%Y-%m-%d %H:%M:%SZ"),
244 'difference': str(match - now)})
245 self.generate('cron.html', values)
248 class MemcachePageHandler(BaseRequestHandler):
249 """Shows stats about memcache and query form to get values."""
250 PATH = '/memcache'
252 TYPES = ((str, str, 'String'),
253 (unicode, unicode, 'Unicode String'),
254 (bool, lambda value: MemcachePageHandler._ToBool(value), 'Boolean'),
255 (int, int, 'Integer'),
256 (long, long, 'Long Integer'),
257 (float, float, 'Float'))
258 DEFAULT_TYPESTR_FOR_NEW = 'String'
260 @staticmethod
261 def _ToBool(string_value):
262 """Convert string to boolean value.
264 Args:
265 string_value: A string.
267 Returns:
268 Boolean. True if string_value is "true", False if string_value is
269 "false". This is case-insensitive.
271 Raises:
272 ValueError: string_value not "true" or "false".
274 string_value_low = string_value.lower()
275 if string_value_low not in ('false', 'true'):
276 raise ValueError('invalid literal for boolean: %s' % string_value)
277 return string_value_low == 'true'
279 def _GetValueAndType(self, key):
280 """Fetch value from memcache and detect its type.
282 Args:
283 key: String
285 Returns:
286 (value, type), value is a Python object or None if the key was not set in
287 the cache, type is a string describing the type of the value.
289 try:
290 value = memcache.get(key)
291 except (pickle.UnpicklingError, AttributeError, EOFError, ImportError,
292 IndexError), e:
293 msg = 'Failed to retrieve value from cache: %s' % e
294 return msg, 'error'
296 if value is None:
297 return None, self.DEFAULT_TYPESTR_FOR_NEW
299 for typeobj, _, typestr in self.TYPES:
300 if isinstance(value, typeobj):
301 break
302 else:
303 typestr = 'pickled'
304 value = pprint.pformat(value, indent=2)
306 return value, typestr
308 def _SetValue(self, key, type_, value):
309 """Convert a string value and store the result in memcache.
311 Args:
312 key: String
313 type_: String, describing what type the value should have in the cache.
314 value: String, will be converted according to type_.
316 Returns:
317 Result of memcache.set(ket, converted_value). True if value was set.
319 Raises:
320 ValueError: Value can't be converted according to type_.
322 for _, converter, typestr in self.TYPES:
323 if typestr == type_:
324 value = converter(value)
325 break
326 else:
327 raise ValueError('Type %s not supported.' % type_)
328 return memcache.set(key, value)
330 def get(self):
331 """Show template and prepare stats and/or key+value to display/edit."""
332 values = {'request': self.request,
333 'message': self.request.get('message')}
335 edit = self.request.get('edit')
336 key = self.request.get('key')
337 if edit:
338 key = edit
339 values['show_stats'] = False
340 values['show_value'] = False
341 values['show_valueform'] = True
342 values['types'] = [typestr for _, _, typestr in self.TYPES]
343 elif key:
344 values['show_stats'] = True
345 values['show_value'] = True
346 values['show_valueform'] = False
347 else:
348 values['show_stats'] = True
349 values['show_valueform'] = False
350 values['show_value'] = False
352 if key:
353 values['key'] = key
354 values['value'], values['type'] = self._GetValueAndType(key)
355 values['key_exists'] = values['value'] is not None
357 if values['type'] in ('pickled', 'error'):
358 values['writable'] = False
359 else:
360 values['writable'] = True
362 if values['show_stats']:
363 memcache_stats = memcache.get_stats()
364 if not memcache_stats:
365 memcache_stats = {'hits': 0, 'misses': 0, 'byte_hits': 0, 'items': 0,
366 'bytes': 0, 'oldest_item_age': 0}
367 values['stats'] = memcache_stats
368 try:
369 hitratio = memcache_stats['hits'] * 100 / (memcache_stats['hits']
370 + memcache_stats['misses'])
371 except ZeroDivisionError:
372 hitratio = 0
373 values['hitratio'] = hitratio
374 delta_t = datetime.timedelta(seconds=memcache_stats['oldest_item_age'])
375 values['oldest_item_age'] = datetime.datetime.now() - delta_t
377 self.generate('memcache.html', values)
379 def _urlencode(self, query):
380 """Encode a dictionary into a URL query string.
382 In contrast to urllib this encodes unicode characters as UTF8.
384 Args:
385 query: Dictionary of key/value pairs.
387 Returns:
388 String.
390 return '&'.join('%s=%s' % (urllib.quote_plus(k.encode('utf8')),
391 urllib.quote_plus(v.encode('utf8')))
392 for k, v in query.iteritems())
394 def post(self):
395 """Handle modifying actions and/or redirect to GET page."""
396 next_param = {}
398 if self.request.get('action:flush'):
399 if memcache.flush_all():
400 next_param['message'] = 'Cache flushed, all keys dropped.'
401 else:
402 next_param['message'] = 'Flushing the cache failed. Please try again.'
404 elif self.request.get('action:display'):
405 next_param['key'] = self.request.get('key')
407 elif self.request.get('action:edit'):
408 next_param['edit'] = self.request.get('key')
410 elif self.request.get('action:delete'):
411 key = self.request.get('key')
412 result = memcache.delete(key)
413 if result == memcache.DELETE_NETWORK_FAILURE:
414 next_param['message'] = ('ERROR: Network failure, key "%s" not deleted.'
415 % key)
416 elif result == memcache.DELETE_ITEM_MISSING:
417 next_param['message'] = 'Key "%s" not in cache.' % key
418 elif result == memcache.DELETE_SUCCESSFUL:
419 next_param['message'] = 'Key "%s" deleted.' % key
420 else:
421 next_param['message'] = ('Unknown return value. Key "%s" might still '
422 'exist.' % key)
424 elif self.request.get('action:save'):
425 key = self.request.get('key')
426 value = self.request.get('value')
427 type_ = self.request.get('type')
428 next_param['key'] = key
429 try:
430 if self._SetValue(key, type_, value):
431 next_param['message'] = 'Key "%s" saved.' % key
432 else:
433 next_param['message'] = 'ERROR: Failed to save key "%s".' % key
434 except ValueError, e:
435 next_param['message'] = 'ERROR: Unable to encode value: %s' % e
437 elif self.request.get('action:cancel'):
438 next_param['key'] = self.request.get('key')
440 else:
441 next_param['message'] = 'Unknown action.'
443 next = self.request.path_url
444 if next_param:
445 next = '%s?%s' % (next, self._urlencode(next_param))
446 self.redirect(next)
449 class DatastoreRequestHandler(BaseRequestHandler):
450 """The base request handler for our datastore admin pages.
452 We provide utility functions for quering the datastore and infering the
453 types of entity properties.
456 def start(self):
457 """Returns the santized "start" argument from the URL."""
458 return self.request.get_range('start', min_value=0, default=0)
460 def num(self):
461 """Returns the sanitized "num" argument from the URL."""
462 return self.request.get_range('num', min_value=1, max_value=100,
463 default=10)
465 def execute_query(self, start=0, num=0, no_order=False):
466 """Parses the URL arguments and executes the query.
468 We return a tuple (list of entities, total entity count).
470 If the appropriate URL arguments are not given, we return an empty
471 set of results and 0 for the entity count.
473 kind = self.request.get('kind')
474 if not kind:
475 return ([], 0)
476 query = datastore.Query(kind)
478 order = self.request.get('order')
479 order_type = self.request.get('order_type')
480 if order and order_type:
481 order_type = DataType.get_by_name(order_type).python_type()
482 if order.startswith('-'):
483 direction = datastore.Query.DESCENDING
484 order = order[1:]
485 else:
486 direction = datastore.Query.ASCENDING
487 try:
488 query.Order((order, order_type, direction))
489 except datastore_errors.BadArgumentError:
490 pass
492 if not start:
493 start = self.start()
494 if not num:
495 num = self.num()
496 total = query.Count()
497 entities = query.Get(start + num)[start:]
498 return (entities, total)
500 def get_key_values(self, entities):
501 """Returns the union of key names used by the given list of entities.
503 We return the union as a dictionary mapping the key names to a sample
504 value from one of the entities for the key name.
506 key_dict = {}
507 for entity in entities:
508 for key, value in entity.iteritems():
509 if key_dict.has_key(key):
510 key_dict[key].append(value)
511 else:
512 key_dict[key] = [value]
513 return key_dict
516 class DatastoreQueryHandler(DatastoreRequestHandler):
517 """Our main request handler that executes queries and lists entities.
519 We use execute_query() in our base request handler to parse URL arguments
520 and execute the datastore query.
523 PATH = '/datastore'
525 def get_kinds(self):
526 """Get sorted list of kind names the datastore knows about.
528 This should only be called in the development environment as GetSchema is
529 expensive and no caching is done.
531 schema = datastore_admin.GetSchema()
532 kinds = []
533 for entity_proto in schema:
534 kinds.append(entity_proto.key().path().element_list()[-1].type())
535 kinds.sort()
536 return kinds
538 def get(self):
539 """Formats the results from execute_query() for datastore.html.
541 The only complex part of that process is calculating the pager variables
542 to generate the Gooooogle pager at the bottom of the page.
544 result_set, total = self.execute_query()
545 key_values = self.get_key_values(result_set)
546 keys = key_values.keys()
547 keys.sort()
549 headers = []
550 for key in keys:
551 sample_value = key_values[key][0]
552 headers.append({
553 'name': key,
554 'type': DataType.get(sample_value).name(),
557 entities = []
558 edit_path = self.base_path() + DatastoreEditHandler.PATH
559 for entity in result_set:
560 attributes = []
561 for key in keys:
562 if entity.has_key(key):
563 raw_value = entity[key]
564 value = DataType.get(raw_value).format(raw_value)
565 short_value = DataType.get(raw_value).short_format(raw_value)
566 else:
567 value = ''
568 short_value = ''
569 attributes.append({
570 'name': key,
571 'value': value,
572 'short_value': short_value,
574 entities.append({
575 'key': str(entity.key()),
576 'key_name': entity.key().name(),
577 'key_id': entity.key().id(),
578 'shortened_key': str(entity.key())[:8] + '...',
579 'attributes': attributes,
580 'edit_uri': edit_path + '?key=' + str(entity.key()) + '&kind=' + urllib.quote(self.request.get('kind')) + '&next=' + urllib.quote(self.request.uri),
583 start = self.start()
584 num = self.num()
585 max_pager_links = 8
586 current_page = start / num
587 num_pages = int(math.ceil(total * 1.0 / num))
588 page_start = max(math.floor(current_page - max_pager_links / 2), 0)
589 page_end = min(page_start + max_pager_links, num_pages)
591 pages = []
592 for page in range(page_start + 1, page_end + 1):
593 pages.append({
594 'number': page,
595 'start': (page - 1) * num,
597 current_page += 1
599 in_production = self.in_production()
600 if in_production:
601 kinds = None
602 else:
603 kinds = self.get_kinds()
605 values = {
606 'request': self.request,
607 'in_production': in_production,
608 'kinds': kinds,
609 'kind': self.request.get('kind'),
610 'order': self.request.get('order'),
611 'headers': headers,
612 'entities': entities,
613 'message': self.request.get('msg'),
614 'pages': pages,
615 'current_page': current_page,
616 'num': num,
617 'next_start': -1,
618 'prev_start': -1,
619 'start': start,
620 'total': total,
621 'start_base_url': self.filter_url(['kind', 'order', 'order_type',
622 'num']),
623 'order_base_url': self.filter_url(['kind', 'num']),
625 if current_page > 1:
626 values['prev_start'] = int((current_page - 2) * num)
627 if current_page < num_pages:
628 values['next_start'] = int(current_page * num)
630 self.generate('datastore.html', values)
633 class DatastoreBatchEditHandler(DatastoreRequestHandler):
634 """Request handler for a batch operation on entities.
636 Supports deleting multiple entities by key, then redirecting to another url.
639 PATH = DatastoreQueryHandler.PATH + '/batchedit'
641 def post(self):
642 kind = self.request.get('kind')
644 keys = []
645 index = 0
646 num_keys = int(self.request.get('numkeys'))
647 for i in xrange(1, num_keys+1):
648 key = self.request.get('key%d' % i)
649 if key:
650 keys.append(key)
652 if self.request.get('action') == 'Delete':
653 num_deleted = 0
654 for key in keys:
655 datastore.Delete(datastore.Key(key))
656 num_deleted = num_deleted + 1
657 message = '%d entit%s deleted.' % (
658 num_deleted, ('ies', 'y')[num_deleted == 1])
659 self.redirect(
660 '%s&msg=%s' % (self.request.get('next'), urllib.quote_plus(message)))
661 return
663 self.error(404)
666 class DatastoreEditHandler(DatastoreRequestHandler):
667 """Request handler for the entity create/edit form.
669 We determine how to generate a form to edit an entity by doing a query
670 on the entity kind and looking at the set of keys and their types in
671 the result set. We use the DataType subclasses for those introspected types
672 to generate the form and parse the form results.
675 PATH = DatastoreQueryHandler.PATH + '/edit'
677 def get(self):
678 kind = self.request.get('kind')
679 sample_entities = self.execute_query()[0]
680 if len(sample_entities) < 1:
681 next_uri = self.request.get('next')
682 kind_param = 'kind=%s' % kind
683 if not kind_param in next_uri:
684 if '?' in next_uri:
685 next_uri += '&' + kind_param
686 else:
687 next_uri += '?' + kind_param
688 self.redirect(next_uri)
689 return
691 entity_key = self.request.get('key')
692 if entity_key:
693 key_instance = datastore.Key(entity_key)
694 entity_key_name = key_instance.name()
695 entity_key_id = key_instance.id()
696 parent_key = key_instance.parent()
697 entity = datastore.Get(key_instance)
698 else:
699 key_instance = None
700 entity_key_name = None
701 entity_key_id = None
702 parent_key = None
703 entity = None
705 if parent_key:
706 parent_kind = parent_key.kind()
707 else:
708 parent_kind = None
710 fields = []
711 key_values = self.get_key_values(sample_entities)
712 for key, sample_values in key_values.iteritems():
713 if entity and entity.has_key(key):
714 data_type = DataType.get(entity[key])
715 else:
716 data_type = DataType.get(sample_values[0])
717 name = data_type.name() + "|" + key
718 if entity and entity.has_key(key):
719 value = entity[key]
720 else:
721 value = None
722 field = data_type.input_field(name, value, sample_values)
723 fields.append((key, data_type.name(), field))
725 self.generate('datastore_edit.html', {
726 'kind': kind,
727 'key': entity_key,
728 'key_name': entity_key_name,
729 'key_id': entity_key_id,
730 'fields': fields,
731 'focus': self.request.get('focus'),
732 'next': self.request.get('next'),
733 'parent_key': parent_key,
734 'parent_kind': parent_kind,
737 def post(self):
738 kind = self.request.get('kind')
739 entity_key = self.request.get('key')
740 if entity_key:
741 if self.request.get('action') == 'Delete':
742 datastore.Delete(datastore.Key(entity_key))
743 self.redirect(self.request.get('next'))
744 return
745 entity = datastore.Get(datastore.Key(entity_key))
746 else:
747 entity = datastore.Entity(kind)
749 args = self.request.arguments()
750 for arg in args:
751 bar = arg.find('|')
752 if bar > 0:
753 data_type_name = arg[:bar]
754 field_name = arg[bar + 1:]
755 form_value = self.request.get(arg)
756 data_type = DataType.get_by_name(data_type_name)
757 if entity and entity.has_key(field_name):
758 old_formatted_value = data_type.format(entity[field_name])
759 if old_formatted_value == form_value:
760 continue
762 if len(form_value) > 0:
763 value = data_type.parse(form_value)
764 entity[field_name] = value
765 elif entity.has_key(field_name):
766 del entity[field_name]
768 datastore.Put(entity)
770 self.redirect(self.request.get('next'))
773 class DataType(object):
774 """A DataType represents a data type in the datastore.
776 Each DataType subtype defines four methods:
778 format: returns a formatted string for a datastore value
779 input_field: returns a string HTML <input> element for this DataType
780 name: the friendly string name of this DataType
781 parse: parses the formatted string representation of this DataType
782 python_type: the canonical Python type for this datastore type
784 We use DataType instances to display formatted values in our result lists,
785 and we uses input_field/format/parse to generate forms and parse the results
786 from those forms to allow editing of entities.
788 @staticmethod
789 def get(value):
790 return _DATA_TYPES[value.__class__]
792 @staticmethod
793 def get_by_name(name):
794 return _NAMED_DATA_TYPES[name]
796 def format(self, value):
797 return str(value)
799 def short_format(self, value):
800 return self.format(value)
802 def input_field(self, name, value, sample_values):
803 if value is not None:
804 string_value = self.format(value)
805 else:
806 string_value = ''
807 return '<input class="%s" name="%s" type="text" size="%d" value="%s"/>' % (cgi.escape(self.name()), cgi.escape(name), self.input_field_size(),
808 cgi.escape(string_value, True))
810 def input_field_size(self):
811 return 30
814 class StringType(DataType):
815 def format(self, value):
816 return value
818 def input_field(self, name, value, sample_values):
819 multiline = False
820 if value:
821 multiline = len(value) > 255 or value.find('\n') >= 0
822 if not multiline:
823 for sample_value in sample_values:
824 if sample_value and (len(sample_value) > 255 or
825 sample_value.find('\n') >= 0):
826 multiline = True
827 break
828 if multiline:
829 if not value:
830 value = ''
831 return '<textarea name="%s" rows="5" cols="50">%s</textarea>' % (cgi.escape(name), cgi.escape(value))
832 else:
833 return DataType.input_field(self, name, value, sample_values)
835 def name(self):
836 return 'string'
838 def parse(self, value):
839 return value
841 def python_type(self):
842 return str
844 def input_field_size(self):
845 return 50
848 class TextType(StringType):
849 def name(self):
850 return 'Text'
852 def input_field(self, name, value, sample_values):
853 return '<textarea name="%s" rows="5" cols="50">%s</textarea>' % (cgi.escape(name), cgi.escape(str(value)))
855 def parse(self, value):
856 return datastore_types.Text(value)
858 def python_type(self):
859 return datastore_types.Text
862 class BlobType(StringType):
863 def name(self):
864 return 'Blob'
866 def input_field(self, name, value, sample_values):
867 return '&lt;binary&gt;'
869 def format(self, value):
870 return '<binary>'
872 def python_type(self):
873 return datastore_types.Blob
876 class TimeType(DataType):
877 _FORMAT = '%Y-%m-%d %H:%M:%S'
879 def format(self, value):
880 return value.strftime(TimeType._FORMAT)
882 def name(self):
883 return 'datetime'
885 def parse(self, value):
886 return datetime.datetime(*(time.strptime(value, TimeType._FORMAT)[0:6]))
888 def python_type(self):
889 return datetime.datetime
892 class ListType(DataType):
893 def format(self, value):
894 value_file = cStringIO.StringIO()
895 try:
896 writer = csv.writer(value_file)
897 writer.writerow(value)
898 return value_file.getvalue()
899 finally:
900 value_file.close()
902 def name(self):
903 return 'list'
905 def parse(self, value):
906 value_file = cStringIO.StringIO(value)
907 try:
908 reader = csv.reader(value_file)
909 return reader.next()
910 finally:
911 value_file.close()
913 def python_type(self):
914 return list
917 class BoolType(DataType):
918 def name(self):
919 return 'bool'
921 def input_field(self, name, value, sample_values):
922 selected = { None: '', False: '', True: '' };
923 selected[value] = "selected"
924 return """<select class="%s" name="%s">
925 <option %s value=''></option>
926 <option %s value='0'>False</option>
927 <option %s value='1'>True</option></select>""" % (cgi.escape(self.name()), cgi.escape(name), selected[None],
928 selected[False], selected[True])
930 def parse(self, value):
931 if value.lower() is 'true':
932 return True
933 if value.lower() is 'false':
934 return False
935 return bool(int(value))
937 def python_type(self):
938 return bool
941 class NumberType(DataType):
942 def input_field_size(self):
943 return 10
946 class IntType(NumberType):
947 def name(self):
948 return 'int'
950 def parse(self, value):
951 return int(value)
953 def python_type(self):
954 return int
957 class LongType(NumberType):
958 def name(self):
959 return 'long'
961 def parse(self, value):
962 return long(value)
964 def python_type(self):
965 return long
968 class FloatType(NumberType):
969 def name(self):
970 return 'float'
972 def parse(self, value):
973 return float(value)
975 def python_type(self):
976 return float
979 class UserType(DataType):
980 def name(self):
981 return 'User'
983 def parse(self, value):
984 return users.User(value)
986 def python_type(self):
987 return users.User
989 def input_field_size(self):
990 return 15
992 class ReferenceType(DataType):
993 def name(self):
994 return 'Key'
996 def short_format(self, value):
997 return str(value)[:8] + '...'
999 def parse(self, value):
1000 return datastore_types.Key(value)
1002 def python_type(self):
1003 return datastore_types.Key
1005 def input_field_size(self):
1006 return 85
1009 class EmailType(StringType):
1010 def name(self):
1011 return 'Email'
1013 def parse(self, value):
1014 return datastore_types.Email(value)
1016 def python_type(self):
1017 return datastore_types.Email
1020 class CategoryType(StringType):
1021 def name(self):
1022 return 'Category'
1024 def parse(self, value):
1025 return datastore_types.Category(value)
1027 def python_type(self):
1028 return datastore_types.Category
1031 class LinkType(StringType):
1032 def name(self):
1033 return 'Link'
1035 def parse(self, value):
1036 return datastore_types.Link(value)
1038 def python_type(self):
1039 return datastore_types.Link
1042 class GeoPtType(DataType):
1043 def name(self):
1044 return 'GeoPt'
1046 def parse(self, value):
1047 return datastore_types.GeoPt(value)
1049 def python_type(self):
1050 return datastore_types.GeoPt
1053 class ImType(DataType):
1054 def name(self):
1055 return 'IM'
1057 def parse(self, value):
1058 return datastore_types.IM(value)
1060 def python_type(self):
1061 return datastore_types.IM
1064 class PhoneNumberType(StringType):
1065 def name(self):
1066 return 'PhoneNumber'
1068 def parse(self, value):
1069 return datastore_types.PhoneNumber(value)
1071 def python_type(self):
1072 return datastore_types.PhoneNumber
1075 class PostalAddressType(StringType):
1076 def name(self):
1077 return 'PostalAddress'
1079 def parse(self, value):
1080 return datastore_types.PostalAddress(value)
1082 def python_type(self):
1083 return datastore_types.PostalAddress
1086 class RatingType(NumberType):
1087 def name(self):
1088 return 'Rating'
1090 def parse(self, value):
1091 return datastore_types.Rating(value)
1093 def python_type(self):
1094 return datastore_types.Rating
1097 class NoneType(DataType):
1098 def name(self):
1099 return 'None'
1101 def parse(self, value):
1102 return None
1104 def format(self, value):
1105 return 'None'
1107 _DATA_TYPES = {
1108 types.NoneType: NoneType(),
1109 types.StringType: StringType(),
1110 types.UnicodeType: StringType(),
1111 datastore_types.Text: TextType(),
1112 datastore_types.Blob: BlobType(),
1113 types.BooleanType: BoolType(),
1114 types.IntType: IntType(),
1115 types.LongType: LongType(),
1116 types.FloatType: FloatType(),
1117 datetime.datetime: TimeType(),
1118 users.User: UserType(),
1119 datastore_types.Key: ReferenceType(),
1120 types.ListType: ListType(),
1121 datastore_types.Email: EmailType(),
1122 datastore_types.Category: CategoryType(),
1123 datastore_types.Link: LinkType(),
1124 datastore_types.GeoPt: GeoPtType(),
1125 datastore_types.IM: ImType(),
1126 datastore_types.PhoneNumber: PhoneNumberType(),
1127 datastore_types.PostalAddress: PostalAddressType(),
1128 datastore_types.Rating: RatingType(),
1131 _NAMED_DATA_TYPES = {}
1132 for data_type in _DATA_TYPES.values():
1133 _NAMED_DATA_TYPES[data_type.name()] = data_type
1136 def _ParseCronYaml():
1137 """Load the cron.yaml file and parse it."""
1138 cronyaml_files = 'cron.yaml', 'cron.yml'
1139 for cronyaml in cronyaml_files:
1140 try:
1141 fh = open(cronyaml, "r")
1142 except IOError:
1143 continue
1144 try:
1145 cron_info = croninfo.LoadSingleCron(fh)
1146 return cron_info
1147 finally:
1148 fh.close()
1149 return None
1152 def main():
1153 handlers = [
1154 ('.*' + DatastoreQueryHandler.PATH, DatastoreQueryHandler),
1155 ('.*' + DatastoreEditHandler.PATH, DatastoreEditHandler),
1156 ('.*' + DatastoreBatchEditHandler.PATH, DatastoreBatchEditHandler),
1157 ('.*' + InteractivePageHandler.PATH, InteractivePageHandler),
1158 ('.*' + InteractiveExecuteHandler.PATH, InteractiveExecuteHandler),
1159 ('.*' + MemcachePageHandler.PATH, MemcachePageHandler),
1160 ('.*' + ImageHandler.PATH, ImageHandler),
1161 ('.*', DefaultPageHandler),
1163 if HAVE_CRON:
1164 handlers.insert(0, ('.*' + CronPageHandler.PATH, CronPageHandler))
1165 application = webapp.WSGIApplication(handlers, debug=_DEBUG)
1166 wsgiref.handlers.CGIHandler().run(application)
1169 import django
1170 if django.VERSION[:2] < (0, 97):
1171 from django.template import defaultfilters
1172 def safe(text, dummy=None):
1173 return text
1174 defaultfilters.register.filter("safe", safe)
1177 if __name__ == '__main__':
1178 main()