From 4afe7620157e7f424b1e234115d9d2d86c76b34b Mon Sep 17 00:00:00 2001 From: "G.Allegri" Date: Fri, 14 Nov 2025 16:53:26 +0100 Subject: [PATCH 01/54] Track client 5.0.x --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1798cffadf6..21bf6096d78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -82,7 +82,7 @@ git+https://github.com/GeoNode/pinax-notifications@master#egg=geonode-pinax-noti # GeoNode org maintained apps. # django-geonode-mapstore-client==4.0.5 -git+https://github.com/GeoNode/geonode-mapstore-client.git@master#egg=django_geonode_mapstore_client +git+https://github.com/GeoNode/geonode-mapstore-client.git@5.0.x#egg=django_geonode_mapstore_client django-avatar==8.0.1 geonode-oauth-toolkit==2.2.2.2 geonode-announcements==2.0.2.2 From ef93bef1e27f3baf67feda1b05a4b92c01bb5cde Mon Sep 17 00:00:00 2001 From: Giovanni Allegri Date: Mon, 17 Nov 2025 09:19:13 +0100 Subject: [PATCH 02/54] Update .clabot (cherry picked from commit a492be2cce598718c76d3aa06249dfab28bd85f4) --- .clabot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.clabot b/.clabot index d6f6c4a1777..0342a7afa8a 100644 --- a/.clabot +++ b/.clabot @@ -83,6 +83,7 @@ "ZuitAMB", "marlowp", "sijandh35", - "mcihad" + "mcihad", + "nrjadkry" ] } \ No newline at end of file From 3716fe33aaac5e85808716e011ac7c3d1704f302 Mon Sep 17 00:00:00 2001 From: "G.Allegri" Date: Fri, 14 Nov 2025 18:06:29 +0100 Subject: [PATCH 03/54] Upgrade to Geoserver 2.27.3 (cherry picked from commit 1ddae09988049a71f9b5b8b771b4811bf39234c2) --- docker-compose-dev.yml | 4 ++-- docker-compose-geoserver-server.yml | 4 ++-- docker-compose-test.yml | 4 ++-- docker-compose.yml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index add55fc3364..714e04e5ade 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -79,7 +79,7 @@ services: # Geoserver backend geoserver: - image: geonode/geoserver:2.24.3-latest + image: geonode/geoserver:2.27.3-latest container_name: geoserver4${COMPOSE_PROJECT_NAME} healthcheck: test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://geoserver:8080/geoserver/ows" @@ -105,7 +105,7 @@ services: condition: service_healthy data-dir-conf: - image: geonode/geoserver_data:2.24.3-latest + image: geonode/geoserver_data:2.27.3-latest container_name: gsconf4${COMPOSE_PROJECT_NAME} entrypoint: sleep infinity volumes: diff --git a/docker-compose-geoserver-server.yml b/docker-compose-geoserver-server.yml index 5a83f79b7c7..e634476f17e 100644 --- a/docker-compose-geoserver-server.yml +++ b/docker-compose-geoserver-server.yml @@ -2,7 +2,7 @@ version: '2.2' services: data-dir-conf: - image: geonode/geoserver_data:latest + image: geonode/geoserver_data:2.27.3-latest restart: on-failure container_name: gsconf4${COMPOSE_PROJECT_NAME} labels: @@ -13,7 +13,7 @@ services: - geoserver-data-dir:/geoserver_data/data geoserver: - image: geonode/geoserver:latest + image: geonode/geoserver:2.27.3-latest restart: unless-stopped container_name: geoserver4${COMPOSE_PROJECT_NAME} stdin_open: true diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 29fa6a23b39..fb531aba4fd 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -80,7 +80,7 @@ services: # Geoserver backend geoserver: - image: geonode/geoserver:2.24.4-latest + image: geonode/geoserver:2.27.3-latest container_name: geoserver4${COMPOSE_PROJECT_NAME} healthcheck: test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://geoserver:8080/geoserver/ows" @@ -106,7 +106,7 @@ services: condition: service_healthy data-dir-conf: - image: geonode/geoserver_data:2.24.4-latest + image: geonode/geoserver_data:2.27.3-latest container_name: gsconf4${COMPOSE_PROJECT_NAME} entrypoint: sleep infinity volumes: diff --git a/docker-compose.yml b/docker-compose.yml index 8c2b1f456cd..04d0a0ca0aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,7 +92,7 @@ services: # Geoserver backend geoserver: - image: geonode/geoserver:2.27.x-latest + image: geonode/geoserver:2.27.3-latest container_name: geoserver4${COMPOSE_PROJECT_NAME} healthcheck: test: "curl -m 10 --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://geoserver:8080/geoserver/ows" @@ -118,7 +118,7 @@ services: condition: service_healthy data-dir-conf: - image: geonode/geoserver_data:2.27.2-latest + image: geonode/geoserver_data:2.27.3-latest container_name: gsconf4${COMPOSE_PROJECT_NAME} entrypoint: sleep infinity volumes: From a5814bd163f03eea215fd97ad457c2d80adfadc4 Mon Sep 17 00:00:00 2001 From: Mattia Date: Mon, 17 Nov 2025 13:05:47 +0100 Subject: [PATCH 04/54] Backport 5.0.x: fix linestring mapping --- geonode/upload/handlers/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/geonode/upload/handlers/utils.py b/geonode/upload/handlers/utils.py index 61b3a1b6778..4e57ff36281 100644 --- a/geonode/upload/handlers/utils.py +++ b/geonode/upload/handlers/utils.py @@ -52,6 +52,7 @@ GEOM_TYPE_MAPPING = { "Line String": "django.contrib.gis.db.models.fields.LineStringField", "Linestring": "django.contrib.gis.db.models.fields.LineStringField", + "LineString": "django.contrib.gis.db.models.fields.LineStringField", "3D Line String": "django.contrib.gis.db.models.fields.LineStringField", "Multi Line String": "django.contrib.gis.db.models.fields.MultiLineStringField", "Multilinestring": "django.contrib.gis.db.models.fields.MultiLineStringField", From 344c480c32150fa5ac1079719fd51d424a768d56 Mon Sep 17 00:00:00 2001 From: Niraj Adhikari Date: Mon, 17 Nov 2025 11:50:16 +0545 Subject: [PATCH 05/54] [Fixes #13726] Removed static prefix from MAPSTORE_EXTENSIONS_FOLDER_PATH value in settings --- geonode/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geonode/settings.py b/geonode/settings.py index 0c988cd7142..c401eac0231 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -1618,7 +1618,7 @@ def get_geonode_catalogue_service(): MAPSTORE_PLUGINS_CONFIG_PATCH_RULES = [] # Extensions path to use in importing custom extensions into geonode - MAPSTORE_EXTENSIONS_FOLDER_PATH = "/static/mapstore/extensions/" + MAPSTORE_EXTENSIONS_FOLDER_PATH = "/mapstore/extensions/" # Supported Dataset file types for uploading Datasets. This setting is being from from the client From c7be7a0c37c1741b9f9002bd9f866addd9b8a4c1 Mon Sep 17 00:00:00 2001 From: "G.Allegri" Date: Mon, 17 Nov 2025 16:12:54 +0100 Subject: [PATCH 06/54] Removed DOCKER_API_VERSION (cherry picked from commit 281e98464b0d90d0f802d1e8153b02f79a7fb11b) --- .devcontainer/.env | 3 --- .env.sample | 3 --- .env_dev | 3 --- .env_local | 3 --- .env_test | 3 --- entrypoint.sh | 1 - 6 files changed, 16 deletions(-) diff --git a/.devcontainer/.env b/.devcontainer/.env index 56a59c2138e..4d4e75899e3 100644 --- a/.devcontainer/.env +++ b/.devcontainer/.env @@ -1,9 +1,6 @@ COMPOSE_PROJECT_NAME=geonode DOCKER_HOST_IP= DOCKER_ENV=production -# See https://github.com/geosolutions-it/geonode-generic/issues/28 -# to see why we force API version to 1.24 -DOCKER_API_VERSION="1.24" BACKUPS_VOLUME_DRIVER=local C_FORCE_ROOT=1 diff --git a/.env.sample b/.env.sample index 579a8a782fe..f6496e369f8 100644 --- a/.env.sample +++ b/.env.sample @@ -2,9 +2,6 @@ COMPOSE_PROJECT_NAME=geonode # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production -# See https://github.com/geosolutions-it/geonode-generic/issues/28 -# to see why we force API version to 1.24 -DOCKER_API_VERSION="1.24" BACKUPS_VOLUME_DRIVER=local C_FORCE_ROOT=1 diff --git a/.env_dev b/.env_dev index 8b012d82e4b..791ef250456 100644 --- a/.env_dev +++ b/.env_dev @@ -2,9 +2,6 @@ COMPOSE_PROJECT_NAME=geonode # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production -# See https://github.com/geosolutions-it/geonode-generic/issues/28 -# to see why we force API version to 1.24 -DOCKER_API_VERSION="1.24" BACKUPS_VOLUME_DRIVER=local C_FORCE_ROOT=1 diff --git a/.env_local b/.env_local index b3a2fcac0e4..7cd1839a09d 100644 --- a/.env_local +++ b/.env_local @@ -2,9 +2,6 @@ COMPOSE_PROJECT_NAME=geonode # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production -# See https://github.com/geosolutions-it/geonode-generic/issues/28 -# to see why we force API version to 1.24 -DOCKER_API_VERSION="1.24" BACKUPS_VOLUME_DRIVER=local C_FORCE_ROOT=1 diff --git a/.env_test b/.env_test index 5e270acd875..8bb590f0fd5 100644 --- a/.env_test +++ b/.env_test @@ -2,9 +2,6 @@ COMPOSE_PROJECT_NAME=geonode # See https://github.com/containers/podman/issues/13889 # DOCKER_BUILDKIT=0 DOCKER_ENV=production -# See https://github.com/geosolutions-it/geonode-generic/issues/28 -# to see why we force API version to 1.24 -DOCKER_API_VERSION="1.24" BACKUPS_VOLUME_DRIVER=local C_FORCE_ROOT=1 diff --git a/entrypoint.sh b/entrypoint.sh index e149adc3d93..ed9469efac2 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -27,7 +27,6 @@ invoke update source $HOME/.bashrc source $HOME/.override_env -echo DOCKER_API_VERSION=$DOCKER_API_VERSION echo POSTGRES_USER=$POSTGRES_USER echo POSTGRES_PASSWORD=$POSTGRES_PASSWORD echo DATABASE_URL=$DATABASE_URL From 08342fa1582ec94c81a6af2f27909b4e002476b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:12:10 +0100 Subject: [PATCH 07/54] Fix GDAL version also in requirements (#13743) (cherry picked from commit 36654b9c5efbf341d7d36e409469f584dace7b04) Co-authored-by: Mattia --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 21bf6096d78..423ba2a3573 100644 --- a/requirements.txt +++ b/requirements.txt @@ -121,7 +121,7 @@ requests==2.32.5 timeout-decorator==0.5.0 pylibmc==1.6.3 sherlock==0.4.1 - +GDAL==3.8.4 psutil==5.9.8 django-cors-headers==4.9.0 django-user-agents==0.4.0 diff --git a/setup.cfg b/setup.cfg index 1b273406b02..2fc4c93a019 100644 --- a/setup.cfg +++ b/setup.cfg @@ -144,7 +144,7 @@ install_requires = timeout-decorator==0.5.0 pylibmc==1.6.3 sherlock==0.4.1 - + GDAL==3.8.4 # required by monitoring psutil==5.9.8 django-cors-headers==4.9.0 From 175c244643ef5b552ccc7cf8509e8210d3a340c2 Mon Sep 17 00:00:00 2001 From: mattiagiupponi <51856725+mattiagiupponi@users.noreply.github.com> Date: Tue, 18 Nov 2025 06:57:26 -0500 Subject: [PATCH 08/54] [Fixes #13719] Upsert / replace workflow bugfixes (#13720) * [Fixes #13719] Upsert with authority different that 4326 raise error * [Fixes #13719] fix field creation on dynamic model for upsert/replace * [Fixes #13719] Cannot upsert a dataset after replace * [Fixes #13719] Make all geom to be promoted to multi (cherry picked from commit e817494f6c61b05561a0fc00dd7bc7da2934a731) --- geonode/upload/celery_tasks.py | 32 +++-- geonode/upload/handlers/common/raster.py | 5 + .../upload/handlers/common/tests_vector.py | 1 - geonode/upload/handlers/common/vector.py | 135 +++++++++++++----- geonode/upload/handlers/shapefile/handler.py | 7 +- geonode/upload/handlers/utils.py | 2 +- 6 files changed, 121 insertions(+), 61 deletions(-) diff --git a/geonode/upload/celery_tasks.py b/geonode/upload/celery_tasks.py index ac7fc7970ed..4bf50555d8d 100644 --- a/geonode/upload/celery_tasks.py +++ b/geonode/upload/celery_tasks.py @@ -533,6 +533,9 @@ def create_geonode_resource( else: handler.create_resourcehandlerinfo(handler_module_path, resource, _exec, **kwargs) + if _overwrite and handler.have_table: + handler.fixup_dynamic_model_fields(_exec, _files) + # at the end recall the import_orchestrator for the next step import_orchestrator.apply_async( ( @@ -714,6 +717,9 @@ def _create_field(dynamic_model_schema, field, _kwargs): dynamic_model_schema = dynamic_model_schema.first() + # clearing existing fields for this chunk + FieldSchema.objects.filter(model_schema=dynamic_model_schema, name__in=(x["name"] for x in fields)).delete() + row_to_insert = [] for field in fields: # setup kwargs for the class provided @@ -733,20 +739,13 @@ def _create_field(dynamic_model_schema, field, _kwargs): # setting the dimension for the gemetry. So that we can handle also 3d geometries _kwargs = {**_kwargs, **{"dim": field.get("dim")}} + if authority := field.get("authority"): + srid_str = authority.split(":")[-1] + if srid_str.isdigit(): + _kwargs["srid"] = int(srid_str) + # if is a new creation we generate the field model from scratch - if not overwrite: - row_to_insert.append(_create_field(dynamic_model_schema, field, _kwargs)) - else: - # otherwise if is an overwrite, we update the existing one and create the one that does not exists - _field_exists = FieldSchema.objects.filter(name=field["name"], model_schema=dynamic_model_schema) - if _field_exists.exists(): - _field_exists.update( - class_name=field["class_name"], - model_schema=dynamic_model_schema, - kwargs=_kwargs, - ) - else: - row_to_insert.append(_create_field(dynamic_model_schema, field, _kwargs)) + row_to_insert.append(_create_field(dynamic_model_schema, field, _kwargs)) if row_to_insert: if dynamic_model_schema.managed: @@ -765,7 +764,12 @@ def _create_field(dynamic_model_schema, field, _kwargs): field.save() else: # the build creation improves the overall permformance with the DB - FieldSchema.objects.bulk_create(row_to_insert, 30) + FieldSchema.objects.bulk_create( + row_to_insert, + update_conflicts=True, + update_fields=["name", "model_schema_id", "class_name", "kwargs"], + unique_fields=["id"], + ) # fixing the schema model in django return "dynamic_model", layer_name, execution_id diff --git a/geonode/upload/handlers/common/raster.py b/geonode/upload/handlers/common/raster.py index 7c36ae47592..ce63310f7f1 100644 --- a/geonode/upload/handlers/common/raster.py +++ b/geonode/upload/handlers/common/raster.py @@ -569,6 +569,11 @@ def _publish_resource_rollback(self, exec_id, instance_name=None, *args, **kwarg publisher = DataPublisher(handler_module_path=handler_module_path) publisher.delete_resource(instance_name) + def fixup_dynamic_model_fields(self, _exec, files): + """ + Raster dataset does not have the dynamic model, so this can be skept + """ + @importer_app.task( base=UpdateTaskClass, diff --git a/geonode/upload/handlers/common/tests_vector.py b/geonode/upload/handlers/common/tests_vector.py index bf6209439cd..5151b1f717d 100644 --- a/geonode/upload/handlers/common/tests_vector.py +++ b/geonode/upload/handlers/common/tests_vector.py @@ -643,7 +643,6 @@ def test_validate_single_feature_raise_error(self): with self.assertRaises(Exception) as exp: self.json_handler.upsert_data(self.original, exec_id) - self.assertEqual( str(exp.exception), "An internal error occurred during upsert save. All features are rolled back." ) diff --git a/geonode/upload/handlers/common/vector.py b/geonode/upload/handlers/common/vector.py index ff8eb9efcef..591cb228a06 100644 --- a/geonode/upload/handlers/common/vector.py +++ b/geonode/upload/handlers/common/vector.py @@ -18,7 +18,7 @@ ######################################################################### import ast import csv - +import re from datetime import datetime from itertools import islice from pathlib import Path @@ -258,6 +258,8 @@ def create_ogr2ogr_command(files, original_name, ovverwrite_layer, alternate, ** This is a default command that is needed to import a vector file """ _datastore = settings.DATABASES["datastore"] + layers = ogr.Open(files.get("base_file")) + layer = layers.GetLayer(original_name) options = "--config PG_USE_COPY YES" copy_with_dump = ast.literal_eval(os.getenv("OGR2OGR_COPY_WITH_DUMP", "False")) @@ -281,6 +283,9 @@ def create_ogr2ogr_command(files, original_name, ovverwrite_layer, alternate, ** options += f'-nln {alternate} "{original_name}"' + if layer is not None and "Point" not in ogr.GeometryTypeToName(layer.GetGeomType()): + options += " -nlt PROMOTE_TO_MULTI" + if ovverwrite_layer: options += " -overwrite" @@ -734,12 +739,15 @@ def _define_dynamic_layer_schema(self, layer, **kwargs): return layer_schema - def promote_to_multi(self, geometry_name: str): + def promote_to_multi(self, geometry_name): """ If needed change the name of the geometry, by promoting it to Multi example if is Point -> MultiPoint Needed for the shapefiles + Later this is used to map the geometry coming from ogr2ogr with a django class """ + if "Multi" not in geometry_name and "Point" not in geometry_name and "3D" not in geometry_name: + return f"Multi {geometry_name.title()}" return geometry_name def promote_geom_to_multi(self, geom): @@ -748,7 +756,21 @@ def promote_geom_to_multi(self, geom): example if is Point -> MultiPoint Needed for the shapefiles """ - return geom + match geom.GetGeometryType(): + case ogr.wkbMultiLineString | ogr.wkbMultiPolygon: + # if is already multi, we dont need to do anything + return geom + case ogr.wkbLineString: + new_multi_geom = ogr.Geometry(ogr.wkbMultiLineString) + new_multi_geom.AddGeometry(geom) + return new_multi_geom + case ogr.wkbPolygon: + new_multi_geom = ogr.Geometry(ogr.wkbMultiPolygon) + new_multi_geom.AddGeometry(geom) + return new_multi_geom + case _: + # we dont convert points + return geom def create_geonode_resource( self, @@ -796,37 +818,7 @@ def create_geonode_resource( saved_dataset.refresh_from_db() - # if dynamic model is enabled, we can save up with is the primary key of the table - if settings.IMPORTER_ENABLE_DYN_MODELS and self.have_table: - from django.db import connections - - # then we can check for the PK - column = None - connection = connections["datastore"] - table_name = saved_dataset.alternate.split(":")[1] - - schema = ModelSchema.objects.filter(name=table_name).first() - schema.managed = False - schema.save() - - with connection.cursor() as cursor: - column = connection.introspection.get_primary_key_columns(cursor, table_name) - if column: - # getting the relative model schema - # better to always ensure that the schema is NOT managed - field = FieldSchema.objects.filter(name=column[0], model_schema__name=table_name).first() - if field: - field.kwargs.update({"primary_key": True}) - field.save() - else: - # creating the field needed as primary key - pk_field = FieldSchema( - name=column[0], - model_schema=schema, - class_name="django.db.models.BigAutoField", - kwargs={"null": False, "primary_key": True}, - ) - pk_field.save() + self.__fixup_primary_key(saved_dataset) return saved_dataset @@ -1159,6 +1151,40 @@ def __get_new_and_original_schema(self, files, execution_id): return target_schema_fields, new_file_schema_fields + def __fixup_primary_key(self, saved_dataset): + + # if dynamic model is enabled, we can save up with is the primary key of the table + if settings.IMPORTER_ENABLE_DYN_MODELS and self.have_table: + from django.db import connections + + # then we can check for the PK + column = None + connection = connections["datastore"] + table_name = saved_dataset.alternate.split(":")[1] + + schema = ModelSchema.objects.filter(name=table_name).first() + schema.managed = False + schema.save() + + with connection.cursor() as cursor: + column = connection.introspection.get_primary_key_columns(cursor, table_name) + if column: + # getting the relative model schema + # better to always ensure that the schema is NOT managed + field = FieldSchema.objects.filter(name=column[0], model_schema__name=table_name).first() + if field: + field.kwargs.update({"primary_key": True}) + field.save() + else: + # creating the field needed as primary key + pk_field = FieldSchema( + name=column[0], + model_schema=schema, + class_name="django.db.models.BigAutoField", + kwargs={"null": False, "primary_key": True}, + ) + pk_field.save() + def upsert_data(self, files, execution_id, **kwargs): """ Function used to upsert the data for a vector resource. @@ -1175,9 +1201,7 @@ def upsert_data(self, files, execution_id, **kwargs): exec_obj = orchestrator.get_execution_object(execution_id) # getting the related model schema for the resource - original_resource = ResourceBase.objects.filter(pk=exec_obj.input_params.get("resource_pk")).first() - self.real_instance = original_resource.get_real_instance() - model = ModelSchema.objects.filter(name=original_resource.alternate.split(":")[-1]).first() + original_resource, model = self.___get_dynamic_schema(exec_obj) if not model: raise UpsertException( "This dataset does't support updates. Please upload the dataset again to have the upsert operations enabled" @@ -1218,6 +1242,12 @@ def upsert_data(self, files, execution_id, **kwargs): "layer_name": original_resource.title, } + def ___get_dynamic_schema(self, exec_obj): + original_resource = ResourceBase.objects.filter(pk=exec_obj.input_params.get("resource_pk")).first() + self.real_instance = original_resource.get_real_instance() + model = ModelSchema.objects.filter(name=original_resource.alternate.split(":")[-1]).first() + return original_resource, model + def _commit_upsert(self, model_obj, OriginalResource, upsert_key, layer_iterator): valid_create = 0 valid_update = 0 @@ -1310,7 +1340,11 @@ def _validate_feature(self, data_chunk, model_instance, upsert_key, errors): # need to simulate the "promote to multi" used by the upload process. # here we cannot rely on ogr2ogr so we need to do it manually geom = feature.GetGeometryRef() - feature_as_dict.update({self.default_geometry_column_name: self.promote_geom_to_multi(geom).ExportToWkt()}) + if geom: + wkt = self.promote_geom_to_multi(geom).ExportToWkt() + if code := geom.GetSpatialReference().GetAuthorityCode(None): + wkt = f"SRID={code};{wkt}" + feature_as_dict.update({self.default_geometry_column_name: wkt}) feature_as_dict, is_valid = self.validate_feature(feature_as_dict) if not is_valid: @@ -1330,10 +1364,23 @@ def _save_feature(self, data_chunk, model_obj, model_instance, upsert_key, valid feature_to_save = [] for feature in data_chunk: feature_as_dict = feature.items() + # evaluate if there is any date in the schema of the feature + schema = feature.DumpReadableAsString().split("\n") + if any(date_fields := [f for f in schema if ("(Date)" in f or "(DateTime)" in f) and "(null)" not in f]): + # if any field schema as date is found, we can normalize the date + pattern = re.compile(r"^\s*(?P