Commits

Vladimir Mihailenco committed 8215477 Merge

Merge with upstream

  • Participants
  • Parent commits 2d4a4e9, f9175cf

Comments (0)

Files changed (9)

 from djangotoolbox.db.base import NonrelDatabaseFeatures, \
     NonrelDatabaseOperations, NonrelDatabaseWrapper, NonrelDatabaseClient, \
     NonrelDatabaseValidation, NonrelDatabaseIntrospection
+from google.appengine.api.datastore import Query
 from urllib2 import HTTPError, URLError
 import logging
 import os
 import time
 
 REMOTE_API_SCRIPT = '$PYTHON_LIB/google/appengine/ext/remote_api/handler.py'
+DATASTORE_PATHS = {
+    'datastore_path': os.path.join(DATA_ROOT, 'datastore'),
+    'blobstore_path': os.path.join(DATA_ROOT, 'blobstore'),
+    'rdbms_sqlite_path': os.path.join(DATA_ROOT, 'rdbms'),
+    'prospective_search_path': os.path.join(DATA_ROOT, 'prospective-search'),
+}
 
 def auth_func():
     import getpass
     return appengine_rpc.HttpRpcServer(*args, ** kwargs)
 
 def get_datastore_paths(options):
-    """Returns a tuple with the path to the datastore and history file.
+    paths = {}
+    for key, path in DATASTORE_PATHS.items():
+        paths[key] = options.get(key, path)
+    return paths
 
-    The datastore is stored in the same location as dev_appserver uses by
-    default, but the name is altered to be unique to this project so multiple
-    Django projects can be developed on the same machine in parallel.
+def get_test_datastore_paths(options, inmemory=True):
+    paths = get_datastore_paths(options)
+    for key in paths:
+        paths[key] += '.test'
+    if inmemory:
+        for key in ('datastore_path', 'blobstore_path'):
+            paths[key] = None
+    return paths
 
-    Returns:
-      (datastore_path, history_path)
-    """
-    from google.appengine.tools import dev_appserver_main
-    datastore_path = options.get('datastore_path',
-                                 os.path.join(DATA_ROOT, 'datastore'))
-    blobstore_path = options.get('blobstore_path',
-                                 os.path.join(DATA_ROOT, 'blobstore'))
-    history_path = options.get('history_path',
-                               os.path.join(DATA_ROOT, 'history'))
-    return datastore_path, blobstore_path, history_path
-
-def get_test_datastore_paths(inmemory=True):
-    """Returns a tuple with the path to the test datastore and history file.
-
-    If inmemory is true, (None, None) is returned to request an in-memory
-    datastore. If inmemory is false the path returned will be similar to the path
-    returned by get_datastore_paths but with a different name.
-
-    Returns:
-      (datastore_path, history_path)
-    """
-    if inmemory:
-        return None, None, None
-    datastore_path, blobstore_path, history_path = get_datastore_paths()
-    datastore_path = datastore_path.replace('.datastore', '.testdatastore')
-    blobstore_path = blobstore_path.replace('.blobstore', '.testblobstore')
-    history_path = history_path.replace('.datastore', '.testdatastore')
-    return datastore_path, blobstore_path, history_path
-
-def destroy_datastore(*args):
+def destroy_datastore(paths):
     """Destroys the appengine datastore at the specified paths."""
-    for path in args:
+    for path in paths.values():
         if not path:
             continue
         try:
     pass
 
 class DatabaseIntrospection(NonrelDatabaseIntrospection):
-    pass
+    def table_names(self):
+        """Returns a list of names of all tables that exist in the database."""
+        return [kind.key().name() for kind in Query(kind='__kind__').Run()]
 
 class DatabaseWrapper(NonrelDatabaseWrapper):
     def __init__(self, *args, **kwds):
         if on_production_server:
             self.remote = False
         self.remote_app_id = options.get('REMOTE_APP_ID', appid)
