From 5edcbdc826b4d87cdb9ba28b4467b3806bdcdb52 Mon Sep 17 00:00:00 2001 From: Jeremy Dunck Date: Wed, 7 Mar 2007 23:09:33 +0000 Subject: [PATCH] gis: Added beginnings of django.contrib.gis. Changed ManyToManyField to provide get_internal_type() -> 'NoField'. GIS fields use NoField? and new _post_create_sql for AddGeometryColumn. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@4674 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/__init__.py | 71 ++++++++++++++++++ django/contrib/gis/db/__init__.py | 0 django/contrib/gis/db/models/__init__.py | 0 .../contrib/gis/db/models/fields/__init__.py | 72 +++++++++++++++++++ django/core/management.py | 4 ++ django/db/backends/ado_mssql/creation.py | 2 +- django/db/backends/mysql/creation.py | 2 +- django/db/backends/oracle/creation.py | 2 +- django/db/backends/postgresql/creation.py | 2 +- django/db/backends/sqlite3/creation.py | 2 +- django/db/models/fields/related.py | 3 + 11 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 django/contrib/gis/__init__.py create mode 100644 django/contrib/gis/db/__init__.py create mode 100644 django/contrib/gis/db/models/__init__.py create mode 100644 django/contrib/gis/db/models/fields/__init__.py diff --git a/django/contrib/gis/__init__.py b/django/contrib/gis/__init__.py new file mode 100644 index 0000000000..2266ce4b95 --- /dev/null +++ b/django/contrib/gis/__init__.py @@ -0,0 +1,71 @@ +from geos import geomFromWKT, geomToWKT +from decimal import Decimal +from django.db import models +from django.db.models.query import QuerySet + + +class GeoQuerySet(QuerySet): + # The list of valid query terms + # override the local QUERY_TERMS in the namespace + # not sure how to do that locals() hackery + # possibly in the init change its locals() variables + # not sure if that will work + + QUERY_TERMS = ( + 'exact', 'iexact', 'contains', 'icontains', 'overlaps', + 'gt', 'gte', 'lt', 'lte', 'in', + 'startswith', 'istartswith', 'endswith', 'iendswith', + 'range', 'year', 'month', 'day', 'isnull', 'search', +) + + +def dprint(arg): + import re + import inspect + print re.match("^\s*dprint\(\s*(.+)\s*\)", inspect.stack()[1][4][0]).group(1) + ": " + repr(arg) + + + +class GeometryManager(models.Manager): + #def filter(self, *args, **kwargs): + # super(Manager, self).filter(*args, **kwargs) + # return self.get_query_set().filter(*args, **kwargs) + + def get_query_set(self): + return GeoQuerySet(self.model) + + + + +class BoundingBox: + + def _geom(self): + return geomToWKT(self._g) + + geom = property(_geom) + + def _area(self): + return self._g.area() + + area = property(_area) + + + def __init__(self, ne, sw): + """ + Create a bounding box using two points + This points come from a JSON request, so they are strings + """ + ne = [Decimal(i.strip(' ')) for i in ne[1:-1].split(',')] + sw = [Decimal(i.strip(' ')) for i in sw[1:-1].split(',')] + ne_lat = ne[0] + ne_lng = ne[1] + sw_lat = sw[0] + sw_lng = sw[1] + bb = 'POLYGON((' + bb += str(ne_lng) + " " + str(ne_lat) + "," + bb += str(ne_lng) + " " + str(sw_lat) + "," + bb += str(sw_lng) + " " + str(sw_lat) + "," + bb += str(sw_lng) + " " + str(ne_lat) + "," + bb += str(ne_lng) + " " + str(ne_lat) + bb += '))' + self._g = geomFromWKT(bb) diff --git a/django/contrib/gis/db/__init__.py b/django/contrib/gis/db/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/gis/db/models/__init__.py b/django/contrib/gis/db/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/gis/db/models/fields/__init__.py b/django/contrib/gis/db/models/fields/__init__.py new file mode 100644 index 0000000000..a0d4dad6e8 --- /dev/null +++ b/django/contrib/gis/db/models/fields/__init__.py @@ -0,0 +1,72 @@ +from django.db import models +from django.db.models.fields import Field +#from GeoTypes import Point + +# Creates the SQL to add the model to the database. Defaults to using an +# SRID of 4326 (WGS84 Datum -- 'normal' lat/lon coordinates) +def _add_geom(geom, srid, style, model, field, dim=2): + from django.db import backend + + # Constructing the AddGeometryColumn(...) command -- the style + # object is passed in from the management module and is used + # to syntax highlight the command for 'sqlall' + sql = style.SQL_KEYWORD('SELECT ') + \ + style.SQL_TABLE('AddGeometryColumn') + "('" + \ + style.SQL_TABLE(model) + "', '" + \ + style.SQL_FIELD(field) + "', " + \ + style.SQL_FIELD(str(srid)) + ", '" + \ + style.SQL_KEYWORD(geom) + "', " + \ + style.SQL_KEYWORD(str(dim)) + \ + ');' + return sql + +class GeometryField(Field): + """The base GIS field -- maps to an OpenGIS Geometry type.""" + + _geom = 'GEOMETRY' + _srid = 4326 + + def _post_create_sql(self, *args, **kwargs): + """Returns SQL that will be executed after the model has been created. Geometry + columns must be added after creation with the PostGIS AddGeometryColumn() function.""" + return _add_geom(self._geom, self._srid, *args, **kwargs) + + def get_db_prep_lookup(self, lookup_type, value): + "Returns field's value prepared for database lookup." + if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search', 'overlaps'): + return [value] + elif lookup_type in ('range', 'in'): + return value + elif lookup_type in ('contains', 'icontains'): + return ["%%%s%%" % prep_for_like_query(value)] + elif lookup_type == 'iexact': + return [prep_for_like_query(value)] + elif lookup_type in ('startswith', 'istartswith'): + return ["%s%%" % prep_for_like_query(value)] + elif lookup_type in ('endswith', 'iendswith'): + return ["%%%s" % prep_for_like_query(value)] + elif lookup_type == 'isnull': + return [] + elif lookup_type == 'year': + try: + value = int(value) + except ValueError: + raise ValueError("The __year lookup type requires an integer argument") + return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value] + raise TypeError("Field has invalid lookup: %s" % lookup_type) + + def get_db_prep_save(self, value): + return 'SRID=%d;%s' % (self._srid, value) + + +class PointField(GeometryField): + _geom = 'POINT' + +class PolygonField(GeometryField): + _geom = 'POLYGON' + +class MultiPolygonField(GeometryField): + _geom = 'MULTIPOLYGON' + +class GeometryManager(models.Manager): + pass diff --git a/django/core/management.py b/django/core/management.py index 0a04a7e830..a78c9f91d4 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -386,6 +386,10 @@ def get_custom_sql_for_model(model): output.append(statement + ";") fp.close() + for f in opts.fields: + if hasattr(f, '_post_create_sql'): + output.append(f._post_create_sql(style, model._meta.db_table, f.column)) + return output def get_custom_sql(app): diff --git a/django/db/backends/ado_mssql/creation.py b/django/db/backends/ado_mssql/creation.py index 5158ba02f9..03af91c9af 100644 --- a/django/db/backends/ado_mssql/creation.py +++ b/django/db/backends/ado_mssql/creation.py @@ -11,7 +11,6 @@ DATA_TYPES = { 'ImageField': 'varchar(100)', 'IntegerField': 'int', 'IPAddressField': 'char(15)', - 'ManyToManyField': None, 'NullBooleanField': 'bit', 'OneToOneField': 'int', 'PhoneNumberField': 'varchar(20)', @@ -22,4 +21,5 @@ DATA_TYPES = { 'TextField': 'text', 'TimeField': 'time', 'USStateField': 'varchar(2)', + 'NoField': None, } diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py index 22ed901653..15741ad388 100644 --- a/django/db/backends/mysql/creation.py +++ b/django/db/backends/mysql/creation.py @@ -15,7 +15,6 @@ DATA_TYPES = { 'ImageField': 'varchar(100)', 'IntegerField': 'integer', 'IPAddressField': 'char(15)', - 'ManyToManyField': None, 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PhoneNumberField': 'varchar(20)', @@ -26,4 +25,5 @@ DATA_TYPES = { 'TextField': 'longtext', 'TimeField': 'time', 'USStateField': 'varchar(2)', + 'NoField': None, } diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index da65df172e..fc71df4c80 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -11,7 +11,6 @@ DATA_TYPES = { 'ImageField': 'varchar2(100)', 'IntegerField': 'integer', 'IPAddressField': 'char(15)', - 'ManyToManyField': None, 'NullBooleanField': 'integer', 'OneToOneField': 'integer', 'PhoneNumberField': 'varchar(20)', @@ -22,4 +21,5 @@ DATA_TYPES = { 'TextField': 'long', 'TimeField': 'timestamp', 'USStateField': 'varchar(2)', + 'NoField': None, } diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index 6c130f368e..43e421e455 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -15,7 +15,6 @@ DATA_TYPES = { 'ImageField': 'varchar(100)', 'IntegerField': 'integer', 'IPAddressField': 'inet', - 'ManyToManyField': None, 'NullBooleanField': 'boolean', 'OneToOneField': 'integer', 'PhoneNumberField': 'varchar(20)', @@ -26,4 +25,5 @@ DATA_TYPES = { 'TextField': 'text', 'TimeField': 'time', 'USStateField': 'varchar(2)', + 'NoField': None, } diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py index 77f570b2e8..cb23706c42 100644 --- a/django/db/backends/sqlite3/creation.py +++ b/django/db/backends/sqlite3/creation.py @@ -14,7 +14,6 @@ DATA_TYPES = { 'ImageField': 'varchar(100)', 'IntegerField': 'integer', 'IPAddressField': 'char(15)', - 'ManyToManyField': None, 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PhoneNumberField': 'varchar(20)', @@ -25,4 +24,5 @@ DATA_TYPES = { 'TextField': 'text', 'TimeField': 'time', 'USStateField': 'varchar(2)', + 'NoField': None, } diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index fad9c164c1..ffecfa1c1a 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -644,6 +644,9 @@ class ManyToManyField(RelatedField, Field): msg = gettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') self.help_text = string_concat(self.help_text, ' ', msg) + def get_internal_type(self): + return "NoField" + def get_manipulator_field_objs(self): if self.rel.raw_id_admin: return [oldforms.RawIdAdminField]