Browse Source

Fixed #1142 -- Added multiple database support.

This monster of a patch is the result of Alex Gaynor's 2009 Google Summer of Code project.
Congratulations to Alex for a job well done.

Big thanks also go to:
 * Justin Bronn for keeping GIS in line with the changes,
 * Karen Tracey and Jani Tiainen for their help testing Oracle support
 * Brett Hoerner, Jon Loyens, and Craig Kimmerer for their feedback.
 * Malcolm Treddinick for his guidance during the GSoC submission process.
 * Simon Willison for driving the original design process
 * Cal Henderson for complaining about ponies he wanted.

... and everyone else too numerous to mention that helped to bring this feature into fruition.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11952 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Russell Keith-Magee 15 years ago
parent
commit
ff60c5f9de
100 changed files with 2917 additions and 3131 deletions
  1. 3 0
      django/conf/global_settings.py
  2. 10 6
      django/conf/project_template/settings.py
  3. 21 10
      django/contrib/admin/options.py
  4. 5 4
      django/contrib/admin/widgets.py
  5. 5 9
      django/contrib/auth/models.py
  6. 10 8
      django/contrib/comments/forms.py
  7. 4 4
      django/contrib/comments/models.py
  8. 2 2
      django/contrib/comments/views/comments.py
  9. 11 11
      django/contrib/contenttypes/generic.py
  10. 5 4
      django/contrib/contenttypes/management.py
  11. 10 10
      django/contrib/contenttypes/models.py
  12. 0 20
      django/contrib/gis/db/backend/__init__.py
  13. 0 26
      django/contrib/gis/db/backend/base.py
  14. 0 13
      django/contrib/gis/db/backend/mysql/__init__.py
  15. 0 5
      django/contrib/gis/db/backend/mysql/creation.py
  16. 0 53
      django/contrib/gis/db/backend/mysql/field.py
  17. 0 59
      django/contrib/gis/db/backend/mysql/query.py
  18. 0 35
      django/contrib/gis/db/backend/oracle/__init__.py
  19. 0 5
      django/contrib/gis/db/backend/oracle/adaptor.py
  20. 0 5
      django/contrib/gis/db/backend/oracle/creation.py
  21. 0 102
      django/contrib/gis/db/backend/oracle/field.py
  22. 0 154
      django/contrib/gis/db/backend/oracle/query.py
  23. 0 51
      django/contrib/gis/db/backend/postgis/__init__.py
  24. 0 231
      django/contrib/gis/db/backend/postgis/creation.py
  25. 0 95
      django/contrib/gis/db/backend/postgis/field.py
  26. 0 54
      django/contrib/gis/db/backend/postgis/management.py
  27. 0 313
      django/contrib/gis/db/backend/postgis/query.py
  28. 0 60
      django/contrib/gis/db/backend/spatialite/__init__.py
  29. 0 61
      django/contrib/gis/db/backend/spatialite/creation.py
  30. 0 82
      django/contrib/gis/db/backend/spatialite/field.py
  31. 0 160
      django/contrib/gis/db/backend/spatialite/query.py
  32. 0 0
      django/contrib/gis/db/backends/__init__.py
  33. 1 1
      django/contrib/gis/db/backends/adapter.py
  34. 327 0
      django/contrib/gis/db/backends/base.py
  35. 0 0
      django/contrib/gis/db/backends/mysql/__init__.py
  36. 11 0
      django/contrib/gis/db/backends/mysql/base.py
  37. 18 0
      django/contrib/gis/db/backends/mysql/creation.py
  38. 64 0
      django/contrib/gis/db/backends/mysql/operations.py
  39. 0 0
      django/contrib/gis/db/backends/oracle/__init__.py
  40. 5 0
      django/contrib/gis/db/backends/oracle/adapter.py
  41. 10 0
      django/contrib/gis/db/backends/oracle/base.py
  42. 44 0
      django/contrib/gis/db/backends/oracle/compiler.py
  43. 42 0
      django/contrib/gis/db/backends/oracle/creation.py
  44. 9 4
      django/contrib/gis/db/backends/oracle/models.py
  45. 289 0
      django/contrib/gis/db/backends/oracle/operations.py
  46. 0 0
      django/contrib/gis/db/backends/postgis/__init__.py
  47. 3 4
      django/contrib/gis/db/backends/postgis/adapter.py
  48. 10 0
      django/contrib/gis/db/backends/postgis/base.py
  49. 60 0
      django/contrib/gis/db/backends/postgis/creation.py
  50. 3 2
      django/contrib/gis/db/backends/postgis/models.py
  51. 570 0
      django/contrib/gis/db/backends/postgis/operations.py
  52. 0 0
      django/contrib/gis/db/backends/spatialite/__init__.py
  53. 2 2
      django/contrib/gis/db/backends/spatialite/adapter.py
  54. 73 0
      django/contrib/gis/db/backends/spatialite/base.py
  55. 5 0
      django/contrib/gis/db/backends/spatialite/client.py
  56. 97 0
      django/contrib/gis/db/backends/spatialite/creation.py
  57. 4 3
      django/contrib/gis/db/backends/spatialite/models.py
  58. 329 0
      django/contrib/gis/db/backends/spatialite/operations.py
  59. 24 23
      django/contrib/gis/db/backends/util.py
  60. 5 22
      django/contrib/gis/db/models/aggregates.py
  61. 130 101
      django/contrib/gis/db/models/fields.py
  62. 1 5
      django/contrib/gis/db/models/manager.py
  63. 86 65
      django/contrib/gis/db/models/query.py
  64. 30 95
      django/contrib/gis/db/models/sql/aggregates.py
  65. 276 0
      django/contrib/gis/db/models/sql/compiler.py
  66. 1 3
      django/contrib/gis/db/models/sql/conversion.py
  67. 39 286
      django/contrib/gis/db/models/sql/query.py
  68. 0 39
      django/contrib/gis/db/models/sql/subqueries.py
  69. 37 84
      django/contrib/gis/db/models/sql/where.py
  70. 0 0
      django/contrib/gis/geometry/__init__.py
  71. 21 0
      django/contrib/gis/geometry/backend/__init__.py
  72. 3 0
      django/contrib/gis/geometry/backend/geos.py
  73. 0 233
      django/contrib/gis/models.py
  74. 13 11
      django/contrib/gis/sitemaps/views.py
  75. 12 74
      django/contrib/gis/tests/__init__.py
  76. 7 7
      django/contrib/gis/tests/distapp/tests.py
  77. 5 0
      django/contrib/gis/tests/geoapp/fixtures/initial_data.json
  78. 0 8
      django/contrib/gis/tests/geoapp/sql/city.mysql.sql
  79. 0 8
      django/contrib/gis/tests/geoapp/sql/city.oracle.sql
  80. 0 8
      django/contrib/gis/tests/geoapp/sql/city.postgresql_psycopg2.sql
  81. 0 8
      django/contrib/gis/tests/geoapp/sql/city.sqlite3.sql
  82. 0 0
      django/contrib/gis/tests/geoapp/sql/co.wkt
  83. 0 2
      django/contrib/gis/tests/geoapp/sql/country.mysql.sql
  84. 0 2
      django/contrib/gis/tests/geoapp/sql/country.postgresql_psycopg2.sql
  85. 0 2
      django/contrib/gis/tests/geoapp/sql/country.sqlite3.sql
  86. 0 0
      django/contrib/gis/tests/geoapp/sql/ks.wkt
  87. 0 0
      django/contrib/gis/tests/geoapp/sql/nz.wkt
  88. 0 3
      django/contrib/gis/tests/geoapp/sql/state.mysql.sql
  89. 0 3
      django/contrib/gis/tests/geoapp/sql/state.postgresql_psycopg2.sql
  90. 0 3
      django/contrib/gis/tests/geoapp/sql/state.sqlite3.sql
  91. 0 0
      django/contrib/gis/tests/geoapp/sql/tx.wkt
  92. 0 1
      django/contrib/gis/tests/geoapp/test_regress.py
  93. 120 142
      django/contrib/gis/tests/geoapp/tests.py
  94. 0 186
      django/contrib/gis/tests/geoapp/tests_mysql.py
  95. 15 11
      django/contrib/gis/tests/layermap/tests.py
  96. 0 1
      django/contrib/gis/tests/layermap/tests_mysql.py
  97. 11 11
      django/contrib/gis/tests/relatedapp/tests.py
  98. 0 1
      django/contrib/gis/tests/relatedapp/tests_mysql.py
  99. 10 8
      django/contrib/gis/tests/test_spatialrefsys.py
  100. 9 7
      django/contrib/gis/tests/utils.py

+ 3 - 0
django/conf/global_settings.py

@@ -131,6 +131,9 @@ DATABASE_HOST = ''             # Set to empty string for localhost. Not used wit
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
 
+DATABASES = {
+}
+
 # The email backend to use. For possible shortcuts see django.core.mail.
 # The default is to use the SMTP backend.
 # Third-party backends can be specified by providing a Python path

+ 10 - 6
django/conf/project_template/settings.py

@@ -9,12 +9,16 @@ ADMINS = (
 
 MANAGERS = ADMINS
 
-DATABASE_ENGINE = ''           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
-DATABASE_NAME = ''             # Or path to database file if using sqlite3.
-DATABASE_USER = ''             # Not used with sqlite3.
-DATABASE_PASSWORD = ''         # Not used with sqlite3.
-DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
-DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': '',                      # Or path to database file if using sqlite3.
+        'USER': '',                      # Not used with sqlite3.
+        'PASSWORD': '',                  # Not used with sqlite3.
+        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
+        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+    }
+}
 
 # Local time zone for this installation. Choices can be found here:
 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name

+ 21 - 10
django/contrib/admin/options.py

@@ -141,8 +141,9 @@ class BaseModelAdmin(object):
         """
         Get a form Field for a ForeignKey.
         """
+        db = kwargs.get('using')
         if db_field.name in self.raw_id_fields:
-            kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
+            kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
         elif db_field.name in self.radio_fields:
             kwargs['widget'] = widgets.AdminRadioSelect(attrs={
                 'class': get_ul_class(self.radio_fields[db_field.name]),
@@ -159,9 +160,10 @@ class BaseModelAdmin(object):
         # a field in admin.
         if not db_field.rel.through._meta.auto_created:
             return None
+        db = kwargs.get('using')
 
         if db_field.name in self.raw_id_fields:
-            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
+            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
             kwargs['help_text'] = ''
         elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
             kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
@@ -739,7 +741,7 @@ class ModelAdmin(BaseModelAdmin):
                 form_validated = False
                 new_object = self.model()
             prefixes = {}
-            for FormSet in self.get_formsets(request):
+            for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
                 prefix = FormSet.get_default_prefix()
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
                 if prefixes[prefix] != 1:
@@ -747,7 +749,7 @@ class ModelAdmin(BaseModelAdmin):
                 formset = FormSet(data=request.POST, files=request.FILES,
                                   instance=new_object,
                                   save_as_new=request.POST.has_key("_saveasnew"),
-                                  prefix=prefix)
+                                  prefix=prefix, queryset=inline.queryset(request))
                 formsets.append(formset)
             if all_valid(formsets) and form_validated:
                 self.save_model(request, new_object, form, change=False)
@@ -770,12 +772,14 @@ class ModelAdmin(BaseModelAdmin):
                     initial[k] = initial[k].split(",")
             form = ModelForm(initial=initial)
             prefixes = {}
-            for FormSet in self.get_formsets(request):
+            for FormSet, inline in zip(self.get_formsets(request),
+                                       self.inline_instances):
                 prefix = FormSet.get_default_prefix()
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
                 if prefixes[prefix] != 1:
                     prefix = "%s-%s" % (prefix, prefixes[prefix])
-                formset = FormSet(instance=self.model(), prefix=prefix)
+                formset = FormSet(instance=self.model(), prefix=prefix,
+                                  queryset=inline.queryset(request))
                 formsets.append(formset)
 
         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
@@ -837,13 +841,16 @@ class ModelAdmin(BaseModelAdmin):
                 form_validated = False
                 new_object = obj
             prefixes = {}
-            for FormSet in self.get_formsets(request, new_object):
+            for FormSet, inline in zip(self.get_formsets(request, new_object),
+                                       self.inline_instances):
                 prefix = FormSet.get_default_prefix()
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
                 if prefixes[prefix] != 1:
                     prefix = "%s-%s" % (prefix, prefixes[prefix])
                 formset = FormSet(request.POST, request.FILES,
-                                  instance=new_object, prefix=prefix)
+                                  instance=new_object, prefix=prefix,
+                                  queryset=inline.queryset(request))
+
                 formsets.append(formset)
 
             if all_valid(formsets) and form_validated:
@@ -859,12 +866,13 @@ class ModelAdmin(BaseModelAdmin):
         else:
             form = ModelForm(instance=obj)
             prefixes = {}
-            for FormSet in self.get_formsets(request, obj):
+            for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
                 prefix = FormSet.get_default_prefix()
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
                 if prefixes[prefix] != 1:
                     prefix = "%s-%s" % (prefix, prefixes[prefix])
-                formset = FormSet(instance=obj, prefix=prefix)
+                formset = FormSet(instance=obj, prefix=prefix,
+                                  queryset=inline.queryset(request))
                 formsets.append(formset)
 
         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
@@ -1187,6 +1195,9 @@ class InlineModelAdmin(BaseModelAdmin):
         form = self.get_formset(request).form
         return [(None, {'fields': form.base_fields.keys()})]
 
+    def queryset(self, request):
+        return self.model._default_manager.all()
+
 class StackedInline(InlineModelAdmin):
     template = 'admin/edit_inline/stacked.html'
 

+ 5 - 4
django/contrib/admin/widgets.py

@@ -102,8 +102,9 @@ class ForeignKeyRawIdWidget(forms.TextInput):
     A Widget for displaying ForeignKeys in the "raw_id" interface rather than
     in a <select> box.
     """
-    def __init__(self, rel, attrs=None):
+    def __init__(self, rel, attrs=None, using=None):
         self.rel = rel
+        self.db = using
         super(ForeignKeyRawIdWidget, self).__init__(attrs)
 
     def render(self, name, value, attrs=None):
@@ -148,7 +149,7 @@ class ForeignKeyRawIdWidget(forms.TextInput):
 
     def label_for_value(self, value):
         key = self.rel.get_related_field().name
-        obj = self.rel.to._default_manager.get(**{key: value})
+        obj = self.rel.to._default_manager.using(self.db).get(**{key: value})
         return '&nbsp;<strong>%s</strong>' % escape(truncate_words(obj, 14))
 
 class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
@@ -156,8 +157,8 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
     A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
     in a <select multiple> box.
     """
-    def __init__(self, rel, attrs=None):
-        super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
+    def __init__(self, rel, attrs=None, using=None):
+        super(ManyToManyRawIdWidget, self).__init__(rel, attrs, using=None)
 
     def render(self, name, value, attrs=None):
         attrs['class'] = 'vManyToManyRawIdAdminField'

+ 5 - 9
django/contrib/auth/models.py

@@ -3,19 +3,15 @@ import urllib
 
 from django.contrib import auth
 from django.core.exceptions import ImproperlyConfigured
-from django.db import models
+from django.db import models, DEFAULT_DB_ALIAS
 from django.db.models.manager import EmptyManager
 from django.contrib.contenttypes.models import ContentType
 from django.utils.encoding import smart_str
 from django.utils.hashcompat import md5_constructor, sha_constructor
 from django.utils.translation import ugettext_lazy as _
 
-UNUSABLE_PASSWORD = '!' # This will never be a valid hash
 
-try:
-    set
-except NameError:
-    from sets import Set as set   # Python 2.3 fallback
+UNUSABLE_PASSWORD = '!' # This will never be a valid hash
 
 def get_hexdigest(algorithm, salt, raw_password):
     """
@@ -114,7 +110,7 @@ class UserManager(models.Manager):
             user.set_password(password)
         else:
             user.set_unusable_password()
-        user.save()
+        user.save(using=self.db)
         return user
 
     def create_superuser(self, username, email, password):
@@ -122,7 +118,7 @@ class UserManager(models.Manager):
         u.is_staff = True
         u.is_active = True
         u.is_superuser = True
-        u.save()
+        u.save(using=self.db)
         return u
 
     def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
@@ -319,7 +315,7 @@ class User(models.Model):
             try:
                 app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
                 model = models.get_model(app_label, model_name)
-                self._profile_cache = model._default_manager.get(user__id__exact=self.id)
+                self._profile_cache = model._default_manager.using(self._state.db).get(user__id__exact=self.id)
                 self._profile_cache.user = self
             except (ImportError, ImproperlyConfigured):
                 raise SiteProfileNotAvailable

+ 10 - 8
django/contrib/comments/forms.py

@@ -28,7 +28,7 @@ class CommentSecurityForm(forms.Form):
             initial = {}
         initial.update(self.generate_security_data())
         super(CommentSecurityForm, self).__init__(data=data, initial=initial)
-        
+
     def security_errors(self):
         """Return just those errors associated with security"""
         errors = ErrorDict()
@@ -107,13 +107,13 @@ class CommentDetailsForm(CommentSecurityForm):
         """
         if not self.is_valid():
             raise ValueError("get_comment_object may only be called on valid forms")
-        
+
         CommentModel = self.get_comment_model()
         new = CommentModel(**self.get_comment_create_data())
         new = self.check_for_duplicate_comment(new)
-        
+
         return new
-        
+
     def get_comment_model(self):
         """
         Get the comment model to create with this form. Subclasses in custom
@@ -121,7 +121,7 @@ class CommentDetailsForm(CommentSecurityForm):
         check_for_duplicate_comment to provide custom comment models.
         """
         return Comment
-        
+
     def get_comment_create_data(self):
         """
         Returns the dict of data to be used to create a comment. Subclasses in
@@ -140,13 +140,15 @@ class CommentDetailsForm(CommentSecurityForm):
             is_public    = True,
             is_removed   = False,
         )
-        
+
     def check_for_duplicate_comment(self, new):
         """
         Check that a submitted comment isn't a duplicate. This might be caused
         by someone posting a comment twice. If it is a dup, silently return the *previous* comment.
         """
-        possible_duplicates = self.get_comment_model()._default_manager.filter(
+        possible_duplicates = self.get_comment_model()._default_manager.using(
+            self.target_object._state.db
+        ).filter(
             content_type = new.content_type,
             object_pk = new.object_pk,
             user_name = new.user_name,
@@ -156,7 +158,7 @@ class CommentDetailsForm(CommentSecurityForm):
         for old in possible_duplicates:
             if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment:
                 return old
-                
+
         return new
 
     def clean_comment(self):

+ 4 - 4
django/contrib/comments/models.py

@@ -79,10 +79,10 @@ class Comment(BaseCommentAbstractModel):
     def __unicode__(self):
         return "%s: %s..." % (self.name, self.comment[:50])
 
-    def save(self, force_insert=False, force_update=False):
+    def save(self, *args, **kwargs):
         if self.submit_date is None:
             self.submit_date = datetime.datetime.now()
-        super(Comment, self).save(force_insert, force_update)
+        super(Comment, self).save(*args, **kwargs)
 
     def _get_userinfo(self):
         """
@@ -185,7 +185,7 @@ class CommentFlag(models.Model):
         return "%s flag of comment ID %s by %s" % \
             (self.flag, self.comment_id, self.user.username)
 
-    def save(self, force_insert=False, force_update=False):
+    def save(self, *args, **kwargs):
         if self.flag_date is None:
             self.flag_date = datetime.datetime.now()
-        super(CommentFlag, self).save(force_insert, force_update)
+        super(CommentFlag, self).save(*args, **kwargs)

+ 2 - 2
django/contrib/comments/views/comments.py

@@ -25,7 +25,7 @@ class CommentPostBadRequest(http.HttpResponseBadRequest):
 
 @csrf_protect
 @require_POST
-def post_comment(request, next=None):
+def post_comment(request, next=None, using=None):
     """
     Post a comment.
 
@@ -50,7 +50,7 @@ def post_comment(request, next=None):
         return CommentPostBadRequest("Missing content_type or object_pk field.")
     try:
         model = models.get_model(*ctype.split(".", 1))
-        target = model._default_manager.get(pk=object_pk)
+        target = model._default_manager.using(using).get(pk=object_pk)
     except TypeError:
         return CommentPostBadRequest(
             "Invalid content_type value: %r" % escape(ctype))

+ 11 - 11
django/contrib/contenttypes/generic.py

@@ -5,7 +5,7 @@ Classes allowing "generic" relations through ContentType and object-id fields.
 from django.core.exceptions import ObjectDoesNotExist
 from django.db import connection
 from django.db.models import signals
-from django.db import models
+from django.db import models, DEFAULT_DB_ALIAS
 from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
 from django.db.models.loading import get_model
 from django.forms import ModelForm
@@ -45,14 +45,14 @@ class GenericForeignKey(object):
             kwargs[self.ct_field] = self.get_content_type(obj=value)
             kwargs[self.fk_field] = value._get_pk_val()
 
-    def get_content_type(self, obj=None, id=None):
+    def get_content_type(self, obj=None, id=None, using=None):
         # Convenience function using get_model avoids a circular import when
         # using this model
         ContentType = get_model("contenttypes", "contenttype")
         if obj:
-            return ContentType.objects.get_for_model(obj)
+             return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
         elif id:
-            return ContentType.objects.get_for_id(id)
+             return ContentType.objects.db_manager(using).get_for_id(id)
         else:
             # This should never happen. I love comments like this, don't you?
             raise Exception("Impossible arguments to GFK.get_content_type!")
@@ -73,7 +73,7 @@ class GenericForeignKey(object):
             f = self.model._meta.get_field(self.ct_field)
             ct_id = getattr(instance, f.get_attname(), None)
             if ct_id:
-                ct = self.get_content_type(id=ct_id)
+                ct = self.get_content_type(id=ct_id, using=instance._state.db)
                 try:
                     rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field))
                 except ObjectDoesNotExist:
@@ -149,7 +149,7 @@ class GenericRelation(RelatedField, Field):
     def get_internal_type(self):
         return "ManyToManyField"
 
-    def db_type(self):
+    def db_type(self, connection):
         # Since we're simulating a ManyToManyField, in effect, best return the
         # same db_type as well.
         return None
@@ -201,7 +201,7 @@ class ReverseGenericRelatedObjectsDescriptor(object):
             join_table = qn(self.field.m2m_db_table()),
             source_col_name = qn(self.field.m2m_column_name()),
             target_col_name = qn(self.field.m2m_reverse_name()),
-            content_type = ContentType.objects.get_for_model(instance),
+            content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(instance),
             content_type_field_name = self.field.content_type_field_name,
             object_id_field_name = self.field.object_id_field_name
         )
@@ -247,7 +247,7 @@ def create_generic_related_manager(superclass):
                 '%s__pk' % self.content_type_field_name : self.content_type.id,
                 '%s__exact' % self.object_id_field_name : self.pk_val,
             }
-            return superclass.get_query_set(self).filter(**query)
+            return superclass.get_query_set(self).using(self.instance._state.db).filter(**query)
 
         def add(self, *objs):
             for obj in objs:
@@ -255,17 +255,17 @@ def create_generic_related_manager(superclass):
                     raise TypeError, "'%s' instance expected" % self.model._meta.object_name
                 setattr(obj, self.content_type_field_name, self.content_type)
                 setattr(obj, self.object_id_field_name, self.pk_val)
-                obj.save()
+                obj.save(using=self.instance._state.db)
         add.alters_data = True
 
         def remove(self, *objs):
             for obj in objs:
-                obj.delete()
+                obj.delete(using=self.instance._state.db)
         remove.alters_data = True
 
         def clear(self):
             for obj in self.all():
-                obj.delete()
+                obj.delete(using=self.instance._state.db)
         clear.alters_data = True
 
         def create(self, **kwargs):

+ 5 - 4
django/contrib/contenttypes/management.py

@@ -7,21 +7,22 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
     Creates content types for models in the given app, removing any model
     entries that no longer have a matching model class.
     """
+    db = kwargs['db']
     ContentType.objects.clear_cache()
-    content_types = list(ContentType.objects.filter(app_label=app.__name__.split('.')[-2]))
+    content_types = list(ContentType.objects.using(db).filter(app_label=app.__name__.split('.')[-2]))
     app_models = get_models(app)
     if not app_models:
         return
     for klass in app_models:
         opts = klass._meta
         try:
-            ct = ContentType.objects.get(app_label=opts.app_label,
-                                         model=opts.object_name.lower())
+            ct = ContentType.objects.using(db).get(app_label=opts.app_label,
+                                                   model=opts.object_name.lower())
             content_types.remove(ct)
         except ContentType.DoesNotExist:
             ct = ContentType(name=smart_unicode(opts.verbose_name_raw),
                 app_label=opts.app_label, model=opts.object_name.lower())
-            ct.save()
+            ct.save(using=db)
             if verbosity >= 2:
                 print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
     # The presence of any remaining content types means the supplied app has an

+ 10 - 10
django/contrib/contenttypes/models.py

@@ -1,4 +1,4 @@
-from django.db import models
+from django.db import models, DEFAULT_DB_ALIAS
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode
 
@@ -10,7 +10,7 @@ class ContentTypeManager(models.Manager):
 
     def get_by_natural_key(self, app_label, model):
         try:
-            ct = self.__class__._cache[(app_label, model)]
+            ct = self.__class__._cache[self.db][(app_label, model)]
         except KeyError:
             ct = self.get(app_label=app_label, model=model)
         return ct
@@ -27,7 +27,7 @@ class ContentTypeManager(models.Manager):
             opts = model._meta
         key = (opts.app_label, opts.object_name.lower())
         try:
-            ct = self.__class__._cache[key]
+            ct = self.__class__._cache[self.db][key]
         except KeyError:
             # Load or create the ContentType entry. The smart_unicode() is
             # needed around opts.verbose_name_raw because name_raw might be a
@@ -37,7 +37,7 @@ class ContentTypeManager(models.Manager):
                 model = opts.object_name.lower(),
                 defaults = {'name': smart_unicode(opts.verbose_name_raw)},
             )
-            self._add_to_cache(ct)
+            self._add_to_cache(self.db, ct)
 
         return ct
 
@@ -47,12 +47,12 @@ class ContentTypeManager(models.Manager):
         (though ContentTypes are obviously not created on-the-fly by get_by_id).
         """
         try:
-            ct = self.__class__._cache[id]
+            ct = self.__class__._cache[self.db][id]
         except KeyError:
             # This could raise a DoesNotExist; that's correct behavior and will
             # make sure that only correct ctypes get stored in the cache dict.
             ct = self.get(pk=id)
-            self._add_to_cache(ct)
+            self._add_to_cache(self.db, ct)
         return ct
 
     def clear_cache(self):
@@ -64,12 +64,12 @@ class ContentTypeManager(models.Manager):
         """
         self.__class__._cache.clear()
 
-    def _add_to_cache(self, ct):
+    def _add_to_cache(self, using, ct):
         """Insert a ContentType into the cache."""
         model = ct.model_class()
         key = (model._meta.app_label, model._meta.object_name.lower())
-        self.__class__._cache[key] = ct
-        self.__class__._cache[ct.id] = ct
+        self.__class__._cache.setdefault(using, {})[key] = ct
+        self.__class__._cache.setdefault(using, {})[ct.id] = ct
 
 class ContentType(models.Model):
     name = models.CharField(max_length=100)
