Skip to content

Commit

Permalink
feat: implement dumpdata view and admin command
Browse files Browse the repository at this point in the history
This feature provides ways to serialize the data of an APIS instance to
be able to import it in a freshly installed instance later on.

The main logic is implemented in the `utils.helper.datadump_serializer`
method. It serializes the important models to json - with natural
foreign keys.

There is both a view ('/api/dumpdata') and an admin command
('apisdumpdata') that output the serialized data as API Response or to
stdout.

Closes: #273
  • Loading branch information
b1rger committed Nov 30, 2023
1 parent c1881de commit 0db11e0
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 1 deletion.
18 changes: 18 additions & 0 deletions apis_core/core/management/commands/apisdumpdata.py
Original file line number Diff line number Diff line change
@@ -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"))
23 changes: 23 additions & 0 deletions apis_core/core/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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")))
3 changes: 2 additions & 1 deletion apis_core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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:
Expand Down
55 changes: 55 additions & 0 deletions apis_core/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -77,3 +80,55 @@ 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
)

0 comments on commit 0db11e0

Please sign in to comment.