web/lib/django/contrib/gis/tests/layermap/tests.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 import os
       
     2 import unittest
       
     3 from decimal import Decimal
       
     4 
       
     5 from django.utils.copycompat import copy
       
     6 
       
     7 from django.contrib.gis.gdal import DataSource
       
     8 from django.contrib.gis.tests.utils import mysql
       
     9 from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
       
    10 
       
    11 from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
       
    12 
       
    13 shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
       
    14 city_shp = os.path.join(shp_path, 'cities', 'cities.shp')
       
    15 co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
       
    16 inter_shp = os.path.join(shp_path, 'interstates', 'interstates.shp')
       
    17 
       
    18 # Dictionaries to hold what's expected in the county shapefile.  
       
    19 NAMES  = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
       
    20 NUMS   = [1, 2, 1, 19, 1] # Number of polygons for each.                                                                                                                                                  
       
    21 STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
       
    22 
       
    23 class LayerMapTest(unittest.TestCase):
       
    24 
       
    25     def test01_init(self):
       
    26         "Testing LayerMapping initialization."
       
    27 
       
    28         # Model field that does not exist.
       
    29         bad1 = copy(city_mapping)
       
    30         bad1['foobar'] = 'FooField'
       
    31 
       
    32         # Shapefile field that does not exist.
       
    33         bad2 = copy(city_mapping)
       
    34         bad2['name'] = 'Nombre'
       
    35 
       
    36         # Nonexistent geographic field type.
       
    37         bad3 = copy(city_mapping)
       
    38         bad3['point'] = 'CURVE'
       
    39 
       
    40         # Incrementing through the bad mapping dictionaries and
       
    41         # ensuring that a LayerMapError is raised.
       
    42         for bad_map in (bad1, bad2, bad3):
       
    43             try:
       
    44                 lm = LayerMapping(City, city_shp, bad_map)
       
    45             except LayerMapError:
       
    46                 pass
       
    47             else:
       
    48                 self.fail('Expected a LayerMapError.')
       
    49 
       
    50         # A LookupError should be thrown for bogus encodings.
       
    51         try:
       
    52             lm = LayerMapping(City, city_shp, city_mapping, encoding='foobar')
       
    53         except LookupError:
       
    54             pass
       
    55         else:
       
    56             self.fail('Expected a LookupError')
       
    57 
       
    58     def test02_simple_layermap(self):
       
    59         "Test LayerMapping import of a simple point shapefile."
       
    60         # Setting up for the LayerMapping.
       
    61         lm = LayerMapping(City, city_shp, city_mapping)
       
    62         lm.save()
       
    63 
       
    64         # There should be three cities in the shape file.
       
    65         self.assertEqual(3, City.objects.count())
       
    66 
       
    67         # Opening up the shapefile, and verifying the values in each
       
    68         # of the features made it to the model.
       
    69         ds = DataSource(city_shp)
       
    70         layer = ds[0]
       
    71         for feat in layer:
       
    72             city = City.objects.get(name=feat['Name'].value)
       
    73             self.assertEqual(feat['Population'].value, city.population)
       
    74             self.assertEqual(Decimal(str(feat['Density'])), city.density)
       
    75             self.assertEqual(feat['Created'].value, city.dt)
       
    76 
       
    77             # Comparing the geometries.
       
    78             pnt1, pnt2 = feat.geom, city.point
       
    79             self.assertAlmostEqual(pnt1.x, pnt2.x, 6)
       
    80             self.assertAlmostEqual(pnt1.y, pnt2.y, 6)
       
    81 
       
    82     def test03_layermap_strict(self):
       
    83         "Testing the `strict` keyword, and import of a LineString shapefile."
       
    84         # When the `strict` keyword is set an error encountered will force
       
    85         # the importation to stop.
       
    86         try:
       
    87             lm = LayerMapping(Interstate, inter_shp, inter_mapping)
       
    88             lm.save(silent=True, strict=True)
       
    89         except InvalidDecimal:
       
    90             # No transactions for geoms on MySQL; delete added features.
       
    91             if mysql: Interstate.objects.all().delete()
       
    92         else:
       
    93             self.fail('Should have failed on strict import with invalid decimal values.')
       
    94 
       
    95         # This LayerMapping should work b/c `strict` is not set.
       
    96         lm = LayerMapping(Interstate, inter_shp, inter_mapping)
       
    97         lm.save(silent=True)
       
    98 
       
    99         # Two interstate should have imported correctly.
       
   100         self.assertEqual(2, Interstate.objects.count())
       
   101         
       
   102         # Verifying the values in the layer w/the model.
       
   103         ds = DataSource(inter_shp)
       
   104 
       
   105         # Only the first two features of this shapefile are valid.
       
   106         valid_feats = ds[0][:2]
       
   107         for feat in valid_feats:
       
   108             istate = Interstate.objects.get(name=feat['Name'].value)
       
   109             
       
   110             if feat.fid == 0:
       
   111                 self.assertEqual(Decimal(str(feat['Length'])), istate.length)
       
   112             elif feat.fid == 1:
       
   113                 # Everything but the first two decimal digits were truncated,
       
   114                 # because the Interstate model's `length` field has decimal_places=2.
       
   115                 self.assertAlmostEqual(feat.get('Length'), float(istate.length), 2)
       
   116 
       
   117             for p1, p2 in zip(feat.geom, istate.path):
       
   118                 self.assertAlmostEqual(p1[0], p2[0], 6)
       
   119                 self.assertAlmostEqual(p1[1], p2[1], 6)
       
   120 
       
   121     def county_helper(self, county_feat=True):
       
   122         "Helper function for ensuring the integrity of the mapped County models."
       
   123         for name, n, st in zip(NAMES, NUMS, STATES):
       
   124             # Should only be one record b/c of `unique` keyword.
       
   125             c = County.objects.get(name=name)
       
   126             self.assertEqual(n, len(c.mpoly))
       
   127             self.assertEqual(st, c.state.name) # Checking ForeignKey mapping.
       
   128             
       
   129             # Multiple records because `unique` was not set.
       
   130             if county_feat:
       
   131                 qs = CountyFeat.objects.filter(name=name)
       
   132                 self.assertEqual(n, qs.count())
       
   133 
       
   134     def test04_layermap_unique_multigeometry_fk(self):
       
   135         "Testing the `unique`, and `transform`, geometry collection conversion, and ForeignKey mappings."
       
   136         # All the following should work.
       
   137         try:
       
   138             # Telling LayerMapping that we want no transformations performed on the data.
       
   139             lm = LayerMapping(County, co_shp, co_mapping, transform=False)
       
   140         
       
   141             # Specifying the source spatial reference system via the `source_srs` keyword.
       
   142             lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269)
       
   143             lm = LayerMapping(County, co_shp, co_mapping, source_srs='NAD83')
       
   144 
       
   145             # Unique may take tuple or string parameters.
       
   146             for arg in ('name', ('name', 'mpoly')):
       
   147                 lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg)
       
   148         except:
       
   149             self.fail('No exception should be raised for proper use of keywords.')
       
   150             
       
   151         # Testing invalid params for the `unique` keyword.
       
   152         for e, arg in ((TypeError, 5.0), (ValueError, 'foobar'), (ValueError, ('name', 'mpolygon'))):
       
   153             self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg)
       
   154 
       
   155         # No source reference system defined in the shapefile, should raise an error.
       
   156         if not mysql:
       
   157             self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
       
   158 
       
   159         # Passing in invalid ForeignKey mapping parameters -- must be a dictionary
       
   160         # mapping for the model the ForeignKey points to.
       
   161         bad_fk_map1 = copy(co_mapping); bad_fk_map1['state'] = 'name'
       
   162         bad_fk_map2 = copy(co_mapping); bad_fk_map2['state'] = {'nombre' : 'State'}
       
   163         self.assertRaises(TypeError, LayerMapping, County, co_shp, bad_fk_map1, transform=False)
       
   164         self.assertRaises(LayerMapError, LayerMapping, County, co_shp, bad_fk_map2, transform=False)
       
   165 
       
   166         # There exist no State models for the ForeignKey mapping to work -- should raise
       
   167         # a MissingForeignKey exception (this error would be ignored if the `strict`
       
   168         # keyword is not set).
       
   169         lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
       
   170         self.assertRaises(MissingForeignKey, lm.save, silent=True, strict=True)
       
   171 
       
   172         # Now creating the state models so the ForeignKey mapping may work.
       
   173         co, hi, tx = State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
       
   174         co.save(), hi.save(), tx.save()
       
   175 
       
   176         # If a mapping is specified as a collection, all OGR fields that
       
   177         # are not collections will be converted into them.  For example,
       
   178         # a Point column would be converted to MultiPoint. Other things being done
       
   179         # w/the keyword args:
       
   180         #  `transform=False`: Specifies that no transform is to be done; this 
       
   181         #    has the effect of ignoring the spatial reference check (because the
       
   182         #    county shapefile does not have implicit spatial reference info).
       
   183         # 
       
   184         #  `unique='name'`: Creates models on the condition that they have 
       
   185         #    unique county names; geometries from each feature however will be
       
   186         #    appended to the geometry collection of the unique model.  Thus,
       
   187         #    all of the various islands in Honolulu county will be in in one
       
   188         #    database record with a MULTIPOLYGON type.
       
   189         lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
       
   190         lm.save(silent=True, strict=True)
       
   191 
       
   192         # A reference that doesn't use the unique keyword; a new database record will
       
   193         # created for each polygon.
       
   194         lm = LayerMapping(CountyFeat, co_shp, cofeat_mapping, transform=False)
       
   195         lm.save(silent=True, strict=True)
       
   196 
       
   197         # The county helper is called to ensure integrity of County models.
       
   198         self.county_helper()
       
   199 
       
   200     def test05_test_fid_range_step(self):
       
   201         "Tests the `fid_range` keyword and the `step` keyword of .save()."
       
   202         # Function for clearing out all the counties before testing.
       
   203         def clear_counties(): County.objects.all().delete()
       
   204         
       
   205         # Initializing the LayerMapping object to use in these tests.
       
   206         lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
       
   207 
       
   208         # Bad feature id ranges should raise a type error.
       
   209         clear_counties()
       
   210         bad_ranges = (5.0, 'foo', co_shp)
       
   211         for bad in bad_ranges:
       
   212             self.assertRaises(TypeError, lm.save, fid_range=bad)
       
   213 
       
   214         # Step keyword should not be allowed w/`fid_range`.
       
   215         fr = (3, 5) # layer[3:5]
       
   216         self.assertRaises(LayerMapError, lm.save, fid_range=fr, step=10) 
       
   217         lm.save(fid_range=fr)
       
   218         
       
   219         # Features IDs 3 & 4 are for Galveston County, Texas -- only
       
   220         # one model is returned because the `unique` keyword was set.
       
   221         qs = County.objects.all()
       
   222         self.assertEqual(1, qs.count())
       
   223         self.assertEqual('Galveston', qs[0].name)
       
   224 
       
   225         # Features IDs 5 and beyond for Honolulu County, Hawaii, and
       
   226         # FID 0 is for Pueblo County, Colorado.
       
   227         clear_counties()
       
   228         lm.save(fid_range=slice(5, None), silent=True, strict=True) # layer[5:]
       
   229         lm.save(fid_range=slice(None, 1), silent=True, strict=True) # layer[:1]
       
   230 
       
   231         # Only Pueblo & Honolulu counties should be present because of
       
   232         # the `unique` keyword.  Have to set `order_by` on this QuerySet 
       
   233         # or else MySQL will return a different ordering than the other dbs.
       
   234         qs = County.objects.order_by('name') 
       
   235         self.assertEqual(2, qs.count())
       
   236         hi, co = tuple(qs)
       
   237         hi_idx, co_idx = tuple(map(NAMES.index, ('Honolulu', 'Pueblo')))
       
   238         self.assertEqual('Pueblo', co.name); self.assertEqual(NUMS[co_idx], len(co.mpoly))
       
   239         self.assertEqual('Honolulu', hi.name); self.assertEqual(NUMS[hi_idx], len(hi.mpoly))
       
   240 
       
   241         # Testing the `step` keyword -- should get the same counties
       
   242         # regardless of we use a step that divides equally, that is odd,
       
   243         # or that is larger than the dataset.
       
   244         for st in (4,7,1000):
       
   245             clear_counties()
       
   246             lm.save(step=st, strict=True)
       
   247             self.county_helper(county_feat=False)
       
   248 
       
   249     def test06_model_inheritance(self):
       
   250         "Tests LayerMapping on inherited models.  See #12093."
       
   251         icity_mapping = {'name' : 'Name',
       
   252                          'population' : 'Population',
       
   253                          'density' : 'Density',
       
   254                          'point' : 'POINT',
       
   255                          'dt' : 'Created',
       
   256                          }
       
   257 
       
   258         # Parent model has geometry field.
       
   259         lm1 = LayerMapping(ICity1, city_shp, icity_mapping)
       
   260         lm1.save()
       
   261 
       
   262         # Grandparent has geometry field.
       
   263         lm2 = LayerMapping(ICity2, city_shp, icity_mapping)
       
   264         lm2.save()
       
   265 
       
   266         self.assertEqual(6, ICity1.objects.count())
       
   267         self.assertEqual(3, ICity2.objects.count())
       
   268         
       
   269 def suite():
       
   270     s = unittest.TestSuite()
       
   271     s.addTest(unittest.makeSuite(LayerMapTest))
       
   272     return s