@@ -99,7 +99,7 @@ class ContentType(models.Model):
         method. The ObjectNotExist exception, if thrown, will not be caught,
         so code that calls this method should catch it.
         """
-        return self.model_class()._default_manager.get(**kwargs)
+        return self.model_class()._default_manager.using(self._state.db).get(**kwargs)
 
     def natural_key(self):
         return (self.app_label, self.model)

+ 0 - 20
django/contrib/gis/db/backend/__init__.py

@@ -1,20 +0,0 @@
-"""
- This module provides the backend for spatial SQL construction with Django.
-
- Specifically, this module will import the correct routines and modules
- needed for GeoDjango to interface with the spatial database.
-"""
-from django.conf import settings
-from django.contrib.gis.db.backend.util import gqn
-
-# Retrieving the necessary settings from the backend.
-if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
-    from django.contrib.gis.db.backend.postgis import create_test_spatial_db, get_geo_where_clause, SpatialBackend
-elif settings.DATABASE_ENGINE == 'oracle':
-    from django.contrib.gis.db.backend.oracle import create_test_spatial_db, get_geo_where_clause, SpatialBackend
-elif settings.DATABASE_ENGINE == 'mysql':
-    from django.contrib.gis.db.backend.mysql import create_test_spatial_db, get_geo_where_clause, SpatialBackend
-elif settings.DATABASE_ENGINE == 'sqlite3':
-    from django.contrib.gis.db.backend.spatialite import create_test_spatial_db, get_geo_where_clause, SpatialBackend
-else:
-    raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE)

+ 0 - 26
django/contrib/gis/db/backend/base.py

@@ -1,26 +0,0 @@
-"""
- This module holds the base `SpatialBackend` object, which is
- instantiated by each spatial backend with the features it has.
-"""
-# TODO: Create a `Geometry` protocol and allow user to use
-# different Geometry objects -- for now we just use GEOSGeometry.
-from django.contrib.gis.geos import GEOSGeometry, GEOSException
-
-class BaseSpatialBackend(object):
-    Geometry = GEOSGeometry
-    GeometryException = GEOSException
-
-    def __init__(self, **kwargs):
-        kwargs.setdefault('distance_functions', {})
-        kwargs.setdefault('limited_where', {})
-        for k, v in kwargs.iteritems(): setattr(self, k, v)
- 
-    def __getattr__(self, name):
-        """
-        All attributes of the spatial backend return False by default.
-        """
-        try:
-            return self.__dict__[name]
-        except KeyError:
-            return False
-

+ 0 - 13
django/contrib/gis/db/backend/mysql/__init__.py

@@ -1,13 +0,0 @@
-__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
-
-from django.contrib.gis.db.backend.base import BaseSpatialBackend
-from django.contrib.gis.db.backend.adaptor import WKTAdaptor
-from django.contrib.gis.db.backend.mysql.creation import create_test_spatial_db
-from django.contrib.gis.db.backend.mysql.field import MySQLGeoField
-from django.contrib.gis.db.backend.mysql.query import *
-
-SpatialBackend = BaseSpatialBackend(name='mysql', mysql=True,
-                                    gis_terms=MYSQL_GIS_TERMS,
-                                    select=GEOM_SELECT,
-                                    Adaptor=WKTAdaptor,
-                                    Field=MySQLGeoField)

+ 0 - 5
django/contrib/gis/db/backend/mysql/creation.py

@@ -1,5 +0,0 @@
-
-def create_test_spatial_db(verbosity=1, autoclobber=False):
-    "A wrapper over the MySQL `create_test_db` method."
-    from django.db import connection
-    connection.creation.create_test_db(verbosity, autoclobber)

+ 0 - 53
django/contrib/gis/db/backend/mysql/field.py

@@ -1,53 +0,0 @@
-from django.db import connection
-from django.db.models.fields import Field # Django base Field class
-from django.contrib.gis.db.backend.mysql.query import GEOM_FROM_TEXT
-
-# Quotename & geographic quotename, respectively.
-qn = connection.ops.quote_name
-
-class MySQLGeoField(Field):
-    """
-    The backend-specific geographic field for MySQL.
-    """
-
-    def _geom_index(self, style, db_table):
-        """
-        Creates a spatial index for the geometry column.  If MyISAM tables are
-        used an R-Tree index is created, otherwise a B-Tree index is created.
-        Thus, for best spatial performance, you should use MyISAM tables
-        (which do not support transactions).  For more information, see Ch.
-        16.6.1 of the MySQL 5.0 documentation.
-        """
-
-        # Getting the index name.
-        idx_name = '%s_%s_id' % (db_table, self.column)
-
-        sql = (style.SQL_KEYWORD('CREATE SPATIAL INDEX ') +
-               style.SQL_TABLE(qn(idx_name)) +
-               style.SQL_KEYWORD(' ON ') +
-               style.SQL_TABLE(qn(db_table)) + '(' +
-               style.SQL_FIELD(qn(self.column)) + ');')
-        return sql
-
-    def post_create_sql(self, style, db_table):
-        """
-        Returns SQL that will be executed after the model has been
-        created.
-        """
-        # Getting the geometric index for this Geometry column.
-        if self.spatial_index:
-            return (self._geom_index(style, db_table),)
-        else:
-            return ()
-
-    def db_type(self):
-        "The OpenGIS name is returned for the MySQL database column type."
-        return self.geom_type
-
-    def get_placeholder(self, value):
-        """
-        The placeholder here has to include MySQL's WKT constructor.  Because
-        MySQL does not support spatial transformations, there is no need to
-        modify the placeholder based on the contents of the given value.
-        """
-        return '%s(%%s)' % GEOM_FROM_TEXT

+ 0 - 59
django/contrib/gis/db/backend/mysql/query.py

@@ -1,59 +0,0 @@
-"""
- This module contains the spatial lookup types, and the `get_geo_where_clause`
- routine for MySQL.
-
- Please note that MySQL only supports bounding box queries, also
- known as MBRs (Minimum Bounding Rectangles).  Moreover, spatial
- indices may only be used on MyISAM tables -- if you need
- transactions, take a look at PostGIS.
-"""
-from django.db import connection
-qn = connection.ops.quote_name
-
-# To ease implementation, WKT is passed to/from MySQL.
-GEOM_FROM_TEXT = 'GeomFromText'
-GEOM_FROM_WKB = 'GeomFromWKB'
-GEOM_SELECT = 'AsText(%s)'
-
-# WARNING: MySQL is NOT compliant w/the OpenGIS specification and
-# _every_ one of these lookup types is on the _bounding box_ only.
-MYSQL_GIS_FUNCTIONS = {
-    'bbcontains' : 'MBRContains', # For consistency w/PostGIS API
-    'bboverlaps' : 'MBROverlaps', # .. ..
-    'contained' : 'MBRWithin',    # .. ..
-    'contains' : 'MBRContains',
-    'disjoint' : 'MBRDisjoint',
-    'equals' : 'MBREqual',
-    'exact' : 'MBREqual',
-    'intersects' : 'MBRIntersects',
-    'overlaps' : 'MBROverlaps',
-    'same_as' : 'MBREqual',
-    'touches' : 'MBRTouches',
-    'within' : 'MBRWithin',
-    }
-
-# This lookup type does not require a mapping.
-MISC_TERMS = ['isnull']
-
-# Assacceptable lookup types for Oracle spatial.
-MYSQL_GIS_TERMS  = MYSQL_GIS_FUNCTIONS.keys()
-MYSQL_GIS_TERMS += MISC_TERMS
-MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary
-
-def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
-    "Returns the SQL WHERE clause for use in MySQL spatial SQL construction."
-    # Getting the quoted field as `geo_col`.
-    geo_col = '%s.%s' % (qn(table_alias), qn(name))
-
-    # See if a MySQL Geometry function matches the lookup type next
-    lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False)
-    if lookup_info:
-        return "%s(%s, %%s)" % (lookup_info, geo_col)
-
-    # Handling 'isnull' lookup type
-    # TODO: Is this needed because MySQL cannot handle NULL
-    # geometries in its spatial indices.
-    if lookup_type == 'isnull':
-        return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
-
-    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))

+ 0 - 35
django/contrib/gis/db/backend/oracle/__init__.py

@@ -1,35 +0,0 @@
-__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
-
-from django.contrib.gis.db.backend.base import BaseSpatialBackend
-from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor
-from django.contrib.gis.db.backend.oracle.creation import create_test_spatial_db
-from django.contrib.gis.db.backend.oracle.field import OracleSpatialField
-from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
-from django.contrib.gis.db.backend.oracle.query import *
-
-SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
-                                    area=AREA,
-                                    centroid=CENTROID,
-                                    difference=DIFFERENCE,
-                                    distance=DISTANCE,
-                                    distance_functions=DISTANCE_FUNCTIONS,
-                                    extent=EXTENT,
-                                    gis_terms=ORACLE_SPATIAL_TERMS,
-                                    gml=ASGML,
-                                    intersection=INTERSECTION,
-                                    length=LENGTH,
-                                    limited_where = {'relate' : None},
-                                    num_geom=NUM_GEOM,
-                                    num_points=NUM_POINTS,
-                                    perimeter=LENGTH,
-                                    point_on_surface=POINT_ON_SURFACE,
-                                    select=GEOM_SELECT,
-                                    sym_difference=SYM_DIFFERENCE,
-                                    transform=TRANSFORM,
-                                    unionagg=UNIONAGG,
-                                    union=UNION,
-                                    Adaptor=OracleSpatialAdaptor,
-                                    Field=OracleSpatialField,
-                                    GeometryColumns=GeometryColumns,
-                                    SpatialRefSys=SpatialRefSys,
-                                    )

+ 0 - 5
django/contrib/gis/db/backend/oracle/adaptor.py

@@ -1,5 +0,0 @@
-from cx_Oracle import CLOB
-from django.contrib.gis.db.backend.adaptor import WKTAdaptor
-
-class OracleSpatialAdaptor(WKTAdaptor):
-    input_size = CLOB

+ 0 - 5
django/contrib/gis/db/backend/oracle/creation.py

@@ -1,5 +0,0 @@
-
-def create_test_spatial_db(verbosity=1, autoclobber=False):
-    "A wrapper over the Oracle `create_test_db` routine."
-    from django.db import connection
-    connection.creation.create_test_db(verbosity, autoclobber)

+ 0 - 102
django/contrib/gis/db/backend/oracle/field.py

@@ -1,102 +0,0 @@
-from django.db import connection
-from django.db.backends.util import truncate_name
-from django.db.models.fields import Field # Django base Field class
-from django.contrib.gis.db.backend.util import gqn
-from django.contrib.gis.db.backend.oracle.query import TRANSFORM
-
-# Quotename & geographic quotename, respectively.
-qn = connection.ops.quote_name
-
-class OracleSpatialField(Field):
-    """
-    The backend-specific geographic field for Oracle Spatial.
-    """
-
-    empty_strings_allowed = False
-
-    def __init__(self, extent=(-180.0, -90.0, 180.0, 90.0), tolerance=0.05, **kwargs):
-        """
-        Oracle Spatial backend needs to have the extent -- for projected coordinate
-        systems _you must define the extent manually_, since the coordinates are
-        for geodetic systems.  The `tolerance` keyword specifies the tolerance
-        for error (in meters), and defaults to 0.05 (5 centimeters).
-        """
-        # Oracle Spatial specific keyword arguments.
-        self._extent = extent
-        self._tolerance = tolerance
-        # Calling the Django field initialization.
-        super(OracleSpatialField, self).__init__(**kwargs)
-
-    def _add_geom(self, style, db_table):
-        """
-        Adds this geometry column into the Oracle USER_SDO_GEOM_METADATA
-        table.
-        """
-        # Checking the dimensions.
-        # TODO: Add support for 3D geometries.
-        if self.dim != 2:
-            raise Exception('3D geometries not yet supported on Oracle Spatial backend.')
-
-        # Constructing the SQL that will be used to insert information about
-        # the geometry column into the USER_GSDO_GEOM_METADATA table.
-        meta_sql = (style.SQL_KEYWORD('INSERT INTO ') +
-                    style.SQL_TABLE('USER_SDO_GEOM_METADATA') +
-                    ' (%s, %s, %s, %s)\n  ' % tuple(map(qn, ['TABLE_NAME', 'COLUMN_NAME', 'DIMINFO', 'SRID'])) +
-                    style.SQL_KEYWORD(' VALUES ') + '(\n    ' +
-                    style.SQL_TABLE(gqn(db_table)) + ',\n    ' +
-                    style.SQL_FIELD(gqn(self.column)) + ',\n    ' +
-                    style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n      ' +
-                    style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
-                    ("('LONG', %s, %s, %s),\n      " % (self._extent[0], self._extent[2], self._tolerance)) +
-                    style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
-                    ("('LAT', %s, %s, %s)\n    ),\n" % (self._extent[1], self._extent[3], self._tolerance)) +
-                    '    %s\n  );' % self.srid)
-        return meta_sql
-
-    def _geom_index(self, style, db_table):
-        "Creates an Oracle Geometry index (R-tree) for this geometry field."
-
-        # Getting the index name, Oracle doesn't allow object
-        # names > 30 characters.
-        idx_name = truncate_name('%s_%s_id' % (db_table, self.column), 30)
-
-        sql = (style.SQL_KEYWORD('CREATE INDEX ') +
-               style.SQL_TABLE(qn(idx_name)) +
-               style.SQL_KEYWORD(' ON ') +
-               style.SQL_TABLE(qn(db_table)) + '(' +
-               style.SQL_FIELD(qn(self.column)) + ') ' +
-               style.SQL_KEYWORD('INDEXTYPE IS ') +
-               style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';')
-        return sql
-
-    def post_create_sql(self, style, db_table):
-        """
-        Returns SQL that will be executed after the model has been
-        created.
-        """
-        # Getting the meta geometry information.
-        post_sql = self._add_geom(style, db_table)
-
-        # Getting the geometric index for this Geometry column.
-        if self.spatial_index:
-            return (post_sql, self._geom_index(style, db_table))
-        else:
-            return (post_sql,)
-
-    def db_type(self):
-        "The Oracle geometric data type is MDSYS.SDO_GEOMETRY."
-        return 'MDSYS.SDO_GEOMETRY'
-
-    def get_placeholder(self, value):
-        """
-        Provides a proper substitution value for Geometries that are not in the
-        SRID of the field.  Specifically, this routine will substitute in the
-        SDO_CS.TRANSFORM() function call.
-        """
-        if value is None:
-            return 'NULL'
-        elif value.srid != self.srid:
-            # Adding Transform() to the SQL placeholder.
-            return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (TRANSFORM, value.srid, self.srid)
-        else:
-            return 'SDO_GEOMETRY(%%s, %s)' % self.srid

+ 0 - 154
django/contrib/gis/db/backend/oracle/query.py

@@ -1,154 +0,0 @@
-"""
- This module contains the spatial lookup types, and the `get_geo_where_clause`
- routine for Oracle Spatial.
-
- Please note that WKT support is broken on the XE version, and thus
- this backend will not work on such platforms.  Specifically, XE lacks
- support for an internal JVM, and Java libraries are required to use
- the WKT constructors.
-"""
-import re
-from decimal import Decimal
-from django.db import connection
-from django.contrib.gis.db.backend.util import SpatialFunction
-from django.contrib.gis.measure import Distance
-qn = connection.ops.quote_name
-
-# The GML, distance, transform, and union procedures.
-AREA = 'SDO_GEOM.SDO_AREA'
-ASGML = 'SDO_UTIL.TO_GMLGEOMETRY'
-CENTROID = 'SDO_GEOM.SDO_CENTROID'
-DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE'
-DISTANCE = 'SDO_GEOM.SDO_DISTANCE'
-EXTENT = 'SDO_AGGR_MBR'
-INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION'
-LENGTH = 'SDO_GEOM.SDO_LENGTH'
-NUM_GEOM = 'SDO_UTIL.GETNUMELEM'
-NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES'
-POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE'
-SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR'
-TRANSFORM = 'SDO_CS.TRANSFORM'
-UNION = 'SDO_GEOM.SDO_UNION'
-UNIONAGG = 'SDO_AGGR_UNION'
-
-# We want to get SDO Geometries as WKT because it is much easier to
-# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
-# However, this adversely affects performance (i.e., Java is called
-# to convert to WKT on every query).  If someone wishes to write a
-# SDO_GEOMETRY(...) parser in Python, let me know =)
-GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
-
-#### Classes used in constructing Oracle spatial SQL ####
-class SDOOperation(SpatialFunction):
-    "Base class for SDO* Oracle operations."
-    def __init__(self, func, **kwargs):
-        kwargs.setdefault('operator', '=')
-        kwargs.setdefault('result', 'TRUE')
-        kwargs.setdefault('end_subst', ") %s '%s'")
-        super(SDOOperation, self).__init__(func, **kwargs)
-
-class SDODistance(SpatialFunction):
-    "Class for Distance queries."
-    def __init__(self, op, tolerance=0.05):
-        super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
-                                          operator=op, result='%%s')
-
-class SDOGeomRelate(SpatialFunction):
-    "Class for using SDO_GEOM.RELATE."
-    def __init__(self, mask, tolerance=0.05):
-        # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
-        # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
-        end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
-        beg_subst = "%%s(%%s, '%s'" % mask
-        super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
-
-class SDORelate(SpatialFunction):
-    "Class for using SDO_RELATE."
-    masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
-    mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
-    def __init__(self, mask):
-        func = 'SDO_RELATE'
-        if not self.mask_regex.match(mask):
-            raise ValueError('Invalid %s mask: "%s"' % (func, mask))
-        super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask)
-
-#### Lookup type mapping dictionaries of Oracle spatial operations ####
-
-# Valid distance types and substitutions
-dtypes = (Decimal, Distance, float, int, long)
-DISTANCE_FUNCTIONS = {
-    'distance_gt' : (SDODistance('>'), dtypes),
-    'distance_gte' : (SDODistance('>='), dtypes),
-    'distance_lt' : (SDODistance('<'), dtypes),
-    'distance_lte' : (SDODistance('<='), dtypes),
-    'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
-                              beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes),
-    }
-
-ORACLE_GEOMETRY_FUNCTIONS = {
-    'contains' : SDOOperation('SDO_CONTAINS'),
-    'coveredby' : SDOOperation('SDO_COVEREDBY'),
-    'covers' : SDOOperation('SDO_COVERS'),
-    'disjoint' : SDOGeomRelate('DISJOINT'),
-    'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
-    'equals' : SDOOperation('SDO_EQUAL'),
-    'exact' : SDOOperation('SDO_EQUAL'),
-    'overlaps' : SDOOperation('SDO_OVERLAPS'),
-    'same_as' : SDOOperation('SDO_EQUAL'),
-    'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
-    'touches' : SDOOperation('SDO_TOUCH'),
-    'within' : SDOOperation('SDO_INSIDE'),
-    }
-ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
-
-# This lookup type does not require a mapping.
-MISC_TERMS = ['isnull']
-
-# Acceptable lookup types for Oracle spatial.
-ORACLE_SPATIAL_TERMS  = ORACLE_GEOMETRY_FUNCTIONS.keys()
-ORACLE_SPATIAL_TERMS += MISC_TERMS
-ORACLE_SPATIAL_TERMS = dict((term, None) for term in ORACLE_SPATIAL_TERMS) # Making dictionary for fast lookups
-
-#### The `get_geo_where_clause` function for Oracle ####
-def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
-    "Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
-    # Getting the quoted table name as `geo_col`.
-    geo_col = '%s.%s' % (qn(table_alias), qn(name))
-
-    # See if a Oracle Geometry function matches the lookup type next
-    lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
-    if lookup_info:
-        # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
-        # 'dwithin' lookup types.
-        if isinstance(lookup_info, tuple):
-            # First element of tuple is lookup type, second element is the type
-            # of the expected argument (e.g., str, float)
-            sdo_op, arg_type = lookup_info
-
-            # Ensuring that a tuple _value_ was passed in from the user
-            if not isinstance(geo_annot.value, tuple):
-                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
-            if len(geo_annot.value) != 2:
-                raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
-
-            # Ensuring the argument type matches what we expect.
-            if not isinstance(geo_annot.value[1], arg_type):
-                raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
-
-            if lookup_type == 'relate':
-                # The SDORelate class handles construction for these queries,
-                # and verifies the mask argument.
-                return sdo_op(geo_annot.value[1]).as_sql(geo_col)
-            else:
-                # Otherwise, just call the `as_sql` method on the SDOOperation instance.
-                return sdo_op.as_sql(geo_col)
-        else:
-            # Lookup info is a SDOOperation instance, whose `as_sql` method returns
-            # the SQL necessary for the geometry function call. For example:
-            #  SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
-            return lookup_info.as_sql(geo_col)
-    elif lookup_type == 'isnull':
-        # Handling 'isnull' lookup type
-        return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
-
-    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))

+ 0 - 51
django/contrib/gis/db/backend/postgis/__init__.py

@@ -1,51 +0,0 @@
-__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
-
-from django.contrib.gis.db.backend.base import BaseSpatialBackend
-from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor
-from django.contrib.gis.db.backend.postgis.creation import create_test_spatial_db
-from django.contrib.gis.db.backend.postgis.field import PostGISField
-from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
-from django.contrib.gis.db.backend.postgis.query import *
-
-SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
-                                    area=AREA,
-                                    centroid=CENTROID,
-                                    collect=COLLECT,
-                                    difference=DIFFERENCE,
-                                    distance=DISTANCE,
-                                    distance_functions=DISTANCE_FUNCTIONS,
-                                    distance_sphere=DISTANCE_SPHERE,
-                                    distance_spheroid=DISTANCE_SPHEROID,
-                                    envelope=ENVELOPE,
-                                    extent=EXTENT,
-                                    extent3d=EXTENT3D,
-                                    gis_terms=POSTGIS_TERMS,
-                                    geojson=ASGEOJSON,
-                                    gml=ASGML,
-                                    intersection=INTERSECTION,
-                                    kml=ASKML,
-                                    length=LENGTH,
-                                    length3d=LENGTH3D,
-                                    length_spheroid=LENGTH_SPHEROID,
-                                    make_line=MAKE_LINE,
-                                    mem_size=MEM_SIZE,
-                                    num_geom=NUM_GEOM,
-                                    num_points=NUM_POINTS,
-                                    perimeter=PERIMETER,
-                                    perimeter3d=PERIMETER3D,
-                                    point_on_surface=POINT_ON_SURFACE,
-                                    scale=SCALE,
-                                    select=GEOM_SELECT,
-                                    snap_to_grid=SNAP_TO_GRID,
-                                    svg=ASSVG,
-                                    sym_difference=SYM_DIFFERENCE,
-                                    transform=TRANSFORM,
-                                    translate=TRANSLATE,
-                                    union=UNION,
-                                    unionagg=UNIONAGG,
-                                    version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2),
-                                    Adaptor=PostGISAdaptor,
-                                    Field=PostGISField,
-                                    GeometryColumns=GeometryColumns,
-                                    SpatialRefSys=SpatialRefSys,
-                                    )

+ 0 - 231
django/contrib/gis/db/backend/postgis/creation.py

@@ -1,231 +0,0 @@
-import os, re, sys
-
-from django.conf import settings
-from django.core.management import call_command
-from django.db import connection
-from django.db.backends.creation import TEST_DATABASE_PREFIX
-from django.contrib.gis.db.backend.util import getstatusoutput
-
-def create_lang(db_name, verbosity=1):
-    "Sets up the pl/pgsql language on the given database."
-
-    # Getting the command-line options for the shell command
-    options = get_cmd_options(db_name)
-
-    # Constructing the 'createlang' command.
-    createlang_cmd = 'createlang %splpgsql' % options
-    if verbosity >= 1: print createlang_cmd
-
-    # Must have database super-user privileges to execute createlang -- it must
-    # also be in your path.
-    status, output = getstatusoutput(createlang_cmd)
-
-    # Checking the status of the command, 0 => execution successful
-    if status:
-        raise Exception("Error executing 'plpgsql' command: %s\n" % output)
-
-def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
-    "Creates database with psycopg2 cursor."
-    qn = connection.ops.quote_name
-
-    # Constructing the necessary SQL to create the database.
-    create_sql = 'CREATE DATABASE %s' % qn(db_name)
-
-    # If there's a template database for PostGIS set, then use it.
-    if hasattr(settings, 'POSTGIS_TEMPLATE'):
-        create_sql += ' TEMPLATE %s' % qn(settings.POSTGIS_TEMPLATE)
-
-    # The DATABASE_USER must possess the privileges to create a spatial database.
-    if settings.DATABASE_USER:
-        create_sql += ' OWNER %s' % qn(settings.DATABASE_USER)
-
-    cursor = connection.cursor()
-    connection.creation.set_autocommit()
-
-    try:
-        # Trying to create the database first.
-        cursor.execute(create_sql)
-    except Exception, e:
-        if 'already exists' in e.pgerror.lower():
-            # Database already exists, drop and recreate if user agrees.
-            if not autoclobber:
-                confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
-            if autoclobber or confirm == 'yes':
-                if verbosity >= 1: print 'Destroying old spatial database...'
-                drop_db(db_name)
-                if verbosity >= 1: print 'Creating new spatial database...'
-                cursor.execute(create_sql)
-            else:
-                raise Exception('Spatial database creation canceled.')
-        else:
-            raise Exception('Spatial database creation failed: "%s"' % e.pgerror.strip())
-
-created_regex = re.compile(r'^createdb: database creation failed: ERROR:  database ".+" already exists')
-def _create_with_shell(db_name, verbosity=1, autoclobber=False):
-    """
-    If no spatial database already exists, then using a cursor will not work.
-    Thus, a `createdb` command will be issued through the shell to bootstrap
-    creation of the spatial database.
-
-    TODO: Actually allow this method to be used without a spatial database
-    in place first.
-    """
-    # Getting the command-line options for the shell command
-    options = get_cmd_options(False)
-    if hasattr(settings, 'POSTGIS_TEMPLATE'):
-        options += '-T %s ' % settings.POSTGIS_TEMPlATE
-
-    create_cmd = 'createdb -O %s %s%s' % (settings.DATABASE_USER, options, db_name)
-    if verbosity >= 1: print create_cmd
-
-    # Attempting to create the database.
-    status, output = getstatusoutput(create_cmd)
-
-    if status:
-        if created_regex.match(output):
-            if not autoclobber:
-                confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
-            if autoclobber or confirm == 'yes':
-                if verbosity >= 1: print 'Destroying old spatial database...'
-                drop_cmd = 'dropdb %s%s' % (options, db_name)
-                status, output = getstatusoutput(drop_cmd)
-                if status != 0:
-                    raise Exception('Could not drop database %s: %s' % (db_name, output))
-                if verbosity >= 1: print 'Creating new spatial database...'
-                status, output = getstatusoutput(create_cmd)
-                if status != 0:
-                    raise Exception('Could not create database after dropping: %s' % output)
-            else:
-                raise Exception('Spatial Database Creation canceled.')
-        else:
-            raise Exception('Unknown error occurred in creating database: %s' % output)
-
-def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False):
-    "Creates a test spatial database based on the settings."
-
-    # Making sure we're using PostgreSQL and psycopg2
-    if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
-        raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.')
-
-    # Getting the spatial database name
-    db_name = get_spatial_db(test=True)
-    _create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber)
-
-    # If a template database is used, then don't need to do any of the following.
-    if not hasattr(settings, 'POSTGIS_TEMPLATE'):
-        # Creating the db language, does not need to be done on NT platforms
-        # since the PostGIS installer enables this capability.
-        if os.name != 'nt':
-            create_lang(db_name, verbosity=verbosity)
-
-        # Now adding in the PostGIS routines.
-        load_postgis_sql(db_name, verbosity=verbosity)
-
-    if verbosity >= 1: print 'Creation of spatial database %s successful.' % db_name
-
-    # Closing the connection
-    connection.close()
-    settings.DATABASE_NAME = db_name
-    connection.settings_dict["DATABASE_NAME"] = db_name
-    can_rollback = connection.creation._rollback_works()
-    settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback
-    connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback
-
-    # Syncing the database
-    call_command('syncdb', verbosity=verbosity, interactive=interactive)
-
-def drop_db(db_name=False, test=False):
-    """
-    Drops the given database (defaults to what is returned from
-    get_spatial_db()). All exceptions are propagated up to the caller.
-    """
-    if not db_name: db_name = get_spatial_db(test=test)
-    cursor = connection.cursor()
-    cursor.execute('DROP DATABASE %s' % connection.ops.quote_name(db_name))
-
-def get_cmd_options(db_name):
-    "Obtains the command-line PostgreSQL connection options for shell commands."
-    # The db_name parameter is optional
-    options = ''
-    if db_name:
-        options += '-d %s ' % db_name
-    if settings.DATABASE_USER:
-        options += '-U %s ' % settings.DATABASE_USER
-    if settings.DATABASE_HOST:
-        options += '-h %s ' % settings.DATABASE_HOST
-    if settings.DATABASE_PORT:
-        options += '-p %s ' % settings.DATABASE_PORT
-    return options
-
-def get_spatial_db(test=False):
-    """
-    Returns the name of the spatial database.  The 'test' keyword may be set
-    to return the test spatial database name.
-    """
-    if test:
-        if settings.TEST_DATABASE_NAME:
-            test_db_name = settings.TEST_DATABASE_NAME
-        else:
-            test_db_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
-        return test_db_name
-    else:
-        if not settings.DATABASE_NAME:
-            raise Exception('must configure DATABASE_NAME in settings.py')
-        return settings.DATABASE_NAME
-
-def load_postgis_sql(db_name, verbosity=1):
-    """
-    This routine loads up the PostGIS SQL files lwpostgis.sql and
-    spatial_ref_sys.sql.
-    """
-    # Getting the path to the PostGIS SQL
-    try:
-        # POSTGIS_SQL_PATH may be placed in settings to tell GeoDjango where the
-        # PostGIS SQL files are located.  This is especially useful on Win32
-        # platforms since the output of pg_config looks like "C:/PROGRA~1/..".
-        sql_path = settings.POSTGIS_SQL_PATH
-    except AttributeError:
-        status, sql_path = getstatusoutput('pg_config --sharedir')
-        if status:
-            sql_path = '/usr/local/share'
-
-    # The PostGIS SQL post-creation files.
-    lwpostgis_file = os.path.join(sql_path, 'lwpostgis.sql')
-    srefsys_file = os.path.join(sql_path, 'spatial_ref_sys.sql')
-    if not os.path.isfile(lwpostgis_file):
-        raise Exception('Could not find PostGIS function definitions in %s' % lwpostgis_file)
-    if not os.path.isfile(srefsys_file):
-        raise Exception('Could not find PostGIS spatial reference system definitions in %s' % srefsys_file)
-
-    # Getting the psql command-line options, and command format.
-    options = get_cmd_options(db_name)
-    cmd_fmt = 'psql %s-f "%%s"' % options
-
-    # Now trying to load up the PostGIS functions
-    cmd = cmd_fmt % lwpostgis_file
-    if verbosity >= 1: print cmd
-    status, output = getstatusoutput(cmd)
-    if status:
-        raise Exception('Error in loading PostGIS lwgeometry routines.')
-
-    # Now trying to load up the Spatial Reference System table
-    cmd = cmd_fmt % srefsys_file
-    if verbosity >= 1: print cmd
-    status, output = getstatusoutput(cmd)
-    if status:
-        raise Exception('Error in loading PostGIS spatial_ref_sys table.')
-
-    # Setting the permissions because on Windows platforms the owner
-    # of the spatial_ref_sys and geometry_columns tables is always
-    # the postgres user, regardless of how the db is created.
-    if os.name == 'nt': set_permissions(db_name)
-
-def set_permissions(db_name):
-    """
-    Sets the permissions on the given database to that of the user specified
-    in the settings.  Needed specifically for PostGIS on Win32 platforms.
-    """
-    cursor = connection.cursor()
-    user = settings.DATABASE_USER
-    cursor.execute('ALTER TABLE geometry_columns OWNER TO %s' % user)
-    cursor.execute('ALTER TABLE spatial_ref_sys OWNER TO %s' % user)

+ 0 - 95
django/contrib/gis/db/backend/postgis/field.py

@@ -1,95 +0,0 @@
-from django.db import connection
-from django.db.models.fields import Field # Django base Field class
-from django.contrib.gis.db.backend.util import gqn
-from django.contrib.gis.db.backend.postgis.query import TRANSFORM
-
-# Quotename & geographic quotename, respectively
-qn = connection.ops.quote_name
-
-class PostGISField(Field):
-    """
-    The backend-specific geographic field for PostGIS.
-    """
-
-    def _add_geom(self, style, db_table):
-        """
-        Constructs the addition of the geometry to the table using the
-        AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
-
-        Takes the style object (provides syntax highlighting) and the
-        database table as parameters.
-        """
-        sql = (style.SQL_KEYWORD('SELECT ') +
-               style.SQL_TABLE('AddGeometryColumn') + '(' +
-               style.SQL_TABLE(gqn(db_table)) + ', ' +
-               style.SQL_FIELD(gqn(self.column)) + ', ' +
-               style.SQL_FIELD(str(self.srid)) + ', ' +
-               style.SQL_COLTYPE(gqn(self.geom_type)) + ', ' +
-               style.SQL_KEYWORD(str(self.dim)) + ');')
-
-        if not self.null:
-            # Add a NOT NULL constraint to the field
-            sql += ('\n' +
-                    style.SQL_KEYWORD('ALTER TABLE ') +
-                    style.SQL_TABLE(qn(db_table)) +
-                    style.SQL_KEYWORD(' ALTER ') +
-                    style.SQL_FIELD(qn(self.column)) +
-                    style.SQL_KEYWORD(' SET NOT NULL') + ';')
-        return sql
-
-    def _geom_index(self, style, db_table,
-                    index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
-        "Creates a GiST index for this geometry field."
-        sql = (style.SQL_KEYWORD('CREATE INDEX ') +
-              style.SQL_TABLE(qn('%s_%s_id' % (db_table, self.column))) +
-              style.SQL_KEYWORD(' ON ') +
-              style.SQL_TABLE(qn(db_table)) +
-              style.SQL_KEYWORD(' USING ') +
-              style.SQL_COLTYPE(index_type) + ' ( ' +
-              style.SQL_FIELD(qn(self.column)) + ' ' +
-              style.SQL_KEYWORD(index_opts) + ' );')
-        return sql
-
-    def post_create_sql(self, style, db_table):
-        """
-        Returns SQL that will be executed after the model has been
-        created. Geometry columns must be added after creation with the
-        PostGIS AddGeometryColumn() function.
-        """
-
-        # Getting the AddGeometryColumn() SQL necessary to create a PostGIS
-        # geometry field.
-        post_sql = self._add_geom(style, db_table)
-
-        # If the user wants to index this data, then get the indexing SQL as well.
-        if self.spatial_index:
-            return (post_sql, self._geom_index(style, db_table))
-        else:
-            return (post_sql,)
-
-    def _post_delete_sql(self, style, db_table):
-        "Drops the geometry column."
-        sql = (style.SQL_KEYWORD('SELECT ') +
-               style.SQL_KEYWORD('DropGeometryColumn') + '(' +
-               style.SQL_TABLE(gqn(db_table)) + ', ' +
-               style.SQL_FIELD(gqn(self.column)) +  ');')
-        return sql
-
-    def db_type(self):
-        """
-        PostGIS geometry columns are added by stored procedures, should be
-        None.
-        """
-        return None
-
-    def get_placeholder(self, value):
-        """
-        Provides a proper substitution value for Geometries that are not in the
-        SRID of the field.  Specifically, this routine will substitute in the
-        ST_Transform() function call.
-        """
-        if value is None or value.srid == self.srid:
-            return '%s'
-        else:
-            # Adding Transform() to the SQL placeholder.
-            return '%s(%%s, %s)' % (TRANSFORM, self.srid)

+ 0 - 54
django/contrib/gis/db/backend/postgis/management.py

@@ -1,54 +0,0 @@
-"""
-  This utility module is for obtaining information about the PostGIS
-   installation.
-
-  See PostGIS docs at Ch. 6.2.1 for more information on these functions.
-"""
-import re
-
-def _get_postgis_func(func):
-    "Helper routine for calling PostGIS functions and returning their result."
-    from django.db import connection
-    cursor = connection.cursor()
-    cursor.execute('SELECT %s()' % func)
-    row = cursor.fetchone()
-    cursor.close()
-    return row[0]
-
-### PostGIS management functions ###
-def postgis_geos_version():
-    "Returns the version of the GEOS library used with PostGIS."
-    return _get_postgis_func('postgis_geos_version')
-
-def postgis_lib_version():
-    "Returns the version number of the PostGIS library used with PostgreSQL."
-    return _get_postgis_func('postgis_lib_version')
-
-def postgis_proj_version():
-    "Returns the version of the PROJ.4 library used with PostGIS."
-    return _get_postgis_func('postgis_proj_version')
-
-def postgis_version():
-    "Returns PostGIS version number and compile-time options."
-    return _get_postgis_func('postgis_version')
-
-def postgis_full_version():
-    "Returns PostGIS version number and compile-time options."
-    return _get_postgis_func('postgis_full_version')
-
-### Routines for parsing output of management functions. ###
-version_regex = re.compile('^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
-def postgis_version_tuple():
-    "Returns the PostGIS version as a tuple."
-
-    # Getting the PostGIS version
-    version = postgis_lib_version()
-    m = version_regex.match(version)
-    if m:
-        major = int(m.group('major'))
-        minor1 = int(m.group('minor1'))
-        minor2 = int(m.group('minor2'))
-    else:
-        raise Exception('Could not parse PostGIS version string: %s' % version)
-
-    return (version, major, minor1, minor2)

+ 0 - 313
django/contrib/gis/db/backend/postgis/query.py

@@ -1,313 +0,0 @@
-"""
- This module contains the spatial lookup types, and the get_geo_where_clause()
- routine for PostGIS.
-"""
-
-import re
-from decimal import Decimal
-from django.db import connection
-from django.conf import settings
-from django.contrib.gis.measure import Distance
-from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction
-
-qn = connection.ops.quote_name
-
-# Get the PostGIS version information.
-# To avoid the need to do a database query to determine the PostGIS version
-# each time the server starts up, one can optionally specify a
-# POSTGIS_VERSION setting. This setting is intentionally undocumented and
-# should be considered experimental, because an upcoming GIS backend
-# refactoring might remove the need for it.
-if hasattr(settings, 'POSTGIS_VERSION') and settings.POSTGIS_VERSION is not None:
-    version_tuple = settings.POSTGIS_VERSION
-else:
-    # This import is intentionally within the 'else' so that it isn't executed
-    # if the POSTGIS_VERSION setting is available.
-    from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple
-    version_tuple = postgis_version_tuple()
-POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = version_tuple
-
-# The supported PostGIS versions.
-#  TODO: Confirm tests with PostGIS versions 1.1.x -- should work.  
-#        Versions <= 1.0.x do not use GEOS C API, and will not be supported.
-if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1):
-    raise Exception('PostGIS version %s not supported.' % POSTGIS_VERSION)
-
-# Versions of PostGIS >= 1.2.2 changed their naming convention to be
-#  'SQL-MM-centric' to conform with the ISO standard. Practically, this
-#  means that 'ST_' prefixes geometry function names.
-GEOM_FUNC_PREFIX = ''
-if MAJOR_VERSION >= 1:
-    if (MINOR_VERSION1 > 2 or
-        (MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2)):
-        GEOM_FUNC_PREFIX = 'ST_'
-
-    def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
-
-    # Custom selection not needed for PostGIS because GEOS geometries are
-    # instantiated directly from the HEXEWKB returned by default.  If
-    # WKT is needed for some reason in the future, this value may be changed,
-    # e.g,, 'AsText(%s)'.
-    GEOM_SELECT = None
-
-    # Functions used by the GeoManager & GeoQuerySet
-    AREA = get_func('Area')
-    ASGEOJSON = get_func('AsGeoJson')
-    ASKML = get_func('AsKML')
-    ASGML = get_func('AsGML')
-    ASSVG = get_func('AsSVG')
-    CENTROID = get_func('Centroid')
-    COLLECT = get_func('Collect')
-    DIFFERENCE = get_func('Difference')
-    DISTANCE = get_func('Distance')
-    DISTANCE_SPHERE = get_func('distance_sphere')
-    DISTANCE_SPHEROID = get_func('distance_spheroid')
-    ENVELOPE = get_func('Envelope')
-    EXTENT = get_func('Extent')
-    EXTENT3D = get_func('Extent3D')
-    GEOM_FROM_TEXT = get_func('GeomFromText')
-    GEOM_FROM_EWKB = get_func('GeomFromEWKB')
-    GEOM_FROM_WKB = get_func('GeomFromWKB')
-    INTERSECTION = get_func('Intersection')
-    LENGTH = get_func('Length')
-    LENGTH3D = get_func('Length3D')
-    LENGTH_SPHEROID = get_func('length_spheroid')
-    MAKE_LINE = get_func('MakeLine')
-    MEM_SIZE = get_func('mem_size')
-    NUM_GEOM = get_func('NumGeometries')
-    NUM_POINTS = get_func('npoints')
-    PERIMETER = get_func('Perimeter')
-    PERIMETER3D = get_func('Perimeter3D')
-    POINT_ON_SURFACE = get_func('PointOnSurface')
-    SCALE = get_func('Scale')
-    SNAP_TO_GRID = get_func('SnapToGrid')
-    SYM_DIFFERENCE = get_func('SymDifference')
-    TRANSFORM = get_func('Transform')
-    TRANSLATE = get_func('Translate')
-
-    # Special cases for union, KML, and GeoJSON methods.
-    if MINOR_VERSION1 < 3:
-        UNIONAGG = 'GeomUnion'
-        UNION = 'Union'
-    else:
-        UNIONAGG = 'ST_Union'
-        UNION = 'ST_Union'
-
-    if MINOR_VERSION1 == 1:
-        ASKML = False
-
-    # Only 1.3.4+ have AsGeoJson.
-    if (MINOR_VERSION1 < 3 or 
-        (MINOR_VERSION1 == 3 and MINOR_VERSION2 < 4)):
-        ASGEOJSON = False
-else:
-    raise NotImplementedError('PostGIS versions < 1.0 are not supported.')
-
-#### Classes used in constructing PostGIS spatial SQL ####
-class PostGISOperator(SpatialOperation):
-    "For PostGIS operators (e.g. `&&`, `~`)."
-    def __init__(self, operator):
-        super(PostGISOperator, self).__init__(operator=operator, beg_subst='%s %s %%s')
-
-class PostGISFunction(SpatialFunction):
-    "For PostGIS function calls (e.g., `ST_Contains(table, geom)`)."
-    def __init__(self, function, **kwargs):
-        super(PostGISFunction, self).__init__(get_func(function), **kwargs)
-
-class PostGISFunctionParam(PostGISFunction):
-    "For PostGIS functions that take another parameter (e.g. DWithin, Relate)."
-    def __init__(self, func):
-        super(PostGISFunctionParam, self).__init__(func, end_subst=', %%s)')
-
-class PostGISDistance(PostGISFunction):
-    "For PostGIS distance operations."
-    dist_func = 'Distance'
-    def __init__(self, operator):
-        super(PostGISDistance, self).__init__(self.dist_func, end_subst=') %s %s', 
-                                              operator=operator, result='%%s')
-
-class PostGISSpheroidDistance(PostGISFunction):
-    "For PostGIS spherical distance operations (using the spheroid)."
-    dist_func = 'distance_spheroid'
-    def __init__(self, operator):
-        # An extra parameter in `end_subst` is needed for the spheroid string.
-        super(PostGISSpheroidDistance, self).__init__(self.dist_func, 
-                                                      beg_subst='%s(%s, %%s, %%s', 
-                                                      end_subst=') %s %s',
-                                                      operator=operator, result='%%s')
-
-class PostGISSphereDistance(PostGISFunction):
-    "For PostGIS spherical distance operations."
-    dist_func = 'distance_sphere'
-    def __init__(self, operator):
-        super(PostGISSphereDistance, self).__init__(self.dist_func, end_subst=') %s %s',
-                                                    operator=operator, result='%%s')
-                                                    
-class PostGISRelate(PostGISFunctionParam):
-    "For PostGIS Relate(<geom>, <pattern>) calls."
-    pattern_regex = re.compile(r'^[012TF\*]{9}$')
-    def __init__(self, pattern):
-        if not self.pattern_regex.match(pattern):
-            raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
-        super(PostGISRelate, self).__init__('Relate')
-
-#### Lookup type mapping dictionaries of PostGIS operations. ####
-
-# PostGIS-specific operators. The commented descriptions of these
-# operators come from Section 6.2.2 of the official PostGIS documentation.
-POSTGIS_OPERATORS = {
-    # The "&<" operator returns true if A's bounding box overlaps or
-    #  is to the left of B's bounding box.
-    'overlaps_left' : PostGISOperator('&<'),
-    # The "&>" operator returns true if A's bounding box overlaps or
-    #  is to the right of B's bounding box.
-    'overlaps_right' : PostGISOperator('&>'),
-    # The "<<" operator returns true if A's bounding box is strictly
-    #  to the left of B's bounding box.
-    'left' : PostGISOperator('<<'),
-    # The ">>" operator returns true if A's bounding box is strictly
-    #  to the right of B's bounding box.
-    'right' : PostGISOperator('>>'),
-    # The "&<|" operator returns true if A's bounding box overlaps or
-    #  is below B's bounding box.
-    'overlaps_below' : PostGISOperator('&<|'),
-    # The "|&>" operator returns true if A's bounding box overlaps or
-    #  is above B's bounding box.
-    'overlaps_above' : PostGISOperator('|&>'),
-    # The "<<|" operator returns true if A's bounding box is strictly
-    #  below B's bounding box.
-    'strictly_below' : PostGISOperator('<<|'),
-    # The "|>>" operator returns true if A's bounding box is strictly
-    # above B's bounding box.
-    'strictly_above' : PostGISOperator('|>>'),
-    # The "~=" operator is the "same as" operator. It tests actual
-    #  geometric equality of two features. So if A and B are the same feature,
-    #  vertex-by-vertex, the operator returns true.
-    'same_as' : PostGISOperator('~='),
-    'exact' : PostGISOperator('~='),
-    # The "@" operator returns true if A's bounding box is completely contained
-    #  by B's bounding box.
-    'contained' : PostGISOperator('@'),
-    # The "~" operator returns true if A's bounding box completely contains
-    #  by B's bounding box.
-    'bbcontains' : PostGISOperator('~'),
-    # The "&&" operator returns true if A's bounding box overlaps
-    #  B's bounding box.
-    'bboverlaps' : PostGISOperator('&&'),
-    }
-
-# For PostGIS >= 1.2.2 the following lookup types will do a bounding box query
-# first before calling the more computationally expensive GEOS routines (called
-# "inline index magic"):
-# 'touches', 'crosses', 'contains', 'intersects', 'within', 'overlaps', and
-# 'covers'.
-POSTGIS_GEOMETRY_FUNCTIONS = {
-    'equals' : PostGISFunction('Equals'),
-    'disjoint' : PostGISFunction('Disjoint'),
-    'touches' : PostGISFunction('Touches'),
-    'crosses' : PostGISFunction('Crosses'),
-    'within' : PostGISFunction('Within'),
-    'overlaps' : PostGISFunction('Overlaps'),
-    'contains' : PostGISFunction('Contains'),
-    'intersects' : PostGISFunction('Intersects'),
-    'relate' : (PostGISRelate, basestring),
-    }
-
-# Valid distance types and substitutions
-dtypes = (Decimal, Distance, float, int, long)
-def get_dist_ops(operator):
-    "Returns operations for both regular and spherical distances."
-    return (PostGISDistance(operator), PostGISSphereDistance(operator), PostGISSpheroidDistance(operator))
-DISTANCE_FUNCTIONS = {
-    'distance_gt' : (get_dist_ops('>'), dtypes),
-    'distance_gte' : (get_dist_ops('>='), dtypes),
-    'distance_lt' : (get_dist_ops('<'), dtypes),
-    'distance_lte' : (get_dist_ops('<='), dtypes),
-    }
-
-if GEOM_FUNC_PREFIX == 'ST_':
-    # The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+
-    POSTGIS_GEOMETRY_FUNCTIONS.update(
-        {'coveredby' : PostGISFunction('CoveredBy'),
-         'covers' : PostGISFunction('Covers'),
-         })
-    DISTANCE_FUNCTIONS['dwithin'] = (PostGISFunctionParam('DWithin'), dtypes)
-
-# Distance functions are a part of PostGIS geometry functions.
-POSTGIS_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
-
-# Any other lookup types that do not require a mapping.
-MISC_TERMS = ['isnull']
-
-# These are the PostGIS-customized QUERY_TERMS -- a list of the lookup types
-#  allowed for geographic queries.
-POSTGIS_TERMS = POSTGIS_OPERATORS.keys() # Getting the operators first
-POSTGIS_TERMS += POSTGIS_GEOMETRY_FUNCTIONS.keys() # Adding on the Geometry Functions
-POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
-POSTGIS_TERMS = dict((term, None) for term in POSTGIS_TERMS) # Making a dictionary for fast lookups
-
-# For checking tuple parameters -- not very pretty but gets job done.
-def exactly_two(val): return val == 2
-def two_to_three(val): return val >= 2 and val <=3
-def num_params(lookup_type, val):
-    if lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin': return two_to_three(val)
-    else: return exactly_two(val)
-
-#### The `get_geo_where_clause` function for PostGIS. ####
-def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
-    "Returns the SQL WHERE clause for use in PostGIS SQL construction."
-    # Getting the quoted field as `geo_col`.
-    geo_col = '%s.%s' % (qn(table_alias), qn(name))
-    if lookup_type in POSTGIS_OPERATORS:
-        # See if a PostGIS operator matches the lookup type.
-        return POSTGIS_OPERATORS[lookup_type].as_sql(geo_col)
-    elif lookup_type in POSTGIS_GEOMETRY_FUNCTIONS:
-        # See if a PostGIS geometry function matches the lookup type.
-        tmp = POSTGIS_GEOMETRY_FUNCTIONS[lookup_type]
-
-        # Lookup types that are tuples take tuple arguments, e.g., 'relate' and 
-        # distance lookups.
-        if isinstance(tmp, tuple):
-            # First element of tuple is the PostGISOperation instance, and the
-            # second element is either the type or a tuple of acceptable types
-            # that may passed in as further parameters for the lookup type.
-            op, arg_type = tmp
-
-            # Ensuring that a tuple _value_ was passed in from the user
-            if not isinstance(geo_annot.value, (tuple, list)): 
-                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
-           
-            # Number of valid tuple parameters depends on the lookup type.
-            nparams = len(geo_annot.value)
-            if not num_params(lookup_type, nparams):
-                raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
-            
-            # Ensuring the argument type matches what we expect.
-            if not isinstance(geo_annot.value[1], arg_type):
-                raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
-
-            # For lookup type `relate`, the op instance is not yet created (has
-            # to be instantiated here to check the pattern parameter).
-            if lookup_type == 'relate': 
-                op = op(geo_annot.value[1])
-            elif lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin':
-                if geo_annot.geodetic:
-                    # Geodetic distances are only availble from Points to PointFields.
-                    if geo_annot.geom_type != 'POINT':
-                        raise TypeError('PostGIS spherical operations are only valid on PointFields.')
-                    if geo_annot.value[0].geom_typeid != 0:
-                        raise TypeError('PostGIS geometry distance parameter is required to be of type Point.')
-                    # Setting up the geodetic operation appropriately.
-                    if nparams == 3 and geo_annot.value[2] == 'spheroid': op = op[2]
-                    else: op = op[1]
-                else:
-                    op = op[0]
-        else:
-            op = tmp
-        # Calling the `as_sql` function on the operation instance.
-        return op.as_sql(geo_col)
-    elif lookup_type == 'isnull':
-        # Handling 'isnull' lookup type
-        return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
-
-    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))

+ 0 - 60
django/contrib/gis/db/backend/spatialite/__init__.py

@@ -1,60 +0,0 @@
-__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
-
-from ctypes.util import find_library
-from django.conf import settings
-from django.db.backends.signals import connection_created
-
-from django.contrib.gis.db.backend.base import BaseSpatialBackend
-from django.contrib.gis.db.backend.spatialite.adaptor import SpatiaLiteAdaptor
-from django.contrib.gis.db.backend.spatialite.creation import create_test_spatial_db
-from django.contrib.gis.db.backend.spatialite.field import SpatiaLiteField
-from django.contrib.gis.db.backend.spatialite.models import GeometryColumns, SpatialRefSys
-from django.contrib.gis.db.backend.spatialite.query import *
-
-# Here we are figuring out the path to the SpatiLite library (`libspatialite`).
-# If it's not in the system PATH, it may be set manually in the settings via
-# the `SPATIALITE_LIBRARY_PATH` setting.
-spatialite_lib = getattr(settings, 'SPATIALITE_LIBRARY_PATH', find_library('spatialite'))
-if spatialite_lib:
-    def initialize_spatialite(sender=None, **kwargs):
-        """
-        This function initializes the pysqlite2 connection to enable the
-        loading of extensions, and to load up the SpatiaLite library
-        extension.
-        """
-        from django.db import connection
-        connection.connection.enable_load_extension(True)
-        connection.cursor().execute("SELECT load_extension(%s)", (spatialite_lib,))
-    connection_created.connect(initialize_spatialite)
-else:
-    # No SpatiaLite library found.
-    raise Exception('Unable to locate SpatiaLite, needed to use GeoDjango with sqlite3.')
-
-SpatialBackend = BaseSpatialBackend(name='spatialite', spatialite=True,
-                                    area=AREA,
-                                    centroid=CENTROID,
-                                    contained=CONTAINED,
-                                    difference=DIFFERENCE,
-                                    distance=DISTANCE,
-                                    distance_functions=DISTANCE_FUNCTIONS,
-                                    envelope=ENVELOPE,
-                                    from_text=GEOM_FROM_TEXT,
-                                    gis_terms=SPATIALITE_TERMS,
-                                    intersection=INTERSECTION,
-                                    length=LENGTH,
-                                    num_geom=NUM_GEOM,
-                                    num_points=NUM_POINTS,
-                                    point_on_surface=POINT_ON_SURFACE,
-                                    scale=SCALE,
-                                    select=GEOM_SELECT,
-                                    svg=ASSVG,
-                                    sym_difference=SYM_DIFFERENCE,
-                                    transform=TRANSFORM,
-                                    translate=TRANSLATE,
-                                    union=UNION,
-                                    unionagg=UNIONAGG,
-                                    Adaptor=SpatiaLiteAdaptor,
-                                    Field=SpatiaLiteField,
-                                    GeometryColumns=GeometryColumns,
-                                    SpatialRefSys=SpatialRefSys,
-                                    )

+ 0 - 61
django/contrib/gis/db/backend/spatialite/creation.py

@@ -1,61 +0,0 @@
-import os
-from django.conf import settings
-from django.core.management import call_command
-from django.db import connection
-
-def spatialite_init_file():
-    # SPATIALITE_SQL may be placed in settings to tell
-    # GeoDjango to use a specific user-supplied file.
-    return getattr(settings, 'SPATIALITE_SQL', 'init_spatialite-2.3.sql')
-
-def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False):
-    "Creates a spatial database based on the settings."
-
-    # Making sure we're using PostgreSQL and psycopg2
-    if settings.DATABASE_ENGINE != 'sqlite3':
-        raise Exception('SpatiaLite database creation only supported on sqlite3 platform.')
-
-    # Getting the test database name using the SQLite backend's
-    # `_create_test_db`.  Unless `TEST_DATABASE_NAME` is defined,
-    # it returns ":memory:".
-    db_name = connection.creation._create_test_db(verbosity, autoclobber)
-
-    # Closing out the current connection to the database set in
-    # originally in the settings.  This makes it so `initialize_spatialite`
-    # function will be run on the connection for the _test_ database instead.
-    connection.close()
-
-    # Point to the new database
-    settings.DATABASE_NAME = db_name
-    connection.settings_dict["DATABASE_NAME"] = db_name
-    can_rollback = connection.creation._rollback_works()
-    settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback
-    connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback
-
-    # Finally, loading up the SpatiaLite SQL file.
-    load_spatialite_sql(db_name, verbosity=verbosity)
-
-    if verbosity >= 1:
-        print 'Creation of spatial database %s successful.' % db_name
-
-    # Syncing the database
-    call_command('syncdb', verbosity=verbosity, interactive=interactive)
-
-def load_spatialite_sql(db_name, verbosity=1):
-    """
-    This routine loads up the SpatiaLite SQL file.
-    """
-    # Getting the location of the SpatiaLite SQL file, and confirming
-    # it exists.
-    spatialite_sql = spatialite_init_file()
-    if not os.path.isfile(spatialite_sql):
-        raise Exception('Could not find the SpatiaLite initialization SQL file: %s' % spatialite_sql)
-
-    # Opening up the SpatiaLite SQL initialization file and executing
-    # as a script.
-    sql_fh = open(spatialite_sql, 'r')
-    try:
-        cur = connection.cursor()
-        cur.executescript(sql_fh.read())
-    finally:
-        sql_fh.close()

+ 0 - 82
django/contrib/gis/db/backend/spatialite/field.py

@@ -1,82 +0,0 @@
-from django.db.models.fields import Field # Django base Field class
-
-# Quotename & geographic quotename, respectively
-from django.db import connection
-qn = connection.ops.quote_name
-from django.contrib.gis.db.backend.util import gqn
-from django.contrib.gis.db.backend.spatialite.query import GEOM_FROM_TEXT, TRANSFORM
-
-class SpatiaLiteField(Field):
-    """
-    The backend-specific geographic field for SpatiaLite.
-    """
-
-    def _add_geom(self, style, db_table):
-        """
-        Constructs the addition of the geometry to the table using the
-        AddGeometryColumn(...) OpenGIS stored procedure.
-
-        Takes the style object (provides syntax highlighting) and the
-        database table as parameters.
-        """
-        sql = (style.SQL_KEYWORD('SELECT ') +
-               style.SQL_TABLE('AddGeometryColumn') + '(' +
-               style.SQL_TABLE(gqn(db_table)) + ', ' +
-               style.SQL_FIELD(gqn(self.column)) + ', ' +
-               style.SQL_FIELD(str(self.srid)) + ', ' +
-               style.SQL_COLTYPE(gqn(self.geom_type)) + ', ' +
-               style.SQL_KEYWORD(str(self.dim)) + ', ' +
-               style.SQL_KEYWORD(str(int(not self.null))) +
-               ');')
-        return sql
-
-    def _geom_index(self, style, db_table):
-        "Creates a spatial index for this geometry field."
-        sql = (style.SQL_KEYWORD('SELECT ') +
-              style.SQL_TABLE('CreateSpatialIndex') + '(' +
-              style.SQL_TABLE(gqn(db_table)) + ', ' +
-              style.SQL_FIELD(gqn(self.column)) + ');')
-        return sql
-
-    def post_create_sql(self, style, db_table):
-        """
-        Returns SQL that will be executed after the model has been
-        created. Geometry columns must be added after creation with the
-        OpenGIS AddGeometryColumn() function.
-        """
-        # Getting the AddGeometryColumn() SQL necessary to create a OpenGIS
-        # geometry field.
-        post_sql = self._add_geom(style, db_table)
-
-        # If the user wants to index this data, then get the indexing SQL as well.
-        if self.spatial_index:
-            return (post_sql, self._geom_index(style, db_table))
-        else:
-            return (post_sql,)
-
-    def _post_delete_sql(self, style, db_table):
-        "Drops the geometry column."
-        sql = (style.SQL_KEYWORD('SELECT ') +
-               style.SQL_KEYWORD('DropGeometryColumn') + '(' +
-               style.SQL_TABLE(gqn(db_table)) + ', ' +
-               style.SQL_FIELD(gqn(self.column)) +  ');')
-        return sql
-
-    def db_type(self):
-        """
-        SpatiaLite geometry columns are added by stored procedures;
-        should be None.
-        """
-        return None
-
-    def get_placeholder(self, value):
-        """
-        Provides a proper substitution value for Geometries that are not in the
-        SRID of the field.  Specifically, this routine will substitute in the
-        Transform() and GeomFromText() function call(s).
-        """
-        if value is None or value.srid == self.srid:
-            return '%s(%%s,%s)' % (GEOM_FROM_TEXT, self.srid)
-        else:
-            # Adding Transform() to the SQL placeholder.
-            return '%s(%s(%%s,%s), %s)' % (TRANSFORM, GEOM_FROM_TEXT, value.srid, self.srid)

+ 0 - 160
django/contrib/gis/db/backend/spatialite/query.py

@@ -1,160 +0,0 @@
-"""
- This module contains the spatial lookup types, and the get_geo_where_clause()
- routine for SpatiaLite.
-"""
-import re
-from decimal import Decimal
-from django.db import connection
-from django.contrib.gis.measure import Distance
-from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction
-qn = connection.ops.quote_name
-
-GEOM_SELECT = 'AsText(%s)'
-
-# Dummy func, in case we need it later:
-def get_func(str):
-    return str
-
-# Functions used by the GeoManager & GeoQuerySet
-AREA = get_func('Area')
-ASSVG = get_func('AsSVG')
-CENTROID = get_func('Centroid')
-CONTAINED = get_func('MbrWithin')
-DIFFERENCE = get_func('Difference')
-DISTANCE = get_func('Distance')
-ENVELOPE = get_func('Envelope')
-GEOM_FROM_TEXT = get_func('GeomFromText')
-GEOM_FROM_WKB = get_func('GeomFromWKB')
-INTERSECTION = get_func('Intersection')
-LENGTH = get_func('GLength') # OpenGis defines Length, but this conflicts with an SQLite reserved keyword
-NUM_GEOM = get_func('NumGeometries')
-NUM_POINTS = get_func('NumPoints')
-POINT_ON_SURFACE = get_func('PointOnSurface')
-SCALE = get_func('ScaleCoords')
-SYM_DIFFERENCE = get_func('SymDifference')
-TRANSFORM = get_func('Transform')
-TRANSLATE = get_func('ShiftCoords')
-UNION = 'GUnion'# OpenGis defines Union, but this conflicts with an SQLite reserved keyword
-UNIONAGG = 'GUnion'
-
-#### Classes used in constructing SpatiaLite spatial SQL ####
-class SpatiaLiteOperator(SpatialOperation):
-    "For SpatiaLite operators (e.g. `&&`, `~`)."
-    def __init__(self, operator):
-        super(SpatiaLiteOperator, self).__init__(operator=operator, beg_subst='%s %s %%s')
-
-class SpatiaLiteFunction(SpatialFunction):
-    "For SpatiaLite function calls."
-    def __init__(self, function, **kwargs):
-        super(SpatiaLiteFunction, self).__init__(get_func(function), **kwargs)
-
-class SpatiaLiteFunctionParam(SpatiaLiteFunction):
-    "For SpatiaLite functions that take another parameter."
-    def __init__(self, func):
-        super(SpatiaLiteFunctionParam, self).__init__(func, end_subst=', %%s)')
-
-class SpatiaLiteDistance(SpatiaLiteFunction):
-    "For SpatiaLite distance operations."
-    dist_func = 'Distance'
-    def __init__(self, operator):
-        super(SpatiaLiteDistance, self).__init__(self.dist_func, end_subst=') %s %s', 
-                                              operator=operator, result='%%s')
-                                                    
-class SpatiaLiteRelate(SpatiaLiteFunctionParam):
-    "For SpatiaLite Relate(<geom>, <pattern>) calls."
-    pattern_regex = re.compile(r'^[012TF\*]{9}$')
-    def __init__(self, pattern):
-        if not self.pattern_regex.match(pattern):
-            raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
-        super(SpatiaLiteRelate, self).__init__('Relate')
-
-
-SPATIALITE_GEOMETRY_FUNCTIONS = {
-    'equals' : SpatiaLiteFunction('Equals'),
-    'disjoint' : SpatiaLiteFunction('Disjoint'),
-    'touches' : SpatiaLiteFunction('Touches'),
-    'crosses' : SpatiaLiteFunction('Crosses'),
-    'within' : SpatiaLiteFunction('Within'),
-    'overlaps' : SpatiaLiteFunction('Overlaps'),
-    'contains' : SpatiaLiteFunction('Contains'),
-    'intersects' : SpatiaLiteFunction('Intersects'),
-    'relate' : (SpatiaLiteRelate, basestring),
-    # Retruns true if B's bounding box completely contains A's bounding box.
-    'contained' : SpatiaLiteFunction('MbrWithin'),
-    # Returns true if A's bounding box completely contains B's bounding box.
-    'bbcontains' : SpatiaLiteFunction('MbrContains'),
-    # Returns true if A's bounding box overlaps B's bounding box.
-    'bboverlaps' : SpatiaLiteFunction('MbrOverlaps'),
-    # These are implemented here as synonyms for Equals
-    'same_as' : SpatiaLiteFunction('Equals'),
-    'exact' : SpatiaLiteFunction('Equals'),
-    }
-
-# Valid distance types and substitutions
-dtypes = (Decimal, Distance, float, int, long)
-def get_dist_ops(operator):
-    "Returns operations for regular distances; spherical distances are not currently supported."
-    return (SpatiaLiteDistance(operator),)
-DISTANCE_FUNCTIONS = {
-    'distance_gt' : (get_dist_ops('>'), dtypes),
-    'distance_gte' : (get_dist_ops('>='), dtypes),
-    'distance_lt' : (get_dist_ops('<'), dtypes),
-    'distance_lte' : (get_dist_ops('<='), dtypes),
-    }
-
-# Distance functions are a part of SpatiaLite geometry functions.
-SPATIALITE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
-
-# Any other lookup types that do not require a mapping.
-MISC_TERMS = ['isnull']
-
-# These are the SpatiaLite-customized QUERY_TERMS -- a list of the lookup types
-# allowed for geographic queries.
-SPATIALITE_TERMS = SPATIALITE_GEOMETRY_FUNCTIONS.keys() # Getting the Geometry Functions
-SPATIALITE_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
-SPATIALITE_TERMS = dict((term, None) for term in SPATIALITE_TERMS) # Making a dictionary for fast lookups
-
-#### The `get_geo_where_clause` function for SpatiaLite. ####
-def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
-    "Returns the SQL WHERE clause for use in SpatiaLite SQL construction."
-    # Getting the quoted field as `geo_col`.
-    geo_col = '%s.%s' % (qn(table_alias), qn(name))
-    if lookup_type in SPATIALITE_GEOMETRY_FUNCTIONS:
-        # See if a SpatiaLite geometry function matches the lookup type.
-        tmp = SPATIALITE_GEOMETRY_FUNCTIONS[lookup_type]
-
-        # Lookup types that are tuples take tuple arguments, e.g., 'relate' and 
-        # distance lookups.
-        if isinstance(tmp, tuple):
-            # First element of tuple is the SpatiaLiteOperation instance, and the
-            # second element is either the type or a tuple of acceptable types
-            # that may passed in as further parameters for the lookup type.
-            op, arg_type = tmp
-
-            # Ensuring that a tuple _value_ was passed in from the user
-            if not isinstance(geo_annot.value, (tuple, list)): 
-                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
-           
-            # Number of valid tuple parameters depends on the lookup type.
-            if len(geo_annot.value) != 2:
-                raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
-            
-            # Ensuring the argument type matches what we expect.
-            if not isinstance(geo_annot.value[1], arg_type):
-                raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
-
-            # For lookup type `relate`, the op instance is not yet created (has
-            # to be instantiated here to check the pattern parameter).
-            if lookup_type == 'relate': 
-                op = op(geo_annot.value[1])
-            elif lookup_type in DISTANCE_FUNCTIONS:
-                op = op[0]
-        else:
-            op = tmp
-        # Calling the `as_sql` function on the operation instance.
-        return op.as_sql(geo_col)
-    elif lookup_type == 'isnull':
-        # Handling 'isnull' lookup type
-        return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
-
-    raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))

+ 0 - 0
django/contrib/gis/db/backends/__init__.py


+ 1 - 1
django/contrib/gis/db/backend/adaptor.py → django/contrib/gis/db/backends/adapter.py

@@ -1,4 +1,4 @@
-class WKTAdaptor(object):
+class WKTAdapter(object):
     """
     This provides an adaptor for Geometries sent to the
     MySQL and Oracle database backends.

