Skip to content
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions docs/admin/objecttype.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,31 @@ if you click on the "history" button in the top right corner of the object type
:alt: Show the history of changes.

You can see all the versions, their statuses, the creation dates and the related JSON shemas.


Export and import object types
------------------------------

To export object types including their history, select the desired object types in the Objecttypes changelist
dashboard, then use the "Export selected objecttypes as a file" action from the Action dropdown menu and click
"Go". This will download an archive containing the selected object types with all versions.

To import object types, navigate to the Objecttypes changelist and select the "Import from file" action.
Upload the archive previously exported, optionally keeping the original UUIDs by checking the
corresponding checkbox. After successful import, you will see a confirmation message.

.. image:: _assets/img/objecttype_export_import.png
:alt: Export and import


.. note::
If UUIDs are kept during import, existing object types with matching UUIDs should **not** exist.
You cannot overwrite existing objects types. If no objects exist for the types, you should be able to
delete the object type and proceed with importing.

If UUIDs are not kept, new UUIDs will be generated and existing object types with the same name
will not be overwritten.

.. note::
When importing multiple object types, either the whole import succeeds, or the whole import fails.
There are no partial imports.
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ asgiref==3.11.0
# django-structlog
asn1crypto==1.5.1
# via webauthn
attrs==20.3.0
attrs==25.4.0
# via
# glom
# jsonschema
Expand Down
19 changes: 18 additions & 1 deletion requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ asn1crypto==1.5.1
# -c requirements/base.txt
# -r requirements/base.txt
# webauthn
attrs==20.3.0
attrs==25.4.0
# via
# -c requirements/base.txt
# -r requirements/base.txt
# glom
# jsonschema
# referencing
babel==2.16.0
# via sphinx
beautifulsoup4==4.9.3
Expand Down Expand Up @@ -418,6 +419,12 @@ humanize==4.9.0
# -c requirements/base.txt
# -r requirements/base.txt
# flower
hypothesis==6.151.9
# via
# -r requirements/test-tools.in
# hypothesis-jsonschema
hypothesis-jsonschema==0.22.1
# via -r requirements/test-tools.in
idna==3.7
# via
# -c requirements/base.txt
Expand Down Expand Up @@ -455,6 +462,9 @@ jsonschema==4.17.3
# -c requirements/base.txt
# -r requirements/base.txt
# drf-spectacular
# hypothesis-jsonschema
jsonschema-specifications==2025.9.1
# via -r requirements/test-tools.in
kombu==5.5.4
# via
# -c requirements/base.txt
Expand Down Expand Up @@ -733,6 +743,8 @@ redis==6.4.0
# -c requirements/base.txt
# -r requirements/base.txt
# django-redis
referencing==0.37.0
# via jsonschema-specifications
requests==2.32.4
# via
# -c requirements/base.txt
Expand All @@ -755,6 +767,8 @@ requests-mock==1.12.1
# -r requirements/test-tools.in
roman-numerals==4.1.0
# via sphinx
rpds-py==0.30.0
# via referencing
ruamel-yaml==0.18.10
# via
# -c requirements/base.txt
Expand Down Expand Up @@ -790,6 +804,8 @@ six==1.16.0
# webtest
snowballstemmer==2.2.0
# via sphinx
sortedcontainers==2.4.0
# via hypothesis
soupsieve==2.2.1
# via beautifulsoup4
sphinx==9.1.0
Expand Down Expand Up @@ -853,6 +869,7 @@ typing-extensions==4.9.0
# pydantic
# pydantic-core
# pyopenssl
# referencing
# zgw-consumers
tzdata==2025.2
# via
Expand Down
33 changes: 32 additions & 1 deletion requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ asn1crypto==1.5.1
# -c requirements/ci.txt
# -r requirements/ci.txt
# webauthn
attrs==20.3.0
attrs==25.4.0
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
# glom
# jsonschema
# referencing
autopep8==2.3.2
# via django-silk
babel==2.16.0
Expand Down Expand Up @@ -482,6 +483,15 @@ humanize==4.9.0
# -c requirements/ci.txt
# -r requirements/ci.txt
# flower
hypothesis==6.151.9
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
# hypothesis-jsonschema
hypothesis-jsonschema==0.22.1
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
identify==2.6.10
# via pre-commit
idna==3.7
Expand Down Expand Up @@ -532,6 +542,11 @@ jsonschema==4.17.3
# -c requirements/ci.txt
# -r requirements/ci.txt
# drf-spectacular
# hypothesis-jsonschema
jsonschema-specifications==2025.9.1
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
kombu==5.5.4
# via
# -c requirements/ci.txt
Expand Down Expand Up @@ -865,6 +880,11 @@ redis==6.4.0
# -c requirements/ci.txt
# -r requirements/ci.txt
# django-redis
referencing==0.37.0
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
# jsonschema-specifications
requests==2.32.4
# via
# -c requirements/ci.txt
Expand Down Expand Up @@ -895,6 +915,11 @@ roman-numerals==4.1.0
# -c requirements/ci.txt
# -r requirements/ci.txt
# sphinx
rpds-py==0.30.0
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
# referencing
ruamel-yaml==0.18.10
# via
# -c requirements/ci.txt
Expand Down Expand Up @@ -939,6 +964,11 @@ snowballstemmer==2.2.0
# -c requirements/ci.txt
# -r requirements/ci.txt
# sphinx
sortedcontainers==2.4.0
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
# hypothesis
soupsieve==2.2.1
# via
# -c requirements/ci.txt
Expand Down Expand Up @@ -1041,6 +1071,7 @@ typing-extensions==4.9.0
# pydantic
# pydantic-core
# pyopenssl
# referencing
# rich-click
# zgw-consumers
tzdata==2025.2
Expand Down
3 changes: 3 additions & 0 deletions requirements/test-tools.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pyquery # integrates with webtest
requests-mock
tblib
vcrpy
hypothesis
hypothesis-jsonschema
jsonschema-specifications

