diff options
| -rw-r--r-- | ishtar_common/admin.py | 111 | ||||
| -rw-r--r-- | ishtar_common/models_imports.py | 8 | ||||
| -rw-r--r-- | ishtar_common/serializers_utils.py | 47 | ||||
| -rw-r--r-- | ishtar_common/templates/admin/gen_change_list.html | 5 | ||||
| -rw-r--r-- | ishtar_common/templates/admin/json_change_list.html | 19 | 
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 %} +  | 
