Fixed #15277 -- Cleaned up `ogrinspect` command, added tests and extended support beyond file-based OGR data sources. Thanks, willinoed for bug report and jpaulett for initial patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16845 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2011-09-17 19:54:52 +00:00
parent f97a574196
commit 3ac877840a
7 changed files with 155 additions and 40 deletions

View File

@ -1,15 +0,0 @@
from django.core.management.base import BaseCommand, CommandError
class ArgsCommand(BaseCommand):
"""
Command class for commands that take multiple arguments.
"""
args = '<arg arg ...>'
def handle(self, *args, **options):
if not args:
raise CommandError('Must provide the following arguments: %s' % self.args)
return self.handle_args(*args, **options)
def handle_args(self, *args, **options):
raise NotImplementedError()

View File

@ -1,7 +1,7 @@
import os import os
from optparse import make_option from optparse import make_option
from django.contrib.gis import gdal from django.contrib.gis import gdal
from django.contrib.gis.management.base import ArgsCommand, CommandError from django.core.management.base import LabelCommand, CommandError
def layer_option(option, opt, value, parser): def layer_option(option, opt, value, parser):
""" """
@ -26,13 +26,13 @@ def list_option(option, opt, value, parser):
dest = [s for s in value.split(',')] dest = [s for s in value.split(',')]
setattr(parser.values, option.dest, dest) setattr(parser.values, option.dest, dest)
class Command(ArgsCommand): class Command(LabelCommand):
help = ('Inspects the given OGR-compatible data source (e.g., a shapefile) and outputs\n' help = ('Inspects the given OGR-compatible data source (e.g., a shapefile) and outputs\n'
'a GeoDjango model with the given model name. For example:\n' 'a GeoDjango model with the given model name. For example:\n'
' ./manage.py ogrinspect zipcode.shp Zipcode') ' ./manage.py ogrinspect zipcode.shp Zipcode')
args = '[data_source] [model_name]' args = '[data_source] [model_name]'
option_list = ArgsCommand.option_list + ( option_list = LabelCommand.option_list + (
make_option('--blank', dest='blank', type='string', action='callback', make_option('--blank', dest='blank', type='string', action='callback',
callback=list_option, default=False, callback=list_option, default=False,
help='Use a comma separated list of OGR field names to add ' help='Use a comma separated list of OGR field names to add '
@ -72,7 +72,7 @@ class Command(ArgsCommand):
requires_model_validation = False requires_model_validation = False
def handle_args(self, *args, **options): def handle(self, *args, **options):
try: try:
data_source, model_name = args data_source, model_name = args
except ValueError: except ValueError:
@ -81,10 +81,6 @@ class Command(ArgsCommand):
if not gdal.HAS_GDAL: if not gdal.HAS_GDAL:
raise CommandError('GDAL is required to inspect geospatial data sources.') raise CommandError('GDAL is required to inspect geospatial data sources.')
# TODO: Support non file-based OGR datasources.
if not os.path.isfile(data_source):
raise CommandError('The given data source cannot be found: "%s"' % data_source)
# Removing options with `None` values. # Removing options with `None` values.
options = dict([(k, v) for k, v in options.items() if not v is None]) options = dict([(k, v) for k, v in options.items() if not v is None])
@ -97,8 +93,9 @@ class Command(ArgsCommand):
# Whether the user wants to generate the LayerMapping dictionary as well. # Whether the user wants to generate the LayerMapping dictionary as well.
show_mapping = options.pop('mapping', False) show_mapping = options.pop('mapping', False)
# Popping the verbosity global option, as it's not accepted by `_ogrinspect`. # Getting rid of settings that `_ogrinspect` doesn't like.
verbosity = options.pop('verbosity', False) verbosity = options.pop('verbosity', False)
settings = options.pop('settings', False)
# Returning the output of ogrinspect with the given arguments # Returning the output of ogrinspect with the given arguments
# and options. # and options.
@ -119,4 +116,4 @@ class Command(ArgsCommand):
'%s_mapping = {' % model_name.lower()]) '%s_mapping = {' % model_name.lower()])
output.extend([" '%s' : '%s'," % (rev_mapping[ogr_fld], ogr_fld) for ogr_fld in ds[options['layer_key']].fields]) output.extend([" '%s' : '%s'," % (rev_mapping[ogr_fld], ogr_fld) for ogr_fld in ds[options['layer_key']].fields])
output.extend([" '%s' : '%s'," % (options['geom_name'], mapping_dict[options['geom_name']]), '}']) output.extend([" '%s' : '%s'," % (options['geom_name'], mapping_dict[options['geom_name']]), '}'])
return '\n'.join(output) return '\n'.join(output) + '\n'

View File

@ -29,15 +29,13 @@ def geo_apps(namespace=True, runtests=False):
# The following GeoDjango test apps depend on GDAL support. # The following GeoDjango test apps depend on GDAL support.
if HAS_GDAL: if HAS_GDAL:
# Geographic admin requires GDAL # Geographic admin, LayerMapping, and ogrinspect test apps
apps.append('geoadmin') # all require GDAL.
apps.extend(['geoadmin', 'layermap', 'inspectapp'])
# 3D apps use LayerMapping, which uses GDAL. # 3D apps use LayerMapping, which uses GDAL and require GEOS 3.1+.
if connection.ops.postgis and GEOS_PREPARE: if connection.ops.postgis and GEOS_PREPARE:
apps.append('geo3d') apps.append('geo3d')
apps.append('layermap')
if runtests: if runtests:
return [('django.contrib.gis.tests', app) for app in apps] return [('django.contrib.gis.tests', app) for app in apps]
elif namespace: elif namespace:

