Fixed #16408 -- Fixed conversion of dates, and other problems with the SpatiaLite backend.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16749 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
6ce463aadb
commit
43329af2e3
|
@ -2,16 +2,16 @@ from ctypes.util import find_library
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.backends.sqlite3.base import *
|
|
||||||
from django.db.backends.sqlite3.base import (
|
from django.db.backends.sqlite3.base import (
|
||||||
_sqlite_extract, _sqlite_date_trunc, _sqlite_regexp,
|
_sqlite_extract, _sqlite_date_trunc, _sqlite_regexp, _sqlite_format_dtdelta,
|
||||||
DatabaseWrapper as SqliteDatabaseWrapper)
|
connection_created, Database, DatabaseWrapper as SQLiteDatabaseWrapper,
|
||||||
|
SQLiteCursorWrapper)
|
||||||
from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient
|
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.creation import SpatiaLiteCreation
|
||||||
from django.contrib.gis.db.backends.spatialite.introspection import SpatiaLiteIntrospection
|
from django.contrib.gis.db.backends.spatialite.introspection import SpatiaLiteIntrospection
|
||||||
from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations
|
from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations
|
||||||
|
|
||||||
class DatabaseWrapper(SqliteDatabaseWrapper):
|
class DatabaseWrapper(SQLiteDatabaseWrapper):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Before we get too far, make sure pysqlite 2.5+ is installed.
|
# Before we get too far, make sure pysqlite 2.5+ is installed.
|
||||||
if Database.version_info < (2, 5, 0):
|
if Database.version_info < (2, 5, 0):
|
||||||
|
@ -52,6 +52,7 @@ class DatabaseWrapper(SqliteDatabaseWrapper):
|
||||||
self.connection.create_function("django_extract", 2, _sqlite_extract)
|
self.connection.create_function("django_extract", 2, _sqlite_extract)
|
||||||
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
|
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
|
||||||
self.connection.create_function("regexp", 2, _sqlite_regexp)
|
self.connection.create_function("regexp", 2, _sqlite_regexp)
|
||||||
|
self.connection.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta)
|
||||||
connection_created.send(sender=self.__class__, connection=self)
|
connection_created.send(sender=self.__class__, connection=self)
|
||||||
|
|
||||||
## From here on, customized for GeoDjango ##
|
## From here on, customized for GeoDjango ##
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
from django.db.backends.util import typecast_timestamp
|
||||||
|
from django.db.models.sql import compiler
|
||||||
|
from django.db.models.sql.constants import MULTI
|
||||||
|
from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler
|
||||||
|
|
||||||
|
SQLCompiler = compiler.SQLCompiler
|
||||||
|
|
||||||
|
class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
|
||||||
|
pass
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
This is overridden for GeoDjango to properly cast date columns, see #16757.
|
||||||
|
"""
|
||||||
|
def results_iter(self):
|
||||||
|
offset = len(self.query.extra_select)
|
||||||
|
for rows in self.execute_sql(MULTI):
|
||||||
|
for row in rows:
|
||||||
|
date = typecast_timestamp(str(row[offset]))
|
||||||
|
yield date
|
|
@ -3,7 +3,6 @@ from django.conf import settings
|
||||||
from django.core.cache import get_cache
|
from django.core.cache import get_cache
|
||||||
from django.core.cache.backends.db import BaseDatabaseCache
|
from django.core.cache.backends.db import BaseDatabaseCache
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.management import call_command
|
|
||||||
from django.db.backends.sqlite3.creation import DatabaseCreation
|
from django.db.backends.sqlite3.creation import DatabaseCreation
|
||||||
|
|
||||||
class SpatiaLiteCreation(DatabaseCreation):
|
class SpatiaLiteCreation(DatabaseCreation):
|
||||||
|
@ -16,26 +15,65 @@ class SpatiaLiteCreation(DatabaseCreation):
|
||||||
This method is overloaded to load up the SpatiaLite initialization
|
This method is overloaded to load up the SpatiaLite initialization
|
||||||
SQL prior to calling the `syncdb` command.
|
SQL prior to calling the `syncdb` command.
|
||||||
"""
|
"""
|
||||||
if verbosity >= 1:
|
# Don't import django.core.management if it isn't needed.
|
||||||
print "Creating test database '%s'..." % self.connection.alias
|
from django.core.management import call_command
|
||||||
|
|
||||||
test_database_name = self._create_test_db(verbosity, autoclobber)
|
test_database_name = self._get_test_db_name()
|
||||||
|
|
||||||
|
if verbosity >= 1:
|
||||||
|
test_db_repr = ''
|
||||||
|
if verbosity >= 2:
|
||||||
|
test_db_repr = " ('%s')" % test_database_name
|
||||||
|
print "Creating test database for alias '%s'%s..." % (self.connection.alias, test_db_repr)
|
||||||
|
|
||||||
|
self._create_test_db(verbosity, autoclobber)
|
||||||
|
|
||||||
self.connection.close()
|
self.connection.close()
|
||||||
|
|
||||||
self.connection.settings_dict["NAME"] = test_database_name
|
self.connection.settings_dict["NAME"] = test_database_name
|
||||||
|
|
||||||
# Confirm the feature set of the test database
|
# Confirm the feature set of the test database
|
||||||
self.connection.features.confirm()
|
self.connection.features.confirm()
|
||||||
|
|
||||||
# Need to load the SpatiaLite initialization SQL before running `syncdb`.
|
# Need to load the SpatiaLite initialization SQL before running `syncdb`.
|
||||||
self.load_spatialite_sql()
|
self.load_spatialite_sql()
|
||||||
call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
|
call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
|
||||||
|
|
||||||
|
# Report syncdb messages at one level lower than that requested.
|
||||||
|
# This ensures we don't get flooded with messages during testing
|
||||||
|
# (unless you really ask to be flooded)
|
||||||
|
call_command('syncdb',
|
||||||
|
verbosity=max(verbosity - 1, 0),
|
||||||
|
interactive=False,
|
||||||
|
database=self.connection.alias,
|
||||||
|
load_initial_data=False)
|
||||||
|
|
||||||
|
# We need to then do a flush to ensure that any data installed by
|
||||||
|
# custom SQL has been removed. The only test data should come from
|
||||||
|
# test fixtures, or autogenerated from post_syncdb triggers.
|
||||||
|
# This has the side effect of loading initial data (which was
|
||||||
|
# intentionally skipped in the syncdb).
|
||||||
|
call_command('flush',
|
||||||
|
verbosity=max(verbosity - 1, 0),
|
||||||
|
interactive=False,
|
||||||
|
database=self.connection.alias)
|
||||||
|
|
||||||
|
# One effect of calling syncdb followed by flush is that the id of the
|
||||||
|
# default site may or may not be 1, depending on how the sequence was
|
||||||
|
# reset. If the sites app is loaded, then we coerce it.
|
||||||
|
from django.db.models import get_model
|
||||||
|
Site = get_model('sites', 'Site')
|
||||||
|
if Site is not None and Site.objects.using(self.connection.alias).count() == 1:
|
||||||
|
Site.objects.using(self.connection.alias).update(id=settings.SITE_ID)
|
||||||
|
|
||||||
|
from django.core.cache import get_cache
|
||||||
|
from django.core.cache.backends.db import BaseDatabaseCache
|
||||||
for cache_alias in settings.CACHES:
|
for cache_alias in settings.CACHES:
|
||||||
cache = get_cache(cache_alias)
|
cache = get_cache(cache_alias)
|
||||||
if isinstance(cache, BaseDatabaseCache):
|
if isinstance(cache, BaseDatabaseCache):
|
||||||
from django.db import router
|
from django.db import router
|
||||||
if router.allow_syncdb(self.connection.alias, cache.cache_model_class):
|
if router.allow_syncdb(self.connection.alias, cache.cache_model_class):
|
||||||
call_command('createcachetable', cache._table, database=self.connection.alias)
|
call_command('createcachetable', cache._table, database=self.connection.alias)
|
||||||
|
|
||||||
# Get a cursor (even though we don't need one yet). This has
|
# Get a cursor (even though we don't need one yet). This has
|
||||||
# the side effect of initializing the test database.
|
# the side effect of initializing the test database.
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
|
|
|
@ -48,7 +48,7 @@ def get_dist_ops(operator):
|
||||||
return (SpatiaLiteDistance(operator),)
|
return (SpatiaLiteDistance(operator),)
|
||||||
|
|
||||||
class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
||||||
compiler_module = 'django.contrib.gis.db.models.sql.compiler'
|
compiler_module = 'django.contrib.gis.db.backends.spatialite.compiler'
|
||||||
name = 'spatialite'
|
name = 'spatialite'
|
||||||
spatialite = True
|
spatialite = True
|
||||||
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
|
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
|
||||||
|
|
|
@ -202,7 +202,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
|
||||||
#### Routines unique to GeoQuery ####
|
#### Routines unique to GeoQuery ####
|
||||||
def get_extra_select_format(self, alias):
|
def get_extra_select_format(self, alias):
|
||||||
sel_fmt = '%s'
|
sel_fmt = '%s'
|
||||||
if alias in self.query.custom_select:
|
if hasattr(self.query, 'custom_select') and alias in self.query.custom_select:
|
||||||
sel_fmt = sel_fmt % self.query.custom_select[alias]
|
sel_fmt = sel_fmt % self.query.custom_select[alias]
|
||||||
return sel_fmt
|
return sel_fmt
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ class City(models.Model):
|
||||||
# This is an inherited model from City
|
# This is an inherited model from City
|
||||||
class PennsylvaniaCity(City):
|
class PennsylvaniaCity(City):
|
||||||
county = models.CharField(max_length=30)
|
county = models.CharField(max_length=30)
|
||||||
|
founded = models.DateTimeField(null=True)
|
||||||
objects = models.GeoManager() # TODO: This should be implicitly inherited.
|
objects = models.GeoManager() # TODO: This should be implicitly inherited.
|
||||||
|
|
||||||
class State(models.Model):
|
class State(models.Model):
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import unittest
|
from datetime import datetime
|
||||||
from django.contrib.gis.tests.utils import no_mysql, no_spatialite
|
from django.contrib.gis.tests.utils import no_mysql, no_spatialite
|
||||||
from django.contrib.gis.shortcuts import render_to_kmz
|
from django.contrib.gis.shortcuts import render_to_kmz
|
||||||
from models import City
|
from django.test import TestCase
|
||||||
|
from models import City, PennsylvaniaCity
|
||||||
|
|
||||||
class GeoRegressionTests(unittest.TestCase):
|
class GeoRegressionTests(TestCase):
|
||||||
|
|
||||||
def test01_update(self):
|
def test01_update(self):
|
||||||
"Testing GeoQuerySet.update(), see #10411."
|
"Testing GeoQuerySet.update(), see #10411."
|
||||||
|
@ -35,3 +36,10 @@ class GeoRegressionTests(unittest.TestCase):
|
||||||
extent = City.objects.filter(name='Pueblo').extent()
|
extent = City.objects.filter(name='Pueblo').extent()
|
||||||
for ref_val, val in zip(ref_ext, extent):
|
for ref_val, val in zip(ref_ext, extent):
|
||||||
self.assertAlmostEqual(ref_val, val, 4)
|
self.assertAlmostEqual(ref_val, val, 4)
|
||||||
|
|
||||||
|
def test04_unicode_date(self):
|
||||||
|
"Testing dates are converted properly, even on SpatiaLite, see #16408."
|
||||||
|
founded = datetime(1857, 5, 23)
|
||||||
|
mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)',
|
||||||
|
founded=founded)
|
||||||
|
self.assertEqual(founded, PennsylvaniaCity.objects.dates('founded', 'day')[0])
|
||||||
|
|
Loading…
Reference in New Issue