openwisp / django-rest-framework-gis

Geographic add-ons for Django REST Framework. Maintained by the OpenWISP Project.
http://openwisp.org
MIT License
1.09k stars 201 forks source link

TypeError: Cannot set GeoPoint SpatialProxy (POINT) with value of type: <class 'dict'> #284

Open eruiz67 opened 1 year ago

eruiz67 commented 1 year ago

I am working with Django, Django Rest Framework, Django Rest Framework GIS and POSTgis database to create an endpoint that should upload a geografical point and all its related information. I have defined a Model, Serializer and View for that purpose. But when making a request using postman I am getting the following error:

TypeError: Cannot set GeoPoint SpatialProxy (POINT) with value of type: <class 'dict'>

I am currently working with:

Model definition:

from django.contrib.gis.db import models
from django.utils.translation import gettext_lazy as _
class GeoPoint(models.Model):

    POINT_TYPE_CHOICES = (
       ("limite_cusaf","Límite CUSAF"),
       ("limite_cusaf_di","Límite CUSAF y DI"),
       ("limite_2_di","Límite 2 DI"),
       ("limite_3_di_mas","Límite 3 DI o más"),
       ("otros","Otros"),
    )

    geom = models.PointField(verbose_name=_("Localización"), srid=4326)
    id_cusaf = models.CharField(_("ID CUSAF"), max_length=50)
    code = models.CharField(_("Código Punto"), max_length=50)
    point_type = models.CharField(_("Tipo de Punto"), max_length=50, choices=POINT_TYPE_CHOICES)
    observations = models.TextField(_("Observaciones"), null=True, blank=True)

    def __str__(self):
        return self.cod_punto

    class Meta:
        db_table = 'aggregate_geopunto'
        managed = True
        verbose_name = 'Punto'
        verbose_name_plural = 'Puntos'

Serializer:

from rest_framework_gis.serializers import GeoFeatureModelSerializer

class GeoPointSerializer(GeoFeatureModelSerializer):

    class Meta:
        model = GeoPoint
        geo_field = "geom"
        fields = ('id','geom','id_cusaf','code',
        'point_type','observations',)
        read_only_fields = ['id',]

View:

 class GeoPointAPICreate(generics.CreateAPIView):
        authentication_classes = []
        permission_classes = ()
        queryset = GeoPoint.objects.all()
        serializer_class = GeoPointSerializer

This is the image of the POSTman request: imagen

{
    "type": "Feature",
    "geometry": {
        "type": "Point",
        "coordinates": [
            -69.929352,
            18.547504
        ]
    },
    "properties": {
        "id_cusaf": "x001",
        "code": "1",
        "point_type": "limite_cusaf",
        "observations": "observationssdsd"
    }
}

And this is the complete error:

 Traceback (most recent call last):
  File "/home/ernesto/Programming/django/virtualenvs/fichacusaf/lib/python3.10/site-packages/rest_framework/serializers.py", line 939, in create
    instance = ModelClass._default_manager.create(**validated_data)
  File "/home/ernesto/Programming/django/virtualenvs/fichacusaf/lib/python3.10/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/ernesto/Programming/django/virtualenvs/fichacusaf/lib/python3.10/site-packages/django/db/models/query.py", line 451, in create
    obj = self.model(**kwargs)
  File "/home/ernesto/Programming/django/virtualenvs/fichacusaf/lib/python3.10/site-packages/django/db/models/base.py", line 488, in __init__
    _setattr(self, field.attname, val)
  File "/home/ernesto/Programming/django/virtualenvs/fichacusaf/lib/python3.10/site-packages/django/contrib/gis/db/models/proxy.py", line 74, in __set__
    raise TypeError('Cannot set %s SpatialProxy (%s) with value of type: %s' % (
TypeError: Cannot set GeoPoint SpatialProxy (POINT) with value of type: <class 'dict'>
gbip commented 1 year ago

I got the same issue with : django==4.2, djangorestframework==3.14.0 and djangorestframework-gis==1.0.

Note : those versions are not officially supported.

gbip commented 1 year ago

A quick solution is to redefine a new serializer class that correctly instantiate a GEOSGeometry.

Here is such a class :

class InstantiatingGeoFeatureModelSerializer(GeoFeatureModelSerializer):
    def to_internal_value(self, data):
        result = super().to_internal_value(data)
        result[self.Meta.geo_field] = GEOSGeometry(
            json.dumps(result[self.Meta.geo_field])
        )
        return result
gbip commented 1 year ago

A quick solution is to redefine a new serializer class that correctly instantiate a GEOSGeometry.

Here is such a class :

class InstantiatingGeoFeatureModelSerializer(GeoFeatureModelSerializer):
    def to_internal_value(self, data):
        result = super().to_internal_value(data)
        result[self.Meta.geo_field] = GEOSGeometry(
            json.dumps(result[self.Meta.geo_field])
        )
        return result

@nemesifier is it in the scope of this library to merge such a fix ? I am willing to make a PR supporting this use-case.

gbip commented 11 months ago

A quick solution is to redefine a new serializer class that correctly instantiate a GEOSGeometry.

Here is such a class :

class InstantiatingGeoFeatureModelSerializer(GeoFeatureModelSerializer):
    def to_internal_value(self, data):
        result = super().to_internal_value(data)
        result[self.Meta.geo_field] = GEOSGeometry(
            json.dumps(result[self.Meta.geo_field])
        )
        return result

Ok, the best way to solve this is to implement "validate_" and to instatiate a GEOSGeometry there.

For example :

class PolySerializer(GeoFeatureModelSerializer):

    class Meta:
        model = Poly
        geo_field = "geom"
        fields = ("id", "name", "created_at")
        read_only_fields = ["id"]

    def validate_geom(self, value):
        value = GEOSGeometry(json.dumps(value))
        # The following line is not related to this issue, but it's usefull if your geometry field is a multi-collection
        if isinstance(value, Polygon):
            value = MultiPolygon(value)
        return value