+        self.high_replication = options.get('HIGH_REPLICATION', False)
+        self.domain = options.get('DOMAIN', 'appspot.com')
         self.remote_api_path = options.get('REMOTE_API_PATH', None)
         self.secure_remote_api = options.get('SECURE_REMOTE_API', True)
         self._setup_stubs()
 
     def _get_paths(self):
         if self.use_test_datastore:
-            return get_test_datastore_paths(self.test_datastore_inmemory)
+            return get_test_datastore_paths(self.settings_dict, self.test_datastore_inmemory)
         else:
             return get_datastore_paths(self.settings_dict)
 
         if not have_appserver:
             from google.appengine.tools import dev_appserver_main
             args = dev_appserver_main.DEFAULT_ARGS.copy()
-            args['datastore_path'], args['blobstore_path'], args['history_path'] = self._get_paths()
+            args.update(self._get_paths())
+            log_level = logging.getLogger().getEffectiveLevel()
+            logging.getLogger().setLevel(logging.WARNING)
             from google.appengine.tools import dev_appserver
             dev_appserver.SetupStubs(appid, **args)
+            logging.getLogger().setLevel(log_level)
         # If we're supposed to set up the remote_api, do that now.
         if self.remote:
             self.setup_remote()
                     self.remote_api_path = handler.url.split('(', 1)[0]
                     break
         self.remote = True
-        remote_url = 'https://%s.appspot.com%s' % (self.remote_app_id,
-                                                   self.remote_api_path)
+        server = '%s.%s' % (self.remote_app_id, self.domain)
+        remote_url = 'https://%s%s' % (server, self.remote_api_path)
         logging.info('Setting up remote_api for "%s" at %s' %
                      (self.remote_app_id, remote_url))
         if not have_appserver:
                   'App Engine Dashboard if you have problems logging in. '
                   'Login is only supported for Google Accounts.\n')
         from google.appengine.ext.remote_api import remote_api_stub
-        remote_api_stub.ConfigureRemoteApi(self.remote_app_id,
-            self.remote_api_path, auth_func, secure=self.secure_remote_api,
+        remote_app_id = self.remote_app_id
+        if self.high_replication:
+            remote_app_id = 's~' + remote_app_id
+        remote_api_stub.ConfigureRemoteApi(remote_app_id,
+            self.remote_api_path, auth_func, servername=server,
+            secure=self.secure_remote_api,
             rpc_server_factory=rpc_server_factory)
         retry_delay = 1
         while retry_delay <= 16:
                 print 'Aborting'
                 exit()
         else:
-            destroy_datastore(*self._get_paths())
+            destroy_datastore(self._get_paths())
         self._setup_stubs()

File db/compiler.py

             value = self.convert_value_for_db(db_type, value)
             if isinstance(value, Text):
                 raise DatabaseError('TextField is not indexed, by default, '
-                                    "so you can't filter on it. "
-                                    'Please add an index definition for the '
-                                    'column "%s" as described here:\n'
+                                    "so you can't filter on it. Please add "
+                                    'an index definition for the column %s '
+                                    'on the model %s.%s as described here:\n'
                                     'http://www.allbuttonspressed.com/blog/django/2010/07/Managing-per-field-indexes-on-App-Engine'
-                                    % column)
+                                    % (column, self.query.model.__module__, self.query.model.__name__))
             if key in query:
                 existing_value = query[key]
                 if isinstance(existing_value, list):
 
     @safe_call
     def _build_query(self):
+        for query in self.gae_query:
+            query.Order(*self.gae_ordering)
         if len(self.gae_query) > 1:
             return MultiQuery(self.gae_query, self.gae_ordering)
-        query = self.gae_query[0]
-        query.Order(*self.gae_ordering)
-        return query
+        return self.gae_query[0]
 
     def get_matching_pk(self, low_mark=0, high_mark=None):
         if not self.pk_filters:

File db/creation.py

     def destroy_test_db(self, *args, **kw):
         """Destroys the test datastore files."""
         from .base import destroy_datastore, get_test_datastore_paths
-        destroy_datastore(*get_test_datastore_paths())
+        destroy_datastore(get_test_datastore_paths(self.connection.settings_dict))
     if end is not None:
         end = Cursor.from_websafe_string(end)
     queryset.query._gae_end_cursor = end
