Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #15277 -- Cleaned up `ogrinspect` command, added tests and exte…

…nded 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
  • Loading branch information...
commit 3ac877840a42144174e7ffde1721c9ed40a13b22 1 parent f97a574
Justin Bronn authored September 17, 2011
15  django/contrib/gis/management/base.py
... ...
@@ -1,15 +0,0 @@
1  
-from django.core.management.base import BaseCommand, CommandError
2  
-
3  
-class ArgsCommand(BaseCommand):
4  
-    """
5  
-    Command class for commands that take multiple arguments.
6  
-    """
7  
-    args = '<arg arg ...>'
8  
-
9  
-    def handle(self, *args, **options):
10  
-        if not args:
11  
-            raise CommandError('Must provide the following arguments: %s' % self.args)
12  
-        return self.handle_args(*args, **options)
13  
-
14  
-    def handle_args(self, *args, **options):
15  
-        raise NotImplementedError()
31  django/contrib/gis/management/commands/ogrinspect.py
... ...
@@ -1,7 +1,7 @@
1 1
 import os
2 2
 from optparse import make_option
3 3
 from django.contrib.gis import gdal
4  
-from django.contrib.gis.management.base import ArgsCommand, CommandError
  4
+from django.core.management.base import LabelCommand, CommandError
5 5
 
6 6
 def layer_option(option, opt, value, parser):
7 7
     """
@@ -17,7 +17,7 @@ def layer_option(option, opt, value, parser):
17 17
 def list_option(option, opt, value, parser):
18 18
     """
19 19
     Callback for `make_option` for `ogrinspect` keywords that require
20  
-    a string list.  If the string is 'True'/'true' then the option 
  20
+    a string list.  If the string is 'True'/'true' then the option
21 21
     value will be a boolean instead.
