-
Notifications
You must be signed in to change notification settings - Fork 17
Add possibility to load and dump data with superuser access #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a05cbb6
f9bd73d
3a52f95
522014f
0083f1f
b1d74cd
562a552
c28ae31
ba436f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
|
|
||
| ### Added | ||
|
|
||
| - nothing added | ||
| - Add possibility to load and dump data with superuser access | ||
|
|
||
| ### Changed | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,13 @@ | ||
| # coding: utf-8 | ||
| from django.urls import re_path | ||
| from django.conf import settings | ||
|
|
||
| from concrete_datastore.concrete.views import email_confirmation_view | ||
| from concrete_datastore.concrete.views import unsubscribe_notifications_view | ||
| from concrete_datastore.concrete.views import ( | ||
| unsubscribe_notifications_result_view, | ||
| dump_data, | ||
| load_data, | ||
| ) | ||
|
|
||
| app_name = 'concrete_datastore.concrete' | ||
|
|
@@ -26,3 +29,8 @@ | |
| name='unsubscribe_notifications_result', | ||
| ), | ||
| ] | ||
|
|
||
| if settings.ENABLE_DATABASE_DUMP: | ||
| urlpatterns.append(re_path(r'dump-data', dump_data, name="dump_data")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. url names are not dash cased ?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both ways are used in the project.
Since the code added is in the module Should we homogenize the names of all the urls into dash-cased names ? |
||
| if settings.ENABLE_DATABASE_LOAD: | ||
| urlpatterns.append(re_path(r'load-data', load_data, name="load_data")) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,19 @@ | ||
| # coding: utf-8 | ||
| import json | ||
| import logging | ||
|
|
||
| import sys | ||
| import os | ||
| from io import StringIO | ||
| from tempfile import NamedTemporaryFile | ||
| from django.utils import timezone | ||
| from django.core.management import call_command | ||
| from django.conf import settings | ||
| from django.http import HttpResponse | ||
| from django.http import ( | ||
| HttpResponse, | ||
| JsonResponse, | ||
| HttpResponseForbidden, | ||
| StreamingHttpResponse, | ||
| ) | ||
| from django.shortcuts import get_object_or_404, render, redirect | ||
| from django.contrib.auth import get_user_model | ||
| from concrete_datastore.concrete.models import UserConfirmation, DIVIDER_MODEL | ||
|
|
@@ -105,3 +115,83 @@ def unsubscribe_notifications_result_view(request, token): | |
| 'platform_name': settings.PLATFORM_NAME, | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| class RedirectStdStreams: | ||
| def __init__(self, stdout=sys.stdout, stderr=sys.stderr): | ||
| self._stdout = stdout | ||
| self._stderr = stderr | ||
|
|
||
| def __enter__(self): | ||
| self.old_stdout, self.old_stderr = sys.stdout, sys.stderr | ||
| self.old_stdout.flush() | ||
| self.old_stderr.flush() | ||
| sys.stdout, sys.stderr = self._stdout, self._stderr | ||
|
|
||
| def __exit__(self, exc_type, exc_value, traceback): | ||
| self._stdout.flush() | ||
| self._stderr.flush() | ||
| sys.stdout = self.old_stdout | ||
| sys.stderr = self.old_stderr | ||
|
|
||
|
|
||
| def dump_data(request): | ||
| if request.user.is_anonymous is True or request.user.is_superuser is False: | ||
| return HttpResponseForbidden() | ||
| resp = StringIO() | ||
| errors = StringIO() | ||
| try: | ||
| with RedirectStdStreams(stdout=resp, stderr=errors): | ||
| call_command('dumpdata', 'concrete') | ||
| resp_value = resp.getvalue() | ||
| errors_value = errors.getvalue() | ||
| if errors.getvalue(): | ||
| return JsonResponse(data={'error': errors_value}, status=400) | ||
| if request.GET.get('download', '').lower() == 'true': | ||
| #: Download the json file | ||
| now = timezone.now() | ||
| filename = 'dump_{}.json'.format(now.strftime("%Y-%m-%d_%H-%M")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be interesting to add instance name |
||
| response = StreamingHttpResponse(resp_value, content_type="json") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ensure resp_value is a valid json before streaming it |
||
| response[ | ||
| 'Content-Disposition' | ||
| ] = 'attachment; filename="{}"'.format(filename) | ||
| return response | ||
|
|
||
| json_resp = json.loads(resp_value) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ensure |
||
| return JsonResponse( | ||
| data={'result': json_resp, 'objects_count': len(json_resp)}, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| status=200, | ||
| ) | ||
| except Exception as e: | ||
| return JsonResponse(data={'error': str(e)}, status=400) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a log is required here |
||
|
|
||
|
|
||
| def load_data(request): | ||
| if request.user.is_anonymous is True or request.user.is_superuser is False: | ||
| return HttpResponseForbidden() | ||
|
|
||
| file = request.FILES.get('file') | ||
| if file is None: | ||
| return JsonResponse(data={'error': 'No file was given'}, status=400) | ||
| full_path = "" | ||
| fd = NamedTemporaryFile(suffix='.json', mode='w') | ||
| try: | ||
| full_path = os.path.join(os.getcwd(), fd.name) | ||
| json.dump(json.loads(file.read().decode('utf-8')), fd) | ||
| resp = StringIO() | ||
| errors = StringIO() | ||
| with RedirectStdStreams(stdout=resp, stderr=errors): | ||
| call_command('loaddata', full_path) | ||
| resp_value = resp.getvalue() | ||
| errors_value = errors.getvalue() | ||
| if errors_value: | ||
| response = JsonResponse(data={'error': errors_value}, status=400) | ||
| else: | ||
| response = JsonResponse(data={'message': resp_value}, status=200) | ||
| except Exception as e: | ||
| response = JsonResponse( | ||
| data={'error': f'An error has occured: {e}'}, status=400 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be homogeneous with line 166 |
||
| ) | ||
| finally: | ||
| fd.close() | ||
| return response | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| # coding: utf-8 | ||
| from importlib import import_module | ||
| import yaml | ||
| import json | ||
| import datetime | ||
| from django.conf import settings | ||
| from django.http import ( | ||
| HttpResponse, | ||
|
|
@@ -15,6 +15,7 @@ | |
| from rest_framework.views import APIView | ||
| from rest_framework import authentication | ||
| from rest_framework.renderers import OpenAPIRenderer | ||
| from concrete_datastore.api.v1.datetime import format_datetime | ||
| from concrete_datastore.api.v1.authentication import ( | ||
| TokenExpiryAuthentication, | ||
| URLTokenExpiryAuthentication, | ||
|
|
@@ -70,9 +71,20 @@ class DatamodelServer(APIView, TemplateView): | |
| ) | ||
|
|
||
| def _get_datamodel_format(self, data_format='yaml'): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move this to a 'datamodel' module |
||
| def default_dumper(value): | ||
| #: Default dumper for json because the DateTime and Date objects | ||
| #: are not json serializable | ||
| if type(value) == datetime.date: | ||
| return value.isoformat() | ||
| if type(value) == datetime.datetime: | ||
| return format_datetime(value) | ||
| return str(value) | ||
|
|
||
| datamodel_content_json = settings.META_MODEL_DEFINITIONS | ||
| if data_format == 'json': | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. too light about |
||
| return json.dumps(datamodel_content_json, indent=4) | ||
| return json.dumps( | ||
| datamodel_content_json, indent=4, default=default_dumper | ||
| ) | ||
| return yaml.dump(datamodel_content_json, allow_unicode=True) | ||
|
|
||
| def get(self, request, *args, **kwargs): | ||
|
|
@@ -103,12 +115,14 @@ def get(self, request, *args, **kwargs): | |
|
|
||
| def get_context_data(self, **kwargs): | ||
| context = super().get_context_data(**kwargs) | ||
| datamodel_content_json = settings.META_MODEL_DEFINITIONS | ||
| context['json_content'] = json.dumps(datamodel_content_json, indent=2) | ||
| datamodel_content_json = self._get_datamodel_format(data_format='json') | ||
| datamodel_content_yml = self._get_datamodel_format() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. specify format here |
||
| content = DatamodelYamlToHtml(datamodel_content_json, indent=2) | ||
| content = DatamodelYamlToHtml( | ||
| settings.META_MODEL_DEFINITIONS, indent=2 | ||
| ) | ||
| context['yaml_displayed_content'] = content.render_yaml() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. class is named Yaml to Html, but only function "render yaml" is called, a yaml is expected as a result. |
||
| context['yaml_content'] = datamodel_content_yml | ||
| context['json_content'] = datamodel_content_json | ||
| return context | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should delete this line if obsolete, or uncomment ?