+ 327 - 0
django/contrib/gis/db/backends/base.py

@@ -0,0 +1,327 @@
+"""
+Base/mixin classes for the spatial backend database operations and the
+`SpatialRefSys` model the backend.
+"""
+import re
+from django.conf import settings
+from django.contrib.gis import gdal
+
+class BaseSpatialOperations(object):
+    """
+    This module holds the base `BaseSpatialBackend` object, which is
+    instantiated by each spatial database backend with the features
+    it has.
+    """
+    distance_functions = {}
+    geometry_functions = {}
+    geometry_operators = {}
+    geography_operators = {}
+    geography_functions = {}
+    gis_terms = {}
+
+    # Quick booleans for the type of this spatial backend, and
+    # an attribute for the spatial database version tuple (if applicable)
+    postgis = False
+    spatialite = False
+    mysql = False
+    oracle = False
+    spatial_version = None
+
+    # How the geometry column should be selected.
+    select = None
+
+    # Does the spatial database have a geography type?
+    geography = False
+
+    area = False
+    centroid = False
+    difference = False
+    distance = False
+    distance_sphere = False
+    distance_spheroid = False
+    envelope = False
+    force_rhr = False
+    mem_size = False
+    bounding_circle = False
+    num_geom = False
+    num_points = False
+    perimeter = False
+    perimeter3d = False
+    point_on_surface = False
+    polygonize = False
+    scale = False
+    snap_to_grid = False
+    sym_difference = False
+    transform = False
+    translate = False
+    union = False
+
+    # Aggregates
+    collect = False
+    extent = False
+    extent3d = False
+    make_line = False
+    unionagg = False
+
+    # Serialization
+    geohash = False
+    geojson = False
+    gml = False
+    kml = False
+    svg = False
+
+    # Constructors
+    from_text = False
+    from_wkb = False
+
+    # Default conversion functions for aggregates; will be overridden if implemented
+    # for the spatial backend.
+    def convert_extent(self, box):
+        raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
+
+    def convert_extent3d(self, box):
+        raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
+
+    def convert_geom(self, geom_val, geom_field):
+        raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
+
+    # For quoting column values, rather than columns.
+    def geo_quote_name(self, name):
+        if isinstance(name, unicode):
+            name = name.encode('ascii')
+        return "'%s'" % name
+
+    # GeometryField operations
+    def geo_db_type(self, f):
+        """
+        Returns the database column type for the geometry field on
+        the spatial backend.
+        """
+        raise NotImplementedError
+
+    def get_distance(self, f, value, lookup_type):
+        """
+        Returns the distance parameters for the given geometry field,
+        lookup value, and lookup type.
+        """
+        raise NotImplementedError('Distance operations not available on this spatial backend.')
+
+    def get_geom_placeholder(self, f, value):
+        """
+        Returns the placeholder for the given geometry field with the given
+        value.  Depending on the spatial backend, the placeholder may contain a
+        stored procedure call to the transformation function of the spatial
+        backend.
+        """
+        raise NotImplementedError
+
+    # Spatial SQL Construction
+    def spatial_aggregate_sql(self, agg):
+        raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
+
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
+        raise NotImplmentedError
+
+class SpatialRefSysMixin(object):
+    """
+    The SpatialRefSysMixin is a class used by the database-dependent
+    SpatialRefSys objects to reduce redundnant code.
+    """
+    # For pulling out the spheroid from the spatial reference string. This
+    # regular expression is used only if the user does not have GDAL installed.
+    # TODO: Flattening not used in all ellipsoids, could also be a minor axis,
+    # or 'b' parameter.
+    spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
+
+    # For pulling out the units on platforms w/o GDAL installed.
+    # TODO: Figure out how to pull out angular units of projected coordinate system and
+    # fix for LOCAL_CS types.  GDAL should be highly recommended for performing
+    # distance queries.
+    units_regex = re.compile(r'.+UNIT ?\["(?P<unit_name>[\w \'\(\)]+)", ?(?P<unit>[\d\.]+)(,AUTHORITY\["(?P<unit_auth_name>[\w \'\(\)]+)","(?P<unit_auth_val>\d+)"\])?\]([\w ]+)?(,AUTHORITY\["(?P<auth_name>[\w \'\(\)]+)","(?P<auth_val>\d+)"\])?\]$')
+
+    @property
+    def srs(self):
+        """
+        Returns a GDAL SpatialReference object, if GDAL is installed.
+        """
+        if gdal.HAS_GDAL:
+            # TODO: Is caching really necessary here?  Is complexity worth it?
+            if hasattr(self, '_srs'):
+                # Returning a clone of the cached SpatialReference object.
+                return self._srs.clone()
+            else:
+                # Attempting to cache a SpatialReference object.
+
+                # Trying to get from WKT first.
+                try:
+                    self._srs = gdal.SpatialReference(self.wkt)
+                    return self.srs
+                except Exception, msg:
+                    pass
+
+                try:
+                    self._srs = gdal.SpatialReference(self.proj4text)
+                    return self.srs
+                except Exception, msg:
+                    pass
+
+                raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
+        else:
+            raise Exception('GDAL is not installed.')
+
+    @property
+    def ellipsoid(self):
+        """
+        Returns a tuple of the ellipsoid parameters:
+        (semimajor axis, semiminor axis, and inverse flattening).
+        """
+        if gdal.HAS_GDAL:
+            return self.srs.ellipsoid
+        else:
+            m = self.spheroid_regex.match(self.wkt)
+            if m: return (float(m.group('major')), float(m.group('flattening')))
+            else: return None
+
+    @property
+    def name(self):
+        "Returns the projection name."
+        return self.srs.name
+
+    @property
+    def spheroid(self):
+        "Returns the spheroid name for this spatial reference."
+        return self.srs['spheroid']
+
+    @property
+    def datum(self):
+        "Returns the datum for this spatial reference."
+        return self.srs['datum']
+
+    @property
+    def projected(self):
+        "Is this Spatial Reference projected?"
+        if gdal.HAS_GDAL:
+            return self.srs.projected
+        else:
+            return self.wkt.startswith('PROJCS')
+
+    @property
+    def local(self):
+        "Is this Spatial Reference local?"
+        if gdal.HAS_GDAL:
+            return self.srs.local
+        else:
+            return self.wkt.startswith('LOCAL_CS')
+
+    @property
+    def geographic(self):
+        "Is this Spatial Reference geographic?"
+        if gdal.HAS_GDAL:
+            return self.srs.geographic
+        else:
+            return self.wkt.startswith('GEOGCS')
+
+    @property
+    def linear_name(self):
+        "Returns the linear units name."
+        if gdal.HAS_GDAL:
+            return self.srs.linear_name
+        elif self.geographic:
+            return None
+        else:
+            m = self.units_regex.match(self.wkt)
+            return m.group('unit_name')
+
+    @property
+    def linear_units(self):
+        "Returns the linear units."
+        if gdal.HAS_GDAL:
+            return self.srs.linear_units
+        elif self.geographic:
+            return None
+        else:
+            m = self.units_regex.match(self.wkt)
+            return m.group('unit')
+
+    @property
+    def angular_name(self):
+        "Returns the name of the angular units."
+        if gdal.HAS_GDAL:
+            return self.srs.angular_name
+        elif self.projected:
+            return None
+        else:
+            m = self.units_regex.match(self.wkt)
+            return m.group('unit_name')
+
+    @property
+    def angular_units(self):
+        "Returns the angular units."
+        if gdal.HAS_GDAL:
+            return self.srs.angular_units
+        elif self.projected:
+            return None
+        else:
+            m = self.units_regex.match(self.wkt)
+            return m.group('unit')
+
+    @property
+    def units(self):
+        "Returns a tuple of the units and the name."
+        if self.projected or self.local:
+            return (self.linear_units, self.linear_name)
+        elif self.geographic:
+            return (self.angular_units, self.angular_name)
+        else:
+            return (None, None)
+
+    @classmethod
+    def get_units(cls, wkt):
+        """
+        Class method used by GeometryField on initialization to
+        retrive the units on the given WKT, without having to use
+        any of the database fields.
+        """
+        if gdal.HAS_GDAL:
+            return gdal.SpatialReference(wkt).units
+        else:
+            m = cls.units_regex.match(wkt)
+            return m.group('unit'), m.group('unit_name')
+
+    @classmethod
+    def get_spheroid(cls, wkt, string=True):
+        """
+        Class method used by GeometryField on initialization to
+        retrieve the `SPHEROID[..]` parameters from the given WKT.
+        """
+        if gdal.HAS_GDAL:
+            srs = gdal.SpatialReference(wkt)
+            sphere_params = srs.ellipsoid
+            sphere_name = srs['spheroid']
+        else:
+            m = cls.spheroid_regex.match(wkt)
+            if m:
+                sphere_params = (float(m.group('major')), float(m.group('flattening')))
+                sphere_name = m.group('name')
+            else:
+                return None
+
+        if not string:
+            return sphere_name, sphere_params
+        else:
+            # `string` parameter used to place in format acceptable by PostGIS
+            if len(sphere_params) == 3:
+                radius, flattening = sphere_params[0], sphere_params[2]
+            else:
+                radius, flattening = sphere_params
+            return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
+
+    def __unicode__(self):
+        """
+        Returns the string representation.  If GDAL is installed,
+        it will be 'pretty' OGC WKT.
+        """
+        try:
+            return unicode(self.srs)
+        except:
+            return unicode(self.wkt)

+ 0 - 0
django/contrib/gis/db/backends/mysql/__init__.py


+ 11 - 0
django/contrib/gis/db/backends/mysql/base.py

@@ -0,0 +1,11 @@
+from django.db.backends.mysql.base import *
+from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper
+from django.contrib.gis.db.backends.mysql.creation import MySQLCreation
+from django.contrib.gis.db.backends.mysql.operations import MySQLOperations
+
+class DatabaseWrapper(MySQLDatabaseWrapper):
+
+    def __init__(self, *args, **kwargs):
+        super(DatabaseWrapper, self).__init__(*args, **kwargs)
+        self.creation = MySQLCreation(self)
+        self.ops = MySQLOperations()

+ 18 - 0
django/contrib/gis/db/backends/mysql/creation.py

@@ -0,0 +1,18 @@
+from django.db.backends.mysql.creation import DatabaseCreation
+
+class MySQLCreation(DatabaseCreation):
+
+    def sql_indexes_for_field(self, model, f, style):
+        from django.contrib.gis.db.models.fields import GeometryField
+        output = super(MySQLCreation, self).sql_indexes_for_field(model, f, style)
+
+        if isinstance(f, GeometryField):
+            qn = self.connection.ops.quote_name
+            db_table = model._meta.db_table
+            idx_name = '%s_%s_id' % (db_table, f.column)
+            output.append(style.SQL_KEYWORD('CREATE SPATIAL INDEX ') +
+                          style.SQL_TABLE(qn(idx_name)) +
+                          style.SQL_KEYWORD(' ON ') +
+                          style.SQL_TABLE(qn(db_table)) + '(' +
+                          style.SQL_FIELD(qn(f.column)) + ');')
+        return output

+ 64 - 0
django/contrib/gis/db/backends/mysql/operations.py

@@ -0,0 +1,64 @@
+from django.db.backends.mysql.base import DatabaseOperations
+
+from django.contrib.gis.db.backends.adapter import WKTAdapter
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+
+class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
+
+    compiler_module = 'django.contrib.gis.db.models.sql.compiler'
+    mysql = True
+    name = 'mysql'
+    select = 'AsText(%s)'
+    from_wkb = 'GeomFromWKB'
+    from_text = 'GeomFromText'
+
+    Adapter = WKTAdapter
+
+    geometry_functions = {
+        'bbcontains' : 'MBRContains', # For consistency w/PostGIS API
+        'bboverlaps' : 'MBROverlaps', # .. ..
+        'contained' : 'MBRWithin',    # .. ..
+        'contains' : 'MBRContains',
+        'disjoint' : 'MBRDisjoint',
+        'equals' : 'MBREqual',
+        'exact' : 'MBREqual',
+        'intersects' : 'MBRIntersects',
+        'overlaps' : 'MBROverlaps',
+        'same_as' : 'MBREqual',
+        'touches' : 'MBRTouches',
+        'within' : 'MBRWithin',
+        }
+
+    gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']])
+
+    def geo_db_type(self, f):
+        return f.geom_type
+
+    def get_geom_placeholder(self, value, srid):
+        """
+        The placeholder here has to include MySQL's WKT constructor.  Because
+        MySQL does not support spatial transformations, there is no need to
+        modify the placeholder based on the contents of the given value.
+        """
+        if hasattr(value, 'expression'):
+            placeholder = '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+        else:
+            placeholder = '%s(%%s)' % self.from_text
+        return placeholder
+
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+        alias, col, db_type = lvalue
+
+        geo_col = '%s.%s' % (qn(alias), qn(col))
+
+        lookup_info = self.geometry_functions.get(lookup_type, False)
+        if lookup_info:
+            return "%s(%s, %s)" % (lookup_info, geo_col,
+                                   self.get_geom_placeholder(value, field.srid))
+
+        # TODO: Is this really necessary? MySQL can't handle NULL geometries
+        #  in its spatial indexes anyways.
+        if lookup_type == 'isnull':
+            return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+        raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))

+ 0 - 0
django/contrib/gis/db/backends/oracle/__init__.py


+ 5 - 0
django/contrib/gis/db/backends/oracle/adapter.py

@@ -0,0 +1,5 @@
+from cx_Oracle import CLOB
+from django.contrib.gis.db.backends.adapter import WKTAdapter
+
+class OracleSpatialAdapter(WKTAdapter):
+    input_size = CLOB

+ 10 - 0
django/contrib/gis/db/backends/oracle/base.py

@@ -0,0 +1,10 @@
+from django.db.backends.oracle.base import *
+from django.db.backends.oracle.base import DatabaseWrapper as OracleDatabaseWrapper
+from django.contrib.gis.db.backends.oracle.creation import OracleCreation
+from django.contrib.gis.db.backends.oracle.operations import OracleOperations
+
+class DatabaseWrapper(OracleDatabaseWrapper):
+    def __init__(self, *args, **kwargs):
+        super(DatabaseWrapper, self).__init__(*args, **kwargs)
+        self.creation = OracleCreation(self)
+        self.ops = OracleOperations(self)

+ 44 - 0
django/contrib/gis/db/backends/oracle/compiler.py

@@ -0,0 +1,44 @@
+from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler
+from django.db.backends.oracle import compiler
+
+SQLCompiler = compiler.SQLCompiler
+
+class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
+    pass
+
+class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
+    def placeholder(self, field, val):
+        if field is None:
+            # A field value of None means the value is raw.
+            return val
+        elif hasattr(field, 'get_placeholder'):
+            # Some fields (e.g. geo fields) need special munging before
+            # they can be inserted.
+            ph = field.get_placeholder(val, self.connection)
+            if ph == 'NULL':
+                # If the placeholder returned is 'NULL', then we need to
+                # to remove None from the Query parameters. Specifically,
+                # cx_Oracle will assume a CHAR type when a placeholder ('%s')
+                # is used for columns of MDSYS.SDO_GEOMETRY.  Thus, we use
+                # 'NULL' for the value, and remove None from the query params.
+                # See also #10888.
+                param_idx = self.query.columns.index(field.column)
+                params = list(self.query.params)
+                params.pop(param_idx)
+                self.query.params = tuple(params)
+            return ph
+        else:
+            # Return the common case for the placeholder
+            return '%s'
+
+class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
+    pass
+
+class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler):
+    pass
+
+class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler):
+    pass
+
+class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler):
+    pass

+ 42 - 0
django/contrib/gis/db/backends/oracle/creation.py

@@ -0,0 +1,42 @@
+from django.db.backends.oracle.creation import DatabaseCreation
+from django.db.backends.util import truncate_name
+
+class OracleCreation(DatabaseCreation):
+
+    def sql_indexes_for_field(self, model, f, style):
+        "Return any spatial index creation SQL for the field."
+        from django.contrib.gis.db.models.fields import GeometryField
+
+        output = super(OracleCreation, self).sql_indexes_for_field(model, f, style)
+
+        if isinstance(f, GeometryField):
+            gqn = self.connection.ops.geo_quote_name
+            qn = self.connection.ops.quote_name
+            db_table = model._meta.db_table
+
+            output.append(style.SQL_KEYWORD('INSERT INTO ') +
+                          style.SQL_TABLE('USER_SDO_GEOM_METADATA') +
+                          ' (%s, %s, %s, %s)\n  ' % tuple(map(qn, ['TABLE_NAME', 'COLUMN_NAME', 'DIMINFO', 'SRID'])) +
+                          style.SQL_KEYWORD(' VALUES ') + '(\n    ' +
+                          style.SQL_TABLE(gqn(db_table)) + ',\n    ' +
+                          style.SQL_FIELD(gqn(f.column)) + ',\n    ' +
+                          style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n      ' +
+                          style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
+                          ("('LONG', %s, %s, %s),\n      " % (f._extent[0], f._extent[2], f._tolerance)) +
+                          style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
+                          ("('LAT', %s, %s, %s)\n    ),\n" % (f._extent[1], f._extent[3], f._tolerance)) +
+                          '    %s\n  );' % f.srid)
+
+            if f.spatial_index:
+                # Getting the index name, Oracle doesn't allow object
+                # names > 30 characters.
+                idx_name = truncate_name('%s_%s_id' % (db_table, f.column), 30)
+
+                output.append(style.SQL_KEYWORD('CREATE INDEX ') +
+                              style.SQL_TABLE(qn(idx_name)) +
+                              style.SQL_KEYWORD(' ON ') +
+                              style.SQL_TABLE(qn(db_table)) + '(' +
+                              style.SQL_FIELD(qn(f.column)) + ') ' +
+                              style.SQL_KEYWORD('INDEXTYPE IS ') +
+                              style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';')
+        return output

+ 9 - 4
django/contrib/gis/db/backend/oracle/models.py → django/contrib/gis/db/backends/oracle/models.py