View File

@ -0,0 +1,13 @@
from django.contrib.gis.db import models
class AllOGRFields(models.Model):
f_decimal = models.FloatField()
f_float = models.FloatField()
f_int = models.IntegerField()
f_char = models.CharField(max_length=10)
f_date = models.DateField()
f_datetime = models.DateTimeField()
f_time = models.TimeField()
geom = models.PolygonField()
objects = models.GeoManager()

View File

@ -0,0 +1,122 @@
import os
from django.db import connections
from django.test import TestCase
from django.contrib.gis.gdal import Driver
from django.contrib.gis.geometry.test_data import TEST_DATA
from django.contrib.gis.utils.ogrinspect import ogrinspect
from models import AllOGRFields
class OGRInspectTest(TestCase):
def test_poly(self):
shp_file = os.path.join(TEST_DATA, 'test_poly', 'test_poly.shp')
model_def = ogrinspect(shp_file, 'MyModel')
expected = [
'# This is an auto-generated Django model module created by ogrinspect.',
'from django.contrib.gis.db import models',
'',
'class MyModel(models.Model):',
' float = models.FloatField()',
' int = models.FloatField()',
' str = models.CharField(max_length=80)',
' geom = models.PolygonField(srid=-1)',
' objects = models.GeoManager()',
]
self.assertEqual(model_def, '\n'.join(expected))
def test_date_field(self):
shp_file = os.path.join(TEST_DATA, 'cities', 'cities.shp')
model_def = ogrinspect(shp_file, 'City')
expected = [
'# This is an auto-generated Django model module created by ogrinspect.',
'from django.contrib.gis.db import models',
'',
'class City(models.Model):',
' name = models.CharField(max_length=80)',
' population = models.FloatField()',
' density = models.FloatField()',
' created = models.DateField()',
' geom = models.PointField(srid=-1)',
' objects = models.GeoManager()',
]
self.assertEqual(model_def, '\n'.join(expected))
def test_time_field(self):
# Only possible to test this on PostGIS at the momemnt. MySQL
# complains about permissions, and SpatiaLite/Oracle are
# insanely difficult to get support compiled in for in GDAL.
if not connections['default'].ops.postgis:
return
# Getting the database identifier used by OGR, if None returned
# GDAL does not have the support compiled in.
ogr_db = get_ogr_db_string()
if not ogr_db:
return
# writing shapefules via GDAL currently does not support writing OGRTime
# fields, so we need to actually use a database
model_def = ogrinspect(ogr_db, 'Measurement',
layer_key=AllOGRFields._meta.db_table,
decimal=['f_decimal'])
expected = [
'# This is an auto-generated Django model module created by ogrinspect.',
'from django.contrib.gis.db import models',
'',
'class Measurement(models.Model):',
' f_decimal = models.DecimalField(max_digits=0, decimal_places=0)',
' f_int = models.IntegerField()',
' f_datetime = models.DateTimeField()',
' f_time = models.TimeField()',
' f_float = models.FloatField()',
' f_char = models.CharField(max_length=10)',
' f_date = models.DateField()',
' geom = models.PolygonField()',
' objects = models.GeoManager()',
]
self.assertEqual(model_def, '\n'.join(expected))
def get_ogr_db_string():
# Construct the DB string that GDAL will use to inspect the database.
# GDAL will create its own connection to the database, so we re-use the
# connection settings from the Django test. This approach is a bit fragile
# and cannot work on any other database other than PostgreSQL at the moment.
db = connections.databases['default']
# Map from the django backend into the OGR driver name and database identifier
# http://www.gdal.org/ogr/ogr_formats.html
#
# TODO: Support Oracle (OCI), MySQL, and SpatiaLite.
drivers = {
'django.contrib.gis.db.backends.postgis': ('PostgreSQL', 'PG'),
}
drv_name, db_str = drivers[db['ENGINE']]
# Ensure that GDAL library has driver support for the database.
try:
Driver(drv_name)
except:
return None
# Build the params of the OGR database connection string
# TODO: connection strings are database-dependent, thus if
# we ever test other backends, this will need to change.
params = ["dbname='%s'" % db['NAME']]
def add(key, template):
value = db.get(key, None)
# Don't add the parameter if it is not in django's settings
if value:
params.append(template % value)
add('HOST', "host='%s'")
add('PORT', "port='%s'")
add('USER', "user='%s'")
add('PASSWORD', "password='%s'")
return '%s:%s' % (db_str, ' '.join(params))

View File

@ -8,7 +8,7 @@ Author: Travis Pinney, Dane Springmeyer, & Justin Bronn
from itertools import izip from itertools import izip
# Requires GDAL to use. # Requires GDAL to use.
from django.contrib.gis.gdal import DataSource from django.contrib.gis.gdal import DataSource
from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False):
""" """
@ -189,7 +189,7 @@ def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=Non
yield ' %s = models.DateField(%s)' % (mfield, kwargs_str[2:]) yield ' %s = models.DateField(%s)' % (mfield, kwargs_str[2:])
elif field_type is OFTDateTime: elif field_type is OFTDateTime:
yield ' %s = models.DateTimeField(%s)' % (mfield, kwargs_str[2:]) yield ' %s = models.DateTimeField(%s)' % (mfield, kwargs_str[2:])
elif field_type is OFTDate: elif field_type is OFTTime:
yield ' %s = models.TimeField(%s)' % (mfield, kwargs_str[2:]) yield ' %s = models.TimeField(%s)' % (mfield, kwargs_str[2:])
else: else:
raise TypeError('Unknown field type %s in %s' % (field_type, mfield)) raise TypeError('Unknown field type %s in %s' % (field_type, mfield))