diff options
| author | Étienne Loks <etienne.loks@iggdrasil.net> | 2021-10-22 19:33:29 +0200 | 
|---|---|---|
| committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2022-12-12 12:20:59 +0100 | 
| commit | 62e05e799962d810804d0cd5f8d377ac8b434a01 (patch) | |
| tree | 2ad3d5f85dde8137c519f9c8e1c362df5ba49d7d | |
| parent | fede10bc3ed5a9df9325d3c9d671a167760a9381 (diff) | |
| download | Ishtar-62e05e799962d810804d0cd5f8d377ac8b434a01.tar.bz2 Ishtar-62e05e799962d810804d0cd5f8d377ac8b434a01.zip | |
Syndication - update keys from match document
| -rw-r--r-- | archaeological_operations/tests.py | 43 | ||||
| -rw-r--r-- | archaeological_operations/tests/default-source.ods | bin | 0 -> 30910 bytes | |||
| -rw-r--r-- | ishtar_common/admin.py | 18 | ||||
| -rw-r--r-- | ishtar_common/libreoffice.py | 42 | ||||
| -rw-r--r-- | ishtar_common/models_imports.py | 4 | ||||
| -rw-r--r-- | ishtar_common/models_rest.py | 90 | 
6 files changed, 183 insertions, 14 deletions
| diff --git a/archaeological_operations/tests.py b/archaeological_operations/tests.py index 409ee5079..69d3e7f26 100644 --- a/archaeological_operations/tests.py +++ b/archaeological_operations/tests.py @@ -31,6 +31,7 @@ from django.apps import apps  from django.conf import settings  from django.contrib.auth.models import Group  from django.contrib.contenttypes.models import ContentType +from django.core.files import File as DjangoFile  from django.core.files.uploadedfile import SimpleUploadedFile  from django.db.models import Q  from django.test import tag @@ -4711,6 +4712,48 @@ class ApiTest(OperationInitTest, APITestCase):                      ope_type = str(ope_type).replace("'", "'")                      self.assertIn(ope_type, content) +    @tag("libreoffice") +    @patch("requests.get") +    def test_match_doc_update(self, mock_get): +        if not settings.USE_LIBREOFFICE: +            return +        self._mock_request(mock_get) +        self.client.login(username=self.username, password=self.password) +        source = self._get_source() + +        url = "/admin/{}/{}/".format( +            "ishtar_common", models_rest.ApiExternalSource.__name__.lower() +        ) +        params = { +            "action": "update_types_from_source", +            "_selected_action": [source.pk], +        } +        self.client.post(url, params, follow=True) + +        base = "default-source.ods" +        filename = settings.ROOT_PATH + "../archaeological_operations/tests/" + base +        with open(filename, "rb") as fle: +            source.match_document.save(base, DjangoFile(fle)) + +        params["action"] = "update_association_from_match_document" +        response = self.client.post(url, params, follow=True).content.decode() +        result = {"updated": 4} +        msg = str(_(f"{result['updated']} match key(s) updated.")) +        self.assertIn(msg, response) +        ctype = "archaeological_operations.remaintype" +        distant_key = distant_label = "ail" +        msg = str( +            _( +                f"{ctype} - {distant_key}, {distant_label} not referenced in the database." +            ) +        ) +        self.assertIn(msg, response) +        sheet_name = ( +            "archaeological_operations.operation-archaeological_operations.ReportState" +        ) +        msg = str(_(f"{sheet_name} is not a correct sheet name.")) +        self.assertIn(msg, response) +      def test_query_transformation(self):          # POV: local          # change query terms from a source Ishtar to match distant Ishtar diff --git a/archaeological_operations/tests/default-source.ods b/archaeological_operations/tests/default-source.odsBinary files differ new file mode 100644 index 000000000..b31497281 --- /dev/null +++ b/archaeological_operations/tests/default-source.ods diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index a974823a5..b1f32620c 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -2279,6 +2279,24 @@ def update_association_from_match_document(modeladmin, request, queryset):              message_type=messages.WARNING,          )      result = queryset.all()[0].update_from_match_document() +    if not result: +        messages.add_message( +            request, +            messages.ERROR, +            str(_("Error on update. Cannot open match document.")), +        ) +    else: +        if result["errors"]: +            errors = ( +                "<p>" + str(_("Error on type update from match document:")) + "</p>" +            ) +            errors += "<ul><li>" + "</li><li>".join(result["errors"]) + "</li></ul>" +            messages.add_message(request, messages.ERROR, mark_safe(errors)) +        messages.add_message( +            request, +            messages.WARNING, +            str(_(f"{result['updated']} match key(s) updated.")), +        )      return HttpResponseRedirect(return_url) diff --git a/ishtar_common/libreoffice.py b/ishtar_common/libreoffice.py index 6337675fe..482364a73 100644 --- a/ishtar_common/libreoffice.py +++ b/ishtar_common/libreoffice.py @@ -74,12 +74,22 @@ class UnoCalc(UnoClient):      def create_calc(self):          return self.create_document('scalc') +    def open_calc(self, filename): +        return self.get_document(filename) +      def save_calc(self, calc, filename):          url = "file://{}".format(filename)          args = (PropertyValue(Name="FilterName", Value="Calc8"),)          calc.storeToURL(url, args) -    def get_sheet(self, calc, sheet_index, name=None): +    def get_sheet_number(self, calc): +        sheets = calc.getSheets() +        return sheets.Count + +    def get_sheet(self, calc, sheet_index): +        return calc.getSheets().getByIndex(sheet_index) + +    def get_or_create_sheet(self, calc, sheet_index, name=None):          sheets = calc.getSheets()          while sheets.Count < (sheet_index + 1):              if name and sheets.Count == sheet_index: @@ -89,6 +99,30 @@ class UnoCalc(UnoClient):              sheets.insertNewByName(sheet_name, sheets.Count)          return calc.getSheets().getByIndex(sheet_index) +    def sheet_get_last_row_number(self, sheet): +        cursor = sheet.createCursor() +        cursor.gotoEndOfUsedArea(False) +        return cursor.getRangeAddress().EndRow + 1 + +    def sheet_get_data(self, sheet, last_column=None): +        if not last_column: +            # assume the first line is header with no empty columns +            last_column = 0 +            has_data = True +            while has_data: +                has_data = sheet.getCellByPosition(last_column, 0).getString() +                if not has_data: +                    break +                last_column += 1 +        last_row = self.sheet_get_last_row_number(sheet) +        data = [] +        for row in range(last_row): +            data.append([]) +            for col in range(last_column): +                value = sheet.getCellByPosition(col, row).getString() +                data[-1].append(value) +        return data +      def create_list(self, sheet, col, start_row, title, items):          items = [title] + items          row_idx = 0 @@ -147,11 +181,11 @@ class UnoCalc(UnoClient):          if not lst:  # no list              return          calc = self.create_calc() -        lst_sheet = self.get_sheet(calc, 1, "List types") +        lst_sheet = self.get_or_create_sheet(calc, 1, "List types")          lst_col, lst_row = 0, 0          end_row = self.create_list(lst_sheet, lst_col, lst_row, title, lst) -        main_sheet = self.get_sheet(calc, 0) +        main_sheet = self.get_or_create_sheet(calc, 0)          self.set_cell_validation_list(main_sheet, 0, 0, 200, lst_sheet, lst_col,                                        [lst_row + 1, end_row])          self.save_calc(calc, "/tmp/test.ods") @@ -162,7 +196,7 @@ class UnoCalc(UnoClient):          propval.Value = True          filename = "/tmp/main.ods"          calc = self.get_document(filename, (propval,)) -        sheet = self.get_sheet(calc, 0) +        sheet = self.get_or_create_sheet(calc, 0)          validation = sheet.getCellByPosition(0, 0).Validation          for k in dir(validation):              if k.startswith("get"): diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py index e530b54e3..799b3aab9 100644 --- a/ishtar_common/models_imports.py +++ b/ishtar_common/models_imports.py @@ -171,8 +171,8 @@ class ImporterType(models.Model):          uno = UnoCalc()          calc = uno.create_calc() -        main_sheet = uno.get_sheet(calc, 0, self.name) -        lst_sheet = uno.get_sheet(calc, 1, str(_("List types"))) +        main_sheet = uno.get_or_create_sheet(calc, 0, self.name) +        lst_sheet = uno.get_or_create_sheet(calc, 1, str(_("List types")))          if not calc:              return diff --git a/ishtar_common/models_rest.py b/ishtar_common/models_rest.py index e29356567..cc07a8803 100644 --- a/ishtar_common/models_rest.py +++ b/ishtar_common/models_rest.py @@ -2,11 +2,13 @@ import datetime  import os  import tempfile +from django.apps import apps  from django.conf import settings  from django.contrib.auth.models import User  from django.contrib.contenttypes.models import ContentType  from django.contrib.gis.db import models  from django.contrib.postgres.fields import ArrayField +from django.core.files import File  from django.utils.text import slugify  try: @@ -153,14 +155,13 @@ class ApiExternalSource(models.Model):              .values_list("associated_type", flat=True)              .distinct()          ) -        lst_sheet = uno.get_sheet(calc, len(types), str(_("List types"))) +        lst_sheet = uno.get_or_create_sheet(calc, len(types), str(_("List types")))          for idx, tpe in enumerate(types):              self._generate_match_page(idx, tpe, uno, calc, lst_sheet)          tmpdir = tempfile.mkdtemp(prefix="ishtar-matches-")          base = "{}-{}.ods".format(datetime.date.today().isoformat(), slugify(self.name))          dest_filename = "{}{}{}".format(tmpdir, os.sep, base)          uno.save_calc(calc, dest_filename) -        from django.core.files import File          with open(dest_filename, "rb") as fle:              self.match_document = File(fle, base) @@ -170,8 +171,14 @@ class ApiExternalSource(models.Model):      def _generate_match_page(self, page_number, tpe, uno, calc, lst_sheet):          model = ContentType.objects.get(pk=tpe).model_class()          ROW_NUMBER = 1000 -        sheet = uno.get_sheet(calc, page_number) -        sheet.Name = str(model._meta.verbose_name) +        sheet = uno.get_or_create_sheet(calc, page_number) +        q = ApiKeyMatch.objects.filter(source=self, associated_type=tpe) +        if not q.count(): +            return +        sm = q.all()[0].search_model +        sheet.Name = ( +            f"{sm.app_label}.{sm.model}-{model._meta.app_label}.{model.__name__.lower()}" +        )          for col_number, column in enumerate(              (_("Distant key"), _("Distant label"), _("Local"))          ): @@ -180,9 +187,7 @@ class ApiExternalSource(models.Model):              cell.CharWeight = 150              cell.setString(str(column)) -        for idx, match in enumerate( -            ApiKeyMatch.objects.filter(source=self, associated_type=tpe).all() -        ): +        for idx, match in enumerate(q.all()):              cell = sheet.getCellByPosition(0, idx + 1)              cell.setString(match.distant_slug)              cell = sheet.getCellByPosition(1, idx + 1) @@ -207,7 +212,76 @@ class ApiExternalSource(models.Model):          )      def update_from_match_document(self): -        pass +        if not self.match_document: +            return +        if not UnoCalc: +            return +        uno = UnoCalc() +        calc = uno.open_calc(self.match_document.path) +        if not calc: +            return +        errors = [] +        updated = 0 +        for idx in range(uno.get_sheet_number(calc) - 1):  # do not read the last sheet +            sheet = uno.get_sheet(calc, idx) +            sheet_name = sheet.Name +            try: +                search_model_name, ctype = sheet.Name.split("-") +                app_label, model_name = search_model_name.split(".") +                search_model = ContentType.objects.get( +                    app_label=app_label, model=model_name +                ) +            except (ValueError, ContentType.DoesNotExist): +                errors.append(str(_(f"{sheet_name} is not a correct sheet name."))) +                continue +            try: +                app_label, model_name = ctype.split(".") +                ct = ContentType.objects.get(app_label=app_label, model=model_name) +            except (ValueError, ContentType.DoesNotExist): +                errors.append(str(_(f"{sheet_name} is not a correct sheet name."))) +                continue +            data = uno.sheet_get_data(sheet) +            base_q = ApiKeyMatch.objects.filter( +                source=self, search_model=search_model, associated_type=ct +            ) +            model = ct.model_class() +            for idx_line, line in enumerate(data): +                if not idx_line:  # header +                    continue +                distant_key, distant_label, local_name = line +                q = base_q.filter(distant_slug=distant_key, distant_label=distant_label) +                if not q.count(): +                    errors.append( +                        str( +                            _( +                                f"{ctype} - {distant_key}, {distant_label} not referenced in the database." +                            ) +                        ) +                    ) +                    continue +                if q.filter(local_label=local_name).count(): +                    # no change +                    continue +                api_key = q.all()[0] +                key_name = "slug" +                if hasattr(model, "txt_idx"): +                    key_name = "txt_idx" +                q = model.objects.filter(label=local_name) +                if not q.count(): +                    errors.append( +                        str( +                            _( +                                f"{ctype} - {local_name} do not exists in local database." +                            ) +                        ) +                    ) +                    continue +                tpe = q.all()[0] +                api_key.local_slug = getattr(tpe, key_name) +                api_key.local_label = local_name +                api_key.save() +                updated += 1 +        return {"errors": errors, "updated": updated}  class ApiKeyMatch(models.Model): | 
