summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ishtar_common/admin.py111
-rw-r--r--ishtar_common/models_imports.py8
-rw-r--r--ishtar_common/serializers_utils.py47
-rw-r--r--ishtar_common/templates/admin/gen_change_list.html5
-rw-r--r--ishtar_common/templates/admin/json_change_list.html19
5 files changed, 176 insertions, 14 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py
index 71dc2cbdc..e606a81e7 100644
--- a/ishtar_common/admin.py
+++ b/ishtar_common/admin.py
@@ -67,6 +67,9 @@ from ishtar_common.apps import admin_site
from ishtar_common.utils import get_cache, create_slug
from ishtar_common import forms as common_forms
+from ishtar_common.serializers import restore_serialized, IMPORT_MODEL_LIST
+from ishtar_common.serializers_utils import generic_get_results, \
+ serialization_info
from archaeological_files import forms as file_forms
from archaeological_files_pdl import forms as file_pdl_forms
from archaeological_operations import forms as operation_forms
@@ -113,7 +116,7 @@ def change_value(attribute, value, description):
return _change_value
-def export_as_csv_action(description=_(u"Export selected as CSV file"),
+def export_as_csv_action(description=_("Export selected as CSV file"),
fields=None, exclude=None, header=True):
"""
This function returns an export csv action
@@ -206,6 +209,44 @@ def export_as_geojson_action(
return export_as_geojson
+def serialize_action(dir_name, model_list):
+ def _serialize_action(modeladmin, request, queryset):
+ if model_list:
+ modellist = model_list[:]
+ else:
+ modellist = [modeladmin.model]
+ opts = modeladmin.model._meta
+ result = generic_get_results(
+ modellist, dir_name,
+ result_queryset={opts.object_name: queryset})
+ basename = str(opts).replace('.', '_')
+ in_memory = BytesIO()
+ zip = zipfile.ZipFile(in_memory, "a")
+ for key in result.keys():
+ __, model_name = key
+ zip.writestr(dir_name + os.sep + model_name + ".json", result[key])
+
+ # info
+ zip.writestr("info.json", json.dumps(serialization_info(), indent=2))
+
+ # fix for Linux zip files read in Windows
+ for file in zip.filelist:
+ file.create_system = 0
+ zip.close()
+ response = HttpResponse(content_type='application/zip')
+ response['Content-Disposition'] = 'attachment; filename={}.zip'.format(
+ basename
+ )
+ in_memory.seek(0)
+ response.write(in_memory.read())
+ return response
+ return _serialize_action
+
+
+SERIALIZE_DESC = _("Export selected as Ishtar (zipped JSON)")
+serialize_type_action = serialize_action("types", None)
+serialize_type_action.short_description = SERIALIZE_DESC
+
TokenAdmin.raw_id_fields = ('user',)
admin_site.register(Token, TokenAdmin)
@@ -693,6 +734,61 @@ class ImportGEOJSONActionAdmin(object):
{'file_form': form, 'current_action': 'import_geojson'})
+class ImportJSONForm(forms.Form):
+ json_file = forms.FileField(
+ _("Zipped JSON file"),
+ help_text=_("Import from a zipped JSON file generated by Ishtar")
+ )
+
+
+class ImportJSONActionAdmin(admin.ModelAdmin):
+ change_list_template = "admin/json_change_list.html"
+ import_keys = ['slug', 'txt_idx']
+
+ def get_urls(self):
+ urls = super(ImportJSONActionAdmin, self).get_urls()
+ my_urls = [
+ url(r'^import-from-json/$', self.import_json),
+ ]
+ return my_urls + urls
+
+ def import_json(self, request):
+ form = None
+
+ if 'apply' in request.POST:
+ form = ImportJSONForm(request.POST, request.FILES)
+ if form.is_valid():
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ filename = tmpdirname + os.sep + "export.zip"
+ with open(filename, "wb+") as zipped_file:
+ for chunk in request.FILES['json_file'].chunks():
+ zipped_file.write(chunk)
+ result = None
+ result = restore_serialized(filename)
+ try:
+ result = restore_serialized(filename)
+ except ValueError as e:
+ self.message_user(request, str(e),
+ level=messages.ERROR)
+ if result:
+ for model, count in result:
+ self.message_user(
+ request,
+ str(_("{} {}(s) created/updated.")).format(
+ count, model))
+ url = reverse(
+ 'admin:%s_%s_changelist' % (
+ self.model._meta.app_label, self.model._meta.model_name)
+ )
+ return HttpResponseRedirect(url)
+ if not form:
+ form = ImportJSONForm()
+ return render(
+ request, 'admin/import_from_file.html',
+ {'file_form': form, 'current_action': 'import_json'}
+ )
+
+
class AdminRelatedTownForm(forms.ModelForm):
class Meta:
model = models.Town.children.through
@@ -744,12 +840,12 @@ class TownAdmin(ImportGEOJSONActionAdmin, ImportActionAdmin):
admin_site.register(models.Town, TownAdmin)
-class GeneralTypeAdmin(ImportActionAdmin):
+class GeneralTypeAdmin(ImportActionAdmin, ImportJSONActionAdmin):
list_display = ['label', 'txt_idx', 'available', 'comment']
search_fields = ('label', 'txt_idx', 'comment',)
list_filter = ('available',)
save_on_top = True
- actions = [export_as_csv_action()]
+ actions = [export_as_csv_action(), serialize_type_action]
prepopulated_fields = {"txt_idx": ("label",)}
@csrf_protect_m
@@ -995,9 +1091,14 @@ if settings.USE_LIBREOFFICE:
importer_type_actions.append(generate_libreoffice_template)
-class ImporterTypeAdmin(admin.ModelAdmin):
+serialize_importer_action = serialize_action("common_imports",
+ IMPORT_MODEL_LIST)
+serialize_importer_action.short_description = SERIALIZE_DESC
+
+
+class ImporterTypeAdmin(ImportJSONActionAdmin):
list_display = ('name', 'associated_models', 'available')
- actions = importer_type_actions
+ actions = importer_type_actions + [serialize_importer_action]
admin_site.register(models.ImporterType, ImporterTypeAdmin)
diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py
index 7fdee8ce3..4fa1abb4a 100644
--- a/ishtar_common/models_imports.py
+++ b/ishtar_common/models_imports.py
@@ -103,11 +103,11 @@ class ImporterType(models.Model):
associated_models = models.ForeignKey(
ImporterModel, verbose_name=_("Associated model"),
on_delete=models.SET_NULL,
- related_name='+', blank=True, null=True)
+ related_name='importer_type_associated', blank=True, null=True)
created_models = models.ManyToManyField(
ImporterModel, verbose_name=_("Models that can accept new items"),
blank=True, help_text=_("Leave blank for no restrictions"),
- related_name='+')
+ related_name='importer_type_created')
is_template = models.BooleanField(_("Can be exported"), default=False)
unicity_keys = models.CharField(_("Unicity keys (separator \";\")"),
blank=True, null=True, max_length=500)
@@ -415,9 +415,11 @@ class ImporterColumn(models.Model):
description = models.TextField(_("Description"), blank=True, null=True)
regexp_pre_filter = models.ForeignKey(
"Regexp", blank=True, null=True, on_delete=models.SET_NULL,
+ related_name="columns",
)
value_format = models.ForeignKey(
"ValueFormater", blank=True, null=True, on_delete=models.SET_NULL,
+ related_name="columns"
)
required = models.BooleanField(_("Required"), default=False)
export_field_name = models.CharField(
@@ -552,7 +554,7 @@ class ImportTarget(models.Model):
"""
column = models.ForeignKey(ImporterColumn, related_name='targets')
target = models.CharField("Target", max_length=500)
- formater_type = models.ForeignKey("FormaterType")
+ formater_type = models.ForeignKey("FormaterType", related_name='targets')
force_new = models.BooleanField(_("Force creation of new items"),
default=False)
concat = models.BooleanField(_("Concatenate with existing"),
diff --git a/ishtar_common/serializers_utils.py b/ishtar_common/serializers_utils.py
index 74ffbe176..c03a55e35 100644
--- a/ishtar_common/serializers_utils.py
+++ b/ishtar_common/serializers_utils.py
@@ -9,6 +9,7 @@ from zipfile import ZipFile
from django.contrib.sites.models import Site
from django.core.serializers import serialize
+from django.db.models import Q
from ishtar_common.version import get_version
from . import models
@@ -19,7 +20,10 @@ SERIALIZATION_VERSION = "1.0"
def get_model_from_filename(filename):
filename = filename.split(".")[0] # remove extension
- module_name, model_name = filename.split("__")
+ splitted = filename.split("__")
+ if len(splitted) != 2:
+ return
+ module_name, model_name = splitted
if module_name == "django":
if model_name in ("Group", "Permission"):
module = importlib.import_module("django.contrib.auth.models")
@@ -107,6 +111,21 @@ def archive_serialization(result, archive_dir=None, archive=False,
return archive_name
+GENERIC_QUERYSET_FILTER = {
+ "Regexp": {"ImporterType": 'columns__importer_type__pk__in'},
+ "ImporterModel": {"ImporterType": ['importer_type_associated__pk__in',
+ 'importer_type_created__pk__in']},
+ "ValueFormater": {"ImporterType": 'columns__importer_type__pk__in'},
+ "ImporterColumn": {"ImporterType": 'importer_type__pk__in'},
+ "ImporterDefault": {"ImporterType": 'importer_type__pk__in'},
+ "ImportTarget": {"ImporterType": 'column__importer_type__pk__in'},
+ "FormaterType": {"ImporterType": 'targets__column__importer_type__pk__in'},
+ "ImporterDefaultValues": {
+ "ImporterType": 'default_target__importer_type__pk__in'},
+ "ImporterDuplicateField": {"ImporterType": 'column__importer_type__pk__in'},
+}
+
+
def generic_get_results(model_list, dirname, no_geo=True,
result_queryset=None, serialization_include=None):
result = OrderedDict()
@@ -114,11 +133,27 @@ def generic_get_results(model_list, dirname, no_geo=True,
base_model_name = model.__name__
model_name = str(model.__module__).split(".")[0] + "__" + \
base_model_name
-
- if result_queryset and base_model_name in result_queryset:
- base_q = result_queryset[base_model_name]
- else:
- base_q = model.objects
+ base_q = model.objects
+ if result_queryset:
+ if result_queryset and base_model_name in result_queryset:
+ base_q = result_queryset[base_model_name]
+ elif base_model_name in GENERIC_QUERYSET_FILTER:
+ alt_filter = GENERIC_QUERYSET_FILTER[base_model_name]
+ for k in alt_filter:
+ if k in result_queryset:
+ terms = alt_filter[k]
+ if not isinstance(terms, (list, tuple)):
+ terms = [terms]
+ ids = [r["pk"]
+ for r in result_queryset[k].values("pk").all()]
+ q = None
+ for term in terms:
+ if not q:
+ q = Q(**{term: ids})
+ else:
+ q |= Q(**{term: ids})
+ base_q = base_q.filter(q)
+ break
q = base_q
recursion = None
if hasattr(model, "parent"):
diff --git a/ishtar_common/templates/admin/gen_change_list.html b/ishtar_common/templates/admin/gen_change_list.html
index 9cb566484..f14b62f27 100644
--- a/ishtar_common/templates/admin/gen_change_list.html
+++ b/ishtar_common/templates/admin/gen_change_list.html
@@ -10,6 +10,11 @@
</a>
</li>
<li>
+ <a href="import-from-json/" class="addlink">
+ {% trans "Import from JSON" %}
+ </a>
+ </li>
+ <li>
<a href="import-from-csv/" class="addlink">
{% trans "Import from CSV" %}
</a>
diff --git a/ishtar_common/templates/admin/json_change_list.html b/ishtar_common/templates/admin/json_change_list.html
new file mode 100644
index 000000000..74a8cabe7
--- /dev/null
+++ b/ishtar_common/templates/admin/json_change_list.html
@@ -0,0 +1,19 @@
+{% extends "admin/change_list.html" %}
+{% load i18n admin_urls static admin_list %}
+
+ {% block object-tools-items %}
+ {% if has_add_permission %}
+ <li>
+ {% url cl.opts|admin_urlname:'add' as add_url %}
+ <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
+ {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
+ </a>
+ </li>
+ <li>
+ <a href="import-from-json/" class="addlink">
+ {% trans "Import from JSON" %}
+ </a>
+ </li>
+ {% endif %}
+ {% endblock %}
+