Django

Code

Changeset 6992

Show
Ignore:
Timestamp:
01/03/08 13:02:18 (6 months ago)
Author:
jbronn
Message:

gis: LayerMapping: Improved the internals (i.e., checking every feature in OGR Layer is no longer needed, removed unnecessary class constants); added real support ForeignKey model fields; added field_types property to Layer; fixed county shapefile because of Harris County, Georgia.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis/django/contrib/gis/gdal/layer.py

    r6916 r6992  
    66from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException 
    77from django.contrib.gis.gdal.feature import Feature 
     8from django.contrib.gis.gdal.field import FIELD_CLASSES 
    89from django.contrib.gis.gdal.geometries import OGRGeomType 
    910from django.contrib.gis.gdal.srs import SpatialReference 
     
    1314    get_extent, get_fd_geom_type, get_fd_name, get_feature, get_feature_count, \ 
    1415    get_field_count, get_field_defn, get_field_name, get_field_precision, \ 
    15     get_field_width, get_layer_defn, get_layer_srs, get_next_feature, \ 
    16     reset_reading 
     16    get_field_width, get_field_type, get_layer_defn, get_layer_srs, \ 
     17    get_next_feature, reset_reading 
    1718from django.contrib.gis.gdal.prototypes.srs import clone_srs 
    1819 
     
    108109    @property 
    109110    def fields(self): 
    110         "Returns a list of the fields available in this Layer." 
     111        """ 
     112        Returns a list of string names corresponding to each of the Fields 
     113        available in this Layer. 
     114        """ 
    111115        return [get_field_name(get_field_defn(self._ldefn, i))  
    112116                for i in xrange(self.num_fields) ] 
    113117     
     118    @property 
     119    def field_types(self): 
     120        """ 
     121        Returns a list of the types of fields in this Layer.  For example, 
     122        the list [OFTInteger, OFTReal, OFTString] would be returned for 
     123        an OGR layer that had an integer, a floating-point, and string 
     124        fields. 
     125        """ 
     126        return [FIELD_CLASSES[get_field_type(get_field_defn(self._ldefn, i))] 
     127                for i in xrange(self.num_fields)] 
     128 
    114129    @property  
    115130    def field_widths(self): 
  • django/branches/gis/django/contrib/gis/tests/layermap/models.py

    r6980 r6992  
    11from django.contrib.gis.db import models 
     2 
     3class State(models.Model): 
     4    name = models.CharField(max_length=20) 
     5    objects = models.GeoManager() 
    26 
    37class County(models.Model): 
    48    name = models.CharField(max_length=25) 
     9    state = models.ForeignKey(State) 
    510    mpoly = models.MultiPolygonField(srid=4269) # Multipolygon in NAD83 
    611    objects = models.GeoManager() 
     
    2732# Mapping dictionaries for the models above. 
    2833co_mapping = {'name' : 'Name', 
     34              'state' : {'name' : 'State'}, # ForeignKey's use another mapping dictionary for the _related_ Model (State in this case). 
    2935              'mpoly' : 'MULTIPOLYGON', # Will convert POLYGON features into MULTIPOLYGONS. 
    3036              } 
  • django/branches/gis/django/contrib/gis/tests/layermap/tests.py

    r6980 r6992  
    33from datetime import date 
    44from decimal import Decimal 
    5 from models import City, County, CountyFeat, Interstate, city_mapping, co_mapping, cofeat_mapping, inter_mapping 
    6 from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal 
     5from models import City, County, CountyFeat, Interstate, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping 
     6from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey 
    77from django.contrib.gis.gdal import DataSource 
    88 
     
    112112                self.assertAlmostEqual(p1[1], p2[1], 6) 
    113113 
    114     def test04_layermap_unique_multigeometry(self): 
    115         "Testing the `unique`, and `transform` keywords and geometry collection conversion." 
     114    def test04_layermap_unique_multigeometry_fk(self): 
     115        "Testing the `unique`, and `transform`, geometry collection conversion, and ForeignKey mappings." 
    116116        # All the following should work. 
    117117        try: 
     
    136136        self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping) 
    137137 
     138        # Passing in invalid ForeignKey mapping parameters -- must be a dictionary 
     139        # mapping for the model the ForeignKey points to. 
     140        bad_fk_map1 = copy(co_mapping); bad_fk_map1['state'] = 'name' 
     141        bad_fk_map2 = copy(co_mapping); bad_fk_map2['state'] = {'nombre' : 'State'} 
     142        self.assertRaises(TypeError, LayerMapping, County, co_shp, bad_fk_map1, transform=False) 
     143        self.assertRaises(LayerMapError, LayerMapping, County, co_shp, bad_fk_map2, transform=False) 
     144 
     145        # There exist no State models for the ForeignKey mapping to work -- should raise 
     146        # a MissingForeignKey exception (this error would be ignored if the `strict` 
     147        # keyword is not set). 
     148        lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name', silent=True, strict=True) 
     149        self.assertRaises(MissingForeignKey, lm.save) 
     150 
     151        # Now creating the state models so the ForeignKey mapping may work. 
     152        co, hi, tx = State(name='Colorado'), State(name='Hawaii'), State(name='Texas') 
     153        co.save(), hi.save(), tx.save() 
     154 
    138155        # If a mapping is specified as a collection, all OGR fields that 
    139156        # are not collections will be converted into them.  For example, 
     
    149166        #    all of the various islands in Honolulu county will be in in one 
    150167        #    database record with a MULTIPOLYGON type. 
    151         lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name', silent=True
     168        lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name', silent=True, strict=True
    152169        lm.save() 
    153170 
    154171        # A reference that doesn't use the unique keyword; a new database record will 
    155172        # created for each polygon. 
    156         lm = LayerMapping(CountyFeat, co_shp, cofeat_mapping, transform=False, silent=True
     173        lm = LayerMapping(CountyFeat, co_shp, cofeat_mapping, transform=False, silent=True, strict=True
    157174        lm.save() 
    158175 
    159176        # Dictionary to hold what's expected in the shapefile. 
    160         exp = {'names' : ('Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo'), 
    161                'num' : (1, 2, 2, 19, 1), # Number of polygons for each. 
    162                } 
    163         for name, n in zip(exp['names'], exp['num']): 
    164             c = County.objects.get(name=name) # Should only be one record. 
     177        names = ('Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo') 
     178        nums  = (1, 2, 1, 19, 1) # Number of polygons for each. 
     179        states = ('Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado') 
     180 
     181        for name, n, st in zip(names, nums, states): 
     182            # Should only be one record b/c of `unique` keyword. 
     183            c = County.objects.get(name=name) 
    165184            self.assertEqual(n, len(c.mpoly)) 
     185            self.assertEqual(st, c.state.name) # Checking ForeignKey mapping. 
     186 
     187            # Multiple records because `unique` was not set. 
    166188            qs = CountyFeat.objects.filter(name=name) 
    167189            self.assertEqual(n, qs.count()) 
  • django/branches/gis/django/contrib/gis/utils/layermapping.py

    r6980 r6992  
    4646 
    4747  check: 
    48    By default, LayerMapping increments through each feature in the 
    49    layer to ensure that it is compatible with the given model and 
    50    mapping.  Setting this keyword to False, disables this action, 
    51    which will speed up execution time for very large files. 
     48   Due to optimizations, this keyword argument is deprecated and will 
     49   be removed in future revisions. 
     50 
     51  pipe: 
     52   Status information will be written to this file handle.  Defaults 
     53   to using `sys.stdout`, but any object with a `write` method is  
     54   supported. 
    5255 
    5356  silent: 
     
    5760  strict: 
    5861   Setting this keyword to True will instruct the save() method to 
    59    cease execution on the first error encountered. 
     62   cease execution on the first error encountered.  The default behavior 
     63   is to attempt to continue even if errors are encountered. 
    6064 
    6165  transaction_mode: 
     
    122126 specify one. 
    123127""" 
     128import sys 
    124129from datetime import date, datetime 
    125130from decimal import Decimal 
    126131from django.core.exceptions import ObjectDoesNotExist 
    127 from django.db import connection, transaction 
    128 from django.db.models.fields.related import ForeignKey 
     132from django.contrib.gis.db.models.fields import GeometryField 
    129133from django.contrib.gis.db.backend import SPATIAL_BACKEND 
    130134from django.contrib.gis.gdal import CoordTransform, DataSource, \ 
    131135    OGRException, OGRGeometry, OGRGeomType, SpatialReference 
    132 from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime 
     136from django.contrib.gis.gdal.field import \ 
     137    OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime 
    133138from django.contrib.gis.models import GeometryColumns, SpatialRefSys 
     139from django.db import models, transaction 
    134140 
    135141# LayerMapping exceptions. 
     
    137143class InvalidString(LayerMapError): pass 
    138144class InvalidDecimal(LayerMapError): pass 
     145class MissingForeignKey(LayerMapError): pass 
    139146 
    140147class LayerMapping(object): 
    141148    "A class that maps OGR Layers to GeoDjango Models." 
    142149     
    143     # A mapping of given geometry types to their OGR integer type. 
    144     OGC_TYPES = {'POINT' : OGRGeomType('Point'), 
    145                  'LINESTRING' : OGRGeomType('LineString'), 
    146                  'POLYGON' : OGRGeomType('Polygon'), 
    147                  'MULTIPOINT' : OGRGeomType('MultiPoint'), 
    148                  'MULTILINESTRING' : OGRGeomType('MultiLineString'), 
    149                  'MULTIPOLYGON' : OGRGeomType('MultiPolygon'), 
    150                  'GEOMETRYCOLLECTION' : OGRGeomType('GeometryCollection'), 
    151                  } 
    152  
    153     # The django.contrib.gis model types. 
    154     GIS_FIELDS = {'PointField' : 'POINT', 
    155                   'LineStringField': 'LINESTRING', 
    156                   'PolygonField': 'POLYGON', 
    157                   'MultiPointField' : 'MULTIPOINT', 
    158                   'MultiLineStringField' : 'MULTILINESTRING', 
    159                   'MultiPolygonField' : 'MULTIPOLYGON', 
    160                   'GeometryCollectionField' : 'GEOMETRYCOLLECTION', 
    161                   } 
    162  
    163150    # Acceptable 'base' types for a multi-geometry type. 
    164     MULTI_TYPES = {'POINT' : OGRGeomType('MultiPoint'), 
    165                    'LINESTRING' : OGRGeomType('MultiLineString'), 
    166                    'POLYGON' : OGRGeomType('MultiPolygon'), 
     151    MULTI_TYPES = {1 : OGRGeomType('MultiPoint'), 
     152                   2 : OGRGeomType('MultiLineString'), 
     153                   3 : OGRGeomType('MultiPolygon'), 
    167154                   } 
    168155 
    169     # The acceptable Django field types that map to OGR fields. 
     156    # Acceptable Django field types and corresponding acceptable OGR 
     157    # counterparts. 
    170158    FIELD_TYPES = { 
    171         'AutoField' : OFTInteger, 
    172         'IntegerField' : OFTInteger
    173         'FloatField' : OFTReal
    174         'DateField' : OFTDate, 
    175         'DateTimeField' : OFTDateTime, 
    176         'TimeField' : OFTTime, 
    177         'DecimalField' : OFTReal
    178         'CharField' : OFTString, 
    179         'TextField' : OFTString, 
    180         'SmallIntegerField' : OFTInteger
    181         'PositiveSmallIntegerField' : OFTInteger
     159        models.AutoField : OFTInteger, 
     160        models.IntegerField : (OFTInteger, OFTReal)
     161        models.FloatField : (OFTInteger, OFTReal)
     162        models.DateField : OFTDate, 
     163        models.DateTimeField : OFTDateTime, 
     164        models.TimeField : OFTTime, 
     165        models.DecimalField : (OFTInteger, OFTReal)
     166        models.CharField : OFTString, 
     167        models.TextField : OFTString, 
     168        models.SmallIntegerField : (OFTInteger, OFTReal)
     169        models.PositiveSmallIntegerField : (OFTInteger, OFTReal)
    182170        } 
    183171 
     
    188176 
    189177    def __init__(self, model, data, mapping, layer=0,  
    190                  source_srs=None, encoding=None, check=True,  
     178                 source_srs=None, encoding=None, check=True, pipe=sys.stdout, 
    191179                 progress=False, interval=1000, strict=False, silent=False, 
    192180                 transaction_mode='commit_on_success', transform=True, 
    193181                 unique=False): 
    194         "Takes the Django model, the data source, and the mapping (dictionary)
    195  
    196         # Getting the field names and types from the model 
    197         self.fields = dict((f.name, self.map_foreign_key(f)) for f in model._meta.fields) 
    198         self.field_classes = dict((f.name, f) for f in model._meta.fields) 
    199  
    200         # Getting the DataSource and its Layer 
     182        ""
     183        A LayerMapping object is initialized using the given Model (not an instance), 
     184        a DataSource (or string path to an OGR-supported data file), and a mapping 
     185        dictionary.  See the module level docstring for more details and keyword 
     186        argument usage. 
     187        """ 
     188        # Getting the DataSource and the associated Layer. 
    201189        if isinstance(data, basestring): 
    202190            self.ds = DataSource(data) 
     
    224212 
    225213        # Checking the layer -- intitialization of the object will fail if 
    226         # things don't check out before hand.  This may be time-consuming, 
    227         # and disabled by setting the `check` keyword to False. 
    228         if check: self.check_layer() 
    229  
    230         # The silent, strict, progress, and interval flags. 
     214        # things don't check out before hand. 
     215        self.check_layer() 
     216 
     217        # The strict flag -- if it is set, exceptions will be propagated up. 
     218        self.strict = strict 
     219 
     220        # Setting the keyword arguments related to status printing. 
    231221        self.silent = silent 
    232         self.strict = strict 
    233222        self.progress = progress 
     223        self.pipe = pipe 
    234224        self.interval = interval 
    235225 
     
    258248        else: 
    259249            raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode) 
    260  
    261     def check_feature(self, feat): 
    262         "Checks the OGR feature against the model fields and mapping." 
    263         HAS_GEO = False    
    264  
    265         # Incrementing through each model_field & ogr_field in the given mapping. 
    266         for model_field, ogr_field in self.mapping.items(): 
    267             # Making sure the given mapping model field is in the given model fields. 
    268             if model_field in self.fields: 
    269                 model_type = self.fields[model_field] 
    270             elif model_field[:-3] in self.fields: #foreign key 
    271                 model_type = self.fields[model_field[:-3]] 
     250     
     251    #### Checking routines used during initialization #### 
     252    def check_layer(self): 
     253        """ 
     254        This checks the Layer metadata, and ensures that it is compatible 
     255        with the mapping information and model.  Unlike previous revisions, 
     256        there is no need to increment through each feature in the Layer. 
     257        """ 
     258        # The geometry field of the model is set here. 
     259        # TODO: Support more than one geometry field / model. 
     260        self.geom_field = False 
     261        self.fields = {} 
     262 
     263        # Getting lists of the field names and the field types available in 
     264        # the OGR Layer. 
     265        ogr_fields = self.layer.fields 
     266        ogr_field_types = self.layer.field_types 
     267 
     268        # Function for determining if the OGR mapping field is in the Layer. 
     269        def check_ogr_fld(ogr_map_fld): 
     270            try: 
     271                idx = ogr_fields.index(ogr_map_fld) 
     272            except ValueError: 
     273                raise LayerMapError('Given mapping OGR field "%s" not found in OGR Layer.' % ogr_map_fld) 
     274            return idx 
     275 
     276        # No need to increment through each feature in the model, simply check 
     277        # the Layer metadata against what was given in the mapping dictionary. 
     278        for field_name, ogr_name in self.mapping.items(): 
     279            # Ensuring that a corresponding field exists in the model 
     280            # for the given field name in the mapping. 
     281            try: 
     282                model_field = self.model._meta.get_field(field_name) 
     283            except models.fields.FieldDoesNotExist: 
     284                raise LayerMapError('Given mapping field "%s" not in given Model fields.' % field_name) 
     285 
     286            # Getting the string name for the Django field class (e.g., 'PointField'). 
     287            fld_name = model_field.__class__.__name__ 
     288 
     289            if isinstance(model_field, GeometryField): 
     290                if self.geom_field: 
     291                    raise LayerMapError('LayerMapping does not support more than one GeometryField per model.') 
     292 
     293                try: 
     294                    gtype = OGRGeomType(ogr_name) 
     295                except OGRException: 
     296                    raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name) 
     297 
     298                # Making sure that the OGR Layer's Geometry is compatible. 
     299                ltype = self.layer.geom_type 
     300                if not (gtype == ltype or self.make_multi(ltype, model_field)): 
     301                    raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s.' % (fld_name, gtype)) 
     302 
     303                # Setting the `geom_field` attribute w/the name of the model field 
     304                # that is a Geometry. 
     305                self.geom_field = field_name 
     306                fields_val = model_field 
     307            elif isinstance(model_field, models.ForeignKey): 
     308                if isinstance(ogr_name, dict): 
     309                    # Is every given related model mapping field in the Layer? 
     310                    rel_model = model_field.rel.to 
     311                    for rel_name, ogr_field in ogr_name.items():  
     312                        idx = check_ogr_fld(ogr_field) 
     313                        try: 
     314                            rel_field = rel_model._meta.get_field(rel_name) 
     315                        except models.fields.FieldDoesNotExist: 
     316                            raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' %  
     317                                                (rel_name, rel_model.__class__.__name__)) 
     318                    fields_val = rel_model 
     319                else: 
     320                    raise TypeError('ForeignKey mapping must be of dictionary type.') 
    272321            else: 
    273                 raise LayerMapError('Given mapping field "%s" not in given Model fields!' % model_field) 
    274  
    275             ### Handling if we get a geometry in the Field ### 
    276             if ogr_field in self.OGC_TYPES: 
    277                 # At this time, no more than one geographic field per model =( 
    278                 if HAS_GEO: 
    279                     raise LayerMapError('More than one geographic field in mapping not allowed (yet).') 
    280                 else: 
    281                     HAS_GEO = ogr_field 
    282  
    283                 # Making sure this geometry field type is a valid Django GIS field. 
    284                 if not model_type in self.GIS_FIELDS: 
    285                     raise LayerMapError('Unknown Django GIS field type "%s"' % model_type) 
    286  
    287                 # Getting the OGRGeometry, it's type (an integer) and it's name (a string) 
    288                 geom  = feat.geom 
    289                 gtype = geom.geom_type 
    290                 gname = geom.geom_name 
    291  
    292                 if self.make_multi(gname, model_type): 
    293                     # Do we have to 'upsample' into a Geometry Collection? 
    294                     pass 
    295                 elif gtype == self.OGC_TYPES[self.GIS_FIELDS[model_type]]: 
    296                     # The geometry type otherwise was expected 
    297                     pass 
    298                 else: 
    299                     raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s' % (model_type, gtype)) 
    300             ### Handling other fields ### 
    301             else: 
    302                 # Making sure the model field is supported. 
    303                 if not model_type in self.FIELD_TYPES: 
    304                     raise LayerMapError('Django field type "%s" has no OGR mapping (yet).' % model_type) 
    305  
    306                 # Otherwise, we've got an OGR Field.  Making sure that an 
    307                 # index exists for the mapping OGR field. 
    308                 try: 
    309                     fi = feat.index(ogr_field) 
    310                 except: 
    311                     raise LayerMapError('Given mapping OGR field "%s" not in given OGR layer feature!' % ogr_field) 
    312  
    313     def check_layer(self): 
    314         "Checks every feature in this object's layer." 
    315         for feat in self.layer: 
    316             self.check_feature(feat) 
     322                # Is the model field type supported by LayerMapping? 
     323                if not model_field.__class__ in self.FIELD_TYPES: 
     324                    raise LayerMapError('Django field type "%s" has no OGR mapping (yet).' % fld_name) 
     325 
     326                # Is the OGR field in the Layer? 
     327                idx = check_ogr_fld(ogr_name) 
     328                 
     329                # Can the OGR field type be mapped to the Django field type? 
     330                if not issubclass(ogr_field_types[idx], self.FIELD_TYPES[model_field.__class__]): 
     331                    raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' %  
     332                                        (ogr_field, ogr_field_types[idx].__name__, fld_name)) 
     333                fields_val = model_field 
     334         
     335            self.fields[field_name] = fields_val 
    317336 
    318337    def check_srs(self, source_srs): 
     
    322341        elif isinstance(source_srs, SpatialRefSys): 
    323342            sr = source_srs.srs 
    324         elif isinstance(source_srs, (int, str)): 
     343        elif isinstance(source_srs, (int, basestring)): 
    325344            sr = SpatialReference(source_srs) 
    326345        else: 
    327346            # Otherwise just pulling the SpatialReference from the layer 
    328347            sr = self.layer.srs 
    329              
     348         
    330349        if not sr: 
    331350            raise LayerMapError('No source reference system defined.') 
     
    335354    def check_unique(self, unique): 
    336355        "Checks the `unique` keyword parameter -- may be a sequence or string." 
    337         # Getting the geometry field; only the first encountered GeometryField 
    338         # will be used. 
    339         self.geom_field = False 
    340         for model_field, ogr_fld in self.mapping.items(): 
    341             if ogr_fld in self.OGC_TYPES: 
    342                 self.geom_field = model_field 
    343                 break 
    344  
    345356        if isinstance(unique, (list, tuple)): 
    346357            # List of fields to determine uniqueness with 
     
    353364            raise TypeError('Unique keyword argument must be set with a tuple, list, or string.') 
    354365 
    355     def coord_transform(self): 
    356         "Returns the coordinate transformation object." 
    357         try: 
    358             # Getting the target spatial reference system 
    359             target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs 
    360          
    361             # Creating the CoordTransform object 
    362             return CoordTransform(self.source_srs, target_srs) 
    363         except Exception, msg: 
    364             raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg) 
    365  
     366    #### Keyword argument retrieval routines #### 
    366367    def feature_kwargs(self, feat): 
    367         "Returns the keyword arguments needed for saving a feature." 
    368          
     368        """ 
     369        Given an OGR Feature, this will return a dictionary of keyword arguments 
     370        for constructing the mapped model.  Also returned is the `all_prepped` 
     371        flag, which is used to signal that a model corresponding to a ForeignKey 
     372        mapping does not exist. 
     373        """ 
    369374        # The keyword arguments for model construction. 
    370375        kwargs = {} 
     
    376381        # Incrementing through each model field and OGR field in the 
    377382        # dictionary mapping. 
    378         for model_field, ogr_field in self.mapping.items(): 
    379             is_fk = False 
    380             try: 
    381                 model_type = self.fields[model_field] 
    382             except KeyError: #foreign key 
    383                 # The -3 index is b/c foreign keys are appended w/'_id'. 
    384                 model_type = self.fields[model_field[:-3]] 
    385                 is_fk = True 
     383        for field_name, ogr_name in self.mapping.items(): 
     384            model_field = self.fields[field_name] 
    386385             
    387             if ogr_field in self.OGC_TYPES
     386            if isinstance(model_field, GeometryField)
    388387                # Verify OGR geometry. 
    389                 val = self.verify_geom(feat.geom, model_type) 
     388                val = self.verify_geom(feat.geom, model_field) 
     389            elif isinstance(model_field, models.base.ModelBase): 
     390                # The related _model_, not a field was passed in -- indicating 
     391                # another mapping for the related Model. 
     392                val = self.verify_fk(feat, model_field, ogr_name) 
     393                if not val: all_prepped = False 
    390394            else: 
    391395                # Otherwise, verify OGR Field type. 
    392                 val = self.verify_field(feat[ogr_field], model_field) 
    393  
    394             if is_fk: 
    395                 # Handling if foreign key. 
    396                 rel_obj = None 
    397                 field_name = model_field[:-3] 
    398                 try: 
    399                     # FIXME: refactor to efficiently fetch FKs. 
    400                     #  Requires significant re-work. :-/ 
    401                     rel = self.model._meta.get_field(field_name).rel 
    402                     rel_obj = rel.to._default_manager.get(**{('%s__exact' % rel.field_name):val}) 
    403                 except ObjectDoesNotExist: 
    404                     all_prepped = False 
    405  
    406                 kwargs[model_field[:-3]] = rel_obj 
    407             else: 
    408                 kwargs[model_field] = val 
     396                val = self.verify_ogr_field(feat[ogr_name], model_field) 
     397 
     398            # Setting the keyword arguments for the field name with the 
     399            # value obtained above. 
     400            kwargs[field_name] = val 
    409401             
    410402        return kwargs, all_prepped 
     
    421413            return dict((fld, kwargs[fld]) for fld in self.unique) 
    422414 
    423     def verify_field(self, fld, model_field): 
     415    #### Verification routines used in constructing model keyword arguments. #### 
     416    def verify_ogr_field(self, ogr_field, model_field): 
    424417        """ 
    425418        Verifies if the OGR Field contents are acceptable to the Django 
     
    427420        otherwise the proper exception is raised. 
    428421        """ 
    429         field_class = self.field_classes[model_field] 
    430         if isinstance(fld, OFTString): 
     422        if isinstance(ogr_field, OFTString): 
    431423            if self.encoding: 
    432424                # The encoding for OGR data sources may be specified here 
    433425                # (e.g., 'cp437' for Census Bureau boundary files). 
    434                 val = unicode(fld.value, self.encoding) 
     426                val = unicode(ogr_field.value, self.encoding) 
    435427            else: 
    436                 val = fld.value 
    437                 if len(val) > field_class.max_length: 
     428                val = ogr_field.value 
     429                if len(val) > model_field.max_length: 
    438430                    raise InvalidString('%s model field maximum string length is %s, given %s characters.' % 
    439                                         (model_field, field_class.max_length, len(val))) 
    440         elif isinstance(fld, OFTReal): 
     431                                        (model_field.name, model_field.max_length, len(val))) 
     432        elif isinstance(ogr_field, OFTReal): 
    441433            try: 
    442434                # Creating an instance of the Decimal value to use. 
    443                 d = Decimal(str(fld.value)) 
     435                d = Decimal(str(ogr_field.value)) 
    444436            except: 
    445                 raise InvalidDecimal('Could not construct decimal from: %s' % fld) 
     437                raise InvalidDecimal('Could not construct decimal from: %s' % ogr_field) 
    446438 
    447439            # Getting the decimal value as a tuple. 
     
    451443 
    452444            # Maximum amount of precision, or digits to the left of the decimal. 
    453             max_prec = field_class.max_digits - field_class.decimal_places 
     445            max_prec = model_field.max_digits - model_field.decimal_places 
    454446 
    455447            # Getting the digits to the left of the decimal place for the  
     
    464456            if n_prec > max_prec: 
    465457                raise InvalidDecimal('A DecimalField with max_digits %d, decimal_places %d must round to an absolute value less than 10^%d.' % 
    466                                      (field_class.max_digits, field_class.decimal_places, max_prec)) 
     458                                     (model_field.max_digits, model_field.decimal_places, max_prec)) 
    467459            val = d 
    468460        else: 
    469             val = fld.value 
     461            val = ogr_field.value 
    470462        return val 
    471463 
    472     def verify_geom(self, geom, model_type): 
    473         "Verifies the geometry." 
    474         if self.make_multi(geom.geom_name, model_type): 
     464    def verify_fk(self, feat, rel_model, rel_mapping): 
     465        """ 
     466        Given an OGR Feature, the related model and its dictionary mapping, 
     467        this routine will retrieve the related model for the ForeignKey 
     468        mapping. 
     469        """ 
     470        # TODO: It is expensive to retrieve a model for every record -- 
     471        #  explore if an efficient mechanism exists for caching related  
     472        #  ForeignKey models. 
     473 
     474        # Constructing and verifying the related model keyword arguments. 
     475        fk_kwargs = {} 
     476        for field_name, ogr_name in rel_mapping.items(): 
     477            fk_kwargs[field_name] = self.verify_ogr_field(feat[ogr_name], rel_model._meta.get_field(field_name)) 
     478 
     479        # Attempting to retrieve and return the related model. 
     480        try: 
     481            return rel_model.objects.get(**fk_kwargs) 
     482        except ObjectDoesNotExist: 
     483            if self.strict: raise MissingForeignKey('No %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs)) 
     484            else: return None 
     485 
     486    def verify_geom(self, geom, model_field): 
     487        """ 
     488        Verifies the geometry -- will construct and return a GeometryCollection 
     489        if necessary (for example if the model field is MultiPolygonField while 
     490        the mapped shapefile only contains Polygons). 
     491        """ 
     492        if self.make_multi(geom.geom_type, model_field): 
    475493            # Constructing a multi-geometry type to contain the single geometry 
    476             multi_type = self.MULTI_TYPES[geom.geom_name
     494            multi_type = self.MULTI_TYPES[geom.geom_type.num
    477495            g = OGRGeometry(multi_type) 
    478496            g.add(geom) 
     
    487505        # Returning the WKT of the geometry. 
    488506        return g.wkt 
    489          
     507 
     508    #### Other model methods #### 
     509    def coord_transform(self): 
     510        "Returns the coordinate transformation object." 
     511        try: 
     512            # Getting the target spatial reference system 
     513            target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs 
     514 
     515            # Creating the CoordTransform object 
     516            return CoordTransform(self.source_srs, target_srs) 
     517        except Exception, msg: 
     518            raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg) 
     519 
    490520    def geometry_column(self): 
    491521        "Returns the GeometryColumn model associated with the geographic column." 
     
    499529            raise LayerMapError('Geometry column does not exist for model. (did you run syncdb?):\n %s' % msg) 
    500530 
    501     def make_multi(self, geom_name, model_type): 
    502         "Determines whether the geometry should be made into a GeometryCollection." 
    503         return (geom_name in self.MULTI_TYPES) and (model_type.startswith('Multi')) 
    504  
    505     def map_foreign_key(self, django_field): 
    506         "Handles fields within foreign keys for the given field." 
    507         if not django_field.__class__ is ForeignKey: 
    508             # Returning the field's class name. 
    509             return django_field.__class__.__name__ 
    510         else: 
    511             # Otherwise, getting the type of the related field's 
    512             # from the Foreign key. 
    513             rf = django_field.rel.get_related_field() 
    514             return rf.get_internal_type() 
     531    def make_multi(self, geom_type, model_field): 
     532        """ 
     533        Given the OGRGeomType for a geometry and its associated GeometryField,  
     534        determine whether the geometry should be turned into a GeometryCollection. 
     535        """ 
     536        return (geom_type.num in self.MULTI_TYPES and  
     537                model_field.__class__.__name__ == 'Multi%s' % geom_type.django) 
    515538 
    516539    def save(self, verbose=False): 
    517         "Runs the layer mapping on the given SHP file, and saves to the database." 
    518          
     540        """ 
     541        Saves the contents from the OGR DataSource Layer into the database 
     542        according to the mapping dictionary given at initialization. If 
     543        the `verbose` keyword is set, information will be printed subsequent 
     544        to each model save executed on the database. 
     545        """ 
     546        # Defining the 'real' save method, utilizing the transaction  
     547        # decorator created during initialization. 
    519548        @self.transaction_decorator 
    520549        def _save(): 
     
    531560                    if self.strict: raise 
    532561                    elif not self.silent:  
    533                         print 'Ignoring Feature ID %s because: %s' % (feat.fid, msg
     562                        self.pipe.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg)
    534563                else: 
    535564                    # Constructing the model using the keyword args 
     
    562591                            m.save() 
    563592                            num_saved += 1 
    564                             if verbose: print 'Saved: %s' % m 
     593                            if verbose: self.pipe.write('Saved: %s\n' % m) 
    565594                        except SystemExit: 
    566595                            raise 
     
    573602                                # Bailing out if the `strict` keyword is set. 
    574603                                if not self.silent: 
    575                                     print 'Failed to save the feature (id: %s) into the model with the keyword arguments:' % feat.fid 
    576                                     print kwargs 
     604                                    self.pipe.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid) 
     605                                    self.pipe.write('%s\n' % kwargs) 
    577606                                raise 
    578607                            elif not self.silent: 
    579                                 print 'Failed to save %s:\n %s\nContinuing' % (kwargs, msg
     608                                self.pipe.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg)
    580609                    else: 
    581                         print 'Skipping %s due to missing relation.' % kwargs 
     610                        if not self.silent: self.pipe.write('Skipping due to missing relation:\n%s\n' % kwargs) 
     611                         
    582612 
    583613                # Printing progress information, if requested. 
    584614                if self.progress and num_feat % self.interval == 0: 
    585                     print 'Processed %d features, saved %d ...' % (num_feat, num_saved
     615                    self.pipe.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved)
    586616                
    587617        # Calling our defined function, which will use the specified