@@ -7,7 +7,9 @@
  For example, the `USER_SDO_GEOM_METADATA` is used for the GeometryColumns
  model and the `SDO_COORD_REF_SYS` is used for the SpatialRefSys model.
 """
-from django.db import models
+from django.contrib.gis.db import models
+from django.contrib.gis.db.models.fields import GeometryField
+from django.contrib.gis.db.backends.base import SpatialRefSysMixin
 
 class GeometryColumns(models.Model):
     "Maps to the Oracle USER_SDO_GEOM_METADATA table."
@@ -39,17 +41,20 @@ class GeometryColumns(models.Model):
     def __unicode__(self):
         return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
 
-class SpatialRefSys(models.Model):
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
     "Maps to the Oracle MDSYS.CS_SRS table."
     cs_name = models.CharField(max_length=68)
     srid = models.IntegerField(primary_key=True)
     auth_srid = models.IntegerField()
     auth_name = models.CharField(max_length=256)
     wktext = models.CharField(max_length=2046)
-    #cs_bounds = models.GeometryField() # TODO
+    # Optional geometry representing the bounds of this coordinate
+    # system.  By default, all are NULL in the table.
+    cs_bounds = models.PolygonField(null=True)
+    objects = models.GeoManager()
 
     class Meta:
-        abstract = True
+        app_label = 'gis'
         db_table = 'CS_SRS'
         managed = False
 

+ 289 - 0
django/contrib/gis/db/backends/oracle/operations.py

@@ -0,0 +1,289 @@
+"""
+ This module contains the spatial lookup types, and the `get_geo_where_clause`
+ routine for Oracle Spatial.
+
+ Please note that WKT support is broken on the XE version, and thus
+ this backend will not work on such platforms.  Specifically, XE lacks
+ support for an internal JVM, and Java libraries are required to use
+ the WKT constructors.
+"""
+import re
+from decimal import Decimal
+
+from django.db.backends.oracle.base import DatabaseOperations
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
+from django.contrib.gis.db.backends.util import SpatialFunction
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Distance
+
+class SDOOperation(SpatialFunction):
+    "Base class for SDO* Oracle operations."
+    sql_template = "%(function)s(%(geo_col)s, %(geometry)s) %(operator)s '%(result)s'"
+
+    def __init__(self, func, **kwargs):
+        kwargs.setdefault('operator', '=')
+        kwargs.setdefault('result', 'TRUE')
+        super(SDOOperation, self).__init__(func, **kwargs)
+
+class SDODistance(SpatialFunction):
+    "Class for Distance queries."
+    sql_template = ('%(function)s(%(geo_col)s, %(geometry)s, %(tolerance)s) '
+                    '%(operator)s %(result)s')
+    dist_func = 'SDO_GEOM.SDO_DISTANCE'
+    def __init__(self, op, tolerance=0.05):
+        super(SDODistance, self).__init__(self.dist_func,
+                                          tolerance=tolerance,
+                                          operator=op, result='%s')
+
+class SDODWithin(SpatialFunction):
+    dwithin_func = 'SDO_WITHIN_DISTANCE'
+    sql_template = "%(function)s(%(geo_col)s, %(geometry)s, %%s) = 'TRUE'"
+    def __init__(self):
+        super(SDODWithin, self).__init__(self.dwithin_func)
+
+class SDOGeomRelate(SpatialFunction):
+    "Class for using SDO_GEOM.RELATE."
+    relate_func = 'SDO_GEOM.RELATE'
+    sql_template = ("%(function)s(%(geo_col)s, '%(mask)s', %(geometry)s, "
+                    "%(tolerance)s) %(operator)s '%(mask)s'")
+    def __init__(self, mask, tolerance=0.05):
+        # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
+        # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
+        super(SDOGeomRelate, self).__init__(self.relate_func, operator='=',
+                                            mask=mask, tolerance=tolerance)
+
+class SDORelate(SpatialFunction):
+    "Class for using SDO_RELATE."
+    masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
+    mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
+    sql_template = "%(function)s(%(geo_col)s, %(geometry)s, 'mask=%(mask)s)' = 'TRUE'"
+    relate_func = 'SDO_RELATE'
+    def __init__(self, mask):
+        if not self.mask_regex.match(mask):
+            raise ValueError('Invalid %s mask: "%s"' % (self.relate_func, mask))
+        super(SDORelate, self).__init__(self.relate_func, mask=mask)
+
+# Valid distance types and substitutions
+dtypes = (Decimal, Distance, float, int, long)
+
+class OracleOperations(DatabaseOperations, BaseSpatialOperations):
+    compiler_module = "django.contrib.gis.db.backends.oracle.compiler"
+
+    name = 'oracle'
+    oracle = True
+    valid_aggregates = dict([(a, None) for a in ('Union', 'Extent')])
+
+    Adapter = OracleSpatialAdapter
+
+    area = 'SDO_GEOM.SDO_AREA'
+    gml= 'SDO_UTIL.TO_GMLGEOMETRY'
+    centroid = 'SDO_GEOM.SDO_CENTROID'
+    difference = 'SDO_GEOM.SDO_DIFFERENCE'
+    distance = 'SDO_GEOM.SDO_DISTANCE'
+    extent= 'SDO_AGGR_MBR'
+    intersection= 'SDO_GEOM.SDO_INTERSECTION'
+    length = 'SDO_GEOM.SDO_LENGTH'
+    num_geom = 'SDO_UTIL.GETNUMELEM'
+    num_points = 'SDO_UTIL.GETNUMVERTICES'
+    perimeter = length
+    point_on_surface = 'SDO_GEOM.SDO_POINTONSURFACE'
+    sym_difference = 'SDO_GEOM.SDO_XOR'
+    transform = 'SDO_CS.TRANSFORM'
+    union = 'SDO_GEOM.SDO_UNION'
+    unionagg = 'SDO_AGGR_UNION'
+
+    # We want to get SDO Geometries as WKT because it is much easier to
+    # instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
+    # However, this adversely affects performance (i.e., Java is called
+    # to convert to WKT on every query).  If someone wishes to write a
+    # SDO_GEOMETRY(...) parser in Python, let me know =)
+    select = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
+
+    distance_functions = {
+        'distance_gt' : (SDODistance('>'), dtypes),
+        'distance_gte' : (SDODistance('>='), dtypes),
+        'distance_lt' : (SDODistance('<'), dtypes),
+        'distance_lte' : (SDODistance('<='), dtypes),
+        'dwithin' : (SDODWithin(), dtypes),
+        }
+
+    geometry_functions = {
+        'contains' : SDOOperation('SDO_CONTAINS'),
+        'coveredby' : SDOOperation('SDO_COVEREDBY'),
+        'covers' : SDOOperation('SDO_COVERS'),
+        'disjoint' : SDOGeomRelate('DISJOINT'),
+        'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
+        'equals' : SDOOperation('SDO_EQUAL'),
+        'exact' : SDOOperation('SDO_EQUAL'),
+        'overlaps' : SDOOperation('SDO_OVERLAPS'),
+        'same_as' : SDOOperation('SDO_EQUAL'),
+        'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
+        'touches' : SDOOperation('SDO_TOUCH'),
+        'within' : SDOOperation('SDO_INSIDE'),
+        }
+    geometry_functions.update(distance_functions)
+
+    gis_terms = ['isnull']
+    gis_terms += geometry_functions.keys()
+    gis_terms = dict([(term, None) for term in gis_terms])
+
+    def __init__(self, connection):
+        super(OracleOperations, self).__init__()
+        self.connection = connection
+
+    def convert_extent(self, clob):
+        if clob:
+            # Generally, Oracle returns a polygon for the extent -- however,
+            # it can return a single point if there's only one Point in the
+            # table.
+            ext_geom = Geometry(clob.read())
+            gtype = str(ext_geom.geom_type)
+            if gtype == 'Polygon':
+                # Construct the 4-tuple from the coordinates in the polygon.
+                shell = ext_geom.shell
+                ll, ur = shell[0][:2], shell[2][:2]
+            elif gtype == 'Point':
+                ll = ext_geom.coords[:2]
+                ur = ll
+            else:
+                raise Exception('Unexpected geometry type returned for extent: %s' % gtype)
+            xmin, ymin = ll
+            xmax, ymax = ur
+            return (xmin, ymin, xmax, ymax)
+        else:
+            return None
+
+    def convert_geom(self, clob, geo_field):
+        if clob:
+            return Geometry(clob.read(), geo_field.srid)
+        else:
+            return None
+
+    def geo_db_type(self, f):
+        """
+        Returns the geometry database type for Oracle.  Unlike other spatial
+        backends, no stored procedure is necessary and it's the same for all
+        geometry types.
+        """
+        return 'MDSYS.SDO_GEOMETRY'
+
+    def get_distance(self, f, value, lookup_type):
+        """
+        Returns the distance parameters given the value and the lookup type.
+        On Oracle, geometry columns with a geodetic coordinate system behave
+        implicitly like a geography column, and thus meters will be used as
+        the distance parameter on them.
+        """
+        if not value:
+            return []
+        value = value[0]
+        if isinstance(value, Distance):
+            if f.geodetic(self.connection):
+                dist_param = value.m
+            else:
+                dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
+        else:
+            dist_param = value
+
+        # dwithin lookups on oracle require a special string parameter
+        # that starts with "distance=".
+        if lookup_type == 'dwithin':
+            dist_param = 'distance=%s' % dist_param
+
+        return [dist_param]
+
+    def get_geom_placeholder(self, f, value):
+        """
+        Provides a proper substitution value for Geometries that are not in the
+        SRID of the field.  Specifically, this routine will substitute in the
+        SDO_CS.TRANSFORM() function call.
+        """
+        if value is None:
+            return 'NULL'
+
+        def transform_value(val, srid):
+            return val.srid != srid
+
+        if hasattr(value, 'expression'):
+            if transform_value(value, f.srid):
+                placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
+            else:
+                placeholder = '%s'
+            # No geometry value used for F expression, substitue in
+            # the column name instead.
+            return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+        else:
+            if transform_value(value, f.srid):
+                return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)
+            else:
+                return 'SDO_GEOMETRY(%%s, %s)' % f.srid
+
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+        "Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
+        alias, col, db_type = lvalue
+
+        # Getting the quoted table name as `geo_col`.
+        geo_col = '%s.%s' % (qn(alias), qn(col))
+
+        # See if a Oracle Geometry function matches the lookup type next
+        lookup_info = self.geometry_functions.get(lookup_type, False)
+        if lookup_info:
+            # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+            # 'dwithin' lookup types.
+            if isinstance(lookup_info, tuple):
+                # First element of tuple is lookup type, second element is the type
+                # of the expected argument (e.g., str, float)
+                sdo_op, arg_type = lookup_info
+                geom = value[0]
+
+                # Ensuring that a tuple _value_ was passed in from the user
+                if not isinstance(value, tuple):
+                    raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
+                if len(value) != 2:
+                    raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
+
+                # Ensuring the argument type matches what we expect.
+                if not isinstance(value[1], arg_type):
+                    raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+
+                if lookup_type == 'relate':
+                    # The SDORelate class handles construction for these queries,
+                    # and verifies the mask argument.
+                    return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(field, geom))
+                else:
+                    # Otherwise, just call the `as_sql` method on the SDOOperation instance.
+                    return sdo_op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
+            else:
+                # Lookup info is a SDOOperation instance, whose `as_sql` method returns
+                # the SQL necessary for the geometry function call. For example:
+                #  SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
+                return lookup_info.as_sql(geo_col, self.get_geom_placeholder(field, value))
+        elif lookup_type == 'isnull':
+            # Handling 'isnull' lookup type
+            return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+        raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
+
+    def spatial_aggregate_sql(self, agg):
+        """
+        Returns the spatial aggregate SQL template and function for the
+        given Aggregate instance.
+        """
+        agg_name = agg.__class__.__name__.lower()
+        if agg_name == 'union' : agg_name += 'agg'
+        if agg.is_extent:
+            sql_template = '%(function)s(%(field)s)'
+        else:
+            sql_template = '%(function)s(SDOAGGRTYPE(%(field)s,%(tolerance)s))'
+        sql_function = getattr(self, agg_name)
+        return self.select % sql_template, sql_function
+
+    # Routines for getting the OGC-compliant models.
+    def geometry_columns(self):
+        from django.contrib.gis.db.backends.oracle.models import GeometryColumns
+        return GeometryColumns
+
+    def spatial_ref_sys(self):
+        from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
+        return SpatialRefSys

+ 0 - 0
django/contrib/gis/db/backends/postgis/__init__.py


+ 3 - 4
django/contrib/gis/db/backend/postgis/adaptor.py → django/contrib/gis/db/backends/postgis/adapter.py

@@ -2,11 +2,10 @@
  This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
 """
 
-from django.contrib.gis.db.backend.postgis.query import GEOM_FROM_EWKB
 from psycopg2 import Binary
 from psycopg2.extensions import ISQLQuote
 
-class PostGISAdaptor(object):
+class PostGISAdapter(object):
     def __init__(self, geom):
         "Initializes on the geometry."
         # Getting the WKB (in string form, to allow easy pickling of
@@ -22,7 +21,7 @@ class PostGISAdaptor(object):
             raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?')
 
     def __eq__(self, other):
-        return (self.wkb == other.wkb) and (self.srid == other.srid)
+        return (self.ewkb == other.ewkb) and (self.srid == other.srid)
 
     def __str__(self):
         return self.getquoted()
@@ -30,7 +29,7 @@ class PostGISAdaptor(object):
     def getquoted(self):
         "Returns a properly quoted string for use in PostgreSQL/PostGIS."
         # Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
-        return "%s(E%s)" % (GEOM_FROM_EWKB, Binary(self.ewkb))
+        return 'ST_GeomFromEWKB(E%s)' % Binary(self.ewkb)
 
     def prepare_database_save(self, unused):
         return self

+ 10 - 0
django/contrib/gis/db/backends/postgis/base.py

@@ -0,0 +1,10 @@
+from django.db.backends.postgresql_psycopg2.base import *
+from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper as Psycopg2DatabaseWrapper
+from django.contrib.gis.db.backends.postgis.creation import PostGISCreation
+from django.contrib.gis.db.backends.postgis.operations import PostGISOperations
+
+class DatabaseWrapper(Psycopg2DatabaseWrapper):
+    def __init__(self, *args, **kwargs):
+        super(DatabaseWrapper, self).__init__(*args, **kwargs)
+        self.creation = PostGISCreation(self)
+        self.ops = PostGISOperations(self)

+ 60 - 0
django/contrib/gis/db/backends/postgis/creation.py

