How can I flatten a foreignkey object with django-rest-framework-(gis)

血红的双手。 提交于 2021-01-02 06:34:41

问题


I have searched long and far for a solution that is up to date and specific to my problem but have yet not found a solution or a clear documentation on what I really need to do in order to flatten a relationship to become geojson compliant.

This question is almost identical to mine, however the solutions or answers does not solve the problem and still produces invalid GeoJSON.

  • django-rest-framework-gis related field

Related

  • Set serializer geo_field as PointField from another model - Django
  • Django REST Framework: define fields in nested object?

Problem

I have a Location model that holds a pointfield with SRID=4326. I also have a TrainStation model that has location field as foreign key to Location. When I serialize the TrainStation via the GeoFeatureModelSerializer it produces invalid GeoJSON (see example below "invalid geojson").

(Valid GeoJSON can, of course, be obtained if I where to store the PointField in the TrainStation model, but in my case, I cannot do that so I need to flatten it somehow.)

Question

  • How do I achieve output like the "Valid GeoJSON" example below?

Research

I am a newcomer to both Python and Django thus I am yet not very good at reading other people's source code, however I think I can conclude that I need to somehow override the to_representation() method in order to get what I want, but my searches are so far fruitless so I am stuck.

models.py

class Location(models.Model):

    point = models.PointField()

class TrainStation(models.Model):

    location_signature = models.CharField(primary_key=True, max_length=32)
    advertised_location_name = models.CharField(max_length=32)
    country_code = models.ForeignKey(Country)
    county_no = models.ForeignKey(County)
    location = models.ForeignKey(Location, null=True)

serializers.py

class LocationSerializer(ModelSerializer):

    class Meta:
        model = Location
        geo_field = 'point'
        fields = [
            'point',
        ]


class TrainStationSerializer(GeoFeatureModelSerializer):

    location_signature = PrimaryKeyRelatedField(read_only=True)
    location = LocationSerializer(read_only=True)
    country_code = StringRelatedField(read_only=True)
    county_no = StringRelatedField(read_only=True)

    class Meta:
        model = TrainStation
        geo_field = 'location'
        fields = [
            'location_signature',
            'advertised_location_name',
            'country_code',
            'county_no',
        ]

GeoJSON Output examples:

I have verified the output on http://geojson.io to determine if its valid or not.

Invalid GeoJSON

{
    "type": "FeatureCollection",
    "features": [
        {
            "id": "Ak",
            "type": "Feature",
            "geometry": {
                "point": {           <------+------ offending lines
                    "type": "Point",        |
                    "coordinates": [        |
                        18.8303462142963,   |
                        68.3486410812835    |
                    ]                       |
                }                    <------+
            },
            "properties": {
                "advertised_location_name": "Abisko Östra",
                "country_code": "Sverige",
                "county_no": "Norrbottens län"
            }
        }
    ]
}

Valid GeoJSON

This is the output I am looking for.

{
    "type": "FeatureCollection",
    "features": [
        {
            "id": "Ak",
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [
                    18.8303462142963,
                    68.3486410812835
                ]
            },
            "properties": {
                "advertised_location_name": "Abisko Östra",
                "country_code": "Sverige",
                "county_no": "Norrbottens län"
            }
        }
    ]
}

回答1:


I have now solved this issue with the following code:

class LocationSerializer(ModelSerializer):

    def to_representation(self, obj):

        representation = super().to_representation(obj)
        point_representation = representation.pop('point')
        for key in point_representation:
            representation[key] = point_representation[key]

        return representation

    class Meta:
        model = Location
        geo_field = 'point'
        fields = [
            'point',
        ]

And this does indeed produce valid GeoJSON:

{
    "type": "FeatureCollection",
    "features": [
        {
            "id": "Ak",
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [
                    18.8303462142963,
                    68.3486410812835
                ]
            },
            "properties": {
                "advertised_location_name": "Abisko Östra",
                "country_code": "Sverige",
                "county_no": "Norrbottens län"
            }
        }
    ]
}

If someone has any inputs on this feel free to contribute and add an answer :-)




回答2:


I think the problem might be that you're declaring a whole ModelSerializer as your geo_field and its just sticking the result of that serializer, which is itself an entire geojson object, inside the geometry part of your main geojson object and the Serializers that come with django-restframework-gis just don't know what to do about that.

What the GeoFeatureModelSerializer class would like to see as a geo_field is a GeometryField which has been serialized by its own rest_framework_gis.fields.GeometryField. I imagine any of the following would get you the behavior you want.

  1. just add location.point to your TrainStationSerializer as its geofield using the double underscore format. Disclaimer: I'm not actually sure off the top of my head if drf-gis implements the double underscores properly for the geo_field property, but I think this should work.

    from rest_framework_gis.fields import GeometryField
    from rest_framework_gis.serializers import GeoFeatureModelSerializer
    
    
    class TrainStationSerializer(GeoFeatureModelSerializer):
    
        location_signature = PrimaryKeyRelatedField(read_only=True)
        country_code = StringRelatedField(read_only=True)
        county_no = StringRelatedField(read_only=True)
    
        class Meta:
            model = TrainStation
            geo_field = 'location__point'
            fields = [
                'location_signature',
                'advertised_location_name',
                'country_code',
                'county_no',
            ]
    
  2. Use the fields.GeometryField class that comes with drf-gis and specify your location.point field as its source.

    from rest_framework_gis.fields import GeometryField
    from rest_framework_gis.serializers import GeoFeatureModelSerializer
    
    
    class TrainStationSerializer(GeoFeatureModelSerializer):
    
        location_signature = PrimaryKeyRelatedField(read_only=True)
        location = GeometryField(source='location.point')
        country_code = StringRelatedField(read_only=True)
        county_no = StringRelatedField(read_only=True)
    
        class Meta:
            model = TrainStation
            geo_field = 'location'
            fields = [
                'location_signature',
                'advertised_location_name',
                'country_code',
                'county_no',
            ]
    
  3. Use the GeometrySerializerMethodField as shown in the example on drf-gis's readme

    from rest_framework_gis.fields import GeometrySerializerMethodField
    from rest_framework_gis.serializers import GeoFeatureModelSerializer
    
    
    class TrainStationSerializer(GeoFeatureModelSerializer):
    
        location_signature = PrimaryKeyRelatedField(read_only=True)
        location = GeometrySerializerMethodField()
        country_code = StringRelatedField(read_only=True)
        county_no = StringRelatedField(read_only=True)
    
        def get_location(self, obj):
            return obj.location.point
    
        class Meta:
            model = TrainStation
            geo_field = 'location'
            fields = [
                'location_signature',
                'advertised_location_name',
                'country_code',
                'county_no',
            ]
    


来源:https://stackoverflow.com/questions/44857249/how-can-i-flatten-a-foreignkey-object-with-django-rest-framework-gis

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!