diff --git a/mrmap/.requirements/base.txt b/mrmap/.requirements/base.txt index fddb5b2bd..5b697747a 100644 --- a/mrmap/.requirements/base.txt +++ b/mrmap/.requirements/base.txt @@ -23,7 +23,7 @@ psycopg[binary]==3.1.14 # adds class based xml mappers eulxml==1.1.3 # library to process xml -lxml==4.9.3 +lxml==5.2.0 # ISO 8601 date/time parser isodate==0.6.1 # Python Imaging Library to create security images for example @@ -68,7 +68,7 @@ odin==2.9.0 drf-spectacular-jsonapi==0.4.1 -django-ows-lib==0.14.3 +django-ows-lib==0.14.5 camel-converter==3.0.0 diff --git a/mrmap/MrMap/urls.py b/mrmap/MrMap/urls.py index 939d4bf0c..ea81bb4f6 100644 --- a/mrmap/MrMap/urls.py +++ b/mrmap/MrMap/urls.py @@ -69,7 +69,7 @@ "csw", CswServiceView.as_view(), name="csw-endpoint" - ), + ) ] diff --git a/mrmap/registry/apps.py b/mrmap/registry/apps.py index 522ab0cac..a4c669c24 100644 --- a/mrmap/registry/apps.py +++ b/mrmap/registry/apps.py @@ -39,7 +39,7 @@ def find_orphan_metadata_objects(sender, **kwargs): resource_relation__isnull=True) # TODO: move this to a manager as check function # print info object instead - print("orphans:", orphans.count()) + print("metadata orphans:", orphans.count()) class RegistryConfig(AppConfig): @@ -54,5 +54,5 @@ def ready(self): post_migrate.connect(create_wms_operations, sender=self) post_migrate.connect(create_wfs_operations, sender=self) - # post_migrate.connect(create_file_system_import_task, sender=self) + # post_migrate.connect(create_file_system_import_task, sender=self) post_migrate.connect(find_orphan_metadata_objects, sender=self) diff --git a/mrmap/registry/models/metadata.py b/mrmap/registry/models/metadata.py index 1cf05d2c9..3c0b6398f 100644 --- a/mrmap/registry/models/metadata.py +++ b/mrmap/registry/models/metadata.py @@ -3,12 +3,14 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.contrib.gis.db.models import MultiPolygonField +from django.contrib.gis.gdal.error import GDALException from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q from django.db.models.expressions import CombinedExpression, F from django.db.models.fields.generated import GeneratedField from django.utils.translation import gettext_lazy as _ +from epsg_cache.registry import Registry from eulxml import xmlmap from extras.managers import (DefaultHistoryManager, UniqueConstraintDefaultValueManager) @@ -26,9 +28,12 @@ from registry.models.metadata_query import VALID_RELATIONS from requests import Request, Session from requests.adapters import HTTPAdapter +from requests.exceptions import ConnectionError from simple_history.models import HistoricalRecords from urllib3 import Retry +CRS_Registry = Registry(proxies=PROXIES) + class MimeType(models.Model): mime_type = models.CharField(max_length=500, @@ -125,6 +130,38 @@ def __str__(self): def __eq__(self, __value: object) -> bool: return self.code == __value.code and self.prefix == __value.prefix + @property + def crs(self): + if self.prefix == ReferenceSystemPrefixEnum.EPSG: + try: + return CRS_Registry.get(srid=self.code) + except (ConnectionError, GDALException): + pass + + @property + def wkt(self): + _crs = self.crs + if _crs: + return _crs.wkt + + @property + def extent(self): + _crs = self.crs + if _crs: + return _crs.extent + + @property + def is_xy_order(self): + _crs = self.crs + if _crs: + return _crs.is_xy_order + + @property + def is_yx_order(self): + _crs = self.crs + if _crs: + return _crs.is_yx_order + class MetadataContact(models.Model): name = models.CharField(max_length=256, diff --git a/mrmap/registry/models/service.py b/mrmap/registry/models/service.py index b1a36a93f..03c9b0d22 100644 --- a/mrmap/registry/models/service.py +++ b/mrmap/registry/models/service.py @@ -194,21 +194,21 @@ class OperationUrl(models.Model): # 2048 is the technically specified max length of an url. Some services urls scratches this limit. url: str = models.URLField( max_length=4096, - editable=False, + # editable=False, verbose_name=_("url"), help_text=_("the url for this operation"), ) operation: str = models.CharField( max_length=30, choices=OGCOperationEnum.choices, - editable=False, + # editable=False, verbose_name=_("operation"), help_text=_("the operation you can perform with this url."), ) mime_types = models.ManyToManyField( to="MimeType", # use string to avoid from circular import error blank=True, - editable=False, + # editable=False, related_name="%(class)s_operation_urls", related_query_name="%(class)s_operation_url", verbose_name=_("internet mime type"), @@ -241,7 +241,7 @@ class WebMapServiceOperationUrl(OperationUrl): service = models.ForeignKey( to=WebMapService, on_delete=models.CASCADE, - editable=False, + # editable=False, related_name="operation_urls", related_query_name="operation_url", verbose_name=_("related web map service"), diff --git a/mrmap/registry/proxy/wfs_proxy.py b/mrmap/registry/proxy/wfs_proxy.py index 718666f7d..177865e07 100644 --- a/mrmap/registry/proxy/wfs_proxy.py +++ b/mrmap/registry/proxy/wfs_proxy.py @@ -2,10 +2,10 @@ import json -from axis_order_cache.utils import adjust_axis_order from django.contrib.gis.geos import GEOSGeometry from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt +from epsg_cache.utils import adjust_axis_order from ows_lib.client.wfs.mixins import \ WebFeatureServiceMixin as WebFeatureServiceClient from registry.models.service import WebFeatureService diff --git a/mrmap/registry/serializers/metadata.py b/mrmap/registry/serializers/metadata.py index f40c86730..b3077ca7b 100644 --- a/mrmap/registry/serializers/metadata.py +++ b/mrmap/registry/serializers/metadata.py @@ -2,6 +2,7 @@ from registry.models.metadata import (DatasetMetadataRecord, Keyword, Licence, MetadataContact, ReferenceSystem, Style) from registry.models.service import CatalogueService, FeatureType, Layer +from rest_framework.fields import SerializerMethodField from rest_framework.relations import HyperlinkedIdentityField from rest_framework_json_api.relations import ResourceRelatedField from rest_framework_json_api.serializers import ModelSerializer @@ -33,7 +34,7 @@ class Meta: fields = "__all__" -class ReferenceSystemSerializer( +class ReferenceSystemDefaultSerializer( StringRepresentationSerializer, ModelSerializer): @@ -46,6 +47,37 @@ class Meta: fields = "__all__" +class ReferenceSystemRetrieveSerializer( + StringRepresentationSerializer, + ModelSerializer): + + wkt = SerializerMethodField() + bbox = SerializerMethodField() + is_xy_order = SerializerMethodField() + is_yx_order = SerializerMethodField() + + url = HyperlinkedIdentityField( + view_name="registry:referencesystem-detail", + ) + + class Meta: + model = ReferenceSystem + fields = "__all__" + + def get_wkt(self, obj): + return obj.wkt + + def get_bbox(self, obj): + if obj.extent: + return obj.extent.envelope.geojson + + def get_is_xy_order(self, obj): + return obj.is_xy_order + + def get_is_yx_order(self, obj): + return obj.is_yx_order + + class StyleSerializer( StringRepresentationSerializer, ModelSerializer): diff --git a/mrmap/registry/serializers/service.py b/mrmap/registry/serializers/service.py index 3cfa6d235..6711f874b 100644 --- a/mrmap/registry/serializers/service.py +++ b/mrmap/registry/serializers/service.py @@ -22,13 +22,12 @@ from registry.serializers.metadata import (DatasetMetadataRecordSerializer, KeywordSerializer, MetadataContactSerializer, - ReferenceSystemSerializer, + ReferenceSystemDefaultSerializer, StyleSerializer) from registry.serializers.security import WebFeatureServiceOperationSerializer from rest_framework.fields import (BooleanField, IntegerField, SerializerMethodField, URLField, UUIDField) from rest_framework_gis.fields import GeometryField -from rest_framework_gis.serializers import GeoFeatureModelSerializer from rest_framework_json_api.relations import ( ResourceRelatedField, SerializerMethodResourceRelatedField) from rest_framework_json_api.serializers import (HyperlinkedIdentityField, @@ -165,7 +164,7 @@ class LayerSerializer( "keywords": KeywordSerializer, "created_by": UserSerializer, "last_modified_by": UserSerializer, - "reference_systems": ReferenceSystemSerializer, + "reference_systems": ReferenceSystemDefaultSerializer, # "dimensions": DimensionSerializer, } @@ -415,7 +414,7 @@ class FeatureTypeSerializer( "keywords": KeywordSerializer, "created_by": UserSerializer, "last_modified_by": UserSerializer, - # TODO: "reference_systems": ReferenceSystemSerializer + # TODO: "reference_systems": ReferenceSystemDefaultSerializer } class Meta: diff --git a/mrmap/registry/urls.py b/mrmap/registry/urls.py index 8ac5953ce..f9e2d5edb 100644 --- a/mrmap/registry/urls.py +++ b/mrmap/registry/urls.py @@ -1,4 +1,3 @@ -from django.urls import path from registry.views import harvesting as harvesting_views from registry.views import mapcontext as mapcontext_views from registry.views import metadata as metadata_views diff --git a/mrmap/registry/views/metadata.py b/mrmap/registry/views/metadata.py index a2b0025d1..e13c53501 100644 --- a/mrmap/registry/views/metadata.py +++ b/mrmap/registry/views/metadata.py @@ -1,6 +1,6 @@ from django.db.models.query import Prefetch from extras.permissions import DjangoObjectPermissionsOrAnonReadOnly -from extras.viewsets import NestedModelViewSet +from extras.viewsets import NestedModelViewSet, SerializerClassesMixin from registry.models.metadata import (DatasetMetadataRecord, Keyword, Licence, MetadataContact, ReferenceSystem, Style) from registry.models.service import CatalogueService, FeatureType, Layer @@ -8,7 +8,8 @@ KeywordSerializer, LicenceSerializer, MetadataContactSerializer, - ReferenceSystemSerializer, + ReferenceSystemDefaultSerializer, + ReferenceSystemRetrieveSerializer, StyleSerializer) from rest_framework_json_api.views import ModelViewSet @@ -60,9 +61,12 @@ class NestedLicenceViewSet( pass -class ReferenceSystemViewSetMixin(): +class ReferenceSystemViewSetMixin(SerializerClassesMixin): queryset = ReferenceSystem.objects.all() - serializer_class = ReferenceSystemSerializer + serializer_classes = { + "default": ReferenceSystemDefaultSerializer, + "retrieve": ReferenceSystemRetrieveSerializer, + } filterset_fields = { "code": ["exact", "icontains", "contains"], "prefix": ["exact", "icontains", "contains"] diff --git a/mrmap/tests/django/registry/mrmap-proxy/tests_wfs_proxy.py b/mrmap/tests/django/registry/mrmap-proxy/tests_wfs_proxy.py index 7e2b11297..920512f80 100644 --- a/mrmap/tests/django/registry/mrmap-proxy/tests_wfs_proxy.py +++ b/mrmap/tests/django/registry/mrmap-proxy/tests_wfs_proxy.py @@ -1,11 +1,11 @@ from accounts.models.users import User -from axis_order_cache.utils import adjust_axis_order from django.contrib.gis.geos import GEOSGeometry from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command from django.db.models.query_utils import Q from django.test import Client, TestCase +from epsg_cache.utils import adjust_axis_order from eulxml.xmlmap import load_xmlobject_from_string from MrMap.settings import BASE_DIR from ows_lib.xml_mapper.xml_requests.wfs.get_feature import (GetFeatureRequest, diff --git a/mrmap/tests/django/registry/mrmap-proxy/tests_wms_proxy.py b/mrmap/tests/django/registry/mrmap-proxy/tests_wms_proxy.py index 6764eceb1..95ccb908a 100644 --- a/mrmap/tests/django/registry/mrmap-proxy/tests_wms_proxy.py +++ b/mrmap/tests/django/registry/mrmap-proxy/tests_wms_proxy.py @@ -3,12 +3,12 @@ from unittest.mock import patch from accounts.models.users import User -from axis_order_cache.models import Origin, SpatialReference -from axis_order_cache.registry import Registry from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command from django.db.models.query_utils import Q from django.test import Client, TestCase +from epsg_cache.models import Origin, SpatialReference +from epsg_cache.registry import Registry from lxml import etree, objectify from MrMap.settings import BASE_DIR from PIL import Image, ImageChops