@@ -0,0 +1,60 @@
+from django.conf import settings
+from django.db.backends.postgresql.creation import DatabaseCreation
+
+class PostGISCreation(DatabaseCreation):
+    geom_index_type = 'GIST'
+    geom_index_opts = 'GIST_GEOMETRY_OPS'
+
+    def sql_indexes_for_field(self, model, f, style):
+        "Return any spatial index creation SQL for the field."
+        from django.contrib.gis.db.models.fields import GeometryField
+
+        output = super(PostGISCreation, self).sql_indexes_for_field(model, f, style)
+
+        if isinstance(f, GeometryField):
+            gqn = self.connection.ops.geo_quote_name
+            qn = self.connection.ops.quote_name
+            db_table = model._meta.db_table
+
+            if f.geography:
+                # Geogrophy columns are created normally.
+                pass
+            else:
+                # Geometry columns are created by `AddGeometryColumn`
+                # stored procedure.
+                output.append(style.SQL_KEYWORD('SELECT ') +
+                              style.SQL_TABLE('AddGeometryColumn') + '(' +
+                              style.SQL_TABLE(gqn(db_table)) + ', ' +
+                              style.SQL_FIELD(gqn(f.column)) + ', ' +
+                              style.SQL_FIELD(str(f.srid)) + ', ' +
+                              style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
+                              style.SQL_KEYWORD(str(f.dim)) + ');')
+
+                if not f.null:
+                    # Add a NOT NULL constraint to the field
+                    output.append(style.SQL_KEYWORD('ALTER TABLE ') +
+                                  style.SQL_TABLE(qn(db_table)) +
+                                  style.SQL_KEYWORD(' ALTER ') +
+                                  style.SQL_FIELD(qn(f.column)) +
+                                  style.SQL_KEYWORD(' SET NOT NULL') + ';')
+
+
+            if f.spatial_index:
+                # Spatial indexes created the same way for both Geometry and
+                # Geography columns
+                if f.geography:
+                    index_opts = ''
+                else:
+                    index_opts = ' ' + style.SQL_KEYWORD(self.geom_index_opts)
+                output.append(style.SQL_KEYWORD('CREATE INDEX ') +
+                              style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
+                              style.SQL_KEYWORD(' ON ') +
+                              style.SQL_TABLE(qn(db_table)) +
+                              style.SQL_KEYWORD(' USING ') +
+                              style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
+                              style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
+        return output
+
+    def sql_table_creation_suffix(self):
+        qn = self.connection.ops.quote_name
+        return ' TEMPLATE %s' % qn(getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis'))

+ 3 - 2
django/contrib/gis/db/backend/postgis/models.py → django/contrib/gis/db/backends/postgis/models.py

@@ -2,6 +2,7 @@
  The GeometryColumns and SpatialRefSys models for the PostGIS backend.
 """
 from django.db import models
+from django.contrib.gis.db.backends.base import SpatialRefSysMixin
 
 class GeometryColumns(models.Model):
     """
@@ -42,7 +43,7 @@ class GeometryColumns(models.Model):
                (self.f_table_name, self.f_geometry_column,
                 self.coord_dimension, self.type, self.srid)
 
-class SpatialRefSys(models.Model):
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
     """
     The 'spatial_ref_sys' table from PostGIS. See the PostGIS
     documentaiton at Ch. 4.2.1.
@@ -54,7 +55,7 @@ class SpatialRefSys(models.Model):
     proj4text = models.CharField(max_length=2048)
 
     class Meta:
-        abstract = True
+        app_label = 'gis'
         db_table = 'spatial_ref_sys'
         managed = False
 

+ 570 - 0
django/contrib/gis/db/backends/postgis/operations.py

@@ -0,0 +1,570 @@
+import re
+from decimal import Decimal
+
+from django.conf import settings
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
+from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Distance
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends.postgresql.operations import DatabaseOperations
+from django.db.backends.postgresql_psycopg2.base import Database
+
+#### Classes used in constructing PostGIS spatial SQL ####
+class PostGISOperator(SpatialOperation):
+    "For PostGIS operators (e.g. `&&`, `~`)."
+    def __init__(self, operator):
+        super(PostGISOperator, self).__init__(operator=operator)
+
+class PostGISFunction(SpatialFunction):
+    "For PostGIS function calls (e.g., `ST_Contains(table, geom)`)."
+    def __init__(self, prefix, function, **kwargs):
+        super(PostGISFunction, self).__init__(prefix + function, **kwargs)
+
+class PostGISFunctionParam(PostGISFunction):
+    "For PostGIS functions that take another parameter (e.g. DWithin, Relate)."
+    sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)'
+
+class PostGISDistance(PostGISFunction):
+    "For PostGIS distance operations."
+    dist_func = 'Distance'
+    sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s'
+
+    def __init__(self, prefix, operator):
+        super(PostGISDistance, self).__init__(prefix, self.dist_func,
+                                              operator=operator)
+
+class PostGISSpheroidDistance(PostGISFunction):
+    "For PostGIS spherical distance operations (using the spheroid)."
+    dist_func = 'distance_spheroid'
+    sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s) %(operator)s %%s'
+    def __init__(self, prefix, operator):
+        # An extra parameter in `end_subst` is needed for the spheroid string.
+        super(PostGISSpheroidDistance, self).__init__(prefix, self.dist_func,
+                                                      operator=operator)
+
+class PostGISSphereDistance(PostGISDistance):
+    "For PostGIS spherical distance operations."
+    dist_func = 'distance_sphere'
+
+class PostGISRelate(PostGISFunctionParam):
+    "For PostGIS Relate(<geom>, <pattern>) calls."
+    pattern_regex = re.compile(r'^[012TF\*]{9}$')
+    def __init__(self, prefix, pattern):
+        if not self.pattern_regex.match(pattern):
+            raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
+        super(PostGISRelate, self).__init__(prefix, 'Relate')
+
+
+class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
+    compiler_module = 'django.contrib.gis.db.models.sql.compiler'
+    name = 'postgis'
+    postgis = True
+    version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
+    valid_aggregates = dict([(k, None) for k in
+                             ('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')])
+
+    Adapter = PostGISAdapter
+
+    def __init__(self, connection):
+        super(PostGISOperations, self).__init__(connection)
+
+        # Trying to get the PostGIS version because the function
+        # signatures will depend on the version used.  The cost
+        # here is a database query to determine the version, which
+        # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
+        # comprising user-supplied values for the major, minor, and
+        # subminor revision of PostGIS.
+        try:
+            if hasattr(settings, 'POSTGIS_VERSION'):
+                vtup = settings.POSTGIS_VERSION
+                if len(vtup) == 3:
+                    # The user-supplied PostGIS version.
+                    version = vtup
+                else:
+                    # This was the old documented way, but it's stupid to
+                    # include the string.
+                    version = vtup[1:4]
+            else:
+                vtup = self.postgis_version_tuple()
+                version = vtup[1:]
+
+            # Getting the prefix -- even though we don't officially support
+            # PostGIS 1.2 anymore, keeping it anyway in case a prefix change
+            # for something else is necessary.
+            if version >= (1, 2, 2):
+                prefix = 'ST_'
+            else:
+                prefix = ''
+
+            self.geom_func_prefix = prefix
+            self.spatial_version = version
+        except Database.ProgrammingError:
+            raise ImproperlyConfigured('Cannot determine PostGIS version for database "%s". '
+                                       'GeoDjango requires at least PostGIS version 1.3. '
+                                       'Was the database created from a spatial database '
+                                       'template?' % self.connection.settings_dict['NAME']
+                                       )
+        except Exception, e:
+            # TODO: Raise helpful exceptions as they become known.
+            raise
+
+        # PostGIS-specific operators. The commented descriptions of these
+        # operators come from Section 7.6 of the PostGIS 1.4 documentation.
+        self.geometry_operators = {
+            # The "&<" operator returns true if A's bounding box overlaps or
+            # is to the left of B's bounding box.
+            'overlaps_left' : PostGISOperator('&<'),
+            # The "&>" operator returns true if A's bounding box overlaps or
+            # is to the right of B's bounding box.
+            'overlaps_right' : PostGISOperator('&>'),
+            # The "<<" operator returns true if A's bounding box is strictly
+            # to the left of B's bounding box.
+            'left' : PostGISOperator('<<'),
+            # The ">>" operator returns true if A's bounding box is strictly
+            # to the right of B's bounding box.
+            'right' : PostGISOperator('>>'),
+            # The "&<|" operator returns true if A's bounding box overlaps or
+            # is below B's bounding box.
+            'overlaps_below' : PostGISOperator('&<|'),
+            # The "|&>" operator returns true if A's bounding box overlaps or
+            # is above B's bounding box.
+            'overlaps_above' : PostGISOperator('|&>'),
+            # The "<<|" operator returns true if A's bounding box is strictly
+            # below B's bounding box.
+            'strictly_below' : PostGISOperator('<<|'),
+            # The "|>>" operator returns true if A's bounding box is strictly
+            # above B's bounding box.
+            'strictly_above' : PostGISOperator('|>>'),
+            # The "~=" operator is the "same as" operator. It tests actual
+            # geometric equality of two features. So if A and B are the same feature,
+            # vertex-by-vertex, the operator returns true.
+            'same_as' : PostGISOperator('~='),
+            'exact' : PostGISOperator('~='),
+            # The "@" operator returns true if A's bounding box is completely contained
+            # by B's bounding box.
+            'contained' : PostGISOperator('@'),
+            # The "~" operator returns true if A's bounding box completely contains
+            #  by B's bounding box.
+            'bbcontains' : PostGISOperator('~'),
+            # The "&&" operator returns true if A's bounding box overlaps
+            # B's bounding box.
+            'bboverlaps' : PostGISOperator('&&'),
+            }
+
+        self.geometry_functions = {
+            'equals' : PostGISFunction(prefix, 'Equals'),
+            'disjoint' : PostGISFunction(prefix, 'Disjoint'),
+            'touches' : PostGISFunction(prefix, 'Touches'),
+            'crosses' : PostGISFunction(prefix, 'Crosses'),
+            'within' : PostGISFunction(prefix, 'Within'),
+            'overlaps' : PostGISFunction(prefix, 'Overlaps'),
+            'contains' : PostGISFunction(prefix, 'Contains'),
+            'intersects' : PostGISFunction(prefix, 'Intersects'),
+            'relate' : (PostGISRelate, basestring),
+            }
+
+        # Valid distance types and substitutions
+        dtypes = (Decimal, Distance, float, int, long)
+        def get_dist_ops(operator):
+            "Returns operations for both regular and spherical distances."
+            return {'cartesian' : PostGISDistance(prefix, operator),
+                    'sphere' : PostGISSphereDistance(prefix, operator),
+                    'spheroid' : PostGISSpheroidDistance(prefix, operator),
+                    }
+        self.distance_functions = {
+            'distance_gt' : (get_dist_ops('>'), dtypes),
+            'distance_gte' : (get_dist_ops('>='), dtypes),
+            'distance_lt' : (get_dist_ops('<'), dtypes),
+            'distance_lte' : (get_dist_ops('<='), dtypes),
+            }
+
+        # Versions 1.2.2+ have KML serialization support.
+        if version < (1, 2, 2):
+            ASKML = False
+        else:
+            ASKML = 'ST_AsKML'
+            self.geometry_functions.update(
+                {'coveredby' : PostGISFunction(prefix, 'CoveredBy'),
+                 'covers' : PostGISFunction(prefix, 'Covers'),
+                 })
+            self.distance_functions['dwithin'] = (PostGISFunctionParam(prefix, 'DWithin'), dtypes)
+
+        # Adding the distance functions to the geometries lookup.
+        self.geometry_functions.update(self.distance_functions)
+
+        # The union aggregate and topology operation use the same signature
+        # in versions 1.3+.
+        if version < (1, 3, 0):
+            UNIONAGG = 'GeomUnion'
+            UNION = 'Union'
+        else:
+            UNIONAGG = 'ST_Union'
+            UNION = 'ST_Union'
+
+        # Only PostGIS versions 1.3.4+ have GeoJSON serialization support.
+        if version < (1, 3, 4):
+            GEOJSON = False
+        else:
+            GEOJSON = prefix + 'AsGeoJson'
+
+        # ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4.
+        if version >= (1, 4, 0):
+            GEOHASH = 'ST_GeoHash'
+            MAKELINE = 'ST_MakeLine'
+            BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle'
+            self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
+        else:
+            GEOHASH, MAKELINE, BOUNDINGCIRCLE = False, False, False
+
+        # Geography type support added in 1.5.
+        if version >= (1, 5, 0):
+            self.geography = True
+            # Only a subset of the operators and functions are available
+            # for the geography type.
+            self.geography_functions = self.distance_functions.copy()
+            self.geography_functions.update({
+                    'coveredby' : self.geometry_functions['coveredby'],
+                    'covers' : self.geometry_functions['covers'],
+                    'intersects' : self.geometry_functions['intersects'],
+                    })
+            self.geography_operators = {
+                'bboverlaps' : PostGISOperator('&&'),
+                'exact' : PostGISOperator('~='),
+                'same_as' : PostGISOperator('~='),
+                }
+
+        # Creating a dictionary lookup of all GIS terms for PostGIS.
+        gis_terms = ['isnull']
+        gis_terms += self.geometry_operators.keys()
+        gis_terms += self.geometry_functions.keys()
+        self.gis_terms = dict([(term, None) for term in gis_terms])
+
+        self.area = prefix + 'Area'
+        self.bounding_circle = BOUNDINGCIRCLE
+        self.centroid = prefix + 'Centroid'
+        self.collect = prefix + 'Collect'
+        self.difference = prefix + 'Difference'
+        self.distance = prefix + 'Distance'
+        self.distance_sphere = prefix + 'distance_sphere'
+        self.distance_spheroid = prefix + 'distance_spheroid'
+        self.envelope = prefix + 'Envelope'
+        self.extent = prefix + 'Extent'
+        self.extent3d = prefix + 'Extent3D'
+        self.geohash = GEOHASH
+        self.geojson = GEOJSON
+        self.gml = prefix + 'AsGML'
+        self.intersection = prefix + 'Intersection'
+        self.kml = ASKML
+        self.length = prefix + 'Length'
+        self.length3d = prefix + 'Length3D'
+        self.length_spheroid = prefix + 'length_spheroid'
+        self.makeline = MAKELINE
+        self.mem_size = prefix + 'mem_size'
+        self.num_geom = prefix + 'NumGeometries'
+        self.num_points =prefix + 'npoints'
+        self.perimeter = prefix + 'Perimeter'
+        self.perimeter3d = prefix + 'Perimeter3D'
+        self.point_on_surface = prefix + 'PointOnSurface'
+        self.polygonize = prefix + 'Polygonize'
+        self.scale = prefix + 'Scale'
+        self.snap_to_grid = prefix + 'SnapToGrid'
+        self.svg = prefix + 'AsSVG'
+        self.sym_difference = prefix + 'SymDifference'
+        self.transform = prefix + 'Transform'
+        self.translate = prefix + 'Translate'
+        self.union = UNION
+        self.unionagg = UNIONAGG
+
+    def check_aggregate_support(self, aggregate):
+        """
+        Checks if the given aggregate name is supported (that is, if it's
+        in `self.valid_aggregates`).
+        """
+        agg_name = aggregate.__class__.__name__
+        return agg_name in self.valid_aggregates
+
+    def convert_extent(self, box):
+        """
+        Returns a 4-tuple extent for the `Extent` aggregate by converting
+        the bounding box text returned by PostGIS (`box` argument), for
+        example: "BOX(-90.0 30.0, -85.0 40.0)".
+        """
+        ll, ur = box[4:-1].split(',')
+        xmin, ymin = map(float, ll.split())
+        xmax, ymax = map(float, ur.split())
+        return (xmin, ymin, xmax, ymax)
+
+    def convert_extent3d(self, box3d):
+        """
+        Returns a 6-tuple extent for the `Extent3D` aggregate by converting
+        the 3d bounding-box text returnded by PostGIS (`box3d` argument), for
+        example: "BOX3D(-90.0 30.0 1, -85.0 40.0 2)".
+        """
+        ll, ur = box3d[6:-1].split(',')
+        xmin, ymin, zmin = map(float, ll.split())
+        xmax, ymax, zmax = map(float, ur.split())
+        return (xmin, ymin, zmin, xmax, ymax, zmax)
+
+    def convert_geom(self, hex, geo_field):
+        """
+        Converts the geometry returned from PostGIS aggretates.
+        """
+        if hex:
+            return Geometry(hex)
+        else:
+            return None
+
+    def geo_db_type(self, f):
+        """
+        Return the database field type for the given geometry field.
+        Typically this is `None` because geometry columns are added via
+        the `AddGeometryColumn` stored procedure, unless the field
+        has been specified to be of geography type instead.
+        """
+        if f.geography:
+            if not self.geography:
+                raise NotImplementedError('PostGIS 1.5 required for geography column support.')
+
+            if f.srid != 4326:
+                raise NotImplementedError('PostGIS 1.5 supports geography columns '
+                                          'only with an SRID of 4326.')
+
+            return 'geography(%s,%d)'% (f.geom_type, f.srid)
+        else:
+            return None
+
+    def get_distance(self, f, dist_val, lookup_type):
+        """
+        Retrieve the distance parameters for the given geometry field,
+        distance lookup value, and the distance lookup type.
+
+        This is the most complex implementation of the spatial backends due to
+        what is supported on geodetic geometry columns vs. what's available on
+        projected geometry columns.  In addition, it has to take into account
+        the newly introduced geography column type introudced in PostGIS 1.5.
+        """
+        # Getting the distance parameter and any options.
+        if len(dist_val) == 1:
+            value, option = dist_val[0], None
+        else:
+            value, option = dist_val
+
+        # Shorthand boolean flags.
+        geodetic = f.geodetic(self.connection)
+        geography = f.geography and self.geography
+
+        if isinstance(value, Distance):
+            if geography:
+                dist_param = value.m
+            elif geodetic:
+                if lookup_type == 'dwithin':
+                    raise ValueError('Only numeric values of degree units are '
+                                     'allowed on geographic DWithin queries.')
+                dist_param = value.m
+            else:
+                dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
+        else:
+            # Assuming the distance is in the units of the field.
+            dist_param = value
+
+        if (not geography and geodetic and lookup_type != 'dwithin'
+            and option == 'spheroid'):
+            # using distance_spheroid requires the spheroid of the field as
+            # a parameter.
+            return [f._spheroid, dist_param]
+        else:
+            return [dist_param]
+
+    def get_geom_placeholder(self, f, value):
+        """
+        Provides a proper substitution value for Geometries that are not in the
+        SRID of the field.  Specifically, this routine will substitute in the
+        ST_Transform() function call.
+        """
+        if value is None or value.srid == f.srid:
+            placeholder = '%s'
+        else:
+            # Adding Transform() to the SQL placeholder.
+            placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
+
+        if hasattr(value, 'expression'):
+            # If this is an F expression, then we don't really want
+            # a placeholder and instead substitute in the column
+            # of the expression.
+            placeholder = placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+
+        return placeholder
+
+    def _get_postgis_func(self, func):
+        """
+        Helper routine for calling PostGIS functions and returning their result.
+        """
+        cursor = self.connection._cursor()
+        try:
+            cursor.execute('SELECT %s()' % func)
+            row = cursor.fetchone()
+        except:
+            # Responsibility of callers to perform error handling.
+            raise
+        finally:
+            cursor.close()
+        return row[0]
+
+    def postgis_geos_version(self):
+        "Returns the version of the GEOS library used with PostGIS."
+        return self._get_postgis_func('postgis_geos_version')
+
+    def postgis_lib_version(self):
+        "Returns the version number of the PostGIS library used with PostgreSQL."
+        return self._get_postgis_func('postgis_lib_version')
+
+    def postgis_proj_version(self):
+        "Returns the version of the PROJ.4 library used with PostGIS."
+        return self._get_postgis_func('postgis_proj_version')
+
+    def postgis_version(self):
+        "Returns PostGIS version number and compile-time options."
+        return self._get_postgis_func('postgis_version')
+
+    def postgis_full_version(self):
+        "Returns PostGIS version number and compile-time options."
+        return self._get_postgis_func('postgis_full_version')
+
+    def postgis_version_tuple(self):
+        """
+        Returns the PostGIS version as a tuple (version string, major,
+        minor, subminor).
+        """
+        # Getting the PostGIS version
+        version = self.postgis_lib_version()
+        m = self.version_regex.match(version)
+
+        if m:
+            major = int(m.group('major'))
+            minor1 = int(m.group('minor1'))
+            minor2 = int(m.group('minor2'))
+        else:
+            raise Exception('Could not parse PostGIS version string: %s' % version)
+
+        return (version, major, minor1, minor2)
+
+    def num_params(self, lookup_type, num_param):
+        """
+        Helper routine that returns a boolean indicating whether the number of
+        parameters is correct for the lookup type.
+        """
+        def exactly_two(np): return np == 2
+        def two_to_three(np): return np >= 2 and np <=3
+        if (lookup_type in self.distance_functions and
+            lookup_type != 'dwithin'):
+            return two_to_three(num_param)
+        else:
+            return exactly_two(num_param)
+
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+        """
+        Constructs spatial SQL from the given lookup value tuple a
+        (alias, col, db_type), the lookup type string, lookup value, and
+        the geometry field.
+        """
+        alias, col, db_type = lvalue
+
+        # Getting the quoted geometry column.
+        geo_col = '%s.%s' % (qn(alias), qn(col))
+
+        if lookup_type in self.geometry_operators:
+            if field.geography and not lookup_type in self.geography_operators:
+                raise ValueError('PostGIS geography does not support the '
+                                 '"%s" lookup.' % lookup_type)
+            # Handling a PostGIS operator.
+            op = self.geometry_operators[lookup_type]
+            return op.as_sql(geo_col, self.get_geom_placeholder(field, value))
+        elif lookup_type in self.geometry_functions:
+            if field.geography and not lookup_type in self.geography_functions:
+                raise ValueError('PostGIS geography type does not support the '
+                                 '"%s" lookup.' % lookup_type)
+
+            # See if a PostGIS geometry function matches the lookup type.
+            tmp = self.geometry_functions[lookup_type]
+
+            # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+            # distance lookups.
+            if isinstance(tmp, tuple):
+                # First element of tuple is the PostGISOperation instance, and the
+                # second element is either the type or a tuple of acceptable types
+                # that may passed in as further parameters for the lookup type.
+                op, arg_type = tmp
+
+                # Ensuring that a tuple _value_ was passed in from the user
+                if not isinstance(value, (tuple, list)):
+                    raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
+
+                # Geometry is first element of lookup tuple.
+                geom = value[0]
+
+                # Number of valid tuple parameters depends on the lookup type.
+                nparams = len(value)
+                if not self.num_params(lookup_type, nparams):
+                    raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
+
+                # Ensuring the argument type matches what we expect.
+                if not isinstance(value[1], arg_type):
+                    raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+
+                # For lookup type `relate`, the op instance is not yet created (has
+                # to be instantiated here to check the pattern parameter).
+                if lookup_type == 'relate':
+                    op = op(self.geom_func_prefix, value[1])
+                elif lookup_type in self.distance_functions and lookup_type != 'dwithin':
+                    if not field.geography and field.geodetic(self.connection):
+                        # Geodetic distances are only availble from Points to PointFields.
+                        if field.geom_type != 'POINT':
+                            raise ValueError('PostGIS spherical operations are only valid on PointFields.')
+
+                        if str(geom.geom_type) != 'Point':
+                            raise ValueError('PostGIS geometry distance parameter is required to be of type Point.')
+
+                        # Setting up the geodetic operation appropriately.
+                        if nparams == 3 and value[2] == 'spheroid':
+                            op = op['spheroid']
+                        else:
+                            op = op['sphere']
+                    else:
+                        op = op['cartesian']
+            else:
+                op = tmp
+                geom = value
+
+            # Calling the `as_sql` function on the operation instance.
+            return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
+
+        elif lookup_type == 'isnull':
+            # Handling 'isnull' lookup type
+            return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+        raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
+
+    def spatial_aggregate_sql(self, agg):
+        """
+        Returns the spatial aggregate SQL template and function for the
+        given Aggregate instance.
+        """
+        agg_name = agg.__class__.__name__
+        if not self.check_aggregate_support(agg):
+            raise NotImplementedError('%s spatial aggregate is not implmented for this backend.' % agg_name)
+        agg_name = agg_name.lower()
+        if agg_name == 'union': agg_name += 'agg'
+        sql_template = '%(function)s(%(field)s)'
+        sql_function = getattr(self, agg_name)
+        return sql_template, sql_function
+
+    # Routines for getting the OGC-compliant models.
+    def geometry_columns(self):
+        from django.contrib.gis.db.backends.postgis.models import GeometryColumns
+        return GeometryColumns
+
+    def spatial_ref_sys(self):
+        from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
+        return SpatialRefSys

+ 0 - 0
django/contrib/gis/db/backends/spatialite/__init__.py


+ 2 - 2
django/contrib/gis/db/backend/spatialite/adaptor.py → django/contrib/gis/db/backends/spatialite/adapter.py

@@ -1,7 +1,7 @@
 from django.db.backends.sqlite3.base import Database
-from django.contrib.gis.db.backend.adaptor import WKTAdaptor
+from django.contrib.gis.db.backends.adapter import WKTAdapter
 
-class SpatiaLiteAdaptor(WKTAdaptor):
+class SpatiaLiteAdapter(WKTAdapter):
     "SQLite adaptor for geometry objects."
     def __conform__(self, protocol):
         if protocol is Database.PrepareProtocol:

+ 73 - 0
django/contrib/gis/db/backends/spatialite/base.py

@@ -0,0 +1,73 @@
+from ctypes.util import find_library
+from django.conf import settings
+
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends.sqlite3.base import *
+from django.db.backends.sqlite3.base import DatabaseWrapper as SqliteDatabaseWrapper, \
+    _sqlite_extract, _sqlite_date_trunc, _sqlite_regexp
+from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient
+from django.contrib.gis.db.backends.spatialite.creation import SpatiaLiteCreation
+from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations
+
+
+class DatabaseWrapper(SqliteDatabaseWrapper):
+    def __init__(self, *args, **kwargs):
+        # Before we get too far, make sure pysqlite 2.5+ is installed.
+        if Database.version_info < (2, 5, 0):
+            raise ImproperlyConfigured('Only versions of pysqlite 2.5+ are '
+                                       'compatible with SpatiaLite and GeoDjango.')
+
+        # Trying to find the location of the SpatiaLite library.
+        # Here we are figuring out the path to the SpatiaLite library
+        # (`libspatialite`). If it's not in the system library path (e.g., it
+        # cannot be found by `ctypes.util.find_library`), then it may be set
+        # manually in the settings via the `SPATIALITE_LIBRARY_PATH` setting.
+        self.spatialite_lib = getattr(settings, 'SPATIALITE_LIBRARY_PATH',
+                                      find_library('spatialite'))
+        if not self.spatialite_lib:
+            raise ImproperlyConfigured('Unable to locate the SpatiaLite library. '
+                                       'Make sure it is in your library path, or set '
+                                       'SPATIALITE_LIBRARY_PATH in your settings.'
+                                       )
+        super(DatabaseWrapper, self).__init__(*args, **kwargs)
+        self.ops = SpatiaLiteOperations(self)
+        self.client = SpatiaLiteClient(self)
+        self.creation = SpatiaLiteCreation(self)
+
+    def _cursor(self):
+        if self.connection is None:
+            settings_dict = self.settings_dict
+            if not settings_dict['NAME']:
+                from django.core.exceptions import ImproperlyConfigured
+                raise ImproperlyConfigured, "Please fill out the database NAME in the settings module before using the database."
+            kwargs = {
+                'database': settings_dict['NAME'],
+                'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
+            }
+            kwargs.update(settings_dict['OPTIONS'])
+            self.connection = Database.connect(**kwargs)
+            # Register extract, date_trunc, and regexp functions.
+            self.connection.create_function("django_extract", 2, _sqlite_extract)
+            self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
+            self.connection.create_function("regexp", 2, _sqlite_regexp)
+
+            try:
+                self.connection.enable_load_extension(True)
+            except AttributeError:
+                raise ImproperlyConfigured('The pysqlite library does not support C extension loading. '
+                                           'Both SQLite and pysqlite must be configured to allow '
+                                           'the loading of extensions to use SpatiaLite.'
+                                           )
+
+            connection_created.send(sender=self.__class__)
+        return self.connection.cursor(factory=SQLiteCursorWrapper)
+
+    def load_spatialite(self):
+        """
+        Loads the SpatiaLite library.
+        """
+        try:
+            self._cursor().execute("SELECT load_extension(%s)", (self.spatialite_lib,))
+        except Exception, msg:
+            raise ImproperlyConfigured('Unable to load the SpatiaLite extension '
+                                       '"%s" because: %s' % (self.spatialite_lib, msg))

+ 5 - 0
django/contrib/gis/db/backends/spatialite/client.py

@@ -0,0 +1,5 @@
+from django.db.backends.sqlite3.client import DatabaseClient
+
+class SpatiaLiteClient(DatabaseClient):
+    executable_name = 'spatialite'
+

+ 97 - 0
django/contrib/gis/db/backends/spatialite/creation.py

@@ -0,0 +1,97 @@
+import os
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.management import call_command
+from django.db.backends.sqlite3.creation import DatabaseCreation
+
+class SpatiaLiteCreation(DatabaseCreation):
+
+    def create_test_db(self, verbosity=1, autoclobber=False):
+        """
+        Creates a test database, prompting the user for confirmation if the
+        database already exists. Returns the name of the test database created.
+
+        This method is overloaded to load up the SpatiaLite initialization
+        SQL prior to calling the `syncdb` command.
+        """
+        if verbosity >= 1:
+            print "Creating test database '%s'..." % self.connection.alias
+
+        test_database_name = self._create_test_db(verbosity, autoclobber)
+
+        self.connection.close()
+
+        self.connection.settings_dict["NAME"] = test_database_name
+        can_rollback = self._rollback_works()
+        self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
+        # Need to load the SpatiaLite library and initializatin SQL before running `syncdb`.
+        self.connection.load_spatialite()
+        self.load_spatialite_sql()
+        call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
+
+        if settings.CACHE_BACKEND.startswith('db://'):
+            from django.core.cache import parse_backend_uri
+            _, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND)
+            call_command('createcachetable', cache_name)
+
+        # Get a cursor (even though we don't need one yet). This has
+        # the side effect of initializing the test database.
+        cursor = self.connection.cursor()
+
+        return test_database_name
+
+    def sql_indexes_for_field(self, model, f, style):
+        "Return any spatial index creation SQL for the field."
+        from django.contrib.gis.db.models.fields import GeometryField
+
+        output = super(SpatiaLiteCreation, self).sql_indexes_for_field(model, f, style)
+
+        if isinstance(f, GeometryField):
+            gqn = self.connection.ops.geo_quote_name
+            qn = self.connection.ops.quote_name
+            db_table = model._meta.db_table
+
+            output.append(style.SQL_KEYWORD('SELECT ') +
+                          style.SQL_TABLE('AddGeometryColumn') + '(' +
+                          style.SQL_TABLE(gqn(db_table)) + ', ' +
+                          style.SQL_FIELD(gqn(f.column)) + ', ' +
+                          style.SQL_FIELD(str(f.srid)) + ', ' +
+                          style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
+                          style.SQL_KEYWORD(str(f.dim)) + ', ' +
+                          style.SQL_KEYWORD(str(int(not f.null))) +
+                          ');')
+
+            if f.spatial_index:
+                output.append(style.SQL_KEYWORD('SELECT ') +
+                              style.SQL_TABLE('CreateSpatialIndex') + '(' +
+                              style.SQL_TABLE(gqn(db_table)) + ', ' +
+                              style.SQL_FIELD(gqn(f.column)) + ');')
+
+        return output
+
+    def load_spatialite_sql(self):
+        """
+        This routine loads up the SpatiaLite SQL file.
+        """
+        # Getting the location of the SpatiaLite SQL file, and confirming
+        # it exists.
+        spatialite_sql = self.spatialite_init_file()
+        if not os.path.isfile(spatialite_sql):
+            raise ImproperlyConfigured('Could not find the required SpatiaLite initialization '
+                                       'SQL file (necessary for testing): %s' % spatialite_sql)
+
+        # Opening up the SpatiaLite SQL initialization file and executing
+        # as a script.
+        sql_fh = open(spatialite_sql, 'r')
+        try:
+            cur = self.connection._cursor()
+            cur.executescript(sql_fh.read())
+        finally:
+            sql_fh.close()
+
+    def spatialite_init_file(self):
+        # SPATIALITE_SQL may be placed in settings to tell GeoDjango
+        # to use a specific path to the SpatiaLite initilization SQL.
+        return getattr(settings, 'SPATIALITE_SQL',
+                       'init_spatialite-%s.%s.sql' %
+                       self.connection.ops.spatial_version[:2])

+ 4 - 3
django/contrib/gis/db/backend/spatialite/models.py → django/contrib/gis/db/backends/spatialite/models.py

@@ -2,6 +2,7 @@
  The GeometryColumns and SpatialRefSys models for the SpatiaLite backend.
 """
 from django.db import models
+from django.contrib.gis.db.backends.base import SpatialRefSysMixin
 
 class GeometryColumns(models.Model):
     """
@@ -40,7 +41,7 @@ class GeometryColumns(models.Model):
                (self.f_table_name, self.f_geometry_column,
                 self.coord_dimension, self.type, self.srid)
 
-class SpatialRefSys(models.Model):
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
     """
     The 'spatial_ref_sys' table from SpatiaLite.
     """
@@ -54,8 +55,8 @@ class SpatialRefSys(models.Model):
     def wkt(self):
         from django.contrib.gis.gdal import SpatialReference
         return SpatialReference(self.proj4text).wkt
-    
+
     class Meta:
-        abstract = True
+        app_label = 'gis'
         db_table = 'spatial_ref_sys'
         managed = False

+ 329 - 0
django/contrib/gis/db/backends/spatialite/operations.py

@@ -0,0 +1,329 @@
+import re
+from decimal import Decimal
+
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
+from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Distance
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends.sqlite3.base import DatabaseOperations
+
+class SpatiaLiteOperator(SpatialOperation):
+    "For SpatiaLite operators (e.g. `&&`, `~`)."
+    def __init__(self, operator):
+        super(SpatiaLiteOperator, self).__init__(operator=operator)
+
+class SpatiaLiteFunction(SpatialFunction):
+    "For SpatiaLite function calls."
+    def __init__(self, function, **kwargs):
+        super(SpatiaLiteFunction, self).__init__(function, **kwargs)
+
+class SpatiaLiteFunctionParam(SpatiaLiteFunction):
+    "For SpatiaLite functions that take another parameter."
+    sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)'
+
+class SpatiaLiteDistance(SpatiaLiteFunction):
+    "For SpatiaLite distance operations."
+    dist_func = 'Distance'
+    sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s'
+
+    def __init__(self, operator):
+        super(SpatiaLiteDistance, self).__init__(self.dist_func,
+                                                 operator=operator)
+
+class SpatiaLiteRelate(SpatiaLiteFunctionParam):
+    "For SpatiaLite Relate(<geom>, <pattern>) calls."
+    pattern_regex = re.compile(r'^[012TF\*]{9}$')
+    def __init__(self, pattern):
+        if not self.pattern_regex.match(pattern):
+            raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
+        super(SpatiaLiteRelate, self).__init__('Relate')
+
+# Valid distance types and substitutions
+dtypes = (Decimal, Distance, float, int, long)
+def get_dist_ops(operator):
+    "Returns operations for regular distances; spherical distances are not currently supported."
+    return (SpatiaLiteDistance(operator),)
+
+class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
+    compiler_module = 'django.contrib.gis.db.models.sql.compiler'
+    name = 'spatialite'
+    spatialite = True
+    version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
+    valid_aggregates = dict([(k, None) for k in
+                             ('Extent', 'Union')])
+
+    Adapter = SpatiaLiteAdapter
+
+    area = 'Area'
+    centroid = 'Centroid'
+    contained = 'MbrWithin'
+    difference = 'Difference'
+    distance = 'Distance'
+    envelope = 'Envelope'
+    intersection = 'Intersection'
+    length = 'GLength' # OpenGis defines Length, but this conflicts with an SQLite reserved keyword
+    num_geom = 'NumGeometries'
+    num_points = 'NumPoints'
+    point_on_surface = 'PointOnSurface'
+    scale = 'ScaleCoords'
+    svg = 'AsSVG'
+    sym_difference = 'SymDifference'
+    transform = 'Transform'
+    translate = 'ShiftCoords'
+    union = 'GUnion' # OpenGis defines Union, but this conflicts with an SQLite reserved keyword
+    unionagg = 'GUnion'
+
+    from_text = 'GeomFromText'
+    from_wkb = 'GeomFromWKB'
+    select = 'AsText(%s)'
+
+    geometry_functions = {
+        'equals' : SpatiaLiteFunction('Equals'),
+        'disjoint' : SpatiaLiteFunction('Disjoint'),
+        'touches' : SpatiaLiteFunction('Touches'),
+        'crosses' : SpatiaLiteFunction('Crosses'),
+        'within' : SpatiaLiteFunction('Within'),
+        'overlaps' : SpatiaLiteFunction('Overlaps'),
+        'contains' : SpatiaLiteFunction('Contains'),
+        'intersects' : SpatiaLiteFunction('Intersects'),
+        'relate' : (SpatiaLiteRelate, basestring),
+        # Retruns true if B's bounding box completely contains A's bounding box.
+        'contained' : SpatiaLiteFunction('MbrWithin'),
+        # Returns true if A's bounding box completely contains B's bounding box.
+        'bbcontains' : SpatiaLiteFunction('MbrContains'),
+        # Returns true if A's bounding box overlaps B's bounding box.
+        'bboverlaps' : SpatiaLiteFunction('MbrOverlaps'),
+        # These are implemented here as synonyms for Equals
+        'same_as' : SpatiaLiteFunction('Equals'),
+        'exact' : SpatiaLiteFunction('Equals'),
+        }
+
+    distance_functions = {
+        'distance_gt' : (get_dist_ops('>'), dtypes),
+        'distance_gte' : (get_dist_ops('>='), dtypes),
+        'distance_lt' : (get_dist_ops('<'), dtypes),
+        'distance_lte' : (get_dist_ops('<='), dtypes),
+        }
+    geometry_functions.update(distance_functions)
+
+    def __init__(self, connection):
+        super(DatabaseOperations, self).__init__()
+        self.connection = connection
+
+        # Load the spatialite library (must be done before getting the
+        # SpatiaLite version).
+        self.connection.load_spatialite()
+
+        try:
+            vtup = self.spatialite_version_tuple()
+            version = vtup[1:]
+            if version < (2, 3, 1):
+                raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions '
+                                           '2.3.1 and above')
+            self.spatial_version = version
+        except ImproperlyConfigured:
+            raise
+        except Exception, msg:
+            raise ImproperlyConfigured('Cannot determine the SpatiaLite version for the "%s" '
+                                       'database (error was "%s").  Was the SpatiaLite initialization '
+                                       'SQL loaded on this database?' %
+                                       (self.connection.settings_dict['NAME'], msg))
+
+        # Creating the GIS terms dictionary.
+        gis_terms = ['isnull']
+        gis_terms += self.geometry_functions.keys()
+        self.gis_terms = dict([(term, None) for term in gis_terms])
+
+    def check_aggregate_support(self, aggregate):
+        """
+        Checks if the given aggregate name is supported (that is, if it's
+        in `self.valid_aggregates`).
+        """
+        agg_name = aggregate.__class__.__name__
+        return agg_name in self.valid_aggregates
+
+    def convert_geom(self, wkt, geo_field):
+        """
+        Converts geometry WKT returned from a SpatiaLite aggregate.
+        """
+        if wkt:
+            return Geometry(wkt, geo_field.srid)
+        else:
+            return None
+
+    def geo_db_type(self, f):
+        """
+        Returns None because geometry columnas are added via the
+        `AddGeometryColumn` stored procedure on SpatiaLite.
+        """
+        return None
+
+    def get_distance(self, f, value, lookup_type):
+        """
+        Returns the distance parameters for the given geometry field,
+        lookup value, and lookup type.  SpatiaLite only supports regular
+        cartesian-based queries (no spheroid/sphere calculations for point
+        geometries like PostGIS).
+        """
+        if not value:
+            return []
+        value = value[0]
+        if isinstance(value, Distance):
+            if f.geodetic(self.connection):
+                raise ValueError('SpatiaLite does not support distance queries on '
+                                 'geometry fields with a geodetic coordinate system. '
+                                 'Distance objects; use a numeric value of your '
+                                 'distance in degrees instead.')
+            else:
+                dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
+        else:
+            dist_param = value
+        return [dist_param]
+
+    def get_geom_placeholder(self, f, value):
+        """
+        Provides a proper substitution value for Geometries that are not in the
+        SRID of the field.  Specifically, this routine will substitute in the
+        Transform() and GeomFromText() function call(s).
+        """
+        def transform_value(value, srid):
+            return not (value is None or value.srid == srid)
+        if hasattr(value, 'expression'):
+            if transform_value(value, f.srid):
+                placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
+            else:
+                placeholder = '%s'
+            # No geometry value used for F expression, substitue in
+            # the column name instead.
+            return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+        else:
+            if transform_value(value, f.srid):
+                # Adding Transform() to the SQL placeholder.
+                return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, f.srid)
+            else:
+                return '%s(%%s,%s)' % (self.from_text, f.srid)
+
+    def _get_spatialite_func(self, func):
+        """
+        Helper routine for calling PostGIS functions and returning their result.
+        """
+        cursor = self.connection._cursor()
+        try:
+            cursor.execute('SELECT %s()' % func)
+            row = cursor.fetchone()
+        except:
+            # TODO: raise helpful exception here.
+            raise
+        finally:
+            cursor.close()
+        return row[0]
+
+    def geos_version(self):
+        "Returns the version of GEOS used by SpatiaLite as a string."
+        return self._get_spatialite_func('geos_version')
+
+    def proj4_version(self):
+        "Returns the version of the PROJ.4 library used by SpatiaLite."
+        return self._get_spatialite_func('proj4_version')
+
+    def spatialite_version(self):
+        "Returns the SpatiaLite library version as a string."
+        return self._get_spatialite_func('spatialite_version')
+
+    def spatialite_version_tuple(self):
+        """
+        Returns the SpatiaLite version as a tuple (version string, major,
+        minor, subminor).
+        """
+        # Getting the PostGIS version
+        version = self.spatialite_version()
+        m = self.version_regex.match(version)
+
+        if m:
+            major = int(m.group('major'))
+            minor1 = int(m.group('minor1'))
+            minor2 = int(m.group('minor2'))
+        else:
+            raise Exception('Could not parse SpatiaLite version string: %s' % version)
+
+        return (version, major, minor1, minor2)
+
+    def spatial_aggregate_sql(self, agg):
+        """
+        Returns the spatial aggregate SQL template and function for the
+        given Aggregate instance.
+        """
+        agg_name = agg.__class__.__name__
+        if not self.check_aggregate_support(agg):
+            raise NotImplementedError('%s spatial aggregate is not implmented for this backend.' % agg_name)
+        agg_name = agg_name.lower()
+        if agg_name == 'union': agg_name += 'agg'
+        sql_template = self.select % '%(function)s(%(field)s)'
+        sql_function = getattr(self, agg_name)
+        return sql_template, sql_function
+
+    def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+        """
+        Returns the SpatiaLite-specific SQL for the given lookup value
+        [a tuple of (alias, column, db_type)], lookup type, lookup
+        value, and the model field.
+        """
+        alias, col, db_type = lvalue
+
+        # Getting the quoted field as `geo_col`.
+        geo_col = '%s.%s' % (qn(alias), qn(col))
+
+        if lookup_type in self.geometry_functions:
+            # See if a SpatiaLite geometry function matches the lookup type.
+            tmp = self.geometry_functions[lookup_type]
+
+            # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+            # distance lookups.
+            if isinstance(tmp, tuple):
+                # First element of tuple is the SpatiaLiteOperation instance, and the
+                # second element is either the type or a tuple of acceptable types
+                # that may passed in as further parameters for the lookup type.
+                op, arg_type = tmp
+
+                # Ensuring that a tuple _value_ was passed in from the user
+                if not isinstance(value, (tuple, list)):
+                    raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
+
+                # Geometry is first element of lookup tuple.
+                geom = value[0]
+
+                # Number of valid tuple parameters depends on the lookup type.
+                if len(value) != 2:
+                    raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
+
+                # Ensuring the argument type matches what we expect.
+                if not isinstance(value[1], arg_type):
+                    raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+
+                # For lookup type `relate`, the op instance is not yet created (has
+                # to be instantiated here to check the pattern parameter).
+                if lookup_type == 'relate':
+                    op = op(value[1])
+                elif lookup_type in self.distance_functions:
+                    op = op[0]
+            else:
+                op = tmp
+                geom = value
+            # Calling the `as_sql` function on the operation instance.
+            return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
+        elif lookup_type == 'isnull':
+            # Handling 'isnull' lookup type
+            return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+        raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
+
+    # Routines for getting the OGC-compliant models.
+    def geometry_columns(self):
+        from django.contrib.gis.db.backends.spatialite.models import GeometryColumns
+        return GeometryColumns
+
+    def spatial_ref_sys(self):
+        from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
+        return SpatialRefSys

+ 24 - 23
django/contrib/gis/db/backend/util.py → django/contrib/gis/db/backends/util.py

@@ -19,7 +19,7 @@ def getstatusoutput(cmd):
 
 def gqn(val):
     """
-    The geographic quote name function; used for quoting tables and 
+    The geographic quote name function; used for quoting tables and
     geometries (they use single rather than the double quotes of the
     backend quotename function).
     """
@@ -33,37 +33,38 @@ class SpatialOperation(object):
     """
     Base class for generating spatial SQL.
     """
-    def __init__(self, function='', operator='', result='', beg_subst='', end_subst=''):
+    sql_template = '%(geo_col)s %(operator)s %(geometry)s'
+
+    def __init__(self, function='', operator='', result='', **kwargs):
         self.function = function
         self.operator = operator
         self.result = result
-        self.beg_subst = beg_subst
-        try:
-            # Try and put the operator and result into to the
-            # end substitution.
-            self.end_subst = end_subst % (operator, result)
-        except TypeError:
-            self.end_subst = end_subst
-
-    @property
-    def sql_subst(self):
-        return ''.join([self.beg_subst, self.end_subst])
+        self.extra = kwargs
 
-    def as_sql(self, geo_col):
-        return self.sql_subst % self.params(geo_col)
+    def as_sql(self, geo_col, geometry='%s'):
+        return self.sql_template % self.params(geo_col, geometry)
 
-    def params(self, geo_col):
-        return (geo_col, self.operator)
+    def params(self, geo_col, geometry):
+        params = {'function' : self.function,
+                  'geo_col' : geo_col,
+                  'geometry' : geometry,
+                  'operator' : self.operator,
+                  'result' : self.result,
+                  }
+        params.update(self.extra)
+        return params
 
 class SpatialFunction(SpatialOperation):
     """
     Base class for generating spatial SQL related to a function.
     """
-    def __init__(self, func, beg_subst='%s(%s, %%s', end_subst=')', result='', operator=''):
+    sql_template = '%(function)s(%(geo_col)s, %(geometry)s)'
+
+    def __init__(self, func, result='', operator='', **kwargs):
         # Getting the function prefix.
-        kwargs = {'function' : func, 'operator' : operator, 'result' : result,
-                  'beg_subst' : beg_subst, 'end_subst' : end_subst,}
+        default = {'function' : func,
+                   'operator' : operator,
+                   'result' : result
+                   }
+        kwargs.update(default)
         super(SpatialFunction, self).__init__(**kwargs)
-
-    def params(self, geo_col):
-        return (self.function, geo_col)

+ 5 - 22
django/contrib/gis/db/models/aggregates.py

@@ -1,34 +1,17 @@
 from django.db.models import Aggregate
-from django.contrib.gis.db.backend import SpatialBackend
 from django.contrib.gis.db.models.sql import GeomField
 
-class GeoAggregate(Aggregate):
-
-    def add_to_query(self, query, alias, col, source, is_summary):
-        if hasattr(source, 'geom_type'):
-            # Doing additional setup on the Query object for spatial aggregates.
-            aggregate = getattr(query.aggregates_module, self.name)
-            
-            # Adding a conversion class instance and any selection wrapping
-            # SQL (e.g., needed by Oracle).
-            if aggregate.conversion_class is GeomField:
-                query.extra_select_fields[alias] = GeomField()
-                if SpatialBackend.select:
-                    query.custom_select[alias] = SpatialBackend.select
-     
-        super(GeoAggregate, self).add_to_query(query, alias, col, source, is_summary)
-
-class Collect(GeoAggregate):
+class Collect(Aggregate):
     name = 'Collect'
 
-class Extent(GeoAggregate):
+class Extent(Aggregate):
     name = 'Extent'
 
-class Extent3D(GeoAggregate):
+class Extent3D(Aggregate):
     name = 'Extent3D'
 
-class MakeLine(GeoAggregate):
+class MakeLine(Aggregate):
     name = 'MakeLine'
 
-class Union(GeoAggregate):
+class Union(Aggregate):
     name = 'Union'

+ 130 - 101
django/contrib/gis/db/models/fields/__init__.py → django/contrib/gis/db/models/fields.py

@@ -1,16 +1,20 @@
+from django.db.models.fields import Field
 from django.utils.translation import ugettext_lazy as _
 from django.contrib.gis import forms
-# Getting the SpatialBackend container and the geographic quoting method.
-from django.contrib.gis.db.backend import SpatialBackend, gqn
-# GeometryProxy, GEOS, and Distance imports.
 from django.contrib.gis.db.models.proxy import GeometryProxy
+from django.contrib.gis.geometry.backend import Geometry, GeometryException
 from django.contrib.gis.measure import Distance
+from django.db.models.sql.expressions import SQLEvaluator
 
 # Local cache of the spatial_ref_sys table, which holds static data.
-# This exists so that we don't have to hit the database each time.
-_srid_cache = {}
-
-def get_srid_info(srid):
+# This exists so that we don't have to hit the database each time
+# we construct a distance query.
+_srid_cache = {'postgis' : {},
+               'oracle' : {},
+               'spatialite' : {},
+               }
+
+def get_srid_info(srid, connection):
     """
     Returns the units, unit name, and spheroid WKT associated with the
     given SRID from the `spatial_ref_sys` (or equivalent) spatial database
@@ -18,19 +22,26 @@ def get_srid_info(srid):
     """
     global _srid_cache
 
-    if SpatialBackend.mysql:
+    # No `spatial_ref_sys` table in MySQL.
+    if connection.ops.mysql:
         return None, None, None
 
-    if not srid in _srid_cache:
-        from django.contrib.gis.models import SpatialRefSys
+    name = connection.ops.name
+    if not srid in _srid_cache[name]:
+        if connection.ops.postgis:
+            from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
+        elif connection.ops.oracle:
+            from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
+        elif connection.ops.spatialite:
+            from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
         sr = SpatialRefSys.objects.get(srid=srid)
         units, units_name = sr.units
         spheroid = SpatialRefSys.get_spheroid(sr.wkt)
-        _srid_cache[srid] = (units, units_name, spheroid)
+        _srid_cache[name][srid] = (units, units_name, spheroid)
 
-    return _srid_cache[srid]
+    return _srid_cache[name][srid]
 
-class GeometryField(SpatialBackend.Field):
+class GeometryField(Field):
     "The base GIS field -- maps to the OpenGIS Specification Geometry type."
 
     # The OpenGIS Geometry name.
@@ -41,7 +52,8 @@ class GeometryField(SpatialBackend.Field):
 
     description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.")
 
-    def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kwargs):
+    def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
+                 geography=False, **kwargs):
         """
         The initialization function for geometry fields.  Takes the following
         as keyword arguments:
@@ -57,6 +69,15 @@ class GeometryField(SpatialBackend.Field):
 
         dim:
          The number of dimensions for this geometry.  Defaults to 2.
+
+        extent:
+         Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
+         geometry field entry in the `USER_SDO_GEOM_METADATA` table.  Defaults
+         to (-180.0, -90.0, 180.0, 90.0).
+
+        tolerance:
+         Define the tolerance, in meters, to use for the geometry field
+         entry in the `USER_SDO_GEOM_METADATA` table.  Defaults to 0.05.
         """
 
         # Setting the index flag with the value of the `spatial_index` keyword.
@@ -73,121 +94,116 @@ class GeometryField(SpatialBackend.Field):
         # first parameter, so this works like normal fields.
         kwargs['verbose_name'] = verbose_name
 
-        super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
+        # Is this a geography rather than a geometry column?
+        self.geography = geography
+
+        # Oracle-specific private attributes for creating the entrie in
+        # `USER_SDO_GEOM_METADATA`
+        self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
+        self._tolerance = kwargs.pop('tolerance', 0.05)
+
+        super(GeometryField, self).__init__(**kwargs)
 
-    # The following properties are used to get the units, their name, and
+    # The following functions are used to get the units, their name, and
     # the spheroid corresponding to the SRID of the GeometryField.
-    def _get_srid_info(self):
+    def _get_srid_info(self, connection):
         # Get attributes from `get_srid_info`.
-        self._units, self._units_name, self._spheroid = get_srid_info(self.srid)
+        self._units, self._units_name, self._spheroid = get_srid_info(self.srid, connection)
 
-    @property
-    def spheroid(self):
+    def spheroid(self, connection):
         if not hasattr(self, '_spheroid'):
-            self._get_srid_info()
+            self._get_srid_info(connection)
         return self._spheroid
 
-    @property
-    def units(self):
+    def units(self, connection):
         if not hasattr(self, '_units'):
-            self._get_srid_info()
+            self._get_srid_info(connection)
         return self._units
 
-    @property
-    def units_name(self):
+    def units_name(self, connection):
         if not hasattr(self, '_units_name'):
-            self._get_srid_info()
+            self._get_srid_info(connection)
         return self._units_name
 
-    # The following properties are for formerly private variables that are now
-    # public for GeometryField.  Because of their use by third-party applications,
-    # a deprecation warning is issued to notify them to use new attribute name.
-    def _deprecated_warning(self, old_name, new_name):
-        from warnings import warn
-        warn('The `%s` attribute name is deprecated, please update your code to use `%s` instead.' %
-             (old_name, new_name))
-
-    @property
-    def _geom(self):
-        self._deprecated_warning('_geom', 'geom_type')
-        return self.geom_type
-
-    @property
-    def _index(self):
-        self._deprecated_warning('_index', 'spatial_index')
-        return self.spatial_index
-
-    @property
-    def _srid(self):
-        self._deprecated_warning('_srid', 'srid')
-        return self.srid
-
     ### Routines specific to GeometryField ###
-    @property
-    def geodetic(self):
+    def geodetic(self, connection):
         """
         Returns true if this field's SRID corresponds with a coordinate
         system that uses non-projected units (e.g., latitude/longitude).
         """
-        return self.units_name in self.geodetic_units
+        return self.units_name(connection) in self.geodetic_units
 
-    def get_distance(self, dist_val, lookup_type):
+    def get_distance(self, value, lookup_type, connection):
         """
         Returns a distance number in units of the field.  For example, if
         `D(km=1)` was passed in and the units of the field were in meters,
         then 1000 would be returned.
         """
-        # Getting the distance parameter and any options.
-        if len(dist_val) == 1: dist, option = dist_val[0], None
-        else: dist, option = dist_val
+        return connection.ops.get_distance(self, value, lookup_type)
 
         if isinstance(dist, Distance):
-            if self.geodetic:
+            if self.geodetic(connection):
                 # Won't allow Distance objects w/DWithin lookups on PostGIS.
-                if SpatialBackend.postgis and lookup_type == 'dwithin':
-                    raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.')
+                if connection.ops.postgis and lookup_type == 'dwithin':
+                    raise ValueError('Only numeric values of degree units are allowed on geographic DWithin queries.')
+
                 # Spherical distance calculation parameter should be in meters.
                 dist_param = dist.m
             else:
-                dist_param = getattr(dist, Distance.unit_attname(self.units_name))
+                dist_param = getattr(dist, Distance.unit_attname(self.units_name(connection)))
         else:
             # Assuming the distance is in the units of the field.
             dist_param = dist
 
-        if SpatialBackend.postgis and self.geodetic and lookup_type != 'dwithin' and option == 'spheroid':
+        if connection.ops.oracle and lookup_type == 'dwithin':
+            dist_param = 'distance=%s' % dist_param
+
+        if connection.ops.postgis and self.geodetic(connection) and lookup_type != 'dwithin' and option == 'spheroid':
             # On PostGIS, by default `ST_distance_sphere` is used; but if the
             # accuracy of `ST_distance_spheroid` is needed than the spheroid
             # needs to be passed to the SQL stored procedure.
-            return [gqn(self._spheroid), dist_param]
+            return [self._spheroid, dist_param]
         else:
             return [dist_param]
 
-    def get_geometry(self, value):
+    def get_prep_value(self, value):
         """
-        Retrieves the geometry, setting the default SRID from the given
-        lookup parameters.
+        Spatial lookup values are either a parameter that is (or may be
+        converted to) a geometry, or a sequence of lookup values that
+        begins with a geometry.  This routine will setup the geometry
+        value properly, and preserve any other lookup parameters before
+        returning to the caller.
         """
-        if isinstance(value, (tuple, list)):
+        if isinstance(value, SQLEvaluator):
+            return value
+        elif isinstance(value, (tuple, list)):
             geom = value[0]
+            seq_value = True
         else:
             geom = value
+            seq_value = False
 
         # When the input is not a GEOS geometry, attempt to construct one
         # from the given string input.
-        if isinstance(geom, SpatialBackend.Geometry):
+        if isinstance(geom, Geometry):
             pass
-        elif isinstance(geom, basestring):
+        elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'):
             try:
-                geom = SpatialBackend.Geometry(geom)
-            except SpatialBackend.GeometryException:
-                raise ValueError('Could not create geometry from lookup value: %s' % str(value))
+                geom = Geometry(geom)
+            except GeometryException:
+                raise ValueError('Could not create geometry from lookup value.')
         else:
-            raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
+            raise ValueError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
 
         # Assigning the SRID value.
         geom.srid = self.get_srid(geom)
 
-        return geom
+        if seq_value:
+            lookup_val = [geom]
+            lookup_val.extend(value[1:])
+            return tuple(lookup_val)
+        else:
+            return geom
 
     def get_srid(self, geom):
         """
@@ -206,7 +222,10 @@ class GeometryField(SpatialBackend.Field):
         super(GeometryField, self).contribute_to_class(cls, name)
 
         # Setup for lazy-instantiated Geometry object.
-        setattr(cls, self.attname, GeometryProxy(SpatialBackend.Geometry, self))
+        setattr(cls, self.attname, GeometryProxy(Geometry, self))
+
+    def db_type(self, connection):
+        return connection.ops.geo_db_type(self)
 
     def formfield(self, **kwargs):
         defaults = {'form_class' : forms.GeometryField,
@@ -217,46 +236,56 @@ class GeometryField(SpatialBackend.Field):
         defaults.update(kwargs)
         return super(GeometryField, self).formfield(**defaults)
 
-    def get_db_prep_lookup(self, lookup_type, value):
+    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
         """
-        Returns the spatial WHERE clause and associated parameters for the
-        given lookup type and value.  The value will be prepared for database
-        lookup (e.g., spatial transformation SQL will be added if necessary).
+        Prepare for the database lookup, and return any spatial parameters
+        necessary for the query.  This includes wrapping any geometry
+        parameters with a backend-specific adapter and formatting any distance
+        parameters into the correct units for the coordinate system of the
+        field.
         """
-        if lookup_type in SpatialBackend.gis_terms:
+        if lookup_type in connection.ops.gis_terms:
             # special case for isnull lookup
-            if lookup_type == 'isnull': return [], []
-
-            # Get the geometry with SRID; defaults SRID to that of the field
-            # if it is None.
-            geom = self.get_geometry(value)
-
-            # Getting the WHERE clause list and the associated params list. The params
-            # list is populated with the Adaptor wrapping the Geometry for the
-            # backend.  The WHERE clause list contains the placeholder for the adaptor
-            # (e.g. any transformation SQL).
-            where = [self.get_placeholder(geom)]
-            params = [SpatialBackend.Adaptor(geom)]
+            if lookup_type == 'isnull':
+                return []
 
+            # Populating the parameters list, and wrapping the Geometry
+            # with the Adapter of the spatial backend.
             if isinstance(value, (tuple, list)):
-                if lookup_type in SpatialBackend.distance_functions:
+                params = [connection.ops.Adapter(value[0])]
+                if lookup_type in connection.ops.distance_functions:
                     # Getting the distance parameter in the units of the field.
-                    where += self.get_distance(value[1:], lookup_type)
-                elif lookup_type in SpatialBackend.limited_where:
-                    pass
+                    params += self.get_distance(value[1:], lookup_type, connection)
                 else:
-                    # Otherwise, making sure any other parameters are properly quoted.
-                    where += map(gqn, value[1:])
-            return where, params
+                    params += value[1:]
+            elif isinstance(value, SQLEvaluator):
+                params = []
+            else:
+                params = [connection.ops.Adapter(value)]
+
+            return params
         else:
             raise TypeError("Field has invalid lookup: %s" % lookup_type)
 
-    def get_db_prep_save(self, value):
+    def get_prep_lookup(self, lookup_type, value):
+        if lookup_type == 'isnull':
+            return bool(value)
+        else:
+            return self.get_prep_value(value)
+
+    def get_db_prep_save(self, value, connection):
         "Prepares the value for saving in the database."
         if value is None:
             return None
         else:
-            return SpatialBackend.Adaptor(self.get_geometry(value))
+            return connection.ops.Adapter(self.get_prep_value(value))
+
+    def get_placeholder(self, value, connection):
+        """
+        Returns the placeholder for the geometry column for the
+        given value.
+        """
+        return connection.ops.get_geom_placeholder(self, value)
 
 # The OpenGIS Geometry Type Fields
 class PointField(GeometryField):

+ 1 - 5
django/contrib/gis/db/models/manager.py

@@ -1,6 +1,5 @@
 from django.db.models.manager import Manager
 from django.contrib.gis.db.models.query import GeoQuerySet
-from django.contrib.gis.db.models.sql.subqueries import insert_query
 
 class GeoManager(Manager):
     "Overrides Manager to return Geographic QuerySets."
@@ -54,7 +53,7 @@ class GeoManager(Manager):
 
     def make_line(self, *args, **kwargs):
         return self.get_query_set().make_line(*args, **kwargs)
-    
+
     def mem_size(self, *args, **kwargs):
         return self.get_query_set().mem_size(*args, **kwargs)
 
@@ -93,6 +92,3 @@ class GeoManager(Manager):
 
     def unionagg(self, *args, **kwargs):
         return self.get_query_set().unionagg(*args, **kwargs)
-
-    def _insert(self, values, **kwargs):
-        return insert_query(self.model, values, **kwargs)

+ 86 - 65
django/contrib/gis/db/models/query.py

@@ -1,20 +1,19 @@
-from django.core.exceptions import ImproperlyConfigured
-from django.db import connection
+from django.db import connections
 from django.db.models.query import QuerySet, Q, ValuesQuerySet, ValuesListQuerySet
 
-from django.contrib.gis.db.backend import SpatialBackend
 from django.contrib.gis.db.models import aggregates
 from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField
 from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
+from django.contrib.gis.geometry.backend import Geometry
 from django.contrib.gis.measure import Area, Distance
 
 class GeoQuerySet(QuerySet):
     "The Geographic QuerySet."
 
     ### Methods overloaded from QuerySet ###
-    def __init__(self, model=None, query=None):
+    def __init__(self, model=None, query=None, using=None):
         super(GeoQuerySet, self).__init__(model=model, query=query)
-        self.query = query or GeoQuery(self.model, connection)
+        self.query = query or GeoQuery(self.model)
 
     def values(self, *fields):
         return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields)
@@ -42,14 +41,16 @@ class GeoQuerySet(QuerySet):
              'geo_field' : geo_field,
              'setup' : False,
              }
-        if SpatialBackend.oracle:
+        connection = connections[self.db]
+        backend = connection.ops
+        if backend.oracle:
             s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
             s['procedure_args']['tolerance'] = tolerance
             s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
-        elif SpatialBackend.postgis or SpatialBackend.spatialite:
-            if not geo_field.geodetic:
+        elif backend.postgis or backend.spatialite:
+            if not geo_field.geodetic(connection):
                 # Getting the area units of the geographic field.
-                s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name))
+                s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name(connection)))
             else:
                 # TODO: Do we want to support raw number areas for geodetic fields?
                 raise Exception('Area on geodetic coordinate systems not supported.')
@@ -127,16 +128,16 @@ class GeoQuerySet(QuerySet):
         the coordinate reference system and the bounding box to be included
         in the GeoJSON representation of the geometry.
         """
-        if not SpatialBackend.postgis or not SpatialBackend.geojson:
+        backend = connections[self.db].ops
+        if not backend.geojson:
             raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
-        
+
         if not isinstance(precision, (int, long)):
             raise TypeError('Precision keyword must be set with an integer.')
-        
+
         # Setting the options flag -- which depends on which version of
         # PostGIS we're using.
-        major, minor1, minor2 = SpatialBackend.version
-        if major >=1 and (minor1 >= 4):
+        if backend.spatial_version >= (1, 4, 0):
             options = 0
             if crs and bbox: options = 3
             elif bbox: options = 1
@@ -146,7 +147,7 @@ class GeoQuerySet(QuerySet):
             if crs and bbox: options = 3
             elif crs: options = 1
             elif bbox: options = 2
-        s = {'desc' : 'GeoJSON', 
+        s = {'desc' : 'GeoJSON',
              'procedure_args' : {'precision' : precision, 'options' : options},
              'procedure_fmt' : '%(geo_col)s,%(precision)s,%(options)s',
              }
@@ -157,12 +158,12 @@ class GeoQuerySet(QuerySet):
         Returns GML representation of the given field in a `gml` attribute
         on each element of the GeoQuerySet.
         """
+        backend = connections[self.db].ops
         s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}}
-        if SpatialBackend.postgis:
+        if backend.postgis:
             # PostGIS AsGML() aggregate function parameter order depends on the
             # version -- uggh.
-            major, minor1, minor2 = SpatialBackend.version
-            if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)):
+            if backend.spatial_version > (1, 3, 1):
                 procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
             else:
                 procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
