[Fixed]-GeoDjango LayerMapping & Foreign Key

8👍

layer_mapping = {
    'fk': {'nm_field': 'NAME'}, # foreign key field
    'this_field': 'THIS',
    'that_field': 'THAT',
    'geom': 'POLYGON',
}

the error you’re receiving that the Foreign Key field should be a dictionary is basically requesting an additional mapping to the model which the foreign key relates.

in the above snippet:

  • ‘fk’ is the foreign key field name from the model the data is being loaded into (lets call it ‘load model’)
  • ‘nm_field’ is the field name from the model the ‘load model’ has the foreign key relationship to (lets call it ‘primary model’)
  • ‘NAME’ is the field name from the data being loaded into ‘load model’ which holds the relationship to ‘primary model’

more explicitly, imagine if ‘primary model’ is a dataset of lakes and they have a field called ‘nm_field’ that is the lake name as a string.

now imagine, ‘load model’ is a dataset of points representing all the buoys on all the lakes and has a field name ‘fk’ that is a ForeignKey to ‘primary model’ for the assignment of the lake each buoy belongs to.

finally, the data you’re loading into ‘load model’ has a string field called ‘NAME’ and it contains the pre-populated name of the lake each buoy belongs to. that string name is the relationship tie. it allows the ‘load model’ to use that name to identify which lake in the ‘primary model’ it should establish a foreign key with.

3👍

I tricked the LayerMapper into loading the ForeignKey field as a plain data-type after creating the tables.

  • Give USCounty an FK “state” to USState and run manage.py syncdb
  • Replace the “state” with “state_id” and the real datatype,
    usually models.IntegerField and execute the load.run() LayerMapper.
  • Return the “state” FK to the USCounty model.
  • Use Django normally.

    In my case below, the “state” keys are 2-character FIPS codes.

    class USCounty(models.Model):
        state = models.ForeignKey(USState)
        ## state_id = models.CharField(max_length=2)
        ...
        geom = models.MultiPolygonField(srid=4326)
        objects = models.GeoManager()
    
👤Andrew

1👍

I worked around this by manually adding a temporary pre_save callback. You can connect it just for the record creation, then disconnect as soon as LayerMapping has done its work.

See ‘My Solution’ here – the ‘black box’ method I refer to is in fact exactly this use case.

The code that works for me:

def pre_save_callback(sender, instance, *args, **kwargs):
    fkey = some_method_that_gets_the_foreign_key()
    instance.type = fkey

# other mappings defined as usual
mapping = {
    'key1': 'KEY1',
    ...,
}

lm = LayerMapping(models.MyModel, PATH_TO_SHAPEFILE, mapping, transform=True)
# temporarily connect pre_save method
pre_save.connect(pre_save_callback, sender=models.MyModel)
try:
    lm.save(strict=True)
except Exception as exc:
    optional_error_handling()
    raise
finally:
    # disconnect pre_save callback
    pre_save.disconnect(pre_save_callback, sender=models.MyModel)

0👍

It doesn’t look like there is an easy way to hook into LayerMapping for foreign key fields. I solved this by using a for loop and the get_geoms() call. Thanks to http://invisibleroads.com/tutorials/geodjango-googlemaps-build.html

Here is an example of what I did:

placemark_kml = os.path.abspath(os.path.join(os.path.dirname(locator.__file__), 'data/claim.kml'))
datasource = DataSource(placemark_kml)
lyr = datasource[0]
waypointNames = lyr.get_fields('Name')
waypointDescriptions = lyr.get_fields('Description')
waypointGeometries = lyr.get_geoms()
for waypointName, waypointGeometry, waypointDescription in itertools.izip(waypointNames, waypointGeometries, waypointDescriptions):
    placemark = PlaceMark(name=waypointName, description=waypointDescription, geom=waypointGeometry.wkt)
    placemark.layer = Layer.objects.get(pk=8)
    placemark.save()

0👍

Not an answer but hopefully a hint.

The error thrown comes from this part of the code. line ~220 of layermapping.py

elif isinstance(model_field, models.ForeignKey):
    if isinstance(ogr_name, dict):
        # Is every given related model mapping field in the Layer?
        rel_model = model_field.rel.to
        for rel_name, ogr_field in ogr_name.items():
            idx = check_ogr_fld(ogr_field)
            try:
                rel_model._meta.get_field(rel_name)
            except models.fields.FieldDoesNotExist:
                raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' %
                                    (rel_name, rel_model.__class__.__name__))
        fields_val = rel_model
    else:
        raise TypeError('ForeignKey mapping must be of dictionary type.')

At the beginning of the for loop, it looks for a dict: ogr_name.items()

ogr_name is actually defined as the value part of the mapping dict.
The dict is supposed to be composed of the org field name and the related field name from the related model.

If anyone understands the origin of that ogr_name dict, it would be of great use.

👤jcs

Leave a comment