Description
I currently observe the behavior that a ModelSerializer
does not validate if the geometry field is of the right geometry type. Hence, the serializer does not throw a validation error when it receives data of an unexpected geometry type but later runs into a TypeError
that is thrown by Django when the data is saved to the database.
Example
# models.py
from django.db import models
from django.contrib.gis.db import models as gis_models
class PolygonModel(models.Model):
polygon = gis_models.PolygonField()
# serializer.py
from rest_framework import serializers
class PolygonSerializer(serializers.ModelSerializer):
class Meta:
model = PolygonModel
fields = "__all__"
# view.py
from rest_framework import viewsets
class PolygonViewSet(viewsets.ModelViewSet):
serializer_class = PolygonSerializer
queryset = PolygonModel.objects.all()
# urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register("polygons', PolygonViewSet)
urlpatterns = router.urls
If the /polygons
end point would receive a POST/PUT request with the following body
{
"polygon": {
"type": "Point"
"geometry": [0, 0]
}
}
the following error would be raised by Django:
File "/usr/local/lib/python3.10/site-packages/django/db/models/manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 656, in create
obj = self.model(**kwargs)
File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 546, in __init__
_setattr(self, field.attname, val)
File "/usr/local/lib/python3.10/site-packages/django/contrib/gis/db/models/proxy.py", line 76, in __set__
raise TypeError(
TypeError: Cannot set Plot SpatialProxy (POLYGON) with value of type: <class 'django.contrib.gis.geos.point.Point'>
However, for my opinion a ValidationError
would be more appropriated to be raised.
Problem
I think the problem comes done to the field_mapping
in the apps.py
module which maps the GeoDjango fields to a generic GeometryField
.
django-rest-framework-gis/rest_framework_gis/apps.py
Lines 24 to 35 in 4f244d5
Solution
My idea would be to append a GeometryFieldValidator
to validators
list of the GeometryField
:
class GeometryValidator:
def __init__(self, geom_type):
self.geom_type = geom_type
def __call__(self, value):
if value.geom_type != self.geom_type:
raise serializers.ValidationError("Wrong geometry type provided.", code="invalid")
class GeometryField(Field):
"""
A field to handle GeoDjango Geometry fields
"""
type_name = 'GeometryField'
def __init__(
self, precision=None, remove_duplicates=False, auto_bbox=False, geom_type=None, **kwargs
):
"""
:param auto_bbox: Whether the GeoJSON object should include a bounding box
"""
self.precision = precision
self.auto_bbox = auto_bbox
self.remove_dupes = remove_duplicates
super().__init__(**kwargs)
self.style.setdefault('base_template', 'textarea.html')
if geom_type:
self.validators.append(GeometryValidator(geom_type))
I have not thought about the specifics of the implementation. However, I would be open to work on a PR if the described behavior of raising a ValidationError
is of interest for the community. Or do you think one should simply solve this by adding the described GeometryValidator
manually to each ModelSerializer
which contains a geometry field, i.e.:
# serializer.py
from rest_framework import serializers
class PolygonSerializer(serializers.ModelSerializer):
polygon = GeometryField(validators=[GeometryValidator])
class Meta:
model = PolygonModel
fields = "__all__"
Happy to get your feedback on this :).