@@ -248,7 +249,7 @@ class GeoQuerySet(QuerySet):
         Scales the geometry to a new size by multiplying the ordinates
         with the given x,y,z scale factors.
         """
-        if SpatialBackend.spatialite:
+        if connections[self.db].ops.spatialite:
             if z != 0.0:
                 raise NotImplementedError('SpatiaLite does not support 3D scaling.')
             s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
@@ -308,10 +309,10 @@ class GeoQuerySet(QuerySet):
                         terms of relative moves (rather than absolute).
 
          `precision` => May be used to set the maximum number of decimal
-                        digits used in output (defaults to 8).        
+                        digits used in output (defaults to 8).
         """
         relative = int(bool(relative))
-        if not isinstance(precision, (int, long)): 
+        if not isinstance(precision, (int, long)):
             raise TypeError('SVG precision keyword argument must be an integer.')
         s = {'desc' : 'SVG',
              'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s',
@@ -333,7 +334,7 @@ class GeoQuerySet(QuerySet):
         Translates the geometry to a new location using the given numeric
         parameters as offsets.
         """
-        if SpatialBackend.spatialite:
+        if connections[self.db].ops.spatialite:
             if z != 0.0:
                 raise NotImplementedError('SpatiaLite does not support 3D translation.')
             s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
@@ -368,7 +369,7 @@ class GeoQuerySet(QuerySet):
 
         # Setting the key for the field's column with the custom SELECT SQL to
         # override the geometry column returned from the database.
-        custom_sel = '%s(%s, %s)' % (SpatialBackend.transform, geo_col, srid)
+        custom_sel = '%s(%s, %s)' % (connections[self.db].ops.transform, geo_col, srid)
         # TODO: Should we have this as an alias?
         # custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name))
         self.query.transformed_srid = srid # So other GeoQuerySet methods
@@ -396,9 +397,13 @@ class GeoQuerySet(QuerySet):
         Performs set up for executing the spatial function.
         """
         # Does the spatial backend support this?
-        func = getattr(SpatialBackend, att, False)
+        connection = connections[self.db]
+        func = getattr(connection.ops, att, False)
         if desc is None: desc = att
-        if not func: raise ImproperlyConfigured('%s stored procedure not available.' % desc)
+        if not func:
+            raise NotImplementedError('%s stored procedure not available on '
+                                      'the %s backend.' %
+                                      (desc, connection.ops.name))
 
         # Initializing the procedure arguments.
         procedure_args = {'function' : func}
@@ -442,7 +447,7 @@ class GeoQuerySet(QuerySet):
         # Adding any keyword parameters for the Aggregate object. Oracle backends
         # in particular need an additional `tolerance` parameter.
         agg_kwargs = {}
-        if SpatialBackend.oracle: agg_kwargs['tolerance'] = tolerance
+        if connections[self.db].ops.oracle: agg_kwargs['tolerance'] = tolerance
 
         # Calling the QuerySet.aggregate, and returning only the value of the aggregate.
         return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg']
@@ -479,6 +484,9 @@ class GeoQuerySet(QuerySet):
         settings.setdefault('procedure_fmt', '%(geo_col)s')
         settings.setdefault('select_params', [])
 
+        connection = connections[self.db]
+        backend = connection.ops
+
         # Performing setup for the spatial column, unless told not to.
         if settings.get('setup', True):
             default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name)
@@ -491,13 +499,16 @@ class GeoQuerySet(QuerySet):
 
         # Special handling for any argument that is a geometry.
         for name in settings['geom_args']:
-            # Using the field's get_db_prep_lookup() to get any needed
-            # transformation SQL -- we pass in a 'dummy' `contains` lookup.
-            where, params = geo_field.get_db_prep_lookup('contains', settings['procedure_args'][name])
+            # Using the field's get_placeholder() routine to get any needed
+            # transformation SQL.
+            geom = geo_field.get_prep_value(settings['procedure_args'][name])
+            params = geo_field.get_db_prep_lookup('contains', geom, connection=connection)
+            geom_placeholder = geo_field.get_placeholder(geom, connection)
+
             # Replacing the procedure format with that of any needed
             # transformation SQL.
             old_fmt = '%%(%s)s' % name
-            new_fmt = where[0] % '%%s'
+            new_fmt = geom_placeholder % '%%s'
             settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt)
             settings['select_params'].extend(params)
 
@@ -507,8 +518,10 @@ class GeoQuerySet(QuerySet):
         # If the result of this function needs to be converted.
         if settings.get('select_field', False):
             sel_fld = settings['select_field']
-            if isinstance(sel_fld, GeomField) and SpatialBackend.select:
-                self.query.custom_select[model_att] = SpatialBackend.select
+            if isinstance(sel_fld, GeomField) and backend.select:
+                self.query.custom_select[model_att] = backend.select
+            if connection.ops.oracle:
+                sel_fld.empty_strings_allowed = False
             self.query.extra_select_fields[model_att] = sel_fld
 
         # Finally, setting the extra selection attribute with
@@ -527,10 +540,14 @@ class GeoQuerySet(QuerySet):
         # If geodetic defaulting distance attribute to meters (Oracle and
         # PostGIS spherical distances return meters).  Otherwise, use the
         # units of the geometry field.
-        if geo_field.geodetic:
+        connection = connections[self.db]
+        geodetic = geo_field.geodetic(connection)
+        geography = geo_field.geography
+
+        if geodetic:
             dist_att = 'm'
         else:
-            dist_att = Distance.unit_attname(geo_field.units_name)
+            dist_att = Distance.unit_attname(geo_field.units_name(connection))
 
         # Shortcut booleans for what distance function we're using and
         # whether the geometry field is 3D.
@@ -546,19 +563,24 @@ class GeoQuerySet(QuerySet):
         # parameters that will be passed in to field's function.
         lookup_params = [geom or 'POINT (0 0)', 0]
 
+        # Getting the spatial backend operations.
+        backend = connection.ops
+
         # If the spheroid calculation is desired, either by the `spheroid`
         # keyword or when calculating the length of geodetic field, make
         # sure the 'spheroid' distance setting string is passed in so we
         # get the correct spatial stored procedure.
-        if spheroid or (SpatialBackend.postgis and geo_field.geodetic and length):
+        if spheroid or (backend.postgis and geodetic and
+                        (not geography) and length):
             lookup_params.append('spheroid')
-        where, params = geo_field.get_db_prep_lookup('distance_lte', lookup_params)
+        lookup_params = geo_field.get_prep_value(lookup_params)
+        params = geo_field.get_db_prep_lookup('distance_lte', lookup_params, connection=connection)
 
         # The `geom_args` flag is set to true if a geometry parameter was
         # passed in.
         geom_args = bool(geom)
 
-        if SpatialBackend.oracle:
+        if backend.oracle:
             if distance:
                 procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
             elif length or perimeter:
@@ -568,12 +590,10 @@ class GeoQuerySet(QuerySet):
             # Getting whether this field is in units of degrees since the field may have
             # been transformed via the `transform` GeoQuerySet method.
             if self.query.transformed_srid:
-                u, unit_name, s = get_srid_info(self.query.transformed_srid)
+                u, unit_name, s = get_srid_info(self.query.transformed_srid, connection)
                 geodetic = unit_name in geo_field.geodetic_units
-            else:
-                geodetic = geo_field.geodetic
 
-            if SpatialBackend.spatialite and geodetic:
+            if backend.spatialite and geodetic:
                 raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.')
 
             if distance:
@@ -583,14 +603,14 @@ class GeoQuerySet(QuerySet):
                     # (which will transform to the original SRID of the field rather
                     #  than to what was transformed to).
                     geom_args = False
-                    procedure_fmt = '%s(%%(geo_col)s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
+                    procedure_fmt = '%s(%%(geo_col)s, %s)' % (backend.transform, self.query.transformed_srid)
                     if geom.srid is None or geom.srid == self.query.transformed_srid:
                         # If the geom parameter srid is None, it is assumed the coordinates
                         # are in the transformed units.  A placeholder is used for the
                         # geometry parameter.  `GeomFromText` constructor is also needed
                         # to wrap geom placeholder for SpatiaLite.
-                        if SpatialBackend.spatialite:
-                            procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.from_text, self.query.transformed_srid)
+                        if backend.spatialite:
+                            procedure_fmt += ', %s(%%%%s, %s)' % (backend.from_text, self.query.transformed_srid)
                         else:
                             procedure_fmt += ', %%s'
                     else:
@@ -598,45 +618,45 @@ class GeoQuerySet(QuerySet):
                         # so wrapping the geometry placeholder in transformation SQL.
                         # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
                         # constructor.
-                        if SpatialBackend.spatialite:
-                            procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (SpatialBackend.transform, SpatialBackend.from_text,
+                        if backend.spatialite:
+                            procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (backend.transform, backend.from_text,
                                                                           geom.srid, self.query.transformed_srid)
                         else:
-                            procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
+                            procedure_fmt += ', %s(%%%%s, %s)' % (backend.transform, self.query.transformed_srid)
                 else:
                     # `transform()` was not used on this GeoQuerySet.
                     procedure_fmt  = '%(geo_col)s,%(geom)s'
 
-                if geodetic:
+                if not geography and geodetic:
                     # Spherical distance calculation is needed (because the geographic
                     # field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
                     # procedures may only do queries from point columns to point geometries
                     # some error checking is required.
                     if not isinstance(geo_field, PointField):
                         raise ValueError('Spherical distance calculation only supported on PointFields.')
-                    if not str(SpatialBackend.Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
+                    if not str(Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
                         raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
                     # The `function` procedure argument needs to be set differently for
                     # geodetic distance calculations.
                     if spheroid:
                         # Call to distance_spheroid() requires spheroid param as well.
-                        procedure_fmt += ',%(spheroid)s'
-                        procedure_args.update({'function' : SpatialBackend.distance_spheroid, 'spheroid' : where[1]})
+                        procedure_fmt += ",'%(spheroid)s'"
+                        procedure_args.update({'function' : backend.distance_spheroid, 'spheroid' : params[1]})
                     else:
-                        procedure_args.update({'function' : SpatialBackend.distance_sphere})
+                        procedure_args.update({'function' : backend.distance_sphere})
             elif length or perimeter:
                 procedure_fmt = '%(geo_col)s'
-                if geodetic and length:
+                if not geography and geodetic and length:
                     # There's no `length_sphere`, and `length_spheroid` also
                     # works on 3D geometries.
-                    procedure_fmt += ',%(spheroid)s'
-                    procedure_args.update({'function' : SpatialBackend.length_spheroid, 'spheroid' : where[1]})
-                elif geom_3d and SpatialBackend.postgis:
+                    procedure_fmt += ",'%(spheroid)s'"
+                    procedure_args.update({'function' : backend.length_spheroid, 'spheroid' : params[1]})
+                elif geom_3d and backend.postgis:
                     # Use 3D variants of perimeter and length routines on PostGIS.
                     if perimeter:
-                        procedure_args.update({'function' : SpatialBackend.perimeter3d})
+                        procedure_args.update({'function' : backend.perimeter3d})
                     elif length:
-                        procedure_args.update({'function' : SpatialBackend.length3d})
+                        procedure_args.update({'function' : backend.length3d})
 
         # Setting up the settings for `_spatial_attribute`.
         s = {'select_field' : DistanceField(dist_att),
@@ -651,7 +671,7 @@ class GeoQuerySet(QuerySet):
         elif geom:
             # The geometry is passed in as a parameter because we handled
             # transformation conditions in this routine.
-            s['select_params'] = [SpatialBackend.Adaptor(geom)]
+            s['select_params'] = [backend.Adapter(geom)]
         return self._spatial_attribute(func, s, **kwargs)
 
     def _geom_attribute(self, func, tolerance=0.05, **kwargs):
@@ -660,7 +680,7 @@ class GeoQuerySet(QuerySet):
         Geometry attribute (e.g., `centroid`, `point_on_surface`).
         """
         s = {'select_field' : GeomField(),}
-        if SpatialBackend.oracle:
+        if connections[self.db].ops.oracle:
             s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
             s['procedure_args'] = {'tolerance' : tolerance}
         return self._spatial_attribute(func, s, **kwargs)
@@ -677,7 +697,7 @@ class GeoQuerySet(QuerySet):
              'procedure_fmt' : '%(geo_col)s,%(geom)s',
              'procedure_args' : {'geom' : geom},
             }
-        if SpatialBackend.oracle:
+        if connections[self.db].ops.oracle:
             s['procedure_fmt'] += ',%(tolerance)s'
             s['procedure_args']['tolerance'] = tolerance
         return self._spatial_attribute(func, s, **kwargs)
@@ -694,16 +714,17 @@ class GeoQuerySet(QuerySet):
             # If so, it'll have to be added to the select related information
             # (e.g., if 'location__point' was given as the field name).
             self.query.add_select_related([field_name])
-            self.query.pre_sql_setup()
+            compiler = self.query.get_compiler(self.db)
+            compiler.pre_sql_setup()
             rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
-            return self.query._field_column(geo_field, rel_table)
+            return compiler._field_column(geo_field, rel_table)
         elif not geo_field in opts.local_fields:
             # This geographic field is inherited from another model, so we have to
             # use the db table for the _parent_ model instead.
             tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
-            return self.query._field_column(geo_field, parent_model._meta.db_table)
+            return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table)
         else:
-            return self.query._field_column(geo_field)
+            return self.query.get_compiler(self.db)._field_column(geo_field)
 
 class GeoValuesQuerySet(ValuesQuerySet):
     def __init__(self, *args, **kwargs):

+ 30 - 95
django/contrib/gis/db/models/sql/aggregates.py

@@ -1,84 +1,10 @@
 from django.db.models.sql.aggregates import *
 from django.contrib.gis.db.models.fields import GeometryField
 from django.contrib.gis.db.models.sql.conversion import GeomField
-from django.contrib.gis.db.backend import SpatialBackend
-
-# Default SQL template for spatial aggregates.
-geo_template = '%(function)s(%(field)s)'
-
-# Default conversion functions for aggregates; will be overridden if implemented
-# for the spatial backend.
-def convert_extent(box):
-    raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
-
-def convert_extent3d(box):
-    raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
-
-def convert_geom(wkt, geo_field):
-    raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
-
-if SpatialBackend.postgis:
-    def convert_extent(box):
-        # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
-        # parsing out and returning as a 4-tuple.
-        ll, ur = box[4:-1].split(',')
-        xmin, ymin = map(float, ll.split())
-        xmax, ymax = map(float, ur.split())
-        return (xmin, ymin, xmax, ymax)
-
-    def convert_extent3d(box3d):
-        # Box text will be something like "BOX3D(-90.0 30.0 1, -85.0 40.0 2)";
-        # parsing out and returning as a 4-tuple.
-        ll, ur = box3d[6:-1].split(',')
-        xmin, ymin, zmin = map(float, ll.split())
-        xmax, ymax, zmax = map(float, ur.split())
-        return (xmin, ymin, zmin, xmax, ymax, zmax)
-
-    def convert_geom(hex, geo_field):
-        if hex: return SpatialBackend.Geometry(hex)
-        else: return None
-elif SpatialBackend.oracle:
-    # Oracle spatial aggregates need a tolerance.
-    geo_template = '%(function)s(SDOAGGRTYPE(%(field)s,%(tolerance)s))'
-
-    def convert_extent(clob):
-        if clob:
-            # Generally, Oracle returns a polygon for the extent -- however,
-            # it can return a single point if there's only one Point in the
-            # table.
-            ext_geom = SpatialBackend.Geometry(clob.read())
-            gtype = str(ext_geom.geom_type)
-            if gtype == 'Polygon':
-                # Construct the 4-tuple from the coordinates in the polygon.
-                shell = ext_geom.shell
-                ll, ur = shell[0][:2], shell[2][:2]
-            elif gtype == 'Point':
-                ll = ext_geom.coords[:2]
-                ur = ll
-            else:
-                raise Exception('Unexpected geometry type returned for extent: %s' % gtype)
-            xmin, ymin = ll
-            xmax, ymax = ur
-            return (xmin, ymin, xmax, ymax)
-        else:
-            return None
-
-    def convert_geom(clob, geo_field):
-        if clob:
-            return SpatialBackend.Geometry(clob.read(), geo_field.srid)
-        else:
-            return None
-elif SpatialBackend.spatialite:
-    # SpatiaLite returns WKT.
-    def convert_geom(wkt, geo_field):
-        if wkt:
-            return SpatialBackend.Geometry(wkt, geo_field.srid)
-        else:
-            return None
 
 class GeoAggregate(Aggregate):
-    # Overriding the SQL template with the geographic one.
-    sql_template = geo_template
+    # Default SQL template for spatial aggregates.
+    sql_template = '%(function)s(%(field)s)'
 
     # Conversion class, if necessary.
     conversion_class = None
@@ -86,41 +12,50 @@ class GeoAggregate(Aggregate):
     # Flags for indicating the type of the aggregate.
     is_extent = False
 
-    def __init__(self, col, source=None, is_summary=False, **extra):
+    def __init__(self, col, source=None, is_summary=False, tolerance=0.05, **extra):
         super(GeoAggregate, self).__init__(col, source, is_summary, **extra)
 
-        if not self.is_extent and SpatialBackend.oracle:
-            self.extra.setdefault('tolerance', 0.05)
+        # Required by some Oracle aggregates.
+        self.tolerance = tolerance
 
         # Can't use geographic aggregates on non-geometry fields.
         if not isinstance(self.source, GeometryField):
             raise ValueError('Geospatial aggregates only allowed on geometry fields.')
 
-        # Making sure the SQL function is available for this spatial backend.
-        if not self.sql_function:
-            raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.')
+    def as_sql(self, qn, connection):
+        "Return the aggregate, rendered as SQL."
+
+        if connection.ops.oracle:
+            self.extra['tolerance'] = self.tolerance
+
+        if hasattr(self.col, 'as_sql'):
+            field_name = self.col.as_sql(qn, connection)
+        elif isinstance(self.col, (list, tuple)):
+            field_name = '.'.join([qn(c) for c in self.col])
+        else:
+            field_name = self.col
+
+        sql_template, sql_function = connection.ops.spatial_aggregate_sql(self)
+
+        params = {
+            'function': sql_function,
+            'field': field_name
+        }
+        params.update(self.extra)
+
+        return sql_template % params
 
 class Collect(GeoAggregate):
-    conversion_class = GeomField
-    sql_function = SpatialBackend.collect
+    pass
 
 class Extent(GeoAggregate):
     is_extent = '2D'
-    sql_function = SpatialBackend.extent
-
-if SpatialBackend.oracle:
-    # Have to change Extent's attributes here for Oracle.
-    Extent.conversion_class = GeomField
-    Extent.sql_template = '%(function)s(%(field)s)'
 
 class Extent3D(GeoAggregate):
     is_extent = '3D'
-    sql_function = SpatialBackend.extent3d
 
 class MakeLine(GeoAggregate):
-    conversion_class = GeomField
-    sql_function = SpatialBackend.make_line
+    pass
 
 class Union(GeoAggregate):
-    conversion_class = GeomField
-    sql_function = SpatialBackend.unionagg
+    pass

+ 276 - 0
django/contrib/gis/db/models/sql/compiler.py

@@ -0,0 +1,276 @@
+from itertools import izip
+from django.db.backends.util import truncate_name
+from django.db.models.sql import compiler
+from django.db.models.sql.constants import TABLE_NAME
+from django.db.models.sql.query import get_proxied_model
+
+SQLCompiler = compiler.SQLCompiler
+
+class GeoSQLCompiler(compiler.SQLCompiler):
+
+    def get_columns(self, with_aliases=False):
+        """
+        Return the list of columns to use in the select statement. If no
+        columns have been specified, returns all columns relating to fields in
+        the model.
+
+        If 'with_aliases' is true, any column names that are duplicated
+        (without the table names) are given unique aliases. This is needed in
+        some cases to avoid ambiguitity with nested queries.
+
+        This routine is overridden from Query to handle customized selection of
+        geometry columns.
+        """
+        qn = self.quote_name_unless_alias
+        qn2 = self.connection.ops.quote_name
+        result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias))
+                  for alias, col in self.query.extra_select.iteritems()]
+        aliases = set(self.query.extra_select.keys())
+        if with_aliases:
+            col_aliases = aliases.copy()
+        else:
+            col_aliases = set()
+        if self.query.select:
+            only_load = self.deferred_to_columns()
+            # This loop customized for GeoQuery.
+            for col, field in izip(self.query.select, self.query.select_fields):
+                if isinstance(col, (list, tuple)):
+                    alias, column = col
+                    table = self.query.alias_map[alias][TABLE_NAME]
+                    if table in only_load and col not in only_load[table]:
+                        continue
+                    r = self.get_field_select(field, alias, column)
+                    if with_aliases:
+                        if col[1] in col_aliases:
+                            c_alias = 'Col%d' % len(col_aliases)
+                            result.append('%s AS %s' % (r, c_alias))
+                            aliases.add(c_alias)
+                            col_aliases.add(c_alias)
+                        else:
+                            result.append('%s AS %s' % (r, qn2(col[1])))
+                            aliases.add(r)
+                            col_aliases.add(col[1])
+                    else:
+                        result.append(r)
+                        aliases.add(r)
+                        col_aliases.add(col[1])
+                else:
+                    result.append(col.as_sql(qn=qn))
+
+                    if hasattr(col, 'alias'):
+                        aliases.add(col.alias)
+                        col_aliases.add(col.alias)
+
+        elif self.query.default_cols:
+            cols, new_aliases = self.get_default_columns(with_aliases,
+                    col_aliases)
+            result.extend(cols)
+            aliases.update(new_aliases)
+
+        max_name_length = self.connection.ops.max_name_length()
+        result.extend([
+                '%s%s' % (
+                    self.get_extra_select_format(alias) % aggregate.as_sql(qn=qn, connection=self.connection),
+                    alias is not None
+                        and ' AS %s' % qn(truncate_name(alias, max_name_length))
+                        or ''
+                    )
+                for alias, aggregate in self.query.aggregate_select.items()
+        ])
+
+        # This loop customized for GeoQuery.
+        for (table, col), field in izip(self.query.related_select_cols, self.query.related_select_fields):
+            r = self.get_field_select(field, table, col)
+            if with_aliases and col in col_aliases:
+                c_alias = 'Col%d' % len(col_aliases)
+                result.append('%s AS %s' % (r, c_alias))
+                aliases.add(c_alias)
+                col_aliases.add(c_alias)
+            else:
+                result.append(r)
+                aliases.add(r)
+                col_aliases.add(col)
+
+        self._select_aliases = aliases
+        return result
+
+    def get_default_columns(self, with_aliases=False, col_aliases=None,
+                            start_alias=None, opts=None, as_pairs=False):
+        """
+        Computes the default columns for selecting every field in the base
+        model. Will sometimes be called to pull in related models (e.g. via
+        select_related), in which case "opts" and "start_alias" will be given
+        to provide a starting point for the traversal.
+
+        Returns a list of strings, quoted appropriately for use in SQL
+        directly, as well as a set of aliases used in the select statement (if
+        'as_pairs' is True, returns a list of (alias, col_name) pairs instead
+        of strings as the first component and None as the second component).
+
+        This routine is overridden from Query to handle customized selection of
+        geometry columns.
+        """
+        result = []
+        if opts is None:
+            opts = self.query.model._meta
+        aliases = set()
+        only_load = self.deferred_to_columns()
+        # Skip all proxy to the root proxied model
+        proxied_model = get_proxied_model(opts)
+
+        if start_alias:
+            seen = {None: start_alias}
+        for field, model in opts.get_fields_with_model():
+            if start_alias:
+                try:
+                    alias = seen[model]
+                except KeyError:
+                    if model is proxied_model:
+                        alias = start_alias
+                    else:
+                        link_field = opts.get_ancestor_link(model)
+                        alias = self.query.join((start_alias, model._meta.db_table,
+                                link_field.column, model._meta.pk.column))
+                    seen[model] = alias
+            else:
+                # If we're starting from the base model of the queryset, the
+                # aliases will have already been set up in pre_sql_setup(), so
+                # we can save time here.
+                alias = self.query.included_inherited_models[model]
+            table = self.query.alias_map[alias][TABLE_NAME]
+            if table in only_load and field.column not in only_load[table]:
+                continue
+            if as_pairs:
+                result.append((alias, field.column))
+                aliases.add(alias)
+                continue
+            # This part of the function is customized for GeoQuery. We
+            # see if there was any custom selection specified in the
+            # dictionary, and set up the selection format appropriately.
+            field_sel = self.get_field_select(field, alias)
+            if with_aliases and field.column in col_aliases:
+                c_alias = 'Col%d' % len(col_aliases)
+                result.append('%s AS %s' % (field_sel, c_alias))
+                col_aliases.add(c_alias)
+                aliases.add(c_alias)
+            else:
+                r = field_sel
+                result.append(r)
+                aliases.add(r)
+                if with_aliases:
+                    col_aliases.add(field.column)
+        return result, aliases
+
+    def resolve_columns(self, row, fields=()):
+        """
+        This routine is necessary so that distances and geometries returned
+        from extra selection SQL get resolved appropriately into Python
+        objects.
+        """
+        values = []
+        aliases = self.query.extra_select.keys()
+        if self.query.aggregates:
+            # If we have an aggregate annotation, must extend the aliases
+            # so their corresponding row values are included.
+            aliases.extend([None for i in xrange(len(self.query.aggregates))])
+
+        # Have to set a starting row number offset that is used for
+        # determining the correct starting row index -- needed for
+        # doing pagination with Oracle.
+        rn_offset = 0
+        if self.connection.ops.oracle:
+            if self.query.high_mark is not None or self.query.low_mark: rn_offset = 1
+        index_start = rn_offset + len(aliases)
+
+        # Converting any extra selection values (e.g., geometries and
+        # distance objects added by GeoQuerySet methods).
+        values = [self.query.convert_values(v,
+                               self.query.extra_select_fields.get(a, None),
+                               self.connection)
+                  for v, a in izip(row[rn_offset:index_start], aliases)]
+        if self.connection.ops.oracle or getattr(self.query, 'geo_values', False):
+            # We resolve the rest of the columns if we're on Oracle or if
+            # the `geo_values` attribute is defined.
+            for value, field in izip(row[index_start:], fields):
+                values.append(self.query.convert_values(value, field, self.connection))
+        else:
+            values.extend(row[index_start:])
+        return tuple(values)
+
+    #### Routines unique to GeoQuery ####
+    def get_extra_select_format(self, alias):
+        sel_fmt = '%s'
+        if alias in self.query.custom_select:
+            sel_fmt = sel_fmt % self.query.custom_select[alias]
+        return sel_fmt
+
+    def get_field_select(self, field, alias=None, column=None):
+        """
+        Returns the SELECT SQL string for the given field.  Figures out
+        if any custom selection SQL is needed for the column  The `alias`
+        keyword may be used to manually specify the database table where
+        the column exists, if not in the model associated with this
+        `GeoQuery`.  Similarly, `column` may be used to specify the exact
+        column name, rather than using the `column` attribute on `field`.
+        """
+        sel_fmt = self.get_select_format(field)
+        if field in self.query.custom_select:
+            field_sel = sel_fmt % self.query.custom_select[field]
+        else:
+            field_sel = sel_fmt % self._field_column(field, alias, column)
+        return field_sel
+
+    def get_select_format(self, fld):
+        """
+        Returns the selection format string, depending on the requirements
+        of the spatial backend.  For example, Oracle and MySQL require custom
+        selection formats in order to retrieve geometries in OGC WKT. For all
+        other fields a simple '%s' format string is returned.
+        """
+        if self.connection.ops.select and hasattr(fld, 'geom_type'):
+            # This allows operations to be done on fields in the SELECT,
+            # overriding their values -- used by the Oracle and MySQL
+            # spatial backends to get database values as WKT, and by the
+            # `transform` method.
+            sel_fmt = self.connection.ops.select
+
+            # Because WKT doesn't contain spatial reference information,
+            # the SRID is prefixed to the returned WKT to ensure that the
+            # transformed geometries have an SRID different than that of the
+            # field -- this is only used by `transform` for Oracle and
+            # SpatiaLite backends.
+            if self.query.transformed_srid and ( self.connection.ops.oracle or
+                                                 self.connection.ops.spatialite ):
+                sel_fmt = "'SRID=%d;'||%s" % (self.query.transformed_srid, sel_fmt)
+        else:
+            sel_fmt = '%s'
+        return sel_fmt
+
+    # Private API utilities, subject to change.
+    def _field_column(self, field, table_alias=None, column=None):
+        """
+        Helper function that returns the database column for the given field.
+        The table and column are returned (quoted) in the proper format, e.g.,
+        `"geoapp_city"."point"`.  If `table_alias` is not specified, the
+        database table associated with the model of this `GeoQuery` will be
+        used.  If `column` is specified, it will be used instead of the value
+        in `field.column`.
+        """
+        if table_alias is None: table_alias = self.query.model._meta.db_table
+        return "%s.%s" % (self.quote_name_unless_alias(table_alias),
+                          self.connection.ops.quote_name(column or field.column))
+
+class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
+    pass
+
+class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
+    pass
+
+class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler):
+    pass
+
+class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler):
+    pass
+
+class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler):
+    pass

+ 1 - 3
django/contrib/gis/db/models/sql/conversion.py

@@ -2,15 +2,13 @@
 This module holds simple classes used by GeoQuery.convert_values
 to convert geospatial values from the database.
 """
-from django.contrib.gis.db.backend import SpatialBackend
 
 class BaseField(object):
+    empty_strings_allowed = True
     def get_internal_type(self):
         "Overloaded method so OracleQuery.convert_values doesn't balk."
         return None
 
-if SpatialBackend.oracle: BaseField.empty_strings_allowed = False
-
 class AreaField(BaseField):
     "Wrapper for Area values."
     def __init__(self, area_att):

+ 39 - 286
django/contrib/gis/db/models/sql/query.py

@@ -1,21 +1,25 @@
-from itertools import izip
+from django.db import connections, DEFAULT_DB_ALIAS
 from django.db.models.query import sql
-from django.db.models.fields.related import ForeignKey
 
-from django.contrib.gis.db.backend import SpatialBackend
 from django.contrib.gis.db.models.fields import GeometryField
-from django.contrib.gis.db.models.sql import aggregates as gis_aggregates_module
+from django.contrib.gis.db.models.sql import aggregates as gis_aggregates
 from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
 from django.contrib.gis.db.models.sql.where import GeoWhereNode
+from django.contrib.gis.geometry.backend import Geometry
 from django.contrib.gis.measure import Area, Distance
 
-# Valid GIS query types.
-ALL_TERMS = sql.constants.QUERY_TERMS.copy()
-ALL_TERMS.update(SpatialBackend.gis_terms)
 
-# Pulling out other needed constants/routines to avoid attribute lookups.
-TABLE_NAME = sql.constants.TABLE_NAME
-get_proxied_model = sql.query.get_proxied_model
+ALL_TERMS = dict([(x, None) for x in (
+            'bbcontains', 'bboverlaps', 'contained', 'contains',
+            'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint',
+            'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte',
+            'dwithin', 'equals', 'exact',
+            'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within',
+            'left', 'right', 'overlaps_left', 'overlaps_right',
+            'overlaps_above', 'overlaps_below',
+            'strictly_above', 'strictly_below'
+            )])
+ALL_TERMS.update(sql.constants.QUERY_TERMS)
 
 class GeoQuery(sql.Query):
     """
@@ -23,11 +27,13 @@ class GeoQuery(sql.Query):
     """
     # Overridding the valid query terms.
     query_terms = ALL_TERMS
-    aggregates_module = gis_aggregates_module
+    aggregates_module = gis_aggregates
+
+    compiler = 'GeoSQLCompiler'
 
     #### Methods overridden from the base Query class ####
-    def __init__(self, model, conn):
-        super(GeoQuery, self).__init__(model, conn, where=GeoWhereNode)
+    def __init__(self, model, where=GeoWhereNode):
+        super(GeoQuery, self).__init__(model, where)
         # The following attributes are customized for the GeoQuerySet.
         # The GeoWhereNode and SpatialBackend classes contain backend-specific
         # routines and functions.
@@ -35,13 +41,6 @@ class GeoQuery(sql.Query):
         self.transformed_srid = None
         self.extra_select_fields = {}
 
-    if SpatialBackend.oracle:
-        # Have to override this so that GeoQuery, instead of OracleQuery,
-        # is returned when unpickling.
-        def __reduce__(self):
-            callable, args, data = super(GeoQuery, self).__reduce__()
-            return (unpickle_geoquery, (), data)
-
     def clone(self, *args, **kwargs):
         obj = super(GeoQuery, self).clone(*args, **kwargs)
         # Customized selection dictionary and transformed srid flag have
@@ -51,199 +50,14 @@ class GeoQuery(sql.Query):
         obj.extra_select_fields = self.extra_select_fields.copy()
         return obj
 
-    def get_columns(self, with_aliases=False):
-        """
-        Return the list of columns to use in the select statement. If no
-        columns have been specified, returns all columns relating to fields in
-        the model.
-
-        If 'with_aliases' is true, any column names that are duplicated
-        (without the table names) are given unique aliases. This is needed in
-        some cases to avoid ambiguitity with nested queries.
-
-        This routine is overridden from Query to handle customized selection of
-        geometry columns.
-        """
-        qn = self.quote_name_unless_alias
-        qn2 = self.connection.ops.quote_name
-        result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias))
-                  for alias, col in self.extra_select.iteritems()]
-        aliases = set(self.extra_select.keys())
-        if with_aliases:
-            col_aliases = aliases.copy()
-        else:
-            col_aliases = set()
-        if self.select:
-            only_load = self.deferred_to_columns()
-            # This loop customized for GeoQuery.
-            for col, field in izip(self.select, self.select_fields):
-                if isinstance(col, (list, tuple)):
-                    alias, column = col
-                    table = self.alias_map[alias][TABLE_NAME]
-                    if table in only_load and col not in only_load[table]:
-                        continue
-                    r = self.get_field_select(field, alias, column)
-                    if with_aliases:
-                        if col[1] in col_aliases:
-                            c_alias = 'Col%d' % len(col_aliases)
-                            result.append('%s AS %s' % (r, c_alias))
-                            aliases.add(c_alias)
-                            col_aliases.add(c_alias)
-                        else:
-                            result.append('%s AS %s' % (r, qn2(col[1])))
-                            aliases.add(r)
-                            col_aliases.add(col[1])
-                    else:
-                        result.append(r)
-                        aliases.add(r)
-                        col_aliases.add(col[1])
-                else:
-                    result.append(col.as_sql(quote_func=qn))
-
-                    if hasattr(col, 'alias'):
-                        aliases.add(col.alias)
-                        col_aliases.add(col.alias)
-
-        elif self.default_cols:
-            cols, new_aliases = self.get_default_columns(with_aliases,
-                    col_aliases)
-            result.extend(cols)
-            aliases.update(new_aliases)
-
-        result.extend([
-                '%s%s' % (
-                    self.get_extra_select_format(alias) % aggregate.as_sql(quote_func=qn),
-                    alias is not None and ' AS %s' % alias or ''
-                    )
-                for alias, aggregate in self.aggregate_select.items()
-        ])
-
-        # This loop customized for GeoQuery.
-        for (table, col), field in izip(self.related_select_cols, self.related_select_fields):
-            r = self.get_field_select(field, table, col)
-            if with_aliases and col in col_aliases:
-                c_alias = 'Col%d' % len(col_aliases)
-                result.append('%s AS %s' % (r, c_alias))
-                aliases.add(c_alias)
-                col_aliases.add(c_alias)
-            else:
-                result.append(r)
-                aliases.add(r)
-                col_aliases.add(col)
-
-        self._select_aliases = aliases
-        return result
-
-    def get_default_columns(self, with_aliases=False, col_aliases=None,
-                            start_alias=None, opts=None, as_pairs=False):
-        """
-        Computes the default columns for selecting every field in the base
-        model. Will sometimes be called to pull in related models (e.g. via
-        select_related), in which case "opts" and "start_alias" will be given
-        to provide a starting point for the traversal.
-
-        Returns a list of strings, quoted appropriately for use in SQL
-        directly, as well as a set of aliases used in the select statement (if
-        'as_pairs' is True, returns a list of (alias, col_name) pairs instead
-        of strings as the first component and None as the second component).
-
-        This routine is overridden from Query to handle customized selection of
-        geometry columns.
-        """
-        result = []
-        if opts is None:
-            opts = self.model._meta
-        aliases = set()
-        only_load = self.deferred_to_columns()
-        # Skip all proxy to the root proxied model
-        proxied_model = get_proxied_model(opts)
-
-        if start_alias:
-            seen = {None: start_alias}
-        for field, model in opts.get_fields_with_model():
-            if start_alias:
-                try:
-                    alias = seen[model]
-                except KeyError:
-                    if model is proxied_model:
-                        alias = start_alias
-                    else:
-                        link_field = opts.get_ancestor_link(model)
-                        alias = self.join((start_alias, model._meta.db_table,
-                                           link_field.column, model._meta.pk.column))
-                    seen[model] = alias
-            else:
-                # If we're starting from the base model of the queryset, the
-                # aliases will have already been set up in pre_sql_setup(), so
-                # we can save time here.
-                alias = self.included_inherited_models[model]
-            table = self.alias_map[alias][TABLE_NAME]
-            if table in only_load and field.column not in only_load[table]:
-                continue
-            if as_pairs:
-                result.append((alias, field.column))
-                aliases.add(alias)
-                continue
-            # This part of the function is customized for GeoQuery. We
-            # see if there was any custom selection specified in the
-            # dictionary, and set up the selection format appropriately.
-            field_sel = self.get_field_select(field, alias)
-            if with_aliases and field.column in col_aliases:
-                c_alias = 'Col%d' % len(col_aliases)
-                result.append('%s AS %s' % (field_sel, c_alias))
-                col_aliases.add(c_alias)
-                aliases.add(c_alias)
-            else:
-                r = field_sel
-                result.append(r)
-                aliases.add(r)
-                if with_aliases:
-                    col_aliases.add(field.column)
-        return result, aliases
-
-    def resolve_columns(self, row, fields=()):
-        """
-        This routine is necessary so that distances and geometries returned
-        from extra selection SQL get resolved appropriately into Python
-        objects.
-        """
-        values = []
-        aliases = self.extra_select.keys()
-        if self.aggregates:
-            # If we have an aggregate annotation, must extend the aliases
-            # so their corresponding row values are included.
-            aliases.extend([None for i in xrange(len(self.aggregates))])
-
-        # Have to set a starting row number offset that is used for
-        # determining the correct starting row index -- needed for
-        # doing pagination with Oracle.
-        rn_offset = 0
-        if SpatialBackend.oracle:
-            if self.high_mark is not None or self.low_mark: rn_offset = 1
-        index_start = rn_offset + len(aliases)
-
-        # Converting any extra selection values (e.g., geometries and
-        # distance objects added by GeoQuerySet methods).
-        values = [self.convert_values(v, self.extra_select_fields.get(a, None))
-                  for v, a in izip(row[rn_offset:index_start], aliases)]
-        if SpatialBackend.oracle or getattr(self, 'geo_values', False):
-            # We resolve the rest of the columns if we're on Oracle or if
-            # the `geo_values` attribute is defined.
-            for value, field in izip(row[index_start:], fields):
-                values.append(self.convert_values(value, field))
-        else:
-            values.extend(row[index_start:])
-        return tuple(values)
-
-    def convert_values(self, value, field):
-        """
-        Using the same routines that Oracle does we can convert our
+    def convert_values(self, value, field, connection):
+        """        Using the same routines that Oracle does we can convert our
         extra selection objects into Geometry and Distance objects.
         TODO: Make converted objects 'lazy' for less overhead.
         """
-        if SpatialBackend.oracle:
+        if connection.ops.oracle:
             # Running through Oracle's first.
-            value = super(GeoQuery, self).convert_values(value, field or GeomField())
+            value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
 
         if isinstance(field, DistanceField):
             # Using the field's distance attribute, can instantiate
@@ -252,10 +66,20 @@ class GeoQuery(sql.Query):
         elif isinstance(field, AreaField):
             value = Area(**{field.area_att : value})
         elif isinstance(field, (GeomField, GeometryField)) and value:
-            value = SpatialBackend.Geometry(value)
+            value = Geometry(value)
         return value
 
-    def resolve_aggregate(self, value, aggregate):
+    def get_aggregation(self, using):
+        # Remove any aggregates marked for reduction from the subquery
+        # and move them to the outer AggregateQuery.
+        connection = connections[using]
+        for alias, aggregate in self.aggregate_select.items():
+            if isinstance(aggregate, gis_aggregates.GeoAggregate):
+                if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
+                    self.extra_select_fields[alias] = GeomField()
+        return super(GeoQuery, self).get_aggregation(using)
+
+    def resolve_aggregate(self, value, aggregate, connection):
         """
         Overridden from GeoQuery's normalize to handle the conversion of
         GeoAggregate objects.
@@ -263,77 +87,15 @@ class GeoQuery(sql.Query):
         if isinstance(aggregate, self.aggregates_module.GeoAggregate):
             if aggregate.is_extent:
                 if aggregate.is_extent == '3D':
-                    return self.aggregates_module.convert_extent3d(value)
+                    return connection.ops.convert_extent3d(value)
                 else:
-                    return self.aggregates_module.convert_extent(value)
+                    return connection.ops.convert_extent(value)
             else:
-                return self.aggregates_module.convert_geom(value, aggregate.source)
-        else:
-            return super(GeoQuery, self).resolve_aggregate(value, aggregate)
-
-    #### Routines unique to GeoQuery ####
-    def get_extra_select_format(self, alias):
-        sel_fmt = '%s'
-        if alias in self.custom_select:
-            sel_fmt = sel_fmt % self.custom_select[alias]
-        return sel_fmt
-
-    def get_field_select(self, field, alias=None, column=None):
-        """
-        Returns the SELECT SQL string for the given field.  Figures out
-        if any custom selection SQL is needed for the column  The `alias`
-        keyword may be used to manually specify the database table where
-        the column exists, if not in the model associated with this
-        `GeoQuery`.  Similarly, `column` may be used to specify the exact
-        column name, rather than using the `column` attribute on `field`.
-        """
-        sel_fmt = self.get_select_format(field)
-        if field in self.custom_select:
-            field_sel = sel_fmt % self.custom_select[field]
+                return connection.ops.convert_geom(value, aggregate.source)
         else:
-            field_sel = sel_fmt % self._field_column(field, alias, column)
-        return field_sel
-
-    def get_select_format(self, fld):
-        """
-        Returns the selection format string, depending on the requirements
-        of the spatial backend.  For example, Oracle and MySQL require custom
-        selection formats in order to retrieve geometries in OGC WKT. For all
-        other fields a simple '%s' format string is returned.
-        """
-        if SpatialBackend.select and hasattr(fld, 'geom_type'):
-            # This allows operations to be done on fields in the SELECT,
-            # overriding their values -- used by the Oracle and MySQL
-            # spatial backends to get database values as WKT, and by the
-            # `transform` method.
-            sel_fmt = SpatialBackend.select
-
-            # Because WKT doesn't contain spatial reference information,
-            # the SRID is prefixed to the returned WKT to ensure that the
-            # transformed geometries have an SRID different than that of the
-            # field -- this is only used by `transform` for Oracle and
-            # SpatiaLite backends.
-            if self.transformed_srid and ( SpatialBackend.oracle or
-                                           SpatialBackend.spatialite ):
-                sel_fmt = "'SRID=%d;'||%s" % (self.transformed_srid, sel_fmt)
-        else:
-            sel_fmt = '%s'
-        return sel_fmt
+            return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
 
     # Private API utilities, subject to change.
-    def _field_column(self, field, table_alias=None, column=None):
-        """
-        Helper function that returns the database column for the given field.
-        The table and column are returned (quoted) in the proper format, e.g.,
-        `"geoapp_city"."point"`.  If `table_alias` is not specified, the
-        database table associated with the model of this `GeoQuery` will be
-        used.  If `column` is specified, it will be used instead of the value
-        in `field.column`.
-        """
-        if table_alias is None: table_alias = self.model._meta.db_table
-        return "%s.%s" % (self.quote_name_unless_alias(table_alias),
-                          self.connection.ops.quote_name(column or field.column))
-
     def _geo_field(self, field_name=None):
         """
         Returns the first Geometry field encountered; or specified via the
@@ -350,12 +112,3 @@ class GeoQuery(sql.Query):
             # Otherwise, check by the given field name -- which may be
             # a lookup to a _related_ geographic field.
             return GeoWhereNode._check_geo_field(self.model._meta, field_name)
-
-if SpatialBackend.oracle:
-    def unpickle_geoquery():
-        """
-        Utility function, called by Python's unpickling machinery, that handles
-        unpickling of GeoQuery subclasses of OracleQuery.
-        """
-        return GeoQuery.__new__(GeoQuery)
-    unpickle_geoquery.__safe_for_unpickling__ = True

+ 0 - 39
django/contrib/gis/db/models/sql/subqueries.py

@@ -1,39 +0,0 @@
-from django.contrib.gis.db.backend import SpatialBackend
-from django.db.models.query import insert_query
-
-if SpatialBackend.oracle:
-    from django.db import connection
-    from django.db.models.sql.subqueries import InsertQuery
-
-    class OracleGeoInsertQuery(InsertQuery):
-        def insert_values(self, insert_values, raw_values=False):
-            """
-            This routine is overloaded from InsertQuery so that no parameter is
-            passed into cx_Oracle for NULL geometries.  The reason is that
-            cx_Oracle has no way to bind Oracle object values (like
-            MDSYS.SDO_GEOMETRY).
-            """
-            placeholders, values = [], []
-            for field, val in insert_values:
-                if hasattr(field, 'get_placeholder'):
-                    ph = field.get_placeholder(val)
-                else:
-                    ph = '%s'
-
-                placeholders.append(ph)
-                self.columns.append(field.column)
-
-                # If 'NULL' for the placeholder, omit appending None
-                # to the values list (which is used for db params).
-                if not ph == 'NULL':
-                    values.append(val)
-            if raw_values:
-                self.values.extend(values)
-            else:
-                self.params += tuple(values)
-                self.values.extend(placeholders)
-
-    def insert_query(model, values, return_id=False, raw_values=False):
-        query = OracleGeoInsertQuery(model, connection)
-        query.insert_values(values, raw_values)
-        return query.execute_sql(return_id)

+ 37 - 84
django/contrib/gis/db/models/sql/where.py

@@ -1,23 +1,31 @@
-from django.db import connection
 from django.db.models.fields import Field, FieldDoesNotExist
 from django.db.models.sql.constants import LOOKUP_SEP
 from django.db.models.sql.expressions import SQLEvaluator
-from django.db.models.sql.where import WhereNode
-from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend
+from django.db.models.sql.where import Constraint, WhereNode
 from django.contrib.gis.db.models.fields import GeometryField
-qn = connection.ops.quote_name
 
-class GeoAnnotation(object):
+class GeoConstraint(Constraint):
     """
-    The annotation used for GeometryFields; basically a placeholder
-    for metadata needed by the `get_geo_where_clause` of the spatial
-    backend.
+    This subclass overrides `process` to better handle geographic SQL
+    construction.
     """
-    def __init__(self, field, value, where):
-        self.geodetic = field.geodetic
-        self.geom_type = field.geom_type
-        self.value = value
-        self.where = tuple(where)
+    def __init__(self, init_constraint):
+        self.alias = init_constraint.alias
+        self.col = init_constraint.col
+        self.field = init_constraint.field
+
+    def process(self, lookup_type, value, connection):
+        if isinstance(value, SQLEvaluator):
+            # Make sure the F Expression destination field exists, and
+            # set an `srid` attribute with the same as that of the
+            # destination.
+            geo_fld = GeoWhereNode._check_geo_field(value.opts, value.expression.name)
+            if not geo_fld:
+                raise ValueError('No geographic field found in expression.')
+            value.srid = geo_fld.srid
+        db_type = self.field.db_type(connection=connection)
+        params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection)
+        return (self.alias, self.col, db_type), params
 
 class GeoWhereNode(WhereNode):
     """
@@ -25,81 +33,26 @@ class GeoWhereNode(WhereNode):
     these are tied to the GeoQuery class that created it.
     """
     def add(self, data, connector):
-        """
-        This is overridden from the regular WhereNode to handle the 
-        peculiarties of GeometryFields, because they need a special 
-        annotation object that contains the spatial metadata from the 
-        field to generate the spatial SQL.
-        """
-        if not isinstance(data, (list, tuple)):
-            return super(WhereNode, self).add(data, connector)
-
-        obj, lookup_type, value = data
-        col, field = obj.col, obj.field
-
-        if not hasattr(field, "geom_type"):
-            # Not a geographic field, so call `WhereNode.add`.
-            return super(GeoWhereNode, self).add(data, connector)
-        else:
-            if isinstance(value, SQLEvaluator):
-                # Getting the geographic field to compare with from the expression.
-                geo_fld = self._check_geo_field(value.opts, value.expression.name)
-                if not geo_fld:
-                    raise ValueError('No geographic field found in expression.')
-
-                # Get the SRID of the geometry field that the expression was meant 
-                # to operate on -- it's needed to determine whether transformation 
-                # SQL is necessary.
-                srid = geo_fld.srid
-
-                # Getting the quoted representation of the geometry column that
-                # the expression is operating on.
-                geo_col = '%s.%s' % tuple(map(qn, value.cols[value.expression]))
-
-                # If it's in a different SRID, we'll need to wrap in 
-                # transformation SQL.
-                if not srid is None and srid != field.srid and SpatialBackend.transform:
-                    placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field.srid)
-                else:
-                    placeholder = '%s'
-
-                # Setting these up as if we had called `field.get_db_prep_lookup()`.
-                where =  [placeholder % geo_col]
-                params = ()
-            else:
-                # `GeometryField.get_db_prep_lookup` returns a where clause
-                # substitution array in addition to the parameters.
-                where, params = field.get_db_prep_lookup(lookup_type, value)
-
-            # The annotation will be a `GeoAnnotation` object that
-            # will contain the necessary geometry field metadata for
-            # the `get_geo_where_clause` to construct the appropriate
-            # spatial SQL when `make_atom` is called.
-            annotation = GeoAnnotation(field, value, where)
-            return super(WhereNode, self).add(((obj.alias, col, field.db_type()), lookup_type, annotation, params), connector)
-
-    def make_atom(self, child, qn):
-        obj, lookup_type, value_annot, params = child
-
-        if isinstance(value_annot, GeoAnnotation):
-            if lookup_type in SpatialBackend.gis_terms:
-                # Getting the geographic where clause; substitution parameters
-                # will be populated in the GeoFieldSQL object returned by the
-                # GeometryField.
-                alias, col, db_type = obj
-                gwc = get_geo_where_clause(alias, col, lookup_type, value_annot)
-                return gwc % value_annot.where, params
-            else:
-                raise TypeError('Invalid lookup type: %r' % lookup_type)
+        if isinstance(data, (list, tuple)):
+            obj, lookup_type, value = data
+            if ( isinstance(obj, Constraint) and
+                 isinstance(obj.field, GeometryField) ):
+                data = (GeoConstraint(obj), lookup_type, value)
+        super(GeoWhereNode, self).add(data, connector)
+
+    def make_atom(self, child, qn, connection):
+        lvalue, lookup_type, value_annot, params_or_value = child
+        if isinstance(lvalue, GeoConstraint):
+            data, params = lvalue.process(lookup_type, params_or_value, connection)
+            spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
+            return spatial_sql, params
         else:
-            # If not a GeometryField, call the `make_atom` from the 
-            # base class.
-            return super(GeoWhereNode, self).make_atom(child, qn)
+            return super(GeoWhereNode, self).make_atom(child, qn, connection)
 
     @classmethod
     def _check_geo_field(cls, opts, lookup):
         """
-        Utility for checking the given lookup with the given model options.  
+        Utility for checking the given lookup with the given model options.
         The lookup is a string either specifying the geographic field, e.g.
         'point, 'the_geom', or a related lookup on a geographic field like
         'address__point'.
@@ -121,7 +74,7 @@ class GeoWhereNode(WhereNode):
             # If the field list is still around, then it means that the
             # lookup was for a geometry field across a relationship --
             # thus we keep on getting the related model options and the
-            # model field associated with the next field in the list 
+            # model field associated with the next field in the list
             # until there's no more left.
             while len(field_list):
                 opts = geo_fld.rel.to._meta

+ 0 - 0
django/contrib/gis/geometry/__init__.py


+ 21 - 0
django/contrib/gis/geometry/backend/__init__.py

@@ -0,0 +1,21 @@
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
+
+geom_backend = getattr(settings, 'GEOMETRY_BACKEND', 'geos')
+
+try:
+    module = import_module('.%s' % geom_backend, 'django.contrib.gis.geometry.backend')
+except ImportError, e:
+    try:
+        module = import_module(geom_backend)
+    except ImportError, e_user:
+        raise ImproperlyConfigured('Could not import user-defined GEOMETRY_BACKEND '
+                                   '"%s".' % geom_backend)
+
+try:
+    Geometry = module.Geometry
+    GeometryException = module.GeometryException
+except AttributeError:
+    raise ImproperlyConfigured('Cannot import Geometry from the "%s" '
+                               'geometry backend.' % geom_backend)

+ 3 - 0
django/contrib/gis/geometry/backend/geos.py

@@ -0,0 +1,3 @@
+from django.contrib.gis.geos import \
+    GEOSGeometry as Geometry, \
+    GEOSException as GeometryException

+ 0 - 233
django/contrib/gis/models.py

@@ -1,233 +0,0 @@
-"""
- Imports the SpatialRefSys and GeometryColumns models dependent on the
- spatial database backend.
-"""
-import re
-from django.conf import settings
-
-# Checking for the presence of GDAL (needed for the SpatialReference object)
-from django.contrib.gis.gdal import HAS_GDAL, PYTHON23
-if HAS_GDAL:
-    from django.contrib.gis.gdal import SpatialReference
-
-class SpatialRefSysMixin(object):
-    """
-    The SpatialRefSysMixin is a class used by the database-dependent
-    SpatialRefSys objects to reduce redundnant code.
-    """
-    # For pulling out the spheroid from the spatial reference string. This
-    # regular expression is used only if the user does not have GDAL installed.
-    # TODO: Flattening not used in all ellipsoids, could also be a minor axis,
-    # or 'b' parameter.
-    spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
-
-    # For pulling out the units on platforms w/o GDAL installed.
-    # TODO: Figure out how to pull out angular units of projected coordinate system and
-    # fix for LOCAL_CS types.  GDAL should be highly recommended for performing
-    # distance queries.
-    units_regex = re.compile(r'.+UNIT ?\["(?P<unit_name>[\w \'\(\)]+)", ?(?P<unit>[\d\.]+)(,AUTHORITY\["(?P<unit_auth_name>[\w \'\(\)]+)","(?P<unit_auth_val>\d+)"\])?\]([\w ]+)?(,AUTHORITY\["(?P<auth_name>[\w \'\(\)]+)","(?P<auth_val>\d+)"\])?\]$')
-
-    def srs(self):
-        """
-        Returns a GDAL SpatialReference object, if GDAL is installed.
-        """
-        if HAS_GDAL:
-            # TODO: Is caching really necessary here?  Is complexity worth it?
-            if hasattr(self, '_srs'):
-                # Returning a clone of the cached SpatialReference object.
-                return self._srs.clone()
-            else:
-                # Attempting to cache a SpatialReference object.
-
-                # Trying to get from WKT first.
-                try:
-                    self._srs = SpatialReference(self.wkt)
-                    return self.srs
-                except Exception, msg:
-                    pass
-
-                try:
-                    self._srs = SpatialReference(self.proj4text)
-                    return self.srs
-                except Exception, msg:
-                    pass
-
-                raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
-        else:
-            raise Exception('GDAL is not installed.')
-    srs = property(srs)
-
-    def ellipsoid(self):
-        """
-        Returns a tuple of the ellipsoid parameters:
-        (semimajor axis, semiminor axis, and inverse flattening).
-        """
-        if HAS_GDAL:
-            return self.srs.ellipsoid
-        else:
-            m = self.spheroid_regex.match(self.wkt)
-            if m: return (float(m.group('major')), float(m.group('flattening')))
-            else: return None
-    ellipsoid = property(ellipsoid)
-
-    def name(self):
-        "Returns the projection name."
-        return self.srs.name
-    name = property(name)
-
-    def spheroid(self):
-        "Returns the spheroid name for this spatial reference."
-        return self.srs['spheroid']
-    spheroid = property(spheroid)
-
-    def datum(self):
-        "Returns the datum for this spatial reference."
-        return self.srs['datum']
-    datum = property(datum)
-
-    def projected(self):
-        "Is this Spatial Reference projected?"
-        if HAS_GDAL:
-            return self.srs.projected
-        else:
-            return self.wkt.startswith('PROJCS')
-    projected = property(projected)
-
-    def local(self):
-        "Is this Spatial Reference local?"
-        if HAS_GDAL:
-            return self.srs.local
-        else:
-            return self.wkt.startswith('LOCAL_CS')
-    local = property(local)
-
-    def geographic(self):
-        "Is this Spatial Reference geographic?"
-        if HAS_GDAL:
-            return self.srs.geographic
-        else:
-            return self.wkt.startswith('GEOGCS')
-    geographic = property(geographic)
-
-    def linear_name(self):
-        "Returns the linear units name."
-        if HAS_GDAL:
-            return self.srs.linear_name
-        elif self.geographic:
-            return None
-        else:
-            m = self.units_regex.match(self.wkt)
-            return m.group('unit_name')
-    linear_name = property(linear_name)
-
-    def linear_units(self):
-        "Returns the linear units."
-        if HAS_GDAL:
-            return self.srs.linear_units
-        elif self.geographic:
-            return None
-        else:
-            m = self.units_regex.match(self.wkt)
-            return m.group('unit')
-    linear_units = property(linear_units)
-
-    def angular_name(self):
-        "Returns the name of the angular units."
-        if HAS_GDAL:
-            return self.srs.angular_name
-        elif self.projected:
-            return None
-        else:
-            m = self.units_regex.match(self.wkt)
-            return m.group('unit_name')
-    angular_name = property(angular_name)
-
-    def angular_units(self):
-        "Returns the angular units."
-        if HAS_GDAL:
-            return self.srs.angular_units
-        elif self.projected:
-            return None
-        else:
-            m = self.units_regex.match(self.wkt)
-            return m.group('unit')
-    angular_units = property(angular_units)
-
-    def units(self):
-        "Returns a tuple of the units and the name."
-        if self.projected or self.local:
-            return (self.linear_units, self.linear_name)
-        elif self.geographic:
-            return (self.angular_units, self.angular_name)
-        else:
-            return (None, None)
-    units = property(units)
-
-    def get_units(cls, wkt):
-        """
-        Class method used by GeometryField on initialization to
-        retrive the units on the given WKT, without having to use
-        any of the database fields.
-        """
-        if HAS_GDAL:
-            return SpatialReference(wkt).units
-        else:
-            m = cls.units_regex.match(wkt)
-            return m.group('unit'), m.group('unit_name')
-    get_units = classmethod(get_units)
-
-    def get_spheroid(cls, wkt, string=True):
-        """
-        Class method used by GeometryField on initialization to
-        retrieve the `SPHEROID[..]` parameters from the given WKT.
-        """
-        if HAS_GDAL:
-            srs = SpatialReference(wkt)
-            sphere_params = srs.ellipsoid
-            sphere_name = srs['spheroid']
-        else:
-            m = cls.spheroid_regex.match(wkt)
-            if m:
-                sphere_params = (float(m.group('major')), float(m.group('flattening')))
-                sphere_name = m.group('name')
-            else:
-                return None
-
-        if not string:
-            return sphere_name, sphere_params
-        else:
-            # `string` parameter used to place in format acceptable by PostGIS
-            if len(sphere_params) == 3:
-                radius, flattening = sphere_params[0], sphere_params[2]
-            else:
-                radius, flattening = sphere_params
-            return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
-    get_spheroid = classmethod(get_spheroid)
-
-    def __unicode__(self):
-        """
-        Returns the string representation.  If GDAL is installed,
-        it will be 'pretty' OGC WKT.
-        """
-        try:
-            return unicode(self.srs)
-        except:
-            return unicode(self.wkt)
-
-# Django test suite on 2.3 platforms will choke on code inside this
-# conditional.
-if not PYTHON23:
-    try:
-        # try/except'ing the importation of SpatialBackend.  Have to fail
-        # silently because this module may be inadvertently invoked by
-        # non-GeoDjango users (e.g., when the Django test suite executes
-        # the models.py of all contrib apps).
-        from django.contrib.gis.db.backend import SpatialBackend
-        if SpatialBackend.mysql: raise Exception
-
-        # Exposing the SpatialRefSys and GeometryColumns models.
-        class SpatialRefSys(SpatialBackend.SpatialRefSys, SpatialRefSysMixin):
-            pass
-        GeometryColumns = SpatialBackend.GeometryColumns
-    except:
-        pass

+ 13 - 11
django/contrib/gis/sitemaps/views.py

@@ -1,11 +1,11 @@
 from django.http import HttpResponse, Http404
 from django.template import loader
-from django.contrib.gis.db.backend import SpatialBackend
 from django.contrib.sites.models import Site
 from django.core import urlresolvers
 from django.core.paginator import EmptyPage, PageNotAnInteger
-from django.db.models import get_model
 from django.contrib.gis.db.models.fields import GeometryField
+from django.db import connections, DEFAULT_DB_ALIAS
+from django.db.models import get_model
 from django.utils.encoding import smart_str
 
 from django.contrib.gis.shortcuts import render_to_kml, render_to_kmz
@@ -25,7 +25,7 @@ def index(request, sitemaps):
             pages = site.paginator.num_pages
         sitemap_url = urlresolvers.reverse('django.contrib.gis.sitemaps.views.sitemap', kwargs={'section': section})
         sites.append('%s://%s%s' % (protocol, current_site.domain, sitemap_url))
-      
+
         if pages > 1:
             for page in range(2, pages+1):
                 sites.append('%s://%s%s?p=%s' % (protocol, current_site.domain, sitemap_url, page))
@@ -59,7 +59,7 @@ def sitemap(request, sitemaps, section=None):
     xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls}))
     return HttpResponse(xml, mimetype='application/xml')
 
