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."""
42 import wsgiref
.handlers
45 from google
.appengine
.cron
import groctimespecification
46 from google
.appengine
.api
import croninfo
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
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.
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
)
82 directory
= os
.path
.dirname(__file__
)
83 path
= os
.path
.join(directory
, 'templates', 'images', image_name
)
85 image_stream
= open(path
, 'rb')
87 logging
.error('Cannot open image %s: %s', image_name
, e
)
91 image_data
= image_stream
.read()
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()
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
,
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
))
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?
150 value
= self
.request
.get(arg
)
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.
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."""
170 if self
.request
.path
.endswith('/'):
171 base
= self
.request
.path
[:-1]
173 base
= self
.request
.path
174 self
.redirect(base
+ DatastoreQueryHandler
.PATH
)
177 class InteractivePageHandler(BaseRequestHandler
):
178 """Shows our interactive console HTML."""
179 PATH
= '/interactive'
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
192 PATH
= InteractivePageHandler
.PATH
+ '/execute'
195 save_stdout
= sys
.stdout
196 results_io
= cStringIO
.StringIO()
198 sys
.stdout
= results_io
200 code
= self
.request
.get('code')
201 code
= code
.replace("\r\n", "\n")
204 compiled_code
= compile(code
, '<string>', 'exec')
205 exec(compiled_code
, globals())
207 traceback
.print_exc(file=results_io
)
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."""
219 def get(self
, now
=None):
220 """Shows template displaying the configured cron jobs."""
222 now
= datetime
.datetime
.now()
223 values
= {'request': self
.request
}
224 cron_info
= _ParseCronYaml()
225 values
['cronjobs'] = []
226 values
['now'] = str(now
)
228 for entry
in cron_info
.cron
:
230 values
['cronjobs'].append(job
)
231 if entry
.description
:
232 job
['description'] = entry
.description
234 job
['description'] = '(no description)'
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)
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."""
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'
261 def _ToBool(string_value
):
262 """Convert string to boolean value.
265 string_value: A string.
268 Boolean. True if string_value is "true", False if string_value is
269 "false". This is case-insensitive.
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.
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.
290 value
= memcache
.get(key
)
291 except (pickle
.UnpicklingError
, AttributeError, EOFError, ImportError,
293 msg
= 'Failed to retrieve value from cache: %s' % e
297 return None, self
.DEFAULT_TYPESTR_FOR_NEW
299 for typeobj
, _
, typestr
in self
.TYPES
:
300 if isinstance(value
, typeobj
):
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.
313 type_: String, describing what type the value should have in the cache.
314 value: String, will be converted according to type_.
317 Result of memcache.set(ket, converted_value). True if value was set.
320 ValueError: Value can't be converted according to type_.
322 for _
, converter
, typestr
in self
.TYPES
:
324 value
= converter(value
)
327 raise ValueError('Type %s not supported.' % type_
)
328 return memcache
.set(key
, value
)
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')
339 values
['show_stats'] = False
340 values
['show_value'] = False
341 values
['show_valueform'] = True
342 values
['types'] = [typestr
for _
, _
, typestr
in self
.TYPES
]
344 values
['show_stats'] = True
345 values
['show_value'] = True
346 values
['show_valueform'] = False
348 values
['show_stats'] = True
349 values
['show_valueform'] = False
350 values
['show_value'] = False
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
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
369 hitratio
= memcache_stats
['hits'] * 100 / (memcache_stats
['hits']
370 + memcache_stats
['misses'])
371 except ZeroDivisionError:
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.
385 query: Dictionary of key/value pairs.
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())
395 """Handle modifying actions and/or redirect to GET page."""
398 if self
.request
.get('action:flush'):
399 if memcache
.flush_all():
400 next_param
['message'] = 'Cache flushed, all keys dropped.'
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.'
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
421 next_param
['message'] = ('Unknown return value. Key "%s" might still '
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
430 if self
._SetValue
(key
, type_
, value
):
431 next_param
['message'] = 'Key "%s" saved.' % key
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')
441 next_param
['message'] = 'Unknown action.'
443 next
= self
.request
.path_url
445 next
= '%s?%s' % (next
, self
._urlencode
(next_param
))
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.
457 """Returns the santized "start" argument from the URL."""
458 return self
.request
.get_range('start', min_value
=0, default
=0)
461 """Returns the sanitized "num" argument from the URL."""
462 return self
.request
.get_range('num', min_value
=1, max_value
=100,
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')
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
486 direction
= datastore
.Query
.ASCENDING
488 query
.Order((order
, order_type
, direction
))
489 except datastore_errors
.BadArgumentError
:
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.
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
)
512 key_dict
[key
] = [value
]
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.
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()
533 for entity_proto
in schema
:
534 kinds
.append(entity_proto
.key().path().element_list()[-1].type())
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()
551 sample_value
= key_values
[key
][0]
554 'type': DataType
.get(sample_value
).name(),
558 edit_path
= self
.base_path() + DatastoreEditHandler
.PATH
559 for entity
in result_set
:
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
)
572 'short_value': short_value
,
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
),
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
)
592 for page
in range(page_start
+ 1, page_end
+ 1):
595 'start': (page
- 1) * num
,
599 in_production
= self
.in_production()
603 kinds
= self
.get_kinds()
606 'request': self
.request
,
607 'in_production': in_production
,
609 'kind': self
.request
.get('kind'),
610 'order': self
.request
.get('order'),
612 'entities': entities
,
613 'message': self
.request
.get('msg'),
615 'current_page': current_page
,
621 'start_base_url': self
.filter_url(['kind', 'order', 'order_type',
623 'order_base_url': self
.filter_url(['kind', 'num']),
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'
642 kind
= self
.request
.get('kind')
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
)
652 if self
.request
.get('action') == 'Delete':
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])
660 '%s&msg=%s' % (self
.request
.get('next'), urllib
.quote_plus(message
)))
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'
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
:
685 next_uri
+= '&' + kind_param
687 next_uri
+= '?' + kind_param
688 self
.redirect(next_uri
)
691 entity_key
= self
.request
.get('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
)
700 entity_key_name
= None
706 parent_kind
= parent_key
.kind()
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
])
716 data_type
= DataType
.get(sample_values
[0])
717 name
= data_type
.name() + "|" + key
718 if entity
and entity
.has_key(key
):
722 field
= data_type
.input_field(name
, value
, sample_values
)
723 fields
.append((key
, data_type
.name(), field
))
725 self
.generate('datastore_edit.html', {
728 'key_name': entity_key_name
,
729 'key_id': entity_key_id
,
731 'focus': self
.request
.get('focus'),
732 'next': self
.request
.get('next'),
733 'parent_key': parent_key
,
734 'parent_kind': parent_kind
,
738 kind
= self
.request
.get('kind')
739 entity_key
= self
.request
.get('key')
741 if self
.request
.get('action') == 'Delete':
742 datastore
.Delete(datastore
.Key(entity_key
))
743 self
.redirect(self
.request
.get('next'))
745 entity
= datastore
.Get(datastore
.Key(entity_key
))
747 entity
= datastore
.Entity(kind
)
749 args
= self
.request
.arguments()
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
:
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.
790 return _DATA_TYPES
[value
.__class
__]
793 def get_by_name(name
):
794 return _NAMED_DATA_TYPES
[name
]
796 def format(self
, 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
)
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
):
814 class StringType(DataType
):
815 def format(self
, value
):
818 def input_field(self
, name
, value
, sample_values
):
821 multiline
= len(value
) > 255 or value
.find('\n') >= 0
823 for sample_value
in sample_values
:
824 if sample_value
and (len(sample_value
) > 255 or
825 sample_value
.find('\n') >= 0):
831 return '<textarea name="%s" rows="5" cols="50">%s</textarea>' % (cgi
.escape(name
), cgi
.escape(value
))
833 return DataType
.input_field(self
, name
, value
, sample_values
)
838 def parse(self
, value
):
841 def python_type(self
):
844 def input_field_size(self
):
848 class TextType(StringType
):
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
):
866 def input_field(self
, name
, value
, sample_values
):
867 return '<binary>'
869 def format(self
, value
):
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
)
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()
896 writer
= csv
.writer(value_file
)
897 writer
.writerow(value
)
898 return value_file
.getvalue()
905 def parse(self
, value
):
906 value_file
= cStringIO
.StringIO(value
)
908 reader
= csv
.reader(value_file
)
913 def python_type(self
):
917 class BoolType(DataType
):
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':
933 if value
.lower() is 'false':
935 return bool(int(value
))
937 def python_type(self
):
941 class NumberType(DataType
):
942 def input_field_size(self
):
946 class IntType(NumberType
):
950 def parse(self
, value
):
953 def python_type(self
):
957 class LongType(NumberType
):
961 def parse(self
, value
):
964 def python_type(self
):
968 class FloatType(NumberType
):
972 def parse(self
, value
):
975 def python_type(self
):
979 class UserType(DataType
):
983 def parse(self
, value
):
984 return users
.User(value
)
986 def python_type(self
):
989 def input_field_size(self
):
992 class ReferenceType(DataType
):
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
):
1009 class EmailType(StringType
):
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
):
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
):
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
):
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
):
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
):
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
):
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
):
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
):
1101 def parse(self
, value
):
1104 def format(self
, value
):
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
:
1141 fh
= open(cronyaml
, "r")
1145 cron_info
= croninfo
.LoadSingleCron(fh
)
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
),
1164 handlers
.insert(0, ('.*' + CronPageHandler
.PATH
, CronPageHandler
))
1165 application
= webapp
.WSGIApplication(handlers
, debug
=_DEBUG
)
1166 wsgiref
.handlers
.CGIHandler().run(application
)
1170 if django
.VERSION
[:2] < (0, 97):
1171 from django
.template
import defaultfilters
1172 def safe(text
, dummy
=None):
1174 defaultfilters
.register
.filter("safe", safe
)
1177 if __name__
== '__main__':