-    # Evaluate QuerySet
     return queryset
+from email.MIMEBase import MIMEBase
+
 from django.core.mail.backends.base import BaseEmailBackend
 from django.core.mail import EmailMultiAlternatives
 from django.core.exceptions import ImproperlyConfigured
         if message.bcc:
             gmsg.bcc = list(message.bcc)
         if message.attachments:
-            gmsg.attachments = [(a[0], a[1]) for a in message.attachments]
-        if isinstance(message, EmailMultiAlternatives):  # look for HTML
+            # Must be populated with (filename, filecontents) tuples
+            attachments = []
+            for attachment in message.attachments:
+                if isinstance(attachment, MIMEBase):
+                    attachments.append((attachment.get_filename(),
+                                        attachment.get_payload(decode=True)))
+                else:
+                    attachments.append((attachment[0], attachment[1]))
+            gmsg.attachments = attachments
+        # Look for HTML alternative content
+        if isinstance(message, EmailMultiAlternatives):
             for content, mimetype in message.alternatives:
                 if mimetype == 'text/html':
                     gmsg.html = content

File management/commands/runserver.py

     for name in connections:
         connection = connections[name]
         if isinstance(connection, DatabaseWrapper):
-            p = connection._get_paths()
-            if '--datastore_path' not in args:
-                args.extend(['--datastore_path', p[0]])
-            if '--blobstore_path' not in args:
-                args.extend(['--blobstore_path', p[1]])
-            if '--history_path' not in args:
-                args.extend(['--history_path', p[2]])
+            for key, path in connection._get_paths().items():
+                # XXX/TODO: Remove this when SDK 1.4.3 is released
+                if key == 'prospective_search_path':
+                    continue
+
+                arg = '--' + key
+                if arg not in args:
+                    args.extend([arg, path])
             break
 
     # Reset logging level to INFO as dev_appserver will spew tons of debug logs

File settings_base.py

 DATABASES = {
     'default': {
         'ENGINE': 'djangoappengine.db',
+
+        # Other settings which you might want to override in your settings.py
+
+        # Activates high-replication support for remote_api
+        # 'HIGH_REPLICATION': True,
+
+        # Switch to the App Engine for Business domain
+        # 'DOMAIN': 'googleplex.com',
     },
 }
 
     'django.core.files.uploadhandler.MemoryFileUploadHandler',
 )
 
-CACHE_BACKEND = 'memcached://?timeout=0'
+CACHES = {
+    'default': {
+        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+        'TIMEOUT': 0,
+    }
+}
+
 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
 
 if not on_production_server:

File tests/backend.py

File contents unchanged.

File tests/filter.py

 from ..db.utils import get_cursor, set_cursor
 from .testmodels import FieldsWithOptionsModel, EmailModel, DateTimeModel, \
     OrderedModel, BlobModel
+from django.db import models
 from django.db.models import Q
 from django.db.utils import DatabaseError
 from django.test import TestCase
                                       'rasengan@naruto.com'])],
                           ['app-engine@scholardocs.com', 'rasengan@naruto.com'])
 
+    def test_in_with_order_by(self):
+        class Post(models.Model):
+            writer = models.IntegerField()
+            order = models.IntegerField()
+        Post(writer=1, order=1).save()
+        Post(writer=1, order=2).save()
+        Post(writer=1, order=3).save()
+        Post(writer=2, order=4).save()
+        Post(writer=2, order=5).save()
+        posts = Post.objects.filter(writer__in= [1,2]).order_by('order')
+        orders = [post.order for post in posts]
+        self.assertEqual(orders, range(1, 6))
+        posts = Post.objects.filter(writer__in= [1,2]).order_by('-order')
+        orders = [post.order for post in posts]
+        self.assertEqual(orders, range(5, 0, -1))
+
     def test_inequality(self):
         self.assertEquals([entity.email for entity in
                            FieldsWithOptionsModel.objects.exclude(