-def kml(request, label, model, field_name=None, compress=False):
+def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS):
     """
     This view generates KML for the given app label, model, and field name.
 
@@ -79,17 +79,19 @@ def kml(request, label, model, field_name=None, compress=False):
         except:
             raise Http404('Invalid geometry field.')
 
-    if SpatialBackend.postgis:
+    connection = connections[using]
+
+    if connection.ops.postgis:
         # PostGIS will take care of transformation.
-        placemarks = klass._default_manager.kml(field_name=field_name)
+        placemarks = klass._default_manager.using(using).kml(field_name=field_name)
     else:
         # There's no KML method on Oracle or MySQL, so we use the `kml`
         # attribute of the lazy geometry instead.
         placemarks = []
-        if SpatialBackend.oracle:
-            qs = klass._default_manager.transform(4326, field_name=field_name)
+        if connection.ops.oracle:
+            qs = klass._default_manager.using(using).transform(4326, field_name=field_name)
         else:
-            qs = klass._default_manager.all()
+            qs = klass._default_manager.using(using).all()
         for mod in qs:
             setattr(mod, 'kml', getattr(mod, field_name).kml)
             placemarks.append(mod)
@@ -101,8 +103,8 @@ def kml(request, label, model, field_name=None, compress=False):
         render = render_to_kml
     return render('gis/kml/placemarks.kml', {'places' : placemarks})
 
-def kmz(request, label, model, field_name=None):
+def kmz(request, label, model, field_name=None, using=DEFAULT_DB_ALIAS):
     """
     This view returns KMZ for the given app label, model, and field name.
     """
-    return kml(request, label, model, field_name, True)
+    return kml(request, label, model, field_name, compress=True, using=using)

+ 12 - 74
django/contrib/gis/tests/__init__.py

@@ -1,4 +1,5 @@
 import sys, unittest
+from django.test.simple import run_tests
 from django.utils.importlib import import_module
 
 def geo_suite():
@@ -14,12 +15,11 @@ def geo_suite():
     from django.contrib.gis.utils import HAS_GEOIP
     from django.contrib.gis.tests.utils import postgis, mysql
 
-    # The test suite.
-    s = unittest.TestSuite()
+    gis_tests = []
 
     # Adding the GEOS tests.
     from django.contrib.gis.geos import tests as geos_tests
-    s.addTest(geos_tests.suite())
+    gis_tests.append(geos_tests.suite())
 
     # Tests that require use of a spatial database (e.g., creation of models)
     test_apps = ['geoapp', 'relatedapp']
@@ -44,7 +44,7 @@ def geo_suite():
 
         # Adding the GDAL tests.
         from django.contrib.gis.gdal import tests as gdal_tests
-        s.addTest(gdal_tests.suite())
+        gis_tests.append(gdal_tests.suite())
     else:
         print >>sys.stderr, "GDAL not available - no tests requiring GDAL will be run."
 
@@ -55,9 +55,9 @@ def geo_suite():
     # in the `test_suite_names`.
     for suite_name in test_suite_names:
         tsuite = import_module('django.contrib.gis.tests.' + suite_name)
-        s.addTest(tsuite.suite())
+        gis_tests.append(tsuite.suite())
 
-    return s, test_apps
+    return gis_tests, test_apps
 
 def run_gis_tests(test_labels, **kwargs):
     """
@@ -83,21 +83,13 @@ def run_gis_tests(test_labels, **kwargs):
     # Setting the URLs.
     settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls'
 
-    # Creating the test suite, adding the test models to INSTALLED_APPS, and
-    # adding the model test suites to our suite package.
-    gis_suite, test_apps = geo_suite()
+    # Creating the test suite, adding the test models to INSTALLED_APPS
+    # so they will be tested.
+    gis_tests, test_apps = geo_suite()
     for test_model in test_apps:
         module_name = 'django.contrib.gis.tests.%s' % test_model
-        if mysql:
-            test_module = 'tests_mysql'
-        else:
-            test_module = 'tests'
         new_installed.append(module_name)
 
-        # Getting the model test suite
-        tsuite = import_module(module_name + '.' + test_module)
-        gis_suite.addTest(tsuite.suite())
-
     # Resetting the loaded flag to take into account what we appended to
     # the INSTALLED_APPS (since this routine is invoked through
     # django/core/management, it caches the apps; this ensures that syncdb
@@ -105,67 +97,13 @@ def run_gis_tests(test_labels, **kwargs):
     settings.INSTALLED_APPS = new_installed
     loading.cache.loaded = False
 
+    kwargs['extra_tests'] = gis_tests
+
     # Running the tests using the GIS test runner.
-    result = run_tests(test_labels, suite=gis_suite, **kwargs)
+    result = run_tests(test_labels, **kwargs)
 
     # Restoring modified settings.
     settings.INSTALLED_APPS = old_installed
     settings.ROOT_URLCONF = old_root_urlconf
 
     return result
-
-def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=None):
-    """
-    Set `TEST_RUNNER` in your settings with this routine in order to
-    scaffold test spatial databases correctly for your GeoDjango models.
-    For more documentation, please consult the following URL:
-      http://geodjango.org/docs/testing.html.
-    """
-    from django.conf import settings
-    from django.db import connection
-    from django.db.models import get_app, get_apps
-    from django.test.simple import build_suite, build_test, reorder_suite, TestCase
-    from django.test.utils import setup_test_environment, teardown_test_environment
-
-    # The `create_test_spatial_db` routine abstracts away all the steps needed
-    # to properly construct a spatial database for the backend.
-    from django.contrib.gis.db.backend import create_test_spatial_db
-
-    # Setting up for testing.
-    setup_test_environment()
-    settings.DEBUG = False
-    old_name = settings.DATABASE_NAME
-
-    # Creating the test spatial database.
-    create_test_spatial_db(verbosity=verbosity, autoclobber=not interactive)
-
-    # The suite may be passed in manually, e.g., when we run the GeoDjango test,
-    # we want to build it and pass it in due to some customizations.  Otherwise,
-    # the normal test suite creation process from `django.test.simple.run_tests`
-    # is used to create the test suite.
-    if suite is None:
-        suite = unittest.TestSuite()
-        if test_labels:
-            for label in test_labels:
-                if '.' in label:
-                    suite.addTest(build_test(label))
-                else:
-                    app = get_app(label)
-                    suite.addTest(build_suite(app))
-        else:
-            for app in get_apps():
-                suite.addTest(build_suite(app))
-
-        for test in extra_tests:
-            suite.addTest(test)
-
-    suite = reorder_suite(suite, (TestCase,))
-
-    # Executing the tests (including the model tests), and destorying the
-    # test database after the tests have completed.
-    result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
-    connection.creation.destroy_test_db(old_name, verbosity)
-    teardown_test_environment()
-
-    # Returning the total failures and errors
-    return len(result.failures) + len(result.errors)

+ 7 - 7
django/contrib/gis/tests/distapp/tests.py

@@ -96,9 +96,9 @@ class DistanceTest(unittest.TestCase):
             # Creating the query set.
             qs = AustraliaCity.objects.order_by('name')
             if type_error:
-                # A TypeError should be raised on PostGIS when trying to pass
+                # A ValueError should be raised on PostGIS when trying to pass
                 # Distance objects into a DWithin query using a geodetic field.
-                self.assertRaises(TypeError, AustraliaCity.objects.filter, point__dwithin=(self.au_pnt, dist))
+                self.assertRaises(ValueError, AustraliaCity.objects.filter(point__dwithin=(self.au_pnt, dist)).count)
             else:
                 self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
 
@@ -237,15 +237,15 @@ class DistanceTest(unittest.TestCase):
             # Oracle doesn't have this limitation -- PostGIS only allows geodetic
             # distance queries from Points to PointFields.
             mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)')
-            self.assertRaises(TypeError,
+            self.assertRaises(ValueError, len,
                               AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
             # Too many params (4 in this case) should raise a ValueError.
-            self.assertRaises(ValueError,
-                              AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))
+            self.assertRaises(ValueError, len,
+                              AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))
 
         # Not enough params should raise a ValueError.
-        self.assertRaises(ValueError,
-                          AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)',))
+        self.assertRaises(ValueError, len,
+                          AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)',)))
 
         # Getting all cities w/in 550 miles of Hobart.
         hobart = AustraliaCity.objects.get(name='Hobart')

File diff suppressed because it is too large
+ 5 - 0
django/contrib/gis/tests/geoapp/fixtures/initial_data.json


+ 0 - 8
django/contrib/gis/tests/geoapp/sql/city.mysql.sql

@@ -1,8 +0,0 @@
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Houston', GeomFromText('POINT (-95.363151 29.763374)'));
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Dallas', GeomFromText('POINT (-96.801611 32.782057)'));
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Oklahoma City', GeomFromText('POINT (-97.521157 34.464642)'));
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Wellington', GeomFromText('POINT (174.783117 -41.315268)'));
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Pueblo', GeomFromText('POINT (-104.609252 38.255001)'));
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Lawrence', GeomFromText('POINT (-95.235060 38.971823)'));
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Chicago', GeomFromText('POINT (-87.650175 41.850385)'));
-INSERT INTO geoapp_city (`name`, `point`) VALUES ('Victoria', GeomFromText('POINT (-123.305196 48.462611)'));

+ 0 - 8
django/contrib/gis/tests/geoapp/sql/city.oracle.sql

@@ -1,8 +0,0 @@
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Houston', SDO_GEOMETRY('POINT (-95.363151 29.763374)', 4326));
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Dallas', SDO_GEOMETRY('POINT (-96.801611 32.782057)', 4326));
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Oklahoma City', SDO_GEOMETRY('POINT (-97.521157 34.464642)', 4326));
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Wellington', SDO_GEOMETRY('POINT (174.783117 -41.315268)', 4326));
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Pueblo', SDO_GEOMETRY('POINT (-104.609252 38.255001)', 4326));
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Lawrence', SDO_GEOMETRY('POINT (-95.235060 38.971823)', 4326));
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Chicago', SDO_GEOMETRY('POINT (-87.650175 41.850385)', 4326));
-INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Victoria', SDO_GEOMETRY('POINT (-123.305196 48.462611)', 4326));

+ 0 - 8
django/contrib/gis/tests/geoapp/sql/city.postgresql_psycopg2.sql

@@ -1,8 +0,0 @@
-INSERT INTO geoapp_city ("name", "point") VALUES ('Houston', 'SRID=4326;POINT (-95.363151 29.763374)');
-INSERT INTO geoapp_city ("name", "point") VALUES ('Dallas', 'SRID=4326;POINT (-96.801611 32.782057)');
-INSERT INTO geoapp_city ("name", "point") VALUES ('Oklahoma City', 'SRID=4326;POINT (-97.521157 34.464642)');
-INSERT INTO geoapp_city ("name", "point") VALUES ('Wellington', 'SRID=4326;POINT (174.783117 -41.315268)');
-INSERT INTO geoapp_city ("name", "point") VALUES ('Pueblo', 'SRID=4326;POINT (-104.609252 38.255001)');
-INSERT INTO geoapp_city ("name", "point") VALUES ('Lawrence', 'SRID=4326;POINT (-95.235060 38.971823)');
-INSERT INTO geoapp_city ("name", "point") VALUES ('Chicago', 'SRID=4326;POINT (-87.650175 41.850385)');
-INSERT INTO geoapp_city ("name", "point") VALUES ('Victoria', 'SRID=4326;POINT (-123.305196 48.462611)');

+ 0 - 8
django/contrib/gis/tests/geoapp/sql/city.sqlite3.sql

@@ -1,8 +0,0 @@
-INSERT INTO geoapp_city ("name", "point") VALUES ('Houston', GeomFromText('POINT (-95.363151 29.763374)', 4326));
-INSERT INTO geoapp_city ("name", "point") VALUES ('Dallas', GeomFromText('POINT (-96.801611 32.782057)', 4326));
-INSERT INTO geoapp_city ("name", "point") VALUES ('Oklahoma City', GeomFromText('POINT (-97.521157 34.464642)', 4326));
-INSERT INTO geoapp_city ("name", "point") VALUES ('Wellington', GeomFromText('POINT (174.783117 -41.315268)', 4326));
-INSERT INTO geoapp_city ("name", "point") VALUES ('Pueblo', GeomFromText('POINT (-104.609252 38.255001)', 4326));
-INSERT INTO geoapp_city ("name", "point") VALUES ('Lawrence', GeomFromText('POINT (-95.235060 38.971823)', 4326));
-INSERT INTO geoapp_city ("name", "point") VALUES ('Chicago', GeomFromText('POINT (-87.650175 41.850385)', 4326));
-INSERT INTO geoapp_city ("name", "point") VALUES ('Victoria', GeomFromText('POINT (-123.305196 48.462611)', 4326));

File diff suppressed because it is too large
+ 0 - 0
django/contrib/gis/tests/geoapp/sql/co.wkt


File diff suppressed because it is too large
+ 0 - 2
django/contrib/gis/tests/geoapp/sql/country.mysql.sql


File diff suppressed because it is too large
+ 0 - 2
django/contrib/gis/tests/geoapp/sql/country.postgresql_psycopg2.sql


File diff suppressed because it is too large
+ 0 - 2
django/contrib/gis/tests/geoapp/sql/country.sqlite3.sql


File diff suppressed because it is too large
+ 0 - 0
django/contrib/gis/tests/geoapp/sql/ks.wkt


File diff suppressed because it is too large
+ 0 - 0
django/contrib/gis/tests/geoapp/sql/nz.wkt


File diff suppressed because it is too large
+ 0 - 3
django/contrib/gis/tests/geoapp/sql/state.mysql.sql


File diff suppressed because it is too large
+ 0 - 3
django/contrib/gis/tests/geoapp/sql/state.postgresql_psycopg2.sql


File diff suppressed because it is too large
+ 0 - 3
django/contrib/gis/tests/geoapp/sql/state.sqlite3.sql


File diff suppressed because it is too large
+ 0 - 0
django/contrib/gis/tests/geoapp/sql/tx.wkt


+ 0 - 1
django/contrib/gis/tests/geoapp/test_regress.py

@@ -1,5 +1,4 @@
 import os, unittest
-from django.contrib.gis.db.backend import SpatialBackend
 from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite
 from django.contrib.gis.shortcuts import render_to_kmz
 from models import City

+ 120 - 142
django/contrib/gis/tests/geoapp/tests.py

@@ -1,49 +1,29 @@
-import os, unittest
+import re, os, unittest
+from django.db import connection
 from django.contrib.gis import gdal
-from django.contrib.gis.db.backend import SpatialBackend
 from django.contrib.gis.geos import *
 from django.contrib.gis.measure import Distance
-from django.contrib.gis.tests.utils import no_oracle, no_postgis, no_spatialite
+from django.contrib.gis.tests.utils import \
+    no_mysql, no_oracle, no_postgis, no_spatialite, \
+    mysql, oracle, postgis, spatialite
+from django.test import TestCase
+
 from models import Country, City, PennsylvaniaCity, State
 
-if not SpatialBackend.spatialite:
+if not spatialite:
     from models import Feature, MinusOneSRID
 
