thirdparty/google_appengine/google/appengine/ext/admin/__init__.py
changeset 109 620f9b141567
child 149 f2e327a7c5de
equal deleted inserted replaced
108:261778de26ff 109:620f9b141567
       
     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 '&lt;binary&gt;'
       
   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()