diff options
-rw-r--r-- | archaeological_finds/tests.py | 68 | ||||
-rw-r--r-- | archaeological_finds/tests/bad_lo.zip | bin | 0 -> 10469 bytes | |||
l--------- | archaeological_finds/tests/etiquettes-mobilier | 1 | ||||
-rw-r--r-- | archaeological_finds/tests/etiquettes-mobilier.odt | bin | 0 -> 11151 bytes | |||
-rw-r--r-- | archaeological_finds/tests/etiquettes-mobilier.txt | 1 | ||||
-rw-r--r-- | archaeological_finds/tests/truncated_xml.zip | bin | 0 -> 12404 bytes | |||
-rw-r--r-- | ishtar_common/migrations/0210_auto_20210106_1127.py | 30 | ||||
-rw-r--r-- | ishtar_common/models.py | 95 |
8 files changed, 192 insertions, 3 deletions
diff --git a/archaeological_finds/tests.py b/archaeological_finds/tests.py index 548a74e7b..93548f8fc 100644 --- a/archaeological_finds/tests.py +++ b/archaeological_finds/tests.py @@ -38,7 +38,8 @@ from django.core.urlresolvers import reverse from django.test import tag from django.test.client import Client from ishtar_common.models import ImporterType, IshtarUser, ImporterColumn,\ - FormaterType, ImportTarget, IshtarSiteProfile, ProfileType + FormaterType, ImportTarget, IshtarSiteProfile, ProfileType, ImporterModel, \ + DocumentTemplate from django.utils.text import slugify from django.utils.translation import pgettext_lazy, gettext_lazy as _ @@ -2513,3 +2514,68 @@ class PublicAPITest(FindInit, APITestCase): value = value[key] self.assertEqual(value, result) + +class LabelTest(FindInit, TestCase): + fixtures = FIND_FIXTURES + model = models.Find + + def setUp(self): + templates = [ + settings.ROOT_PATH + '../archaeological_finds/tests/' + t + for t in ("etiquettes-mobilier.odt", "etiquettes-mobilier", + "etiquettes-mobilier.txt", "bad_lo.zip", + "truncated_xml.zip") + ] + self.templates = [] + for template in templates: + filename = template.split("/")[-1] + shutil.copy(template, + os.path.join(settings.MEDIA_ROOT, filename), + follow_symlinks=True) + self.templates.append( + os.path.join(settings.MEDIA_ROOT, filename)) + + def tearDown(self): + for tpl in self.templates: + if os.path.exists(tpl): + os.remove(tpl) + + def test_label(self): + base_targets = ";".join("Cadre{}".format(idx) for idx in range(1, 25)) + base_tpl, missing_ext, text_file, bad_lo, trunc_xml = self.templates + dataset = ( + (base_tpl, base_targets, True, "OK"), + (base_tpl, "", False, "no target"), + (base_tpl, "-;Cadre2;Cadre3", False, "bad first target"), + (base_tpl, "Cadre1;Frame2;Frame3", True, + "first target OK, silently failed other targets"), + (missing_ext, base_targets, True, "missing extension"), + (text_file, base_targets, False, "text file"), + (bad_lo, base_targets, False, "missing content.xml"), + (trunc_xml, base_targets, False, "truncated content.xml"), + ) + for tpl_file, targets, is_ok, msg in dataset: + with open(tpl_file, 'rb') as tpl: + template = SimpleUploadedFile("etiquettes-mobilier.odt", + tpl.read()) + model, __ = ImporterModel.objects.get_or_create( + klass='archaeological_finds.models.Find' + ) + q = DocumentTemplate.objects.filter(slug="test") + if q.count(): + q.all()[0].delete() + doc = DocumentTemplate.objects.create( + name="Test", + slug="test", + associated_model=model, + available=True, + label_targets=targets, + label_template=template) + self.templates.append(doc.label_template.path) + doc = DocumentTemplate.objects.get(pk=doc.pk) + msg = "Fail on dataset: " + msg + if is_ok: + self.assertTrue(doc.template.name, msg=msg) + self.templates.append(doc.template.path) + else: + self.assertFalse(doc.template.name, msg=msg) diff --git a/archaeological_finds/tests/bad_lo.zip b/archaeological_finds/tests/bad_lo.zip Binary files differnew file mode 100644 index 000000000..f05cd2d0b --- /dev/null +++ b/archaeological_finds/tests/bad_lo.zip diff --git a/archaeological_finds/tests/etiquettes-mobilier b/archaeological_finds/tests/etiquettes-mobilier new file mode 120000 index 000000000..89ec4030b --- /dev/null +++ b/archaeological_finds/tests/etiquettes-mobilier @@ -0,0 +1 @@ +etiquettes-mobilier.odt
\ No newline at end of file diff --git a/archaeological_finds/tests/etiquettes-mobilier.odt b/archaeological_finds/tests/etiquettes-mobilier.odt Binary files differnew file mode 100644 index 000000000..7a8ffdedb --- /dev/null +++ b/archaeological_finds/tests/etiquettes-mobilier.odt diff --git a/archaeological_finds/tests/etiquettes-mobilier.txt b/archaeological_finds/tests/etiquettes-mobilier.txt new file mode 100644 index 000000000..de2d58990 --- /dev/null +++ b/archaeological_finds/tests/etiquettes-mobilier.txt @@ -0,0 +1 @@ +Non libreoffice file diff --git a/archaeological_finds/tests/truncated_xml.zip b/archaeological_finds/tests/truncated_xml.zip Binary files differnew file mode 100644 index 000000000..830918c5a --- /dev/null +++ b/archaeological_finds/tests/truncated_xml.zip diff --git a/ishtar_common/migrations/0210_auto_20210106_1127.py b/ishtar_common/migrations/0210_auto_20210106_1127.py new file mode 100644 index 000000000..3dcf7e7ce --- /dev/null +++ b/ishtar_common/migrations/0210_auto_20210106_1127.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2021-01-06 11:27 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0209_auto_20210105_1712'), + ] + + operations = [ + migrations.AddField( + model_name='documenttemplate', + name='label_targets', + field=models.TextField(blank=True, help_text='Each target is separated by a semi-colon. The first target is the name of the object including the data in base template. Following targets will be filled with the content of the first target. For instance: "Cadre1;Cadre2;Cadre3;Cadre4;Cadre5;Cadre6" for a sheet with 6 labels.', null=True, verbose_name='Labels: targets for labels in the LibreOffice file'), + ), + migrations.AddField( + model_name='documenttemplate', + name='label_template', + field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to='templates/%Y/', verbose_name='Base template for labels'), + ), + migrations.AlterField( + model_name='documenttemplate', + name='template', + field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', null=True, upload_to='templates/%Y/', verbose_name='Template'), + ), + ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 23416d406..6ac197af4 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -20,6 +20,7 @@ """ Models description """ +from bs4 import BeautifulSoup import copy import datetime import inspect @@ -55,6 +56,7 @@ from django.contrib.sites.models import Site from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist, ValidationError, \ MultipleObjectsReturned +from django.core.files.base import ContentFile from django.core.files.uploadedfile import SimpleUploadedFile from django.core.urlresolvers import reverse from django.db.models import Q, Max, Count @@ -1436,7 +1438,21 @@ class DocumentTemplate(models.Model): slug = models.SlugField(_("Slug"), max_length=100, unique=True) associated_model = models.ForeignKey(ImporterModel) template = models.FileField( - _("Template"), upload_to="templates/%Y/", help_text=max_size_help()) + _("Template"), upload_to="templates/%Y/", blank=True, null=True, + help_text=max_size_help()) + label_template = models.FileField( + _("Base template for labels"), upload_to="templates/%Y/", + blank=True, null=True, help_text=max_size_help()) + label_targets = models.TextField( + _("Labels: targets for labels in the LibreOffice file"), + blank=True, null=True, + help_text=_("Each target is separated by a semi-colon. The first " + "target is the name of the object including the data in " + "base template. Following targets will be filled with the " + "content of the first target. For instance: " + "\"Cadre1;Cadre2;Cadre3;Cadre4;Cadre5;Cadre6\" for a " + "sheet with 6 labels.") + ) available = models.BooleanField(_("Available"), default=True) for_labels = models.BooleanField(_("Used for labels"), default=False) label_per_page = models.IntegerField( @@ -1462,10 +1478,85 @@ class DocumentTemplate(models.Model): raise ValidationError(_("For label template, you must provide " "number of label per page.")) + def generate_label_template(self): + if not self.label_template.name or not self.label_targets: + return + targets = self.label_targets.split(";") + base_target = targets[0] + try: + with zipfile.ZipFile(self.label_template.path) as zip: + with zip.open('content.xml') as content: + soup = BeautifulSoup(content.read(), 'xml') + base_content = soup.find( + "draw:frame", attrs={"draw:name": base_target}) + if not base_content: + return + base_content = base_content.contents + except (FileNotFoundError, zipfile.BadZipFile, KeyError): + base_content = None + if not base_content: + return + for idx, target in enumerate(targets[1:]): + replace_str = "items." + str(idx + 1) + new_content = [] + for content in base_content: + content = copy.copy(content) + for text in content.find_all(text=re.compile("items.0")): + fixed_text = text.replace("items.0", replace_str) + text.replace_with(fixed_text) + for image in content.find_all( + attrs={"draw:name": re.compile("items.0")}): + image["draw:name"] = image["draw:name"].replace("items.0", + replace_str) + new_content.append(content) + next_target = soup.find( + "draw:frame", attrs={"draw:name": target}) + if next_target: + next_target.contents = new_content + + with tempfile.TemporaryDirectory() as tmp: + sp = self.label_template.name.split(os.sep)[-1].split(".") + if len(sp) == 1: # no extension? + sp.append("odt") + sp[-2] += "-label" + new_filename = ".".join(sp) + new_file = os.path.join(tmp, new_filename) + with zipfile.ZipFile(new_file, 'w') as zip_out: + with zipfile.ZipFile(self.label_template.path, 'r') as zip_in: + zip_out.comment = zip_in.comment + for item in zip_in.infolist(): + if item.filename != "content.xml": + zip_out.writestr(item, + zip_in.read(item.filename)) + with zipfile.ZipFile(new_file, mode='a', + compression=zipfile.ZIP_DEFLATED) as zf: + zf.writestr("content.xml", str(soup)) + + media_dir = "templates/{}/".format(datetime.date.today().year) + full_media_dir = os.path.join(settings.MEDIA_ROOT, media_dir) + if not os.path.exists(full_media_dir): + os.mkdir(full_media_dir) + media_file = new_filename + idx = 0 + while os.path.exists(os.path.join(settings.MEDIA_ROOT, media_file)): + idx += 1 + sp = media_file.split(".") + sub_sp = sp[-2].split("-label") + sub_sp[-1] += str(idx) + sp[-2] = "-label".join(sub_sp) + media_file = ".".join(sp) + with open(new_file, "rb") as file: + with ContentFile(file.read()) as file_content: + self.template.save(media_file, file_content) + self.save() + def save(self, *args, **kwargs): if not self.slug: self.slug = create_slug(DocumentTemplate, self.name) - return super(DocumentTemplate, self).save(*args, **kwargs) + super(DocumentTemplate, self).save(*args, **kwargs) + if self.label_template.name and self.label_targets and not \ + self.template: + self.generate_label_template() @classmethod def get_tuples(cls, dct=None, empty_first=True): |