-# TODO: Some tests depend on the success/failure of previous tests, these should
-# be decoupled.  This flag is an artifact of this problem, and makes debugging easier;
-# specifically, the DISABLE flag will disables all tests, allowing problem tests to
-# be examined individually.
-DISABLE = False
-
-class GeoModelTest(unittest.TestCase):
-
-    def test01_initial_sql(self):
-        "Testing geographic initial SQL."
-        if DISABLE: return
-        if SpatialBackend.oracle:
-            # Oracle doesn't allow strings longer than 4000 characters
-            # in SQL files, and I'm stumped on how to use Oracle BFILE's
-            # in PLSQL, so we set up the larger geometries manually, rather
-            # than relying on the initial SQL.
-
-            # Routine for returning the path to the data files.
-            data_dir = os.path.join(os.path.dirname(__file__), 'sql')
-            def get_file(wkt_file):
-                return os.path.join(data_dir, wkt_file)
-            State(name='Puerto Rico', poly=None).save()
-            State(name='Colorado', poly=fromfile(get_file('co.wkt'))).save()
-            State(name='Kansas', poly=fromfile(get_file('ks.wkt'))).save()
-            Country(name='Texas', mpoly=fromfile(get_file('tx.wkt'))).save()
-            Country(name='New Zealand', mpoly=fromfile(get_file('nz.wkt'))).save()
-
-        # Ensuring that data was loaded from initial SQL.
+class GeoModelTest(TestCase):
+
+    def test01_fixtures(self):
+        "Testing geographic model initialization from fixtures."
+        # Ensuring that data was loaded from initial data fixtures.
         self.assertEqual(2, Country.objects.count())
         self.assertEqual(8, City.objects.count())
-        self.assertEqual(3, State.objects.count())
+        self.assertEqual(2, State.objects.count())
 
     def test02_proxy(self):
         "Testing Lazy-Geometry support (using the GeometryProxy)."
-        if DISABLE: return
         ## Testing on a Point
         pnt = Point(0, 0)
         nullcity = City(name='NullCity', point=pnt)
@@ -110,11 +90,13 @@ class GeoModelTest(unittest.TestCase):
         self.assertEqual(ply, State.objects.get(name='NullState').poly)
         ns.delete()
 
-    @no_oracle # Oracle does not support KML.
-    @no_spatialite # SpatiaLite does not support KML.
     def test03a_kml(self):
         "Testing KML output from the database using GeoQuerySet.kml()."
-        if DISABLE: return
+        # Only PostGIS supports KML serialization
+        if not postgis:
+            self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly')
+            return
+
         # Should throw a TypeError when trying to obtain KML from a
         #  non-geometry field.
         qs = City.objects.all()
@@ -122,14 +104,10 @@ class GeoModelTest(unittest.TestCase):
 
         # The reference KML depends on the version of PostGIS used
         # (the output stopped including altitude in 1.3.3).
-        major, minor1, minor2 = SpatialBackend.version
-        ref_kml1 = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
-        ref_kml2 = '<Point><coordinates>-104.609252,38.255001</coordinates></Point>'
-        if major == 1:
-            if minor1 > 3 or (minor1 == 3 and minor2 >= 3): ref_kml = ref_kml2
-            else: ref_kml = ref_kml1
+        if connection.ops.spatial_version >= (1, 3, 3):
+            ref_kml =  '<Point><coordinates>-104.609252,38.255001</coordinates></Point>'
         else:
-            ref_kml = ref_kml2
+            ref_kml = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
 
         # Ensuring the KML is as expected.
         ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo')
@@ -137,19 +115,20 @@ class GeoModelTest(unittest.TestCase):
         for ptown in [ptown1, ptown2]:
             self.assertEqual(ref_kml, ptown.kml)
 
-    @no_spatialite # SpatiaLite does not support GML.
     def test03b_gml(self):
         "Testing GML output from the database using GeoQuerySet.gml()."
-        if DISABLE: return
+        if mysql or spatialite:
+            self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly')
+            return
+
         # Should throw a TypeError when tyring to obtain GML from a
-        #  non-geometry field.
+        # non-geometry field.
         qs = City.objects.all()
         self.assertRaises(TypeError, qs.gml, field_name='name')
         ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo')
         ptown2 = City.objects.gml(precision=9).get(name='Pueblo')
 
-        import re
-        if SpatialBackend.oracle:
+        if oracle:
             # No precision parameter for Oracle :-/
             gml_regex = re.compile(r'^<gml:Point srsName="SDO:4326" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="\." cs="," ts=" ">-104.60925\d+,38.25500\d+ </gml:coordinates></gml:Point>')
             for ptown in [ptown1, ptown2]:
@@ -159,17 +138,14 @@ class GeoModelTest(unittest.TestCase):
             for ptown in [ptown1, ptown2]:
                 self.failUnless(gml_regex.match(ptown.gml))
 
-    @no_spatialite
-    @no_oracle
     def test03c_geojson(self):
         "Testing GeoJSON output from the database using GeoQuerySet.geojson()."
-        if DISABLE: return
-        # PostGIS only supports GeoJSON on 1.3.4+
-        if not SpatialBackend.geojson:
+        # Only PostGIS 1.3.4+ supports GeoJSON.
+        if not connection.ops.geojson:
+            self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly')
             return
 
-        major, minor1, minor2 = SpatialBackend.version
-        if major >=1 and minor1 >= 4:
+        if connection.ops.spatial_version >= (1, 4, 0):
             pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
             houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}'
             victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}'
@@ -179,10 +155,10 @@ class GeoModelTest(unittest.TestCase):
             houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}'
             victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}'
             chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
-            
+
         # Precision argument should only be an integer
         self.assertRaises(TypeError, City.objects.geojson, precision='foo')
-        
+
         # Reference queries and values.
         # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo';
         self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson)
@@ -200,11 +176,13 @@ class GeoModelTest(unittest.TestCase):
         # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago';
         # Finally, we set every available keyword.
         self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson)
-        
-    @no_oracle
+
     def test03d_svg(self):
         "Testing SVG output using GeoQuerySet.svg()."
-        if DISABLE: return
+        if mysql or oracle:
+            self.assertRaises(NotImplementedError, City.objects.svg)
+            return
+
         self.assertRaises(TypeError, City.objects.svg, precision='foo')
         # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo';
         svg1 = 'cx="-104.609252" cy="-38.255001"'
@@ -214,9 +192,9 @@ class GeoModelTest(unittest.TestCase):
         self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg)
         self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg)
 
+    @no_mysql
     def test04_transform(self):
         "Testing the transform() GeoManager method."
-        if DISABLE: return
         # Pre-transformed points for Houston and Pueblo.
         htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084)
         ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
@@ -224,7 +202,7 @@ class GeoModelTest(unittest.TestCase):
 
         # Asserting the result of the transform operation with the values in
         #  the pre-transformed points.  Oracle does not have the 3084 SRID.
-        if not SpatialBackend.oracle:
+        if not oracle:
             h = City.objects.transform(htown.srid).get(name='Houston')
             self.assertEqual(3084, h.point.srid)
             self.assertAlmostEqual(htown.x, h.point.x, prec)
@@ -237,10 +215,10 @@ class GeoModelTest(unittest.TestCase):
             self.assertAlmostEqual(ptown.x, p.point.x, prec)
             self.assertAlmostEqual(ptown.y, p.point.y, prec)
 
+    @no_mysql
     @no_spatialite # SpatiaLite does not have an Extent function
     def test05_extent(self):
         "Testing the `extent` GeoQuerySet method."
-        if DISABLE: return
         # Reference query:
         # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
         #   =>  BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
@@ -252,11 +230,12 @@ class GeoModelTest(unittest.TestCase):
         for val, exp in zip(extent, expected):
             self.assertAlmostEqual(exp, val, 4)
 
+    # Only PostGIS has support for the MakeLine aggregate.
+    @no_mysql
     @no_oracle
-    @no_spatialite # SpatiaLite does not have a MakeLine function
+    @no_spatialite
     def test06_make_line(self):
         "Testing the `make_line` GeoQuerySet method."
-        if DISABLE: return
         # Ensuring that a `TypeError` is raised on models without PointFields.
         self.assertRaises(TypeError, State.objects.make_line)
         self.assertRaises(TypeError, Country.objects.make_line)
@@ -265,34 +244,26 @@ class GeoModelTest(unittest.TestCase):
         ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326)
         self.assertEqual(ref_line, City.objects.make_line())
 
+    @no_mysql
     def test09_disjoint(self):
         "Testing the `disjoint` lookup type."
-        if DISABLE: return
         ptown = City.objects.get(name='Pueblo')
         qs1 = City.objects.filter(point__disjoint=ptown.point)
         self.assertEqual(7, qs1.count())
 
-        if not (SpatialBackend.postgis or SpatialBackend.spatialite):
-            # TODO: Do NULL columns bork queries on PostGIS?  The following
-            # error is encountered:
-            #  psycopg2.ProgrammingError: invalid memory alloc request size 4294957297
-            #
-            # Similarly, on SpatiaLite Puerto Rico is also returned (could be a
-            # manifestation of
-            qs2 = State.objects.filter(poly__disjoint=ptown.point)
-            self.assertEqual(1, qs2.count())
-            self.assertEqual('Kansas', qs2[0].name)
+        qs2 = State.objects.filter(poly__disjoint=ptown.point)
+        self.assertEqual(1, qs2.count())
+        self.assertEqual('Kansas', qs2[0].name)
 
     def test10_contains_contained(self):
         "Testing the 'contained', 'contains', and 'bbcontains' lookup types."
-        if DISABLE: return
         # Getting Texas, yes we were a country -- once ;)
         texas = Country.objects.get(name='Texas')
 
         # Seeing what cities are in Texas, should get Houston and Dallas,
         #  and Oklahoma City because 'contained' only checks on the
         #  _bounding box_ of the Geometries.
-        if not SpatialBackend.oracle:
+        if not oracle:
             qs = City.objects.filter(point__contained=texas.mpoly)
             self.assertEqual(3, qs.count())
             cities = ['Houston', 'Dallas', 'Oklahoma City']
@@ -313,30 +284,31 @@ class GeoModelTest(unittest.TestCase):
         self.assertEqual('New Zealand', nz.name)
 
         # Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry).
-        if not SpatialBackend.spatialite:
+        if not spatialite:
             ks = State.objects.get(poly__contains=lawrence.point)
             self.assertEqual('Kansas', ks.name)
 
         # Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
-        #  are not contained in Texas or New Zealand.
+        # are not contained in Texas or New Zealand.
         self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object
-        self.assertEqual(0, len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
+        self.assertEqual((mysql and 1) or 0,
+                         len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
 
         # OK City is contained w/in bounding box of Texas.
-        if not SpatialBackend.oracle:
+        if not oracle:
             qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
             self.assertEqual(1, len(qs))
             self.assertEqual('Texas', qs[0].name)
 
+    @no_mysql
     def test11_lookup_insert_transform(self):
         "Testing automatic transform for lookups and inserts."
-        if DISABLE: return
         # San Antonio in 'WGS84' (SRID 4326)
         sa_4326 = 'POINT (-98.493183 29.424170)'
         wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
 
         # Oracle doesn't have SRID 3084, using 41157.
-        if SpatialBackend.oracle:
+        if oracle:
             # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157)
             # Used the following Oracle SQL to get this value:
             #  SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL;
@@ -351,15 +323,14 @@ class GeoModelTest(unittest.TestCase):
         # `SDO_OVERLAPBDYINTERSECT` operates differently from
         # `ST_Intersects`, so contains is used instead.
         nad_pnt = fromstr(nad_wkt, srid=nad_srid)
-        if SpatialBackend.oracle:
+        if oracle:
             tx = Country.objects.get(mpoly__contains=nad_pnt)
         else:
             tx = Country.objects.get(mpoly__intersects=nad_pnt)
         self.assertEqual('Texas', tx.name)
 
         # Creating San Antonio.  Remember the Alamo.
-        sa = City(name='San Antonio', point=nad_pnt)
-        sa.save()
+        sa = City.objects.create(name='San Antonio', point=nad_pnt)
 
         # Now verifying that San Antonio was transformed correctly
         sa = City.objects.get(name='San Antonio')
@@ -369,14 +340,17 @@ class GeoModelTest(unittest.TestCase):
         # If the GeometryField SRID is -1, then we shouldn't perform any
         # transformation if the SRID of the input geometry is different.
         # SpatiaLite does not support missing SRID values.
-        if not SpatialBackend.spatialite:
+        if not spatialite:
             m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
             m1.save()
             self.assertEqual(-1, m1.geom.srid)
 
+    @no_mysql
     def test12_null_geometries(self):
         "Testing NULL geometry support, and the `isnull` lookup type."
-        if DISABLE: return
+        # Creating a state with a NULL boundary.
+        State.objects.create(name='Puerto Rico')
+
         # Querying for both NULL and Non-NULL values.
         nullqs = State.objects.filter(poly__isnull=True)
         validqs = State.objects.filter(poly__isnull=False)
@@ -401,27 +375,28 @@ class GeoModelTest(unittest.TestCase):
         State.objects.filter(name='Northern Mariana Islands').update(poly=None)
         self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly)
 
-    @no_oracle # No specific `left` or `right` operators in Oracle.
-    @no_spatialite # No `left` or `right` operators in SpatiaLite.
+    # Only PostGIS has `left` and `right` lookup types.
+    @no_mysql
+    @no_oracle
+    @no_spatialite
     def test13_left_right(self):
         "Testing the 'left' and 'right' lookup types."
-        if DISABLE: return
         # Left: A << B => true if xmax(A) < xmin(B)
         # Right: A >> B => true if xmin(A) > xmax(B)
-        #  See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
+        # See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
 
         # Getting the borders for Colorado & Kansas
         co_border = State.objects.get(name='Colorado').poly
         ks_border = State.objects.get(name='Kansas').poly
 
         # Note: Wellington has an 'X' value of 174, so it will not be considered
-        #  to the left of CO.
+        # to the left of CO.
 
         # These cities should be strictly to the right of the CO border.
-        cities = ['Houston', 'Dallas', 'San Antonio', 'Oklahoma City',
+        cities = ['Houston', 'Dallas', 'Oklahoma City',
                   'Lawrence', 'Chicago', 'Wellington']
         qs = City.objects.filter(point__right=co_border)
-        self.assertEqual(7, len(qs))
+        self.assertEqual(6, len(qs))
         for c in qs: self.assertEqual(True, c.name in cities)
 
         # These cities should be strictly to the right of the KS border.
@@ -442,16 +417,16 @@ class GeoModelTest(unittest.TestCase):
 
     def test14_equals(self):
         "Testing the 'same_as' and 'equals' lookup types."
-        if DISABLE: return
         pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
         c1 = City.objects.get(point=pnt)
         c2 = City.objects.get(point__same_as=pnt)
         c3 = City.objects.get(point__equals=pnt)
         for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
 
+    @no_mysql
     def test15_relate(self):
         "Testing the 'relate' lookup type."
-        if DISABLE: return
+        return
         # To make things more interesting, we will have our Texas reference point in
         # different SRIDs.
         pnt1 = fromstr('POINT (649287.0363174 4177429.4494686)', srid=2847)
@@ -459,19 +434,20 @@ class GeoModelTest(unittest.TestCase):
 
         # Not passing in a geometry as first param shoud
         # raise a type error when initializing the GeoQuerySet
-        self.assertRaises(TypeError, Country.objects.filter, mpoly__relate=(23, 'foo'))
+        self.assertRaises(ValueError, Country.objects.filter(mpoly__relate=(23, 'foo')).count)
+
         # Making sure the right exception is raised for the given
         # bad arguments.
-        for bad_args, e in [((pnt1, 0), TypeError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
+        for bad_args, e in [((pnt1, 0), ValueError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
             qs = Country.objects.filter(mpoly__relate=bad_args)
             self.assertRaises(e, qs.count)
 
         # Relate works differently for the different backends.
-        if SpatialBackend.postgis or SpatialBackend.spatialite:
+        if postgis or spatialite:
             contains_mask = 'T*T***FF*'
             within_mask = 'T*F**F***'
             intersects_mask = 'T********'
-        elif SpatialBackend.oracle:
+        elif oracle:
             contains_mask = 'contains'
             within_mask = 'inside'
             # TODO: This is not quite the same as the PostGIS mask above
@@ -486,24 +462,23 @@ class GeoModelTest(unittest.TestCase):
         self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
 
         # Testing intersection relation mask.
-        if not SpatialBackend.oracle:
+        if not oracle:
             self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, intersects_mask)).name)
             self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name)
             self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
 
     def test16_createnull(self):
         "Testing creating a model instance and the geometry being None"
-        if DISABLE: return
         c = City()
         self.assertEqual(c.point, None)
 
+    @no_mysql
     def test17_unionagg(self):
         "Testing the `unionagg` (aggregate union) GeoManager method."
-        if DISABLE: return
         tx = Country.objects.get(name='Texas').mpoly
-        # Houston, Dallas, San Antonio -- Oracle has different order.
-        union1 = fromstr('MULTIPOINT(-98.493183 29.424170,-96.801611 32.782057,-95.363151 29.763374)')
-        union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374,-98.493183 29.424170)')
+        # Houston, Dallas -- Oracle has different order.
+        union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
+        union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
         qs = City.objects.filter(point__within=tx)
         self.assertRaises(TypeError, qs.unionagg, 'name')
         # Using `field_name` keyword argument in one query and specifying an
@@ -512,7 +487,7 @@ class GeoModelTest(unittest.TestCase):
         u1 = qs.unionagg(field_name='point')
         u2 = qs.order_by('name').unionagg()
         tol = 0.00001
-        if SpatialBackend.oracle:
+        if oracle:
             union = union2
         else:
             union = union1
@@ -523,8 +498,7 @@ class GeoModelTest(unittest.TestCase):
 
     @no_spatialite # SpatiaLite does not support abstract geometry columns
     def test18_geometryfield(self):
-        "Testing GeometryField."
-        if DISABLE: return
+        "Testing the general GeometryField."
         Feature(name='Point', geom=Point(1, 1)).save()
         Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save()
         Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save()
@@ -545,60 +519,62 @@ class GeoModelTest(unittest.TestCase):
         self.assertEqual(True, isinstance(f_4.geom, GeometryCollection))
         self.assertEqual(f_3.geom, f_4.geom[2])
 
+    @no_mysql
     def test19_centroid(self):
         "Testing the `centroid` GeoQuerySet method."
-        if DISABLE: return
         qs = State.objects.exclude(poly__isnull=True).centroid()
-        if SpatialBackend.oracle:
+        if oracle:
             tol = 0.1
-        elif SpatialBackend.spatialite:
+        elif spatialite:
             tol = 0.000001
         else:
             tol = 0.000000001
         for s in qs:
             self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
 
+    @no_mysql
     def test20_pointonsurface(self):
         "Testing the `point_on_surface` GeoQuerySet method."
-        if DISABLE: return
         # Reference values.
-        if SpatialBackend.oracle:
+        if oracle:
             # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY;
             ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326),
                    'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326),
                    }
 
-        elif SpatialBackend.postgis or SpatialBackend.spatialite:
+        elif postgis or spatialite:
             # Using GEOSGeometry to compute the reference point on surface values
             # -- since PostGIS also uses GEOS these should be the same.
             ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
                    'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface
                    }
-        for cntry in Country.objects.point_on_surface():
-            if SpatialBackend.spatialite:
+
+        for c in Country.objects.point_on_surface():
+            if spatialite:
                 # XXX This seems to be a WKT-translation-related precision issue?
                 tol = 0.00001
-            else: tol = 0.000000001
-            self.assertEqual(True, ref[cntry.name].equals_exact(cntry.point_on_surface, tol))
+            else:
+                tol = 0.000000001
+            self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol))
 
+    @no_mysql
     @no_oracle
     def test21_scale(self):
         "Testing the `scale` GeoQuerySet method."
-        if DISABLE: return
         xfac, yfac = 2, 3
+        tol = 5 # XXX The low precision tolerance is for SpatiaLite
         qs = Country.objects.scale(xfac, yfac, model_att='scaled')
         for c in qs:
             for p1, p2 in zip(c.mpoly, c.scaled):
                 for r1, r2 in zip(p1, p2):
                     for c1, c2 in zip(r1.coords, r2.coords):
-                        # XXX The low precision is for SpatiaLite
-                        self.assertAlmostEqual(c1[0] * xfac, c2[0], 5)
-                        self.assertAlmostEqual(c1[1] * yfac, c2[1], 5)
+                        self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
+                        self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
 
+    @no_mysql
     @no_oracle
     def test22_translate(self):
         "Testing the `translate` GeoQuerySet method."
-        if DISABLE: return
         xfac, yfac = 5, -23
         qs = Country.objects.translate(xfac, yfac, model_att='translated')
         for c in qs:
@@ -609,57 +585,60 @@ class GeoModelTest(unittest.TestCase):
                         self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
                         self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
 
+    @no_mysql
     def test23_numgeom(self):
         "Testing the `num_geom` GeoQuerySet method."
-        if DISABLE: return
         # Both 'countries' only have two geometries.
         for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom)
         for c in City.objects.filter(point__isnull=False).num_geom():
             # Oracle will return 1 for the number of geometries on non-collections,
             # whereas PostGIS will return None.
-            if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
-            else: self.assertEqual(1, c.num_geom)
+            if postgis:
+                self.assertEqual(None, c.num_geom)
+            else:
+                self.assertEqual(1, c.num_geom)
 
+    @no_mysql
     @no_spatialite # SpatiaLite can only count vertices in LineStrings
     def test24_numpoints(self):
         "Testing the `num_points` GeoQuerySet method."
-        if DISABLE: return
         for c in Country.objects.num_points():
             self.assertEqual(c.mpoly.num_points, c.num_points)
-        if not SpatialBackend.oracle:
+
+        if not oracle:
             # Oracle cannot count vertices in Point geometries.
             for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
 
+    @no_mysql
     def test25_geoset(self):
         "Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
-        if DISABLE: return
         geom = Point(5, 23)
         tol = 1
         qs = Country.objects.all().difference(geom).sym_difference(geom).union(geom)
 
         # XXX For some reason SpatiaLite does something screwey with the Texas geometry here.  Also,
         # XXX it doesn't like the null intersection.
-        if SpatialBackend.spatialite:
+        if spatialite:
             qs = qs.exclude(name='Texas')
         else:
             qs = qs.intersection(geom)
-        
+
         for c in qs:
-            if SpatialBackend.oracle:
+            if oracle:
                 # Should be able to execute the queries; however, they won't be the same
                 # as GEOS (because Oracle doesn't use GEOS internally like PostGIS or
                 # SpatiaLite).
                 pass
             else:
                 self.assertEqual(c.mpoly.difference(geom), c.difference)
-                if not SpatialBackend.spatialite:
+                if not spatialite:
                     self.assertEqual(c.mpoly.intersection(geom), c.intersection)
                 self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
                 self.assertEqual(c.mpoly.union(geom), c.union)
 
+    @no_mysql
     def test26_inherited_geofields(self):
         "Test GeoQuerySet methods on inherited Geometry fields."
-        if DISABLE: return
         # Creating a Pennsylvanian city.
         mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
 
@@ -669,13 +648,12 @@ class GeoModelTest(unittest.TestCase):
 
         self.assertEqual(1, qs.count())
         for pc in qs: self.assertEqual(32128, pc.point.srid)
-        
-    @no_spatialite
+
+    @no_mysql
     @no_oracle
+    @no_spatialite
     def test27_snap_to_grid(self):
         "Testing GeoQuerySet.snap_to_grid()."
-        if DISABLE: return
-
         # Let's try and break snap_to_grid() with bad combinations of arguments.
         for bad_args in ((), range(3), range(5)):
             self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args)

+ 0 - 186
django/contrib/gis/tests/geoapp/tests_mysql.py

@@ -1,186 +0,0 @@
-"""
- A limited test module is used for a limited spatial database.
-"""
-import os, unittest
-from models import Country, City, State, Feature
-from django.contrib.gis import gdal
-from django.contrib.gis.geos import *
-from django.core.exceptions import ImproperlyConfigured
-
-class GeoModelTest(unittest.TestCase):
-
-    def test01_initial_sql(self):
-        "Testing geographic initial SQL."
-        # Ensuring that data was loaded from initial SQL.
-        self.assertEqual(2, Country.objects.count())
-        self.assertEqual(8, City.objects.count())
-        self.assertEqual(2, State.objects.count())
-
-    def test02_proxy(self):
-        "Testing Lazy-Geometry support (using the GeometryProxy)."
-        #### Testing on a Point
-        pnt = Point(0, 0)
-        nullcity = City(name='NullCity', point=pnt)
-        nullcity.save()
-
-        # Making sure TypeError is thrown when trying to set with an
-        #  incompatible type.
-        for bad in [5, 2.0, LineString((0, 0), (1, 1))]:
-            try:
-                nullcity.point = bad
-            except TypeError:
-                pass
-            else:
-                self.fail('Should throw a TypeError')
-
-        # Now setting with a compatible GEOS Geometry, saving, and ensuring
-        #  the save took, notice no SRID is explicitly set.
-        new = Point(5, 23)
-        nullcity.point = new
-
-        # Ensuring that the SRID is automatically set to that of the
-        #  field after assignment, but before saving.
-        self.assertEqual(4326, nullcity.point.srid)
-        nullcity.save()
-
-        # Ensuring the point was saved correctly after saving
-        self.assertEqual(new, City.objects.get(name='NullCity').point)
-
-        # Setting the X and Y of the Point
-        nullcity.point.x = 23
-        nullcity.point.y = 5
-        # Checking assignments pre & post-save.
-        self.assertNotEqual(Point(23, 5), City.objects.get(name='NullCity').point)
-        nullcity.save()
-        self.assertEqual(Point(23, 5), City.objects.get(name='NullCity').point)
-        nullcity.delete()
-
-        #### Testing on a Polygon
-        shell = LinearRing((0, 0), (0, 100), (100, 100), (100, 0), (0, 0))
-        inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40))
-
-        # Creating a State object using a built Polygon
-        ply = Polygon(shell, inner)
-        nullstate = State(name='NullState', poly=ply)
-        self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
-        nullstate.save()
-
-        ns = State.objects.get(name='NullState')
-        self.assertEqual(ply, ns.poly)
-
-        # Testing the `ogr` and `srs` lazy-geometry properties.
-        if gdal.HAS_GDAL:
-            self.assertEqual(True, isinstance(ns.poly.ogr, gdal.OGRGeometry))
-            self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb)
-            self.assertEqual(True, isinstance(ns.poly.srs, gdal.SpatialReference))
-            self.assertEqual('WGS 84', ns.poly.srs.name)
-
-        # Changing the interior ring on the poly attribute.
-        new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
-        ns.poly[1] = new_inner
-        ply[1] = new_inner
-        self.assertEqual(4326, ns.poly.srid)
-        ns.save()
-        self.assertEqual(ply, State.objects.get(name='NullState').poly)
-        ns.delete()
-
-    def test03_contains_contained(self):
-        "Testing the 'contained', 'contains', and 'bbcontains' lookup types."
-        # Getting Texas, yes we were a country -- once ;)
-        texas = Country.objects.get(name='Texas')
-
-        # Seeing what cities are in Texas, should get Houston and Dallas,
-        #  and Oklahoma City because MySQL 'within' only checks on the
-        #  _bounding box_ of the Geometries.
-        qs = City.objects.filter(point__within=texas.mpoly)
-        self.assertEqual(3, qs.count())
-        cities = ['Houston', 'Dallas', 'Oklahoma City']
-        for c in qs: self.assertEqual(True, c.name in cities)
-
-        # Pulling out some cities.
-        houston = City.objects.get(name='Houston')
-        wellington = City.objects.get(name='Wellington')
-        pueblo = City.objects.get(name='Pueblo')
-        okcity = City.objects.get(name='Oklahoma City')
-        lawrence = City.objects.get(name='Lawrence')
-
-        # Now testing contains on the countries using the points for
-        #  Houston and Wellington.
-        tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
-        nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
-        ks = State.objects.get(poly__contains=lawrence.point)
-        self.assertEqual('Texas', tx.name)
-        self.assertEqual('New Zealand', nz.name)
-        self.assertEqual('Kansas', ks.name)
-
-        # Pueblo is not contained in Texas or New Zealand.
-        self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object
-
-        # OK City is contained w/in bounding box of Texas.
-        qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
-        self.assertEqual(1, len(qs))
-        self.assertEqual('Texas', qs[0].name)
-
-    def test04_disjoint(self):
-        "Testing the `disjoint` lookup type."
-        ptown = City.objects.get(name='Pueblo')
-        qs1 = City.objects.filter(point__disjoint=ptown.point)
-        self.assertEqual(7, qs1.count())
-        # TODO: This query should work in MySQL, but it appears the
-        # `MBRDisjoint` function doesn't work properly (I went down
-        # to the SQL level for debugging and still got bogus answers).
-        #qs2 = State.objects.filter(poly__disjoint=ptown.point)
-        #self.assertEqual(1, qs2.count())
-        #self.assertEqual('Kansas', qs2[0].name)
-
-    def test05_equals(self):
-        "Testing the 'same_as' and 'equals' lookup types."
-        pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
-        c1 = City.objects.get(point=pnt)
-        c2 = City.objects.get(point__same_as=pnt)
-        c3 = City.objects.get(point__equals=pnt)
-        for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
-
-    def test06_geometryfield(self):
-        "Testing GeometryField."
-        f1 = Feature(name='Point', geom=Point(1, 1))
-        f2 = Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5)))
-        f3 = Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))
-        f4 = Feature(name='GeometryCollection',
-                     geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)),
-                                             Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))))
-        f1.save()
-        f2.save()
-        f3.save()
-        f4.save()
-
-        f_1 = Feature.objects.get(name='Point')
-        self.assertEqual(True, isinstance(f_1.geom, Point))
-        self.assertEqual((1.0, 1.0), f_1.geom.tuple)
-        f_2 = Feature.objects.get(name='LineString')
-        self.assertEqual(True, isinstance(f_2.geom, LineString))
-        self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple)
-
-        f_3 = Feature.objects.get(name='Polygon')
-        self.assertEqual(True, isinstance(f_3.geom, Polygon))
-        f_4 = Feature.objects.get(name='GeometryCollection')
-        self.assertEqual(True, isinstance(f_4.geom, GeometryCollection))
-        self.assertEqual(f_3.geom, f_4.geom[2])
-
-    def test07_mysql_limitations(self):
-        "Testing that union(), kml(), gml() raise exceptions."
-        self.assertRaises(ImproperlyConfigured, City.objects.union, Point(5, 23), field_name='point')
-        self.assertRaises(ImproperlyConfigured, State.objects.all().kml, field_name='poly')
-        self.assertRaises(ImproperlyConfigured, Country.objects.all().gml, field_name='mpoly')
-
-from test_feeds import GeoFeedTest
-from test_regress import GeoRegressionTests
-from test_sitemaps import GeoSitemapTest
-
-def suite():
-    s = unittest.TestSuite()
-    s.addTest(unittest.makeSuite(GeoModelTest))
-    s.addTest(unittest.makeSuite(GeoFeedTest))
-    s.addTest(unittest.makeSuite(GeoSitemapTest))
-    s.addTest(unittest.makeSuite(GeoRegressionTests))
-    return s

+ 15 - 11
django/contrib/gis/tests/layermap/tests.py

@@ -1,15 +1,19 @@
-import os, unittest
+import os
+import unittest
 from decimal import Decimal
-from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
-from django.contrib.gis.db.backend import SpatialBackend
-from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
-from django.contrib.gis.gdal import DataSource
+
 from django.utils.copycompat import copy
 
-shp_path = os.path.dirname(__file__)
-city_shp = os.path.join(shp_path, '../data/cities/cities.shp')
-co_shp = os.path.join(shp_path, '../data/counties/counties.shp')
-inter_shp = os.path.join(shp_path, '../data/interstates/interstates.shp')
+from django.contrib.gis.gdal import DataSource
+from django.contrib.gis.tests.utils import mysql
+from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
+
+from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
+
+shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
+city_shp = os.path.join(shp_path, 'cities', 'cities.shp')
+co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
+inter_shp = os.path.join(shp_path, 'interstates', 'interstates.shp')
 
 # Dictionaries to hold what's expected in the county shapefile.  
 NAMES  = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
@@ -84,7 +88,7 @@ class LayerMapTest(unittest.TestCase):
             lm.save(silent=True, strict=True)
         except InvalidDecimal:
             # No transactions for geoms on MySQL; delete added features.
-            if SpatialBackend.mysql: Interstate.objects.all().delete()
+            if mysql: Interstate.objects.all().delete()
         else:
             self.fail('Should have failed on strict import with invalid decimal values.')
 
@@ -149,7 +153,7 @@ class LayerMapTest(unittest.TestCase):
             self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg)
 
         # No source reference system defined in the shapefile, should raise an error.
-        if not SpatialBackend.mysql:
+        if not mysql:
             self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
 
         # Passing in invalid ForeignKey mapping parameters -- must be a dictionary

+ 0 - 1
django/contrib/gis/tests/layermap/tests_mysql.py

@@ -1 +0,0 @@
-from tests import *

+ 11 - 11
django/contrib/gis/tests/relatedapp/tests.py

@@ -1,8 +1,8 @@
 import os, unittest
 from django.contrib.gis.geos import *
-from django.contrib.gis.db.backend import SpatialBackend
 from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
-from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite, no_mysql, no_oracle, no_spatialite
 from django.conf import settings
 from models import City, Location, DirectoryEntry, Parcel, Book, Author
 
@@ -95,7 +95,7 @@ class RelatedGeoModelTest(unittest.TestCase):
         # geometries than PostGIS.  The second union aggregate is for a union
         # query that includes limiting information in the WHERE clause (in other
         # words a `.filter()` precedes the call to `.unionagg()`).
-        if SpatialBackend.oracle:
+        if oracle:
             ref_u1 = MultiPoint(p3, p1, p2, srid=4326)
             ref_u2 = MultiPoint(p3, p2, srid=4326)
         else:
@@ -144,7 +144,7 @@ class RelatedGeoModelTest(unittest.TestCase):
         self.assertEqual(1, len(qs))
         self.assertEqual('P2', qs[0].name)
 
-        if not SpatialBackend.mysql:
+        if not mysql:
             # This time center2 is in a different coordinate system and needs
             # to be wrapped in transformation SQL.
             qs = Parcel.objects.filter(center2__within=F('border1'))
@@ -157,7 +157,7 @@ class RelatedGeoModelTest(unittest.TestCase):
         self.assertEqual(1, len(qs))
         self.assertEqual('P1', qs[0].name)
 
-        if not SpatialBackend.mysql:
+        if not mysql:
             # This time the city column should be wrapped in transformation SQL.
             qs = Parcel.objects.filter(border2__contains=F('city__location__point'))
             self.assertEqual(1, len(qs))
@@ -175,8 +175,8 @@ class RelatedGeoModelTest(unittest.TestCase):
         for m, d, t in zip(gqs, gvqs, gvlqs):
             # The values should be Geometry objects and not raw strings returned
             # by the spatial database.
-            self.failUnless(isinstance(d['point'], SpatialBackend.Geometry))
-            self.failUnless(isinstance(t[1], SpatialBackend.Geometry))
+            self.failUnless(isinstance(d['point'], Geometry))
+            self.failUnless(isinstance(t[1], Geometry))
             self.assertEqual(m.point, d['point'])
             self.assertEqual(m.point, t[1])
 
@@ -238,7 +238,7 @@ class RelatedGeoModelTest(unittest.TestCase):
         # as Dallas.
         dallas = City.objects.get(name='Dallas')
         ftworth = City.objects.create(name='Fort Worth', state='TX', location=dallas.location)
-        
+
         # Count annotation should be 2 for the Dallas location now.
         loc = Location.objects.annotate(num_cities=Count('city')).get(id=dallas.location.id)
         self.assertEqual(2, loc.num_cities)
@@ -279,11 +279,11 @@ class RelatedGeoModelTest(unittest.TestCase):
     def test14_collect(self):
         "Testing the `collect` GeoQuerySet method and `Collect` aggregate."
         # Reference query:
-        # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN 
-        #    "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id") 
+        # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
+        #    "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
         #    WHERE "relatedapp_city"."state" = 'TX';
         ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
-        
+
         c1 = City.objects.filter(state='TX').collect(field_name='location__point')
         c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
 

+ 0 - 1
django/contrib/gis/tests/relatedapp/tests_mysql.py

@@ -1 +0,0 @@
-from tests import *

+ 10 - 8
django/contrib/gis/tests/test_spatialrefsys.py

@@ -1,8 +1,7 @@
 import unittest
-from django.contrib.gis.db.backend import SpatialBackend
+
+from django.db import connection
 from django.contrib.gis.tests.utils import mysql, no_mysql, oracle, postgis, spatialite
-if not mysql:
-    from django.contrib.gis.models import SpatialRefSys
 
 test_srs = ({'srid' : 4326,
              'auth_name' : ('EPSG', True),
@@ -28,9 +27,12 @@ test_srs = ({'srid' : 4326,
              },
             )
 
-if SpatialBackend.postgis:
-    major, minor1, minor2 = SpatialBackend.version
-    POSTGIS_14 = major >=1 and minor1 >= 4
+if oracle:
+    from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
+elif postgis:
+    from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
+elif spatialite:
+    from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
 
 class SpatialRefSysTest(unittest.TestCase):
 
@@ -52,7 +54,7 @@ class SpatialRefSysTest(unittest.TestCase):
 
             # No proj.4 and different srtext on oracle backends :(
             if postgis:
-                if POSTGIS_14:
+                if connection.ops.spatial_version >= (1, 4, 0):
                     srtext = sd['srtext14']
                 else:
                     srtext = sd['srtext']
@@ -79,7 +81,7 @@ class SpatialRefSysTest(unittest.TestCase):
                 self.assertEqual(sd['proj4'], srs.proj4)
                 # No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
                 if not spatialite:
-                    if POSTGIS_14:
+                    if connection.ops.spatial_version >= (1, 4, 0):
                         srtext = sd['srtext14']
                     else:
                         srtext = sd['srtext']

+ 9 - 7
django/contrib/gis/tests/utils.py

@@ -1,11 +1,12 @@
 from django.conf import settings
+from django.db import DEFAULT_DB_ALIAS
 
 # function that will pass a test.
 def pass_test(*args): return
 
 def no_backend(test_func, backend):
     "Use this decorator to disable test on specified backend."
-    if settings.DATABASE_ENGINE == backend:
+    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1] == backend:
         return pass_test
     else:
         return test_func
@@ -13,12 +14,13 @@ def no_backend(test_func, backend):
 # Decorators to disable entire test functions for specific
 # spatial backends.
 def no_oracle(func): return no_backend(func, 'oracle')
-def no_postgis(func): return no_backend(func, 'postgresql_psycopg2')
+def no_postgis(func): return no_backend(func, 'postgis')
 def no_mysql(func): return no_backend(func, 'mysql')
-def no_spatialite(func): return no_backend(func, 'sqlite3')
+def no_spatialite(func): return no_backend(func, 'spatialite')
 
 # Shortcut booleans to omit only portions of tests.
-oracle  = settings.DATABASE_ENGINE == 'oracle'
-postgis = settings.DATABASE_ENGINE == 'postgresql_psycopg2' 
-mysql   = settings.DATABASE_ENGINE == 'mysql'
-spatialite = settings.DATABASE_ENGINE == 'sqlite3'
+_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1]
+oracle  = _default_db == 'oracle'
+postgis = _default_db == 'postgis'
+mysql   = _default_db == 'mysql'
+spatialite = _default_db == 'spatialite'

Some files were not shown because too many files changed in this diff