Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation of geometry type #296

Open
pjpetersik opened this issue Jun 24, 2024 · 0 comments
Open

Validation of geometry type #296

pjpetersik opened this issue Jun 24, 2024 · 0 comments

Comments

@pjpetersik
Copy link

pjpetersik commented Jun 24, 2024

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.

field_mapping.update(
{
models.GeometryField: GeometryField,
models.PointField: GeometryField,
models.LineStringField: GeometryField,
models.PolygonField: GeometryField,
models.MultiPointField: GeometryField,
models.MultiLineStringField: GeometryField,
models.MultiPolygonField: GeometryField,
models.GeometryCollectionField: GeometryField,
}
)

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 :).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant