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 ('/apisdumpdata.json') and an admin command
('apisdumpdata') that output the serialized data as HttpResponse or to
stdout.

Closes: #273
  • Loading branch information
b1rger committed Nov 20, 2023
1 parent d560fe8 commit 954bb6c
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 0 deletions.
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"))
19 changes: 19 additions & 0 deletions apis_core/core/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.http import HttpResponse
from django.views import View
from django.contrib.auth.mixins import UserPassesTestMixin

from apis_core.utils.helpers import datadump_serializer


class Dumpdata(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.is_staff

def get(self, request, *args, **kwargs):
app_labels = request.GET.get("app_labels", [])
data = datadump_serializer(app_labels, "json")

headers = {
"Content-Type": "application/json",
}
return HttpResponse(headers=headers, content=data)
3 changes: 3 additions & 0 deletions apis_core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ def build_apis_mock_request(method, path, view, original_request, **kwargs):
return request


from apis_core.core.views import Dumpdata

urlpatterns = [
path("", TemplateView.as_view(template_name="base.html"), name="apis_index"),
path("admin/", admin.site.urls),
Expand Down Expand Up @@ -160,6 +162,7 @@ def build_apis_mock_request(method, path, view, original_request, **kwargs):
),
# url(r'^docs/', include('sphinxdoc.urls')),
# url(r'^accounts/', include('registration.backends.simple.urls')),
path("apisdumpdata.json", Dumpdata.as_view()),
]

if "apis_fulltext_download" in settings.INSTALLED_APPS:
Expand Down
54 changes: 54 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,54 @@ 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_serializer(additional_app_labels: list = [], serialier_format="json"):
"""
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)

return serializers.serialize(
serialier_format,
datadump_get_objects(models),
use_natural_foreign_keys=True)

0 comments on commit 954bb6c

Please sign in to comment.