thirdparty/google_appengine/google/appengine/ext/admin/__init__.py
changeset 149 f2e327a7c5de
parent 109 620f9b141567
child 297 35211afcd563
equal deleted inserted replaced
148:37505d64e57b 149:f2e327a7c5de
    26 import cStringIO
    26 import cStringIO
    27 import datetime
    27 import datetime
    28 import logging
    28 import logging
    29 import math
    29 import math
    30 import mimetypes
    30 import mimetypes
       
    31 import os
    31 import os.path
    32 import os.path
       
    33 import pickle
       
    34 import pprint
    32 import random
    35 import random
    33 import sys
    36 import sys
    34 import time
    37 import time
    35 import traceback
    38 import traceback
    36 import types
    39 import types
    37 import urllib
    40 import urllib
    38 import urlparse
    41 import urlparse
    39 import wsgiref.handlers
    42 import wsgiref.handlers
    40 
    43 
    41 from google.appengine.api import datastore
    44 from google.appengine.api import datastore
       
    45 from google.appengine.api import datastore_admin
    42 from google.appengine.api import datastore_types
    46 from google.appengine.api import datastore_types
    43 from google.appengine.api import datastore_errors
    47 from google.appengine.api import datastore_errors
       
    48 from google.appengine.api import memcache
    44 from google.appengine.api import users
    49 from google.appengine.api import users
    45 from google.appengine.ext import db
    50 from google.appengine.ext import db
    46 from google.appengine.ext import webapp
    51 from google.appengine.ext import webapp
    47 from google.appengine.ext.webapp import template
    52 from google.appengine.ext.webapp import template
    48 
    53 
    91   """
    96   """
    92 
    97 
    93   def generate(self, template_name, template_values={}):
    98   def generate(self, template_name, template_values={}):
    94     base_path = self.base_path()
    99     base_path = self.base_path()
    95     values = {
   100     values = {
       
   101       'application_name': self.request.environ['APPLICATION_ID'],
    96       'user': users.get_current_user(),
   102       'user': users.get_current_user(),
    97       'request': self.request,
   103       'request': self.request,
    98       'home_path': base_path + DefaultPageHandler.PATH,
   104       'home_path': base_path + DefaultPageHandler.PATH,
    99       'datastore_path': base_path + DatastoreQueryHandler.PATH,
   105       'datastore_path': base_path + DatastoreQueryHandler.PATH,
   100       'datastore_edit_path': base_path + DatastoreEditHandler.PATH,
   106       'datastore_edit_path': base_path + DatastoreEditHandler.PATH,
   101       'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH,
   107       'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH,
   102       'interactive_path': base_path + InteractivePageHandler.PATH,
   108       'interactive_path': base_path + InteractivePageHandler.PATH,
   103       'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH,
   109       'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH,
       
   110       'memcache_path': base_path + MemcachePageHandler.PATH,
   104     }
   111     }
   105     values.update(template_values)
   112     values.update(template_values)
   106     directory = os.path.dirname(__file__)
   113     directory = os.path.dirname(__file__)
   107     path = os.path.join(directory, os.path.join('templates', template_name))
   114     path = os.path.join(directory, os.path.join('templates', template_name))
   108     self.response.out.write(template.render(path, values, debug=_DEBUG))
   115     self.response.out.write(template.render(path, values, debug=_DEBUG))
   164   """
   171   """
   165 
   172 
   166   PATH = InteractivePageHandler.PATH + '/execute'
   173   PATH = InteractivePageHandler.PATH + '/execute'
   167 
   174 
   168   def post(self):
   175   def post(self):
   169     self.response.headers['Content-Type'] = 'text/plain'
       
   170 
       
   171     save_stdout = sys.stdout
   176     save_stdout = sys.stdout
       
   177     results_io = cStringIO.StringIO()
   172     try:
   178     try:
   173       sys.stdout = self.response.out
   179       sys.stdout = results_io
   174 
   180 
   175       code = self.request.get('code')
   181       code = self.request.get('code')
   176       code = code.replace("\r\n", "\n")
   182       code = code.replace("\r\n", "\n")
   177 
   183 
   178       try:
   184       try:
   179         compiled_code = compile(code, '<string>', 'exec')
   185         compiled_code = compile(code, '<string>', 'exec')
   180         exec(compiled_code, globals())
   186         exec(compiled_code, globals())
   181       except Exception, e:
   187       except Exception, e:
   182         lines = traceback.format_exception(*sys.exc_info())
   188         traceback.print_exc(file=results_io)
   183         self.response.out.write(''.join(lines))
       
   184     finally:
   189     finally:
   185       sys.stdout = save_stdout
   190       sys.stdout = save_stdout
       
   191 
       
   192     results = results_io.getvalue()
       
   193     self.generate('interactive-output.html', {'output': results})
       
   194 
       
   195 
       
   196 class MemcachePageHandler(BaseRequestHandler):
       
   197   """Shows stats about memcache and query form to get values."""
       
   198   PATH = '/memcache'
       
   199 
       
   200   TYPES = ((str, str, 'String'),
       
   201            (unicode, unicode, 'Unicode String'),
       
   202            (bool, lambda value: MemcachePageHandler._ToBool(value), 'Boolean'),
       
   203            (int, int, 'Integer'),
       
   204            (long, long, 'Long Integer'),
       
   205            (float, float, 'Float'))
       
   206   DEFAULT_TYPESTR_FOR_NEW = 'String'
       
   207 
       
   208   @staticmethod
       
   209   def _ToBool(string_value):
       
   210     """Convert string to boolean value.
       
   211 
       
   212     Args:
       
   213       string_value: A string.
       
   214 
       
   215     Returns:
       
   216       Boolean.  True if string_value is "true", False if string_value is
       
   217       "false".  This is case-insensitive.
       
   218 
       
   219     Raises:
       
   220       ValueError: string_value not "true" or "false".
       
   221     """
       
   222     string_value_low = string_value.lower()
       
   223     if string_value_low not in ('false', 'true'):
       
   224       raise ValueError('invalid literal for boolean: %s' % string_value)
       
   225     return string_value_low == 'true'
       
   226 
       
   227   def _GetValueAndType(self, key):
       
   228     """Fetch value from memcache and detect its type.
       
   229 
       
   230     Args:
       
   231       key: String
       
   232 
       
   233     Returns:
       
   234       (value, type), value is a Python object or None if the key was not set in
       
   235       the cache, type is a string describing the type of the value.
       
   236     """
       
   237     try:
       
   238       value = memcache.get(key)
       
   239     except (pickle.UnpicklingError, AttributeError, EOFError, ImportError,
       
   240             IndexError), e:
       
   241       msg = 'Failed to retrieve value from cache: %s' % e
       
   242       return msg, 'error'
       
   243 
       
   244     if value is None:
       
   245       return None, self.DEFAULT_TYPESTR_FOR_NEW
       
   246 
       
   247     for typeobj, _, typestr in self.TYPES:
       
   248       if isinstance(value, typeobj):
       
   249         break
       
   250     else:
       
   251       typestr = 'pickled'
       
   252       value = pprint.pformat(value, indent=2)
       
   253 
       
   254     return value, typestr
       
   255 
       
   256   def _SetValue(self, key, type_, value):
       
   257     """Convert a string value and store the result in memcache.
       
   258 
       
   259     Args:
       
   260       key: String
       
   261       type_: String, describing what type the value should have in the cache.
       
   262       value: String, will be converted according to type_.
       
   263 
       
   264     Returns:
       
   265       Result of memcache.set(ket, converted_value).  True if value was set.
       
   266 
       
   267     Raises:
       
   268       ValueError: Value can't be converted according to type_.
       
   269     """
       
   270     for _, converter, typestr in self.TYPES:
       
   271       if typestr == type_:
       
   272         value = converter(value)
       
   273         break
       
   274     else:
       
   275       raise ValueError('Type %s not supported.' % type_)
       
   276     return memcache.set(key, value)
       
   277 
       
   278   def get(self):
       
   279     """Show template and prepare stats and/or key+value to display/edit."""
       
   280     values = {'request': self.request,
       
   281               'message': self.request.get('message')}
       
   282 
       
   283     edit = self.request.get('edit')
       
   284     key = self.request.get('key')
       
   285     if edit:
       
   286       key = edit
       
   287       values['show_stats'] = False
       
   288       values['show_value'] = False
       
   289       values['show_valueform'] = True
       
   290       values['types'] = [typestr for _, _, typestr in self.TYPES]
       
   291     elif key:
       
   292       values['show_stats'] = True
       
   293       values['show_value'] = True
       
   294       values['show_valueform'] = False
       
   295     else:
       
   296       values['show_stats'] = True
       
   297       values['show_valueform'] = False
       
   298       values['show_value'] = False
       
   299 
       
   300     if key:
       
   301       values['key'] = key
       
   302       values['value'], values['type'] = self._GetValueAndType(key)
       
   303       values['key_exists'] = values['value'] is not None
       
   304 
       
   305       if values['type'] in ('pickled', 'error'):
       
   306         values['writable'] = False
       
   307       else:
       
   308         values['writable'] = True
       
   309 
       
   310     if values['show_stats']:
       
   311       memcache_stats = memcache.get_stats()
       
   312       values['stats'] = memcache_stats
       
   313       try:
       
   314         hitratio = memcache_stats['hits'] * 100 / (memcache_stats['hits']
       
   315                                                    + memcache_stats['misses'])
       
   316       except ZeroDivisionError:
       
   317         hitratio = 0
       
   318       values['hitratio'] = hitratio
       
   319       delta_t = datetime.timedelta(seconds=memcache_stats['oldest_item_age'])
       
   320       values['oldest_item_age'] = datetime.datetime.now() - delta_t
       
   321 
       
   322     self.generate('memcache.html', values)
       
   323 
       
   324   def _urlencode(self, query):
       
   325     """Encode a dictionary into a URL query string.
       
   326 
       
   327     In contrast to urllib this encodes unicode characters as UTF8.
       
   328 
       
   329     Args:
       
   330       query: Dictionary of key/value pairs.
       
   331 
       
   332     Returns:
       
   333       String.
       
   334     """
       
   335     return '&'.join('%s=%s' % (urllib.quote_plus(k.encode('utf8')),
       
   336                                urllib.quote_plus(v.encode('utf8')))
       
   337                     for k, v in query.iteritems())
       
   338 
       
   339   def post(self):
       
   340     """Handle modifying actions and/or redirect to GET page."""
       
   341     next_param = {}
       
   342 
       
   343     if self.request.get('action:flush'):
       
   344       if memcache.flush_all():
       
   345         next_param['message'] = 'Cache flushed, all keys dropped.'
       
   346       else:
       
   347         next_param['message'] = 'Flushing the cache failed.  Please try again.'
       
   348 
       
   349     elif self.request.get('action:display'):
       
   350       next_param['key'] = self.request.get('key')
       
   351 
       
   352     elif self.request.get('action:edit'):
       
   353       next_param['edit'] = self.request.get('key')
       
   354 
       
   355     elif self.request.get('action:delete'):
       
   356       key = self.request.get('key')
       
   357       result = memcache.delete(key)
       
   358       if result == memcache.DELETE_NETWORK_FAILURE:
       
   359         next_param['message'] = ('ERROR: Network failure, key "%s" not deleted.'
       
   360                                  % key)
       
   361       elif result == memcache.DELETE_ITEM_MISSING:
       
   362         next_param['message'] = 'Key "%s" not in cache.' % key
       
   363       elif result == memcache.DELETE_SUCCESSFUL:
       
   364         next_param['message'] = 'Key "%s" deleted.' % key
       
   365       else:
       
   366         next_param['message'] = ('Unknown return value.  Key "%s" might still '
       
   367                                  'exist.' % key)
       
   368 
       
   369     elif self.request.get('action:save'):
       
   370       key = self.request.get('key')
       
   371       value = self.request.get('value')
       
   372       type_ = self.request.get('type')
       
   373       next_param['key'] = key
       
   374       try:
       
   375         if self._SetValue(key, type_, value):
       
   376           next_param['message'] = 'Key "%s" saved.' % key
       
   377         else:
       
   378           next_param['message'] = 'ERROR: Failed to save key "%s".' % key
       
   379       except ValueError, e:
       
   380         next_param['message'] = 'ERROR: Unable to encode value: %s' % e
       
   381 
       
   382     elif self.request.get('action:cancel'):
       
   383       next_param['key'] = self.request.get('key')
       
   384 
       
   385     else:
       
   386       next_param['message'] = 'Unknown action.'
       
   387 
       
   388     next = self.request.path_url
       
   389     if next_param:
       
   390       next = '%s?%s' % (next, self._urlencode(next_param))
       
   391     self.redirect(next)
   186 
   392 
   187 
   393 
   188 class DatastoreRequestHandler(BaseRequestHandler):
   394 class DatastoreRequestHandler(BaseRequestHandler):
   189   """The base request handler for our datastore admin pages.
   395   """The base request handler for our datastore admin pages.
   190 
   396 
   258   We use execute_query() in our base request handler to parse URL arguments
   464   We use execute_query() in our base request handler to parse URL arguments
   259   and execute the datastore query.
   465   and execute the datastore query.
   260   """
   466   """
   261 
   467 
   262   PATH = '/datastore'
   468   PATH = '/datastore'
       
   469 
       
   470   SCHEMA_CACHE_TIMEOUT = 60
       
   471 
       
   472   def get_kinds(self, cache={}):
       
   473     """Return sorted list of kind names the datastore knows about.
       
   474 
       
   475     The list of kinds is cached for a short time.
       
   476     """
       
   477     server_software = os.environ['SERVER_SOFTWARE']
       
   478     in_production = not server_software.startswith('Development')
       
   479 
       
   480     if in_production and ('kinds' in cache):
       
   481       if cache['kinds_timestamp'] + self.SCHEMA_CACHE_TIMEOUT > time.time():
       
   482         return cache['kinds']
       
   483       else:
       
   484         del cache['kinds']
       
   485     schema = datastore_admin.GetSchema()
       
   486     kinds = []
       
   487     for entity_proto in schema:
       
   488       kinds.append(entity_proto.key().path().element_list()[-1].type())
       
   489     kinds.sort()
       
   490     if in_production:
       
   491       cache['kinds'] = kinds
       
   492       cache['kinds_timestamp'] = time.time()
       
   493     return kinds
   263 
   494 
   264   def get(self):
   495   def get(self):
   265     """Formats the results from execute_query() for datastore.html.
   496     """Formats the results from execute_query() for datastore.html.
   266 
   497 
   267     The only complex part of that process is calculating the pager variables
   498     The only complex part of that process is calculating the pager variables
   322       })
   553       })
   323     current_page += 1
   554     current_page += 1
   324 
   555 
   325     values = {
   556     values = {
   326       'request': self.request,
   557       'request': self.request,
       
   558       'kinds': self.get_kinds(),
   327       'kind': self.request.get('kind'),
   559       'kind': self.request.get('kind'),
   328       'order': self.request.get('order'),
   560       'order': self.request.get('order'),
   329       'headers': headers,
   561       'headers': headers,
   330       'entities': entities,
   562       'entities': entities,
   331       'message': self.request.get('msg'),
   563       'message': self.request.get('msg'),
   855     ('.*' + DatastoreQueryHandler.PATH, DatastoreQueryHandler),
  1087     ('.*' + DatastoreQueryHandler.PATH, DatastoreQueryHandler),
   856     ('.*' + DatastoreEditHandler.PATH, DatastoreEditHandler),
  1088     ('.*' + DatastoreEditHandler.PATH, DatastoreEditHandler),
   857     ('.*' + DatastoreBatchEditHandler.PATH, DatastoreBatchEditHandler),
  1089     ('.*' + DatastoreBatchEditHandler.PATH, DatastoreBatchEditHandler),
   858     ('.*' + InteractivePageHandler.PATH, InteractivePageHandler),
  1090     ('.*' + InteractivePageHandler.PATH, InteractivePageHandler),
   859     ('.*' + InteractiveExecuteHandler.PATH, InteractiveExecuteHandler),
  1091     ('.*' + InteractiveExecuteHandler.PATH, InteractiveExecuteHandler),
       
  1092     ('.*' + MemcachePageHandler.PATH, MemcachePageHandler),
   860     ('.*' + ImageHandler.PATH, ImageHandler),
  1093     ('.*' + ImageHandler.PATH, ImageHandler),
   861     ('.*', DefaultPageHandler),
  1094     ('.*', DefaultPageHandler),
   862   ], debug=_DEBUG)
  1095   ], debug=_DEBUG)
   863   wsgiref.handlers.CGIHandler().run(application)
  1096   wsgiref.handlers.CGIHandler().run(application)
   864 
  1097