# Code formatting
ruff
2 changes: 1 addition & 1 deletion src/objects/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class LabelsField(serializers.JSONField):
pass


class ObjectTypeSerializer(serializers.HyperlinkedModelSerializer):
class ObjectTypeSerializer(serializers.HyperlinkedModelSerializer[ObjectType]):
labels = LabelsField(
required=False,
help_text=get_help_text("core.ObjectType", "labels"),
Expand Down
66 changes: 61 additions & 5 deletions src/objects/core/admin.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from __future__ import annotations

import io
import json
from functools import partial
from typing import Sequence

from django import forms
from django.conf import settings
from django.contrib import admin, messages
from django.contrib.gis.db.models import GeometryField
from django.db import models
from django.http import HttpRequest, HttpResponseRedirect
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, render
from django.urls import path, reverse
from django.utils.html import format_html
Expand All @@ -19,7 +23,8 @@
from objects.api.v2.filters import filter_queryset_by_data_attr

from .constants import ObjectTypeVersionStatus
from .forms import ObjectTypeVersionForm, UrlImportForm
from .forms import FileImportForm, ObjectTypeVersionForm, UrlImportForm
from .import_export import export_data, import_upload
from .models import Object, ObjectRecord, ObjectType, ObjectTypeVersion
from .widgets import JSONSuit

Expand Down Expand Up @@ -109,6 +114,8 @@ class ObjectTypeAdmin(admin.ModelAdmin):

change_list_template = "admin/core/objecttype/object_list.html"

actions = ["export_objecttypes_action"]

def get_urls(self):
urls = super().get_urls()
my_urls = [
Expand All @@ -117,6 +124,11 @@ def get_urls(self):
self.admin_site.admin_view(self.import_from_url_view),
name="import_from_url",
),
path(
"import-from-file/",
self.admin_site.admin_view(self.import_from_file_view),
name="import_from_file",
),
]
return my_urls + urls

Expand All @@ -141,10 +153,11 @@ def publish(self, request, obj):

return HttpResponseRedirect(request.path)

def add_new_version(self, request, obj):
def add_new_version(self, request, obj: ObjectType):
new_version = obj.last_version
assert new_version
new_version.pk = None
new_version.version = new_version.version + 1
new_version.version = None
new_version.status = ObjectTypeVersionStatus.draft
new_version.save()

Expand Down Expand Up @@ -183,10 +196,53 @@ def import_from_url_view(self, request):
request, "admin/core/objecttype/object_import_form.html", {"form": form}
)

def import_from_file_view(self, request: HttpRequest) -> HttpResponse:
if request.method == "POST":
form = FileImportForm(request.POST, files=request.FILES)
if form.is_valid():
imported_types = import_upload(
form.files["export_file"],
form.cleaned_data["keep_uuid"],
partial(form.add_error, "export_file"),
)
if not form.errors and not imported_types:
form.add_error(
"export_file", _("Found nothing importable in that file")
)
if not form.errors:
self.message_user(
request,
_("{resource_types} imported successfully.").format(
resource_types=",".join(imported_types)
),
)
return redirect(reverse("admin:core_objecttype_changelist"))
else:
form = FileImportForm()

return render(
request,
"admin/core/objecttype/object_import_form.html",
{"form": form, "source": "file"},
)

@admin.action(description=_("Export selected objecttypes as a file"))
def export_objecttypes_action(
self, request: HttpRequest, queryset: models.QuerySet[ObjectType]
) -> HttpResponse:
output = io.BytesIO()
export_data(output, objecttypes=queryset)

response = HttpResponse(output.getvalue(), content_type="application/zip")
response["Content-Disposition"] = (
'attachment; filename="objecttypes-export.zip"'
)
return response


class ObjectRecordForm(forms.ModelForm):
class Meta:
model: ObjectRecord
model = ObjectRecord
help_texts = {
"geometry": get_help_text("core.ObjectRecord", "geometry")
+ "\n\n format: SRID=4326;POINT|LINESTRING|POLYGON (LAT LONG, ...)"
Expand Down
14 changes: 14 additions & 0 deletions src/objects/core/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ def clean_objecttype_url(self):
self.cleaned_data["json"] = response_json


class FileImportForm(forms.Form):
export_file = forms.FileField(
label=_("Object type export file"),
required=True,
help_text=_("The file exported with the Export option from the Action menu."),
)
keep_uuid = forms.BooleanField(
label=_("Keep the UUIDs the same"),
help_text=_("Import keeping the same UUIDs as in the export."),
initial=False,
required=False,
)


class ObjectTypeVersionForm(forms.ModelForm):
class Meta:
model = ObjectTypeVersion
Expand Down
Loading
Loading