22 22
     """
23 23
     if value.lower() == 'true':
@@ -25,20 +25,20 @@ def list_option(option, opt, value, parser):
25 25
     else:
26 26
         dest = [s for s in value.split(',')]
27 27
     setattr(parser.values, option.dest, dest)
28  
-    
29  
-class Command(ArgsCommand):
  28
+
  29
+class Command(LabelCommand):
30 30
     help = ('Inspects the given OGR-compatible data source (e.g., a shapefile) and outputs\n'
31 31
             'a GeoDjango model with the given model name. For example:\n'
32 32
             ' ./manage.py ogrinspect zipcode.shp Zipcode')
33 33
     args = '[data_source] [model_name]'
34 34
 
35  
-    option_list = ArgsCommand.option_list + (
36  
-        make_option('--blank', dest='blank', type='string', action='callback',  
  35
+    option_list = LabelCommand.option_list + (
  36
+        make_option('--blank', dest='blank', type='string', action='callback',
37 37
                     callback=list_option, default=False,
38 38
                     help='Use a comma separated list of OGR field names to add '
39 39
                     'the `blank=True` option to the field definition.  Set with'
40 40
                     '`true` to apply to all applicable fields.'),
41  
-        make_option('--decimal', dest='decimal', type='string', action='callback', 
  41
+        make_option('--decimal', dest='decimal', type='string', action='callback',
42 42
                     callback=list_option, default=False,
43 43
                     help='Use a comma separated list of OGR float fields to '
44 44
                     'generate `DecimalField` instead of the default '
@@ -46,7 +46,7 @@ class Command(ArgsCommand):
46 46
         make_option('--geom-name', dest='geom_name', type='string', default='geom',
47 47
                     help='Specifies the model name for the Geometry Field '
48 48
                     '(defaults to `geom`)'),
49  
-        make_option('--layer', dest='layer_key', type='string', action='callback', 
  49
+        make_option('--layer', dest='layer_key', type='string', action='callback',
50 50
                     callback=layer_option, default=0,
51 51
                     help='The key for specifying which layer in the OGR data '
52 52
                     'source to use. Defaults to 0 (the first layer). May be '
@@ -58,7 +58,7 @@ class Command(ArgsCommand):
58 58
         make_option('--no-imports', action='store_false', dest='imports', default=True,
59 59
                     help='Do not include `from django.contrib.gis.db import models` '
60 60
                     'statement.'),
61  
-        make_option('--null', dest='null', type='string', action='callback',  
  61
+        make_option('--null', dest='null', type='string', action='callback',
62 62
                     callback=list_option, default=False,
63 63
                     help='Use a comma separated list of OGR field names to add '
64 64
                     'the `null=True` option to the field definition.  Set with'
@@ -72,7 +72,7 @@ class Command(ArgsCommand):
72 72
 
73 73
     requires_model_validation = False
74 74
 
75  
-    def handle_args(self, *args, **options):
  75
+    def handle(self, *args, **options):
76 76
         try:
77 77
             data_source, model_name = args
78 78
         except ValueError:
@@ -81,10 +81,6 @@ def handle_args(self, *args, **options):
81 81
         if not gdal.HAS_GDAL:
82 82
             raise CommandError('GDAL is required to inspect geospatial data sources.')
83 83
 
84  
-        # TODO: Support non file-based OGR datasources.
85  
-        if not os.path.isfile(data_source):
86  
-            raise CommandError('The given data source cannot be found: "%s"' % data_source)
87  
-        
88 84
         # Removing options with `None` values.
89 85
         options = dict([(k, v) for k, v in options.items() if not v is None])
90 86
 
@@ -97,8 +93,9 @@ def handle_args(self, *args, **options):
97 93
         # Whether the user wants to generate the LayerMapping dictionary as well.
98 94
         show_mapping = options.pop('mapping', False)
99 95
 
100  
-        # Popping the verbosity global option, as it's not accepted by `_ogrinspect`.
  96
+        # Getting rid of settings that `_ogrinspect` doesn't like.
101 97
         verbosity = options.pop('verbosity', False)
  98
+        settings = options.pop('settings', False)
102 99
 
103 100
         # Returning the output of ogrinspect with the given arguments
104 101
         # and options.
@@ -115,8 +112,8 @@ def handle_args(self, *args, **options):
115 112
             # This extra legwork is so that the dictionary definition comes
116 113
             # out in the same order as the fields in the model definition.
117 114
             rev_mapping = dict([(v, k) for k, v in mapping_dict.items()])
118  
-            output.extend(['', '# Auto-generated `LayerMapping` dictionary for %s model' % model_name, 
  115
+            output.extend(['', '# Auto-generated `LayerMapping` dictionary for %s model' % model_name,
119 116
                            '%s_mapping = {' % model_name.lower()])
120 117
             output.extend(["    '%s' : '%s'," % (rev_mapping[ogr_fld], ogr_fld) for ogr_fld in ds[options['layer_key']].fields])
121 118
             output.extend(["    '%s' : '%s'," % (options['geom_name'], mapping_dict[options['geom_name']]), '}'])
122  
-        return '\n'.join(output)
  119
+        return '\n'.join(output) + '\n'
10  django/contrib/gis/tests/__init__.py
@@ -29,15 +29,13 @@ def geo_apps(namespace=True, runtests=False):
29 29
 
30 30
     # The following GeoDjango test apps depend on GDAL support.
31 31
     if HAS_GDAL:
32  
-        # Geographic admin requires GDAL
33  
-        apps.append('geoadmin')
  32
+        # Geographic admin, LayerMapping, and ogrinspect test apps
  33
+        # all require GDAL.
  34
+        apps.extend(['geoadmin', 'layermap', 'inspectapp'])
34 35
 
35  
-        # 3D apps use LayerMapping, which uses GDAL.
  36
+        # 3D apps use LayerMapping, which uses GDAL and require GEOS 3.1+.
36 37
         if connection.ops.postgis and GEOS_PREPARE:
37 38
             apps.append('geo3d')
38  
-
39  
-        apps.append('layermap')
40  
-
41 39
     if runtests:
42 40
         return [('django.contrib.gis.tests', app) for app in apps]
43 41
     elif namespace:
13  django/contrib/gis/tests/inspectapp/models.py
... ...
@@ -0,0 +1,13 @@
  1
+from django.contrib.gis.db import models
  2
+
  3
+class AllOGRFields(models.Model):
  4
+    f_decimal = models.FloatField()
  5
+    f_float = models.FloatField()
  6
+    f_int = models.IntegerField()
  7
+    f_char = models.CharField(max_length=10)
  8
+    f_date = models.DateField()
  9
+    f_datetime = models.DateTimeField()
  10
+    f_time = models.TimeField()
  11
+    geom = models.PolygonField()
  12
+
  13
+    objects = models.GeoManager()
122  django/contrib/gis/tests/inspectapp/tests.py
... ...
@@ -0,0 +1,122 @@
  1
+import os
  2
+from django.db import connections
  3
+from django.test import TestCase
  4
+from django.contrib.gis.gdal import Driver
  5
+from django.contrib.gis.geometry.test_data import TEST_DATA
  6
+from django.contrib.gis.utils.ogrinspect import ogrinspect
  7
+from models import AllOGRFields
  8
+
  9
+
  10
+class OGRInspectTest(TestCase):
  11
+    def test_poly(self):
  12
+        shp_file = os.path.join(TEST_DATA, 'test_poly', 'test_poly.shp')
  13
+        model_def = ogrinspect(shp_file, 'MyModel')
  14
+
  15
+        expected = [
  16
+            '# This is an auto-generated Django model module created by ogrinspect.',
  17
+            'from django.contrib.gis.db import models',
  18
+            '',
  19
+            'class MyModel(models.Model):',
  20
+            '    float = models.FloatField()',
  21
+            '    int = models.FloatField()',
  22
+            '    str = models.CharField(max_length=80)',
  23
+            '    geom = models.PolygonField(srid=-1)',
  24
+            '    objects = models.GeoManager()',
  25
+        ]
  26
+
  27
+        self.assertEqual(model_def, '\n'.join(expected))
  28
+
  29
+    def test_date_field(self):
  30
+        shp_file = os.path.join(TEST_DATA, 'cities', 'cities.shp')
  31
+        model_def = ogrinspect(shp_file, 'City')
  32
+
  33
+        expected = [
  34
+            '# This is an auto-generated Django model module created by ogrinspect.',
  35
+            'from django.contrib.gis.db import models',
  36
+            '',
  37
+            'class City(models.Model):',
  38
+            '    name = models.CharField(max_length=80)',
  39
+            '    population = models.FloatField()',
  40
+            '    density = models.FloatField()',
  41
+            '    created = models.DateField()',
  42
+            '    geom = models.PointField(srid=-1)',
  43
+            '    objects = models.GeoManager()',
  44
+        ]
  45
+
  46
+        self.assertEqual(model_def, '\n'.join(expected))
  47
+
  48
+    def test_time_field(self):
  49
+        # Only possible to test this on PostGIS at the momemnt.  MySQL
  50
+        # complains about permissions, and SpatiaLite/Oracle are
  51
+        # insanely difficult to get support compiled in for in GDAL.
  52
+        if not connections['default'].ops.postgis:
  53
+            return
  54
+
  55
+        # Getting the database identifier used by OGR, if None returned
  56
+        # GDAL does not have the support compiled in.
  57
+        ogr_db = get_ogr_db_string()
  58
+        if not ogr_db:
  59
+            return
  60
+
  61
+        # writing shapefules via GDAL currently does not support writing OGRTime
  62
+        # fields, so we need to actually use a database
  63
+        model_def = ogrinspect(ogr_db, 'Measurement',
  64
+                               layer_key=AllOGRFields._meta.db_table,
  65
+                               decimal=['f_decimal'])
  66
+
  67
+        expected = [
  68
+            '# This is an auto-generated Django model module created by ogrinspect.',
  69
+            'from django.contrib.gis.db import models',
  70
+            '',
  71
+            'class Measurement(models.Model):',
  72
+            '    f_decimal = models.DecimalField(max_digits=0, decimal_places=0)',
  73
+            '    f_int = models.IntegerField()',
  74
+            '    f_datetime = models.DateTimeField()',
  75
+            '    f_time = models.TimeField()',
  76
+            '    f_float = models.FloatField()',
  77
+            '    f_char = models.CharField(max_length=10)',
  78
+            '    f_date = models.DateField()',
  79
+            '    geom = models.PolygonField()',
  80
+            '    objects = models.GeoManager()',
  81
+        ]
  82
+
  83
+        self.assertEqual(model_def, '\n'.join(expected))
  84
+
  85
+def get_ogr_db_string():
  86
+    # Construct the DB string that GDAL will use to inspect the database.
  87
+    # GDAL will create its own connection to the database, so we re-use the
  88
+    # connection settings from the Django test.  This approach is a bit fragile
  89
+    # and cannot work on any other database other than PostgreSQL at the moment.
  90
+    db = connections.databases['default']
  91
+
  92
+    # Map from the django backend into the OGR driver name and database identifier
  93
+    # http://www.gdal.org/ogr/ogr_formats.html
  94
+    #
  95
+    # TODO: Support Oracle (OCI), MySQL, and SpatiaLite.
  96
+    drivers = {
  97
+        'django.contrib.gis.db.backends.postgis': ('PostgreSQL', 'PG'),
  98
+    }
  99
+
  100
+    drv_name, db_str = drivers[db['ENGINE']]
  101
+
  102
+    # Ensure that GDAL library has driver support for the database.
  103
+    try:
  104
+        Driver(drv_name)
  105
+    except:
  106
+        return None
  107
+
  108
+    # Build the params of the OGR database connection string
  109
+    # TODO: connection strings are database-dependent, thus if
  110
+    #       we ever test other backends, this will need to change.
  111
+    params = ["dbname='%s'" % db['NAME']]
  112
+    def add(key, template):
  113
+        value = db.get(key, None)
  114
+        # Don't add the parameter if it is not in django's settings
  115
+        if value:
  116
+            params.append(template % value)
  117
+    add('HOST', "host='%s'")
  118
+    add('PORT', "port='%s'")
  119
+    add('USER', "user='%s'")
  120
+    add('PASSWORD', "password='%s'")
  121
+
  122
+    return '%s:%s' % (db_str, ' '.join(params))
4  django/contrib/gis/utils/ogrinspect.py
@@ -8,7 +8,7 @@
8 8
 from itertools import izip
9 9
 # Requires GDAL to use.
10 10
 from django.contrib.gis.gdal import DataSource
11  
-from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString
  11
+from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
12 12
 
13 13
 def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False):
14 14
     """
@@ -189,7 +189,7 @@ def get_kwargs_str(field_name):
189 189
             yield '    %s = models.DateField(%s)' % (mfield, kwargs_str[2:])
190 190
         elif field_type is OFTDateTime:
191 191
             yield '    %s = models.DateTimeField(%s)' % (mfield, kwargs_str[2:])
192  
-        elif field_type is OFTDate:
  192
+        elif field_type is OFTTime:
193 193
             yield '    %s = models.TimeField(%s)' % (mfield, kwargs_str[2:])
194 194
         else:
195 195
             raise TypeError('Unknown field type %s in %s' % (field_type, mfield))
0  gis/tests/inspectapp/__init__.py b/django/contrib/gis/tests/inspectapp/__init__.py
No changes.

0 notes on commit 3ac8778

Please sign in to comment.
Something went wrong with that request. Please try again.