Ajoutez des fichiers projet.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
from django.db.backends.mysql.base import (
|
||||
DatabaseWrapper as MySQLDatabaseWrapper,
|
||||
)
|
||||
|
||||
from .features import DatabaseFeatures
|
||||
from .introspection import MySQLIntrospection
|
||||
from .operations import MySQLOperations
|
||||
from .schema import MySQLGISSchemaEditor
|
||||
|
||||
|
||||
class DatabaseWrapper(MySQLDatabaseWrapper):
|
||||
SchemaEditorClass = MySQLGISSchemaEditor
|
||||
# Classes instantiated in __init__().
|
||||
features_class = DatabaseFeatures
|
||||
introspection_class = MySQLIntrospection
|
||||
ops_class = MySQLOperations
|
@@ -0,0 +1,43 @@
|
||||
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
|
||||
from django.db.backends.mysql.features import (
|
||||
DatabaseFeatures as MySQLDatabaseFeatures,
|
||||
)
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures):
|
||||
has_spatialrefsys_table = False
|
||||
supports_add_srs_entry = False
|
||||
supports_distance_geodetic = False
|
||||
supports_length_geodetic = False
|
||||
supports_area_geodetic = False
|
||||
supports_transform = False
|
||||
supports_null_geometries = False
|
||||
supports_num_points_poly = False
|
||||
unsupported_geojson_options = {'crs'}
|
||||
|
||||
@cached_property
|
||||
def empty_intersection_returns_none(self):
|
||||
return (
|
||||
not self.connection.mysql_is_mariadb and
|
||||
self.connection.mysql_version < (5, 7, 5)
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def supports_geometry_field_unique_index(self):
|
||||
# Not supported in MySQL since https://dev.mysql.com/worklog/task/?id=11808
|
||||
return self.connection.mysql_is_mariadb
|
||||
|
||||
@cached_property
|
||||
def django_test_skips(self):
|
||||
skips = super().django_test_skips
|
||||
if (
|
||||
not self.connection.mysql_is_mariadb and
|
||||
self.connection.mysql_version < (8, 0, 0)
|
||||
):
|
||||
skips.update({
|
||||
'MySQL < 8 gives different results.': {
|
||||
'gis_tests.geoapp.tests.GeoLookupTest.test_disjoint_lookup',
|
||||
},
|
||||
})
|
||||
return skips
|
@@ -0,0 +1,38 @@
|
||||
from MySQLdb.constants import FIELD_TYPE
|
||||
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||
|
||||
|
||||
class MySQLIntrospection(DatabaseIntrospection):
|
||||
# Updating the data_types_reverse dictionary with the appropriate
|
||||
# type for Geometry fields.
|
||||
data_types_reverse = DatabaseIntrospection.data_types_reverse.copy()
|
||||
data_types_reverse[FIELD_TYPE.GEOMETRY] = 'GeometryField'
|
||||
|
||||
def get_geometry_type(self, table_name, description):
|
||||
with self.connection.cursor() as cursor:
|
||||
# In order to get the specific geometry type of the field,
|
||||
# we introspect on the table definition using `DESCRIBE`.
|
||||
cursor.execute('DESCRIBE %s' %
|
||||
self.connection.ops.quote_name(table_name))
|
||||
# Increment over description info until we get to the geometry
|
||||
# column.
|
||||
for column, typ, null, key, default, extra in cursor.fetchall():
|
||||
if column == description.name:
|
||||
# Using OGRGeomType to convert from OGC name to Django field.
|
||||
# MySQL does not support 3D or SRIDs, so the field params
|
||||
# are empty.
|
||||
field_type = OGRGeomType(typ).django
|
||||
field_params = {}
|
||||
break
|
||||
return field_type, field_params
|
||||
|
||||
def supports_spatial_index(self, cursor, table_name):
|
||||
# Supported with MyISAM/Aria, or InnoDB on MySQL 5.7.5+/MariaDB 10.2.2+
|
||||
storage_engine = self.get_storage_engine(cursor, table_name)
|
||||
if storage_engine == 'InnoDB':
|
||||
return self.connection.mysql_version >= (
|
||||
(10, 2, 2) if self.connection.mysql_is_mariadb else (5, 7, 5)
|
||||
)
|
||||
return storage_engine in ('MyISAM', 'Aria')
|
@@ -0,0 +1,108 @@
|
||||
from django.contrib.gis.db import models
|
||||
from django.contrib.gis.db.backends.base.adapter import WKTAdapter
|
||||
from django.contrib.gis.db.backends.base.operations import (
|
||||
BaseSpatialOperations,
|
||||
)
|
||||
from django.contrib.gis.db.backends.utils import SpatialOperator
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometryBase
|
||||
from django.contrib.gis.geos.prototypes.io import wkb_r
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.db.backends.mysql.operations import DatabaseOperations
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
name = 'mysql'
|
||||
geom_func_prefix = 'ST_'
|
||||
|
||||
Adapter = WKTAdapter
|
||||
|
||||
@cached_property
|
||||
def mariadb(self):
|
||||
return self.connection.mysql_is_mariadb
|
||||
|
||||
@cached_property
|
||||
def mysql(self):
|
||||
return not self.connection.mysql_is_mariadb
|
||||
|
||||
@cached_property
|
||||
def select(self):
|
||||
return self.geom_func_prefix + 'AsBinary(%s)'
|
||||
|
||||
@cached_property
|
||||
def from_text(self):
|
||||
return self.geom_func_prefix + 'GeomFromText'
|
||||
|
||||
@cached_property
|
||||
def gis_operators(self):
|
||||
operators = {
|
||||
'bbcontains': SpatialOperator(func='MBRContains'), # For consistency w/PostGIS API
|
||||
'bboverlaps': SpatialOperator(func='MBROverlaps'), # ...
|
||||
'contained': SpatialOperator(func='MBRWithin'), # ...
|
||||
'contains': SpatialOperator(func='ST_Contains'),
|
||||
'crosses': SpatialOperator(func='ST_Crosses'),
|
||||
'disjoint': SpatialOperator(func='ST_Disjoint'),
|
||||
'equals': SpatialOperator(func='ST_Equals'),
|
||||
'exact': SpatialOperator(func='ST_Equals'),
|
||||
'intersects': SpatialOperator(func='ST_Intersects'),
|
||||
'overlaps': SpatialOperator(func='ST_Overlaps'),
|
||||
'same_as': SpatialOperator(func='ST_Equals'),
|
||||
'touches': SpatialOperator(func='ST_Touches'),
|
||||
'within': SpatialOperator(func='ST_Within'),
|
||||
}
|
||||
if self.connection.mysql_is_mariadb:
|
||||
operators['relate'] = SpatialOperator(func='ST_Relate')
|
||||
return operators
|
||||
|
||||
disallowed_aggregates = (
|
||||
models.Collect, models.Extent, models.Extent3D, models.MakeLine,
|
||||
models.Union,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def unsupported_functions(self):
|
||||
unsupported = {
|
||||
'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle',
|
||||
'ForcePolygonCW', 'GeometryDistance', 'LineLocatePoint',
|
||||
'MakeValid', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse',
|
||||
'Scale', 'SnapToGrid', 'Transform', 'Translate',
|
||||
}
|
||||
if self.connection.mysql_is_mariadb:
|
||||
unsupported.remove('PointOnSurface')
|
||||
unsupported.update({'GeoHash', 'IsValid'})
|
||||
if self.connection.mysql_version < (10, 2, 4):
|
||||
unsupported.add('AsGeoJSON')
|
||||
elif self.connection.mysql_version < (5, 7, 5):
|
||||
unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'})
|
||||
return unsupported
|
||||
|
||||
def geo_db_type(self, f):
|
||||
return f.geom_type
|
||||
|
||||
def get_distance(self, f, value, lookup_type):
|
||||
value = value[0]
|
||||
if isinstance(value, Distance):
|
||||
if f.geodetic(self.connection):
|
||||
raise ValueError(
|
||||
'Only numeric values of degree units are allowed on '
|
||||
'geodetic distance queries.'
|
||||
)
|
||||
dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
|
||||
else:
|
||||
dist_param = value
|
||||
return [dist_param]
|
||||
|
||||
def get_geometry_converter(self, expression):
|
||||
read = wkb_r().read
|
||||
srid = expression.output_field.srid
|
||||
if srid == -1:
|
||||
srid = None
|
||||
geom_class = expression.output_field.geom_class
|
||||
|
||||
def converter(value, expression, connection):
|
||||
if value is not None:
|
||||
geom = GEOSGeometryBase(read(memoryview(value)), geom_class)
|
||||
if srid:
|
||||
geom.srid = srid
|
||||
return geom
|
||||
return converter
|
@@ -0,0 +1,77 @@
|
||||
import logging
|
||||
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
from django.db import OperationalError
|
||||
from django.db.backends.mysql.schema import DatabaseSchemaEditor
|
||||
|
||||
logger = logging.getLogger('django.contrib.gis')
|
||||
|
||||
|
||||
class MySQLGISSchemaEditor(DatabaseSchemaEditor):
|
||||
sql_add_spatial_index = 'CREATE SPATIAL INDEX %(index)s ON %(table)s(%(column)s)'
|
||||
sql_drop_spatial_index = 'DROP INDEX %(index)s ON %(table)s'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.geometry_sql = []
|
||||
|
||||
def skip_default(self, field):
|
||||
# Geometry fields are stored as BLOB/TEXT, for which MySQL < 8.0.13 and
|
||||
# MariaDB < 10.2.1 don't support defaults.
|
||||
if isinstance(field, GeometryField) and not self._supports_limited_data_type_defaults:
|
||||
return True
|
||||
return super().skip_default(field)
|
||||
|
||||
def column_sql(self, model, field, include_default=False):
|
||||
column_sql = super().column_sql(model, field, include_default)
|
||||
# MySQL doesn't support spatial indexes on NULL columns
|
||||
if isinstance(field, GeometryField) and field.spatial_index and not field.null:
|
||||
qn = self.connection.ops.quote_name
|
||||
db_table = model._meta.db_table
|
||||
self.geometry_sql.append(
|
||||
self.sql_add_spatial_index % {
|
||||
'index': qn(self._create_spatial_index_name(model, field)),
|
||||
'table': qn(db_table),
|
||||
'column': qn(field.column),
|
||||
}
|
||||
)
|
||||
return column_sql
|
||||
|
||||
def create_model(self, model):
|
||||
super().create_model(model)
|
||||
self.create_spatial_indexes()
|
||||
|
||||
def add_field(self, model, field):
|
||||
super().add_field(model, field)
|
||||
self.create_spatial_indexes()
|
||||
|
||||
def remove_field(self, model, field):
|
||||
if isinstance(field, GeometryField) and field.spatial_index:
|
||||
qn = self.connection.ops.quote_name
|
||||
sql = self.sql_drop_spatial_index % {
|
||||
'index': qn(self._create_spatial_index_name(model, field)),
|
||||
'table': qn(model._meta.db_table),
|
||||
}
|
||||
try:
|
||||
self.execute(sql)
|
||||
except OperationalError:
|
||||
logger.error(
|
||||
"Couldn't remove spatial index: %s (may be expected "
|
||||
"if your storage engine doesn't support them).", sql
|
||||
)
|
||||
|
||||
super().remove_field(model, field)
|
||||
|
||||
def _create_spatial_index_name(self, model, field):
|
||||
return '%s_%s_id' % (model._meta.db_table, field.column)
|
||||
|
||||
def create_spatial_indexes(self):
|
||||
for sql in self.geometry_sql:
|
||||
try:
|
||||
self.execute(sql)
|
||||
except OperationalError:
|
||||
logger.error(
|
||||
"Cannot create SPATIAL INDEX %s. Only MyISAM and (as of "
|
||||
"MySQL 5.7.5) InnoDB support them.", sql
|
||||
)
|
||||
self.geometry_sql = []
|
Reference in New Issue
Block a user