diff --git a/apis_core/core/management/commands/apisdumpdata.py b/apis_core/core/management/commands/apisdumpdata.py new file mode 100644 index 000000000..89e5999b1 --- /dev/null +++ b/apis_core/core/management/commands/apisdumpdata.py @@ -0,0 +1,18 @@ +from django.core.management.base import BaseCommand + +from apis_core.utils.helpers import datadump_serializer + + +class Command(BaseCommand): + help = "Dump APIS data" + + def add_arguments(self, parser): + parser.add_argument( + "args", + metavar="app_labels", + nargs="*", + help=("Optional additional app_labels."), + ) + + def handle(self, *app_labels, **options): + print(datadump_serializer(app_labels, "json")) diff --git a/apis_core/core/views.py b/apis_core/core/views.py new file mode 100644 index 000000000..8a5d4a0b7 --- /dev/null +++ b/apis_core/core/views.py @@ -0,0 +1,24 @@ +import json + +from rest_framework.views import APIView +from rest_framework.response import Response + +from apis_core.utils.helpers import datadump_serializer + + +class Dumpdata(APIView): + """ + provide an API endpoint that outputs the datadump of an APIS installation + + this is a bit of a hack, becaus we first use the Django JSON serializer to + serialize the data using natural keys, then we use json.loads to so we can + output it as an API reponse. + so basically: serialize -> deserialize -> serialize + """ + + def get(self, request, *args, **kwargs): + params = request.query_params.dict() + app_labels = params.pop("app_labels", []) + if app_labels: + app_labels = app_labels.split(",") + return Response(json.loads(datadump_serializer(app_labels, "json"))) diff --git a/apis_core/urls.py b/apis_core/urls.py index 65fc62874..857500ac4 100644 --- a/apis_core/urls.py +++ b/apis_core/urls.py @@ -16,6 +16,7 @@ # from apis_core.apis_vocabularies.api_views import UserViewSet from apis_core.utils import caching from apis_core.apis_metainfo.viewsets import UriToObjectViewSet +from apis_core.core.views import Dumpdata app_name = "apis_core" @@ -103,7 +104,6 @@ def build_apis_mock_request(method, path, view, original_request, **kwargs): from apis_core.apis_entities.api_views import GetEntityGeneric - urlpatterns = [ path("", TemplateView.as_view(template_name="base.html"), name="apis_index"), path("admin/", admin.site.urls), @@ -168,6 +168,7 @@ def build_apis_mock_request(method, path, view, original_request, **kwargs): GetEntityGeneric.as_view(), name="GetEntityGeneric", ), + path("api/dumpdata", Dumpdata.as_view()), ] if "apis_fulltext_download" in settings.INSTALLED_APPS: diff --git a/apis_core/utils/helpers.py b/apis_core/utils/helpers.py index 8082b2e06..c77557642 100644 --- a/apis_core/utils/helpers.py +++ b/apis_core/utils/helpers.py @@ -9,7 +9,10 @@ from apis_core.apis_entities.models import TempEntityClass from apis_core.apis_relations.models import Property +from django.apps import apps +from django.db import DEFAULT_DB_ALIAS, router from django.contrib.contenttypes.models import ContentType +from django.core import serializers @functools.lru_cache @@ -77,3 +80,57 @@ def get_member_for_entity( logging.debug("Found %s.%s", module, model) return members[0][1] return None + + +def datadump_get_objects(models: list = [], *args, **kwargs): + for model in models: + if not model._meta.proxy and router.allow_migrate_model( + DEFAULT_DB_ALIAS, model + ): + objects = model._default_manager + queryset = objects.using(DEFAULT_DB_ALIAS).order_by(model._meta.pk.name) + yield from queryset.iterator() + + +def datadump_get_queryset(additional_app_labels: list = []): + """ + This method is loosely based on the `dumpdata` admin command. + It iterates throug the relevant app models and exports them using + a serializer and natural foreign keys. + Data exported this way can be reimported into a newly created Django APIS app + """ + + # get all APIS apps and all APIS models + apis_app_labels = ["apis_relations", "apis_metainfo"] + apis_app_models = [ + model for model in apps.get_models() if model._meta.app_label in apis_app_labels + ] + + # create a list of app labels we want to iterate + # this allows to extend the apps via the ?app_labels= parameter + app_labels = set(apis_app_labels) + app_labels |= set(additional_app_labels) + + # look for models that inherit from APIS models and add their + # app label to app_labels + for model in apps.get_models(): + if any(map(lambda x: issubclass(model, x), apis_app_models)): + app_labels.add(model._meta.app_label) + + # now go through all app labels + app_list = {} + for app_label in app_labels: + app_config = apps.get_app_config(app_label) + app_list[app_config] = None + + models = serializers.sort_dependencies(app_list.items(), allow_cycles=True) + + yield from datadump_get_objects(models) + + +def datadump_serializer(additional_app_labels: list = [], serialier_format="json"): + return serializers.serialize( + serialier_format, + datadump_get_queryset(additional_app_labels), + use_natural_foreign_keys=True, + )