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

Feature/hasgeometry check #265

Merged
merged 6 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/objects/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from objects.utils.serializers import DynamicFieldsMixin

from .fields import ObjectSlugRelatedField, ObjectTypeField
from .validators import IsImmutableValidator, JsonSchemaValidator
from .validators import GeometryValidator, IsImmutableValidator, JsonSchemaValidator


class ObjectRecordSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -104,7 +104,7 @@ class Meta:
"url": {"lookup_field": "uuid"},
"uuid": {"validators": [IsImmutableValidator()]},
}
validators = [JsonSchemaValidator()]
validators = [JsonSchemaValidator(), GeometryValidator()]

@transaction.atomic
def create(self, validated_data):
Expand Down
8 changes: 6 additions & 2 deletions src/objects/api/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,9 @@ components:
- $ref: '#/components/schemas/GeoJSONGeometry'
nullable: true
description: Point, linestring or polygon object which represents the coordinates
of the object
of the object. Geometry can be added only if the related OBJECTTYPE allows
this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry`
doesn't exist)
startAt:
type: string
format: date
Expand Down Expand Up @@ -793,7 +795,9 @@ components:
- $ref: '#/components/schemas/GeoJSONGeometry'
nullable: true
description: Point, linestring or polygon object which represents the coordinates
of the object
of the object. Geometry can be added only if the related OBJECTTYPE allows
this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry`
doesn't exist)
startAt:
type: string
format: date
Expand Down
8 changes: 6 additions & 2 deletions src/objects/api/v2/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,9 @@ components:
- $ref: '#/components/schemas/GeoJSONGeometry'
nullable: true
description: Point, linestring or polygon object which represents the coordinates
of the object
of the object. Geometry can be added only if the related OBJECTTYPE allows
this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry`
doesn't exist)
startAt:
type: string
format: date
Expand Down Expand Up @@ -865,7 +867,9 @@ components:
- $ref: '#/components/schemas/GeoJSONGeometry'
nullable: true
description: Point, linestring or polygon object which represents the coordinates
of the object
of the object. Geometry can be added only if the related OBJECTTYPE allows
this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry`
doesn't exist)
startAt:
type: string
format: date
Expand Down
33 changes: 33 additions & 0 deletions src/objects/api/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.utils.translation import ugettext_lazy as _

from rest_framework import serializers
from zds_client.client import ClientError

from objects.core.utils import check_objecttype

Expand Down Expand Up @@ -88,3 +89,35 @@ def validate_data_attrs(value: str):
"Operator `%(operator)s` supports only dates and/or numeric values"
) % {"operator": operator}
raise serializers.ValidationError(message, code=code)


class GeometryValidator:
code = "geometry-not-allowed"
message = _("This object type doesn't support geometry")

def set_context(self, serializer):
"""
This hook is called by the serializer instance,
prior to the validation call being made.
"""
self.instance = getattr(serializer, "instance", None)

def __call__(self, attrs):
object_type = attrs.get("object_type") or self.instance.object_type
geometry = attrs.get("record", {}).get("geometry")

if not geometry:
return

client = object_type.service.build_client()
annashamray marked this conversation as resolved.
Show resolved Hide resolved
try:
response = client.retrieve("objecttype", url=object_type.url)
except ClientError as exc:

msg = f"Object type can not be retrieved: {exc.args[0]}"
raise ValidationError(msg)

allow_geometry = response.get("allowGeometry", True)

if geometry and not allow_geometry:
raise serializers.ValidationError(self.message, code=self.code)
37 changes: 37 additions & 0 deletions src/objects/core/migrations/0027_auto_20211203_1209.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 2.2.24 on 2021-12-03 11:09

import django.contrib.gis.db.models.fields
import django.contrib.postgres.fields.jsonb
import django.core.serializers.json
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("core", "0026_auto_20201222_1833"),
]

operations = [
migrations.AlterField(
model_name="objectrecord",
name="data",
field=django.contrib.postgres.fields.jsonb.JSONField(
default=dict,
encoder=django.core.serializers.json.DjangoJSONEncoder,
help_text="Object data, based on OBJECTTYPE",
verbose_name="data",
),
),
migrations.AlterField(
model_name="objectrecord",
name="geometry",
field=django.contrib.gis.db.models.fields.GeometryField(
blank=True,
help_text="Point, linestring or polygon object which represents the coordinates of the object. Geometry can be added only if the related OBJECTTYPE allows this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry` doesn't exist)",
null=True,
srid=4326,
verbose_name="geometry",
),
),
]
5 changes: 4 additions & 1 deletion src/objects/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ class ObjectRecord(models.Model):
blank=True,
null=True,
help_text=_(
"Point, linestring or polygon object which represents the coordinates of the object"
"Point, linestring or polygon object which represents the coordinates of the "
"object. Geometry can be added only if the related OBJECTTYPE allows this "
"(`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry` doesn't "
"exist)"
),
)

Expand Down
15 changes: 11 additions & 4 deletions src/objects/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ def mock_service_oas_get(m: Mocker, url: str, service: str, oas_url: str = "") -
m.get(oas_url, content=content)


