Skip to content

Commit

Permalink
Merge pull request maykinmedia#265 from maykinmedia/feature/hasgeomet…
Browse files Browse the repository at this point in the history
…ry-check

Feature/hasgeometry check
  • Loading branch information
annashamray authored Dec 3, 2021
2 parents c08221d + 28789a8 commit 06f4d51
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 18 deletions.
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()
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

0 comments on commit 06f4d51

Please sign in to comment.