def mock_objecttype(url: str) -> dict:
return {
def mock_objecttype(url: str, attrs=None) -> dict:
attrs = attrs or {}
response = {
"url": url,
"name": "Boom",
"namePlural": "Bomen",
Expand All @@ -52,14 +53,18 @@ def mock_objecttype(url: str) -> dict:
"labels": {},
"createdAt": "2020-12-01",
"modifiedAt": "2020-12-01",
"allowGeometry": True,
"versions": [
f"{url}/versions/1",
],
}
response.update(attrs)
return response


def mock_objecttype_version(url: str) -> dict:
return {
def mock_objecttype_version(url: str, attrs=None) -> dict:
attrs = attrs or {}
response = {
"url": f"{url}/versions/1",
"version": 1,
"objectType": url,
Expand All @@ -82,6 +87,8 @@ def mock_objecttype_version(url: str) -> dict:
"modifiedAt": "2020-11-16",
"publishedAt": "2020-11-16",
}
response.update(attrs)
return response


def notifications_client_mock(value):
Expand Down
7 changes: 4 additions & 3 deletions src/objects/tests/v1/test_notifications_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from rest_framework import status
from rest_framework.test import APITestCase
from vng_api_common.notifications.models import NotificationsConfig
from zds_client.client import Client
from zgw_consumers.constants import APITypes
from zgw_consumers.models import Service

Expand All @@ -19,6 +18,7 @@

from ..constants import GEO_WRITE_KWARGS
from ..utils import (
mock_objecttype,
mock_objecttype_version,
mock_service_oas_get,
notifications_client_mock,
Expand Down Expand Up @@ -69,6 +69,7 @@ def test_send_notif_create_object(self, mocker, mock_client):
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)
mocker.get(self.object_type.url, json=mock_objecttype(self.object_type.url))

url = reverse("object-list")
data = {
Expand Down Expand Up @@ -117,10 +118,10 @@ def test_send_notif_update_object(self, mocker, mock_client):
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)
mocker.get(self.object_type.url, json=mock_objecttype(self.object_type.url))

obj = ObjectFactory.create(object_type=self.object_type)
url = reverse("object-detail", args=[obj.uuid])
full_url = f"http://testserver{url}"

data = {
"type": self.object_type.url,
Expand Down Expand Up @@ -168,10 +169,10 @@ def test_send_notif_partial_update_object(self, mocker, mock_client):
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)
mocker.get(self.object_type.url, json=mock_objecttype(self.object_type.url))

obj = ObjectFactory.create(object_type=self.object_type)
url = reverse("object-detail", args=[obj.uuid])
full_url = f"http://testserver{url}"

data = {
"type": self.object_type.url,
Expand Down
4 changes: 3 additions & 1 deletion src/objects/tests/v1/test_object_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from objects.utils.test import TokenAuthMixin

from ..constants import GEO_WRITE_KWARGS
from ..utils import mock_objecttype_version, mock_service_oas_get
from ..utils import mock_objecttype, mock_objecttype_version, mock_service_oas_get
from .utils import reverse

OBJECT_TYPES_API = "https://example.com/objecttypes/v1/"
Expand Down Expand Up @@ -104,6 +104,7 @@ def test_create_object(self, m):
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)
m.get(self.object_type.url, json=mock_objecttype(self.object_type.url))

url = reverse("object-list")
data = {
Expand Down Expand Up @@ -142,6 +143,7 @@ def test_update_object(self, m):
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)
m.get(self.object_type.url, json=mock_objecttype(self.object_type.url))

# other object - to check that correction works when there is another record with the same index
ObjectRecordFactory.create(object__object_type=self.object_type)
Expand Down
99 changes: 99 additions & 0 deletions src/objects/tests/v1/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,68 @@ def test_create_object_correction_invalid(self, m):
[f"Object with index={record.index} does not exist."],
)

def test_create_object_geometry_not_allowed(self, m):
mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes")
m.get(
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)
m.get(
self.object_type.url,
json=mock_objecttype(self.object_type.url, attrs={"allowGeometry": False}),
)

url = reverse("object-list")
data = {
"type": self.object_type.url,
"record": {
"typeVersion": 1,
"data": {"plantDate": "2020-04-12", "diameter": 30},
"geometry": {
"type": "Point",
"coordinates": [4.910649523925713, 52.37240093589432],
},
"startAt": "2020-01-01",
},
}

response = self.client.post(url, data, **GEO_WRITE_KWARGS)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["non_field_errors"],
["This object type doesn't support geometry"],
)

def test_create_object_with_geometry_without_allowGeometry(self, m):
"""test the support of Objecttypes api without allowGeometry property"""
mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes")
object_type_response = mock_objecttype(self.object_type.url)
del object_type_response["allowGeometry"]
m.get(self.object_type.url, json=object_type_response)
m.get(
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)

url = reverse("object-list")
data = {
"type": self.object_type.url,
"record": {
"typeVersion": 1,
"data": {"plantDate": "2020-04-12", "diameter": 30},
"geometry": {
"type": "Point",
"coordinates": [4.910649523925713, 52.37240093589432],
},
"startAt": "2020-01-01",
},
}

response = self.client.post(url, data, **GEO_WRITE_KWARGS)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_update_object_with_correction_invalid(self, m):
mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes")
m.get(
Expand Down Expand Up @@ -256,3 +318,40 @@ def test_update_uuid_invalid(self, m):

data = response.json()
self.assertEqual(data["uuid"], ["This field can't be changed"])

def test_update_geometry_not_allowed(self, m):
mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes")
m.get(
f"{self.object_type.url}/versions/1",
json=mock_objecttype_version(self.object_type.url),
)
m.get(
self.object_type.url,
json=mock_objecttype(self.object_type.url, attrs={"allowGeometry": False}),
)

initial_record = ObjectRecordFactory.create(
object__object_type=self.object_type, geometry=None
)
object = initial_record.object

url = reverse("object-detail", args=[object.uuid])
data = {
"record": {
"typeVersion": 1,
"data": {"plantDate": "2020-04-12", "diameter": 30},
"geometry": {
"type": "Point",
"coordinates": [4.910649523925713, 52.37240093589432],
},
"startAt": "2020-01-01",
}
}

response = self.client.patch(url, data, **GEO_WRITE_KWARGS)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json()["non_field_errors"],
["This object type doesn't support geometry"],
)
Loading