diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2020-11-18 16:46:37 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2021-02-28 12:15:21 +0100 |
commit | 7f3f35d172c8dd7b56bd88778c3cbe4d65a5da58 (patch) | |
tree | 2b4452bc7fec7de7a90e81d9055a8b9971b18c5c /ishtar_common | |
parent | 53f899c9ded29921c982e67f220716b2f86823f3 (diff) | |
download | Ishtar-7f3f35d172c8dd7b56bd88778c3cbe4d65a5da58.tar.bz2 Ishtar-7f3f35d172c8dd7b56bd88778c3cbe4d65a5da58.zip |
Complex index generation with JINJA2 templates
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/migrations/0201_squashed.py | 16 | ||||
-rw-r--r-- | ishtar_common/migrations/0207_auto_20201118_1210.py (renamed from ishtar_common/migrations/0207_auto_20201117_1021.py) | 30 | ||||
-rw-r--r-- | ishtar_common/models.py | 95 | ||||
-rw-r--r-- | ishtar_common/models_common.py | 30 | ||||
-rw-r--r-- | ishtar_common/tests.py | 88 | ||||
-rw-r--r-- | ishtar_common/utils.py | 92 | ||||
-rw-r--r-- | ishtar_common/views_item.py | 3 |
7 files changed, 271 insertions, 83 deletions
diff --git a/ishtar_common/migrations/0201_squashed.py b/ishtar_common/migrations/0201_squashed.py index d7b65626a..5e97e5f08 100644 --- a/ishtar_common/migrations/0201_squashed.py +++ b/ishtar_common/migrations/0201_squashed.py @@ -12,6 +12,7 @@ import django.core.validators from django.db import migrations, models import django.db.models.deletion import ishtar_common.models +import ishtar_common.models_common import re import uuid @@ -207,7 +208,8 @@ class Migration(migrations.Migration): ishtar_common.models.TemplateItem, ishtar_common.models.OwnPerms, models.Model, ishtar_common.models.CachedGen, - ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem), + ishtar_common.models_common.FixAssociated, + ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem), ), migrations.CreateModel( name='DocumentTemplate', @@ -751,7 +753,11 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Organizations', 'permissions': (('view_organization', 'Can view all Organizations'), ('view_own_organization', 'Can view own Organization'), ('add_own_organization', 'Can add own Organization'), ('change_own_organization', 'Can change own Organization'), ('delete_own_organization', 'Can delete own Organization')), }, - bases=(ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem), + bases=(ishtar_common.models.StatisticItem, + ishtar_common.models.TemplateItem, models.Model, + ishtar_common.models.CachedGen, + ishtar_common.models_common.FixAssociated, + ishtar_common.models.CascasdeUpdate, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem), ), migrations.CreateModel( name='OrganizationType', @@ -823,7 +829,11 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Persons', 'permissions': (('view_person', 'Can view all Persons'), ('view_own_person', 'Can view own Person'), ('add_own_person', 'Can add own Person'), ('change_own_person', 'Can change own Person'), ('delete_own_person', 'Can delete own Person')), }, - bases=(ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem), + bases=(ishtar_common.models.StatisticItem, + ishtar_common.models.TemplateItem, models.Model, + ishtar_common.models.CachedGen, + ishtar_common.models_common.FixAssociated, + ishtar_common.models.CascasdeUpdate, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem), ), migrations.CreateModel( name='PersonType', diff --git a/ishtar_common/migrations/0207_auto_20201117_1021.py b/ishtar_common/migrations/0207_auto_20201118_1210.py index bd6d970fe..343577182 100644 --- a/ishtar_common/migrations/0207_auto_20201117_1021.py +++ b/ishtar_common/migrations/0207_auto_20201118_1210.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.27 on 2020-11-17 10:21 +# Generated by Django 1.11.27 on 2020-11-18 12:10 from __future__ import unicode_literals from django.db import migrations, models @@ -25,46 +25,56 @@ class Migration(migrations.Migration): migrations.AddField( model_name='ishtarsiteprofile', name='basefind_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage base find custom index.', verbose_name='Base find custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage base find custom index. Separate keys with a semicolon.', verbose_name='Base find custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='container_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage container custom index.', verbose_name='Container custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage container custom index. Separate keys with a semicolon.', verbose_name='Container custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='contextrecord_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage context record custom index.', verbose_name='Context record custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage context record custom index. Separate keys with a semicolon.', verbose_name='Context record custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='document_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage document custom index.', verbose_name='Document custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage document custom index. Separate keys with a semicolon.', verbose_name='Document custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='file_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage archaeological file custom index.', verbose_name='Archaeological file custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage archaeological file custom index. Separate keys with a semicolon.', verbose_name='Archaeological file custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='find_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage find custom index.', verbose_name='Find custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage find custom index. Separate keys with a semicolon.', verbose_name='Find custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='operation_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage operation custom index.', verbose_name='Operation custom index key'), + field=models.TextField(default='', help_text='Keys to be used to manage operation custom index. Separate keys with a semicolon.', verbose_name='Operation custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='site_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage archaeological site custom index.', verbose_name='Archaeological site custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage archaeological site custom index. Separate keys with a semicolon.', verbose_name='Archaeological site custom index key'), ), migrations.AddField( model_name='ishtarsiteprofile', name='warehouse_custom_index', - field=models.TextField(default='', help_text='Key to be used to manage warehouse custom index.', verbose_name='Warehouse custom index key'), + field=models.TextField(default='', help_text='Key to be used to manage warehouse custom index. Separate keys with a semicolon.', verbose_name='Warehouse custom index key'), + ), + migrations.AddField( + model_name='sourcetype', + name='code', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='Code'), + ), + migrations.AlterField( + model_name='sourcetype', + name='coins_genre', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='COInS export - genre'), ), ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 6e8e0409e..08009bb41 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -56,9 +56,9 @@ from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist, ValidationError, \ MultipleObjectsReturned from django.core.files.uploadedfile import SimpleUploadedFile -from django.core.urlresolvers import reverse, NoReverseMatch -from django.db.models import Q, Max, Count, F -from django.db.models.signals import post_save, post_delete, m2m_changed +from django.core.urlresolvers import reverse +from django.db.models import Q, Max, Count +from django.db.models.signals import post_save, post_delete from django.db.utils import DatabaseError from django.template import Context, Template from django.template.defaultfilters import slugify @@ -85,8 +85,8 @@ from ishtar_common.utils import get_cache, create_slug, \ generate_relation_graph, max_size_help from ishtar_common.models_common import GeneralType, HierarchicalType, \ - BaseHistorizedItem, LightHistorizedItem, FullSearch, Imported, \ - FixAssociated, SearchAltName, HistoryError, OwnPerms, Cached, \ + BaseHistorizedItem, LightHistorizedItem, FullSearch, \ + SearchAltName, OwnPerms, Cached, \ Address, post_save_cache, TemplateItem, SpatialReferenceSystem, \ DashboardFormItem, document_attached_changed, SearchAltName, \ DynamicRequest, GeoItem, CompleteIdentifierItem, SearchVectorConfig, \ @@ -542,7 +542,8 @@ class JsonDataField(models.Model): help_text=_("Value of the key in the JSON schema. For hierarchical " "key use \"__\" to explain it. For instance for the key " "'my_subkey' with data such as {'my_key': {'my_subkey': " - "'value'}}, its value will be reached with my_key__my_subkey.")) + "'value'}}, its value will be reached with " + "my_key__my_subkey.")) display = models.BooleanField(_("Display"), default=True) value_type = models.CharField(_("Type"), default="T", max_length=10, choices=JSON_VALUE_TYPES) @@ -811,7 +812,8 @@ class IshtarSiteProfile(models.Model, Cached): operation_custom_index = models.TextField( _("Operation custom index key"), default="", - help_text=_("Key to be used to manage operation custom index.")) + help_text=_("Keys to be used to manage operation custom index. " + "Separate keys with a semicolon.")) site_complete_identifier = models.TextField( _("Archaeological site complete identifier"), default="", @@ -820,8 +822,8 @@ class IshtarSiteProfile(models.Model, Cached): site_custom_index = models.TextField( _("Archaeological site custom index key"), default="", - help_text=_("Key to be used to manage archaeological site custom" - " index.")) + help_text=_("Key to be used to manage archaeological site custom " + "index. Separate keys with a semicolon.")) file_external_id = models.TextField( _("File external id"), default="{year}-{numeric_reference}", @@ -838,7 +840,7 @@ class IshtarSiteProfile(models.Model, Cached): _("Archaeological file custom index key"), default="", help_text=_("Key to be used to manage archaeological file custom " - "index.")) + "index. Separate keys with a semicolon.")) parcel_external_id = models.TextField( _("Parcel external id"), default="{associated_file__external_id}{operation__code_patriarche}-" @@ -861,7 +863,8 @@ class IshtarSiteProfile(models.Model, Cached): contextrecord_custom_index = models.TextField( _("Context record custom index key"), default="", - help_text=_("Key to be used to manage context record custom index.")) + help_text=_("Key to be used to manage context record custom index. " + "Separate keys with a semicolon.")) base_find_external_id = models.TextField( _("Base find external id"), default="{context_record__external_id}-{label}", @@ -876,7 +879,8 @@ class IshtarSiteProfile(models.Model, Cached): basefind_custom_index = models.TextField( _("Base find custom index key"), default="", - help_text=_("Key to be used to manage base find custom index.")) + help_text=_("Key to be used to manage base find custom index. " + "Separate keys with a semicolon.")) find_external_id = models.TextField( _("Find external id"), default="{get_first_base_find__context_record__external_id}-{label}", @@ -891,7 +895,8 @@ class IshtarSiteProfile(models.Model, Cached): find_custom_index = models.TextField( _("Find custom index key"), default="", - help_text=_("Key to be used to manage find custom index.")) + help_text=_("Key to be used to manage find custom index. " + "Separate keys with a semicolon.")) container_external_id = models.TextField( _("Container external id"), default="{parent_external_id}-{container_type__txt_idx}-" @@ -907,7 +912,8 @@ class IshtarSiteProfile(models.Model, Cached): container_custom_index = models.TextField( _("Container custom index key"), default="", - help_text=_("Key to be used to manage container custom index.")) + help_text=_("Key to be used to manage container custom index. " + "Separate keys with a semicolon.")) warehouse_external_id = models.TextField( _("Warehouse external id"), default="{name|slug}", @@ -922,7 +928,8 @@ class IshtarSiteProfile(models.Model, Cached): warehouse_custom_index = models.TextField( _("Warehouse custom index key"), default="", - help_text=_("Key to be used to manage warehouse custom index.")) + help_text=_("Key to be used to manage warehouse custom index. " + "Separate keys with a semicolon.")) document_external_id = models.TextField( _("Document external id"), default="{index}", @@ -937,7 +944,8 @@ class IshtarSiteProfile(models.Model, Cached): document_custom_index = models.TextField( _("Document custom index key"), default="", - help_text=_("Key to be used to manage document custom index.")) + help_text=_("Key to be used to manage document custom index. " + "Separate keys with a semicolon.")) person_raw_name = models.TextField( _("Raw name for person"), default="{name|upper} {surname}", @@ -1313,7 +1321,7 @@ class Dashboard(object): for idx in last_ids: try: obj = self.model.objects.get(pk=idx['id']) - except: + except self.model.DoesNotExist: # deleted object are always referenced in history continue obj.history_date = idx['hd'] @@ -1387,12 +1395,12 @@ class Dashboard(object): self.operation_mode = str( Operation.objects.get(pk=operation_mode_pk)) - def get_average(self, vals=[]): + def get_average(self, vals=None): if not vals: vals = self.numbers[:] return sum(vals) / len(vals) - def get_variance(self, vals=[]): + def get_variance(self, vals=None): if not vals: vals = self.numbers[:] avrg = self.get_average(vals) @@ -2610,11 +2618,12 @@ class SourceType(HierarchicalType): coins_type = models.CharField(_("COInS export - type"), default='document', max_length=100) coins_genre = models.CharField(_("COInS export - genre"), blank=True, - max_length=100) + default='', max_length=100) is_localized = models.BooleanField( _("Is localized"), default=False, help_text=_("Setting a language for this type of document is relevant") ) + code = models.CharField(_("Code"), blank=True, default='', max_length=100) class Meta: verbose_name = _("Document type") @@ -3074,16 +3083,46 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, def __str__(self): return self.title + @property + def operation_codes(self): + Operation = apps.get_model("archaeological_operations", "Operation") + return "|".join( + sorted([Operation.objects.get(pk=ope_id).code_patriarche + for ope_id in self.get_related_operation_ids()])) + + def get_related_operation_ids(self): + operations = list( + self.operations.values_list("id", flat=True).all()) + operations += list( + self.context_records.values_list( + "operation_id", flat=True).all()) + operations += list( + self.finds.values_list( + "base_finds__context_record__operation_id", flat=True).all()) + return list(set(operations)) + def get_index_operation(self): - current_operation = None - ope_nb = self.operations.count() - if ope_nb > 1: + operations = self.get_related_operation_ids() + if len(operations) != 1: return - elif ope_nb == 1: - key = 'operations__pk' - cr_nb = self.context_records.count() - if cr_nb: - self.context_records + current_operation = operations[0] + q = Document.objects.exclude(pk=self.pk).filter( + Q(operations__id=current_operation) | + Q(context_records__operation_id=current_operation) | + Q(finds__base_finds__context_record__operation_id=current_operation) + ).order_by("-custom_index") + current_index = None + for doc in q.all(): + if not doc.custom_index: + continue + if len(doc.get_related_operation_ids()) != 1: + continue + current_index = doc.custom_index + break # ordered by "-custom_index" so max current index is reached + if not current_index: + return 1 + else: + return current_index + 1 def natural_key(self): return (self.external_id,) diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index 4abc81a76..4f82cde8f 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -5,16 +5,6 @@ Generic models and tools for models """ -""" -from ishtar_common.models import GeneralType, get_external_id, \ - LightHistorizedItem, OwnPerms, Address, post_save_cache, \ - DashboardFormItem, document_attached_changed, SearchAltName, \ - DynamicRequest, GeoItem, QRCodeItem, SearchVectorConfig, DocumentItem, \ - QuickAction, MainItem, Merge - - -""" - import copy from collections import OrderedDict import datetime @@ -65,6 +55,16 @@ from ishtar_common.utils import get_cache, disable_for_loaddata, \ logger = logging.getLogger(__name__) +""" +from ishtar_common.models import GeneralType, get_external_id, \ + LightHistorizedItem, OwnPerms, Address, post_save_cache, \ + DashboardFormItem, document_attached_changed, SearchAltName, \ + DynamicRequest, GeoItem, QRCodeItem, SearchVectorConfig, DocumentItem, \ + QuickAction, MainItem, Merge + + +""" + class CachedGen(object): @classmethod @@ -2364,6 +2364,8 @@ def document_attached_changed(sender, **kwargs): return for item in items: + for doc in item.documents.all(): + doc.regenerate_all_ids() q = item.documents.filter( image__isnull=False).exclude(image='') if item.main_image: @@ -2727,8 +2729,8 @@ class CompleteIdentifierItem(models.Model, ImageContainerModel): if not key or not key.strip(): return keys = key.strip().split(";") - if len(key) == 1 and hasattr(self, "get_index_" + key): # custom index - # generation + if len(keys) == 1 and hasattr(self, "get_index_" + keys[0]): + # custom index generation return getattr(self, "get_index_" + key)() model = self.__class__ try: @@ -2758,6 +2760,9 @@ class CompleteIdentifierItem(models.Model, ImageContainerModel): def save(self, *args, **kwargs): super(CompleteIdentifierItem, self).save(*args, **kwargs) + self.regenerate_all_ids() + + def regenerate_all_ids(self): if getattr(self, "_prevent_loop", False): return modified = False @@ -2771,6 +2776,7 @@ class CompleteIdentifierItem(models.Model, ImageContainerModel): self.complete_identifier = complete_id if modified: self._prevent_loop = True + self.skip_history_when_saving = True self.save() diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py index 94a636b7b..05799a0aa 100644 --- a/ishtar_common/tests.py +++ b/ishtar_common/tests.py @@ -1253,7 +1253,7 @@ class UserProfileTest(TestCase): base_profile = models.UserProfile.objects.get(pk=base_profile.pk) self.assertEqual( base_profile.name, - u"New name" + "New name" ) self.client.post( @@ -2217,7 +2217,6 @@ class ShortMenuTest(TestCase): class ImportTest(TestCase): - def create_import(self): create_user() imp_model = models.ImporterModel.objects.create( @@ -2494,8 +2493,6 @@ class NewItems(TestCase): self.assertEqual(person.author.count(), 1) - - class AccountWizardTest(WizardTest, TestCase): fixtures = [settings.ROOT_PATH + '../fixtures/initial_data-auth-fr.json', @@ -2538,7 +2535,6 @@ class AccountWizardTest(WizardTest, TestCase): class DashboardTest(TestCase): - def setUp(self): self.username, self.password, self.user = create_superuser() profile, created = models.IshtarSiteProfile.objects.get_or_create( @@ -2568,7 +2564,6 @@ class DashboardTest(TestCase): class CleanMedia(TestCase): - def test_rename(self): test_names = [ ("éofficier2-12-02-04.93_gvK3hAr-1_2m7zZPn-1_nKhh2S2-1_"\ @@ -2646,3 +2641,84 @@ class PersonQATest(TestCase): self.title_2 ) + +class DocumentTest(TestCase): + def test_custom_index(self): + Operation = apps.get_model("archaeological_operations", "Operation") + ContextRecord = apps.get_model("archaeological_context_records", + "ContextRecord") + Unit = apps.get_model("archaeological_context_records", "Unit") + BaseFind = apps.get_model("archaeological_finds", "BaseFind") + Find = apps.get_model("archaeological_finds", "Find") + operation_type, __ = models.OperationType.objects.get_or_create( + txt_idx="arch_diagnostic", label="Diagnostic") + ope1 = Operation.objects.create( + code_patriarche="001", + operation_type_id=operation_type.pk) + ope2 = Operation.objects.create( + code_patriarche="002", + operation_type_id=operation_type.pk) + su, __ = Unit.objects.get_or_create( + txt_idx='stratigraphic-unit', label="Stratigraphic unit", order=1) + cr1 = ContextRecord.objects.create(operation=ope1, unit=su) + cr2 = ContextRecord.objects.create(operation=ope2, unit=su) + bf1 = BaseFind.objects.create(context_record=cr1) + bf2 = BaseFind.objects.create(context_record=cr2) + find1 = Find.objects.create() + find1.base_finds.add(bf1) + find2 = Find.objects.create() + find2.base_finds.add(bf2) + st1 = models.SourceType.objects.create(label="Report", code="REP") + st2 = models.SourceType.objects.create(label="Illustration", code="ILL") + + profile, created = models.IshtarSiteProfile.objects.get_or_create( + slug='default', active=True) + profile.document_complete_identifier = \ + "{operation_codes}-{source_type__code}-{custom_index}" + profile.document_custom_index = "operation" + profile.save() + + doc = models.Document.objects.create(source_type=st1, + title="Operation report") + doc.operations.add(ope1) + doc = models.Document.objects.get(pk=doc.pk) + self.assertEqual(doc.complete_identifier, "001-REP-1") + + doc2 = models.Document.objects.create(source_type=st2, + title="Illustration CR") + doc2.context_records.add(cr1) + doc2 = models.Document.objects.get(pk=doc2.pk) + self.assertEqual(doc2.complete_identifier, "001-ILL-2") + + doc3 = models.Document.objects.create(source_type=st1, + title="Operation report 2") + doc3.operations.add(ope2) + doc3 = models.Document.objects.get(pk=doc3.pk) + self.assertEqual(doc3.complete_identifier, "002-REP-1") + + doc3.operations.add(ope1) + doc3.custom_index = None + doc3.save() + doc3 = models.Document.objects.get(pk=doc3.pk) + self.assertEqual(doc3.custom_index, None) # 2 operations - no index + self.assertEqual(doc3.complete_identifier, '001|002-REP-') + + # complex jinja identifier + profile.document_complete_identifier = \ + "{% if custom_index %}{{operation_codes}}-{{source_type__code}}-" \ + "{{ \"%03d\" % (custom_index|int)}}{% else %}no-code{% endif %}" + profile.save() + + doc3.operations.remove(ope1) + doc3.custom_index = None + doc3.complete_identifier = "" + doc3.save() + doc3 = models.Document.objects.get(pk=doc3.pk) + self.assertEqual(doc3.complete_identifier, '002-REP-001') + + doc3.operations.remove(ope2) + doc3.custom_index = None + doc3.complete_identifier = "" + doc3.save() + doc3 = models.Document.objects.get(pk=doc3.pk) + self.assertEqual(doc3.complete_identifier, 'no-code') diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 70c374731..d86c94d86 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -26,6 +26,7 @@ from inspect import currentframe, getframeinfo import hashlib from importlib import import_module import io +from jinja2 import Template import os import random import re @@ -1764,6 +1765,8 @@ def get_current_profile(force=False): PARSE_FORMULA = re.compile("{([^}]*)}") +PARSE_JINJA = re.compile("{{([^}]*)}") +PARSE_JINJA_IF = re.compile("{% if ([^}]*)}") def _deduplicate(value): @@ -1782,12 +1785,77 @@ FORMULA_FILTERS = { 'deduplicate': _deduplicate } + +def _update_gen_id_dct(item, dct, initial_key, fkey=None, filters=None): + if not fkey: + fkey = initial_key[:] + if fkey.startswith('settings__'): + dct[fkey] = getattr(settings, fkey[len('settings__'):]) or '' + return + obj = item + for k in fkey.split('__'): + try: + obj = getattr(obj, k) + except (ObjectDoesNotExist, AttributeError): + obj = None + if hasattr(obj, 'all') and hasattr(obj, 'count'): # query manager + if not obj.count(): + break + obj = obj.all()[0] + elif callable(obj): + obj = obj() + if obj is None: + break + if obj is None: + dct[initial_key] = '' + else: + dct[initial_key] = str(obj) + if filters: + for filtr in filters: + dct[initial_key] = filtr(dct[initial_key]) + + def get_generated_id(key, item): profile = get_current_profile() if not hasattr(profile, key): return formula = getattr(profile, key) + dct = {} + # jinja2 style + if "{{" in formula or "{%" in formula: + # naive parse - only simple jija2 is managed + key_list = [] + for key in PARSE_JINJA.findall(formula): + key = key.strip().split("|")[0] + key_list.append(key) + + for keys in PARSE_JINJA_IF.findall(formula): + sub_key_list = keys.split(" or ") + res = [] + for keys2 in sub_key_list: + res += keys2.split(" and ") + + key_list = map(lambda x: x.strip(), key_list) + new_keys = [] + for key in key_list: + if key.startswith("not "): + key = key[len("not "):].strip() + key = key.split(".")[0] + if " % " in key: + keys = key.split(" % ")[1] + keys = [ + i.replace("(", "").replace(")", "").split("|")[0].strip() + for i in keys.split(",")] + else: + keys = [key] + new_keys += keys + key_list = new_keys + for key in set(key_list): + _update_gen_id_dct(item, dct, key) + tpl = Template(formula) + return tpl.render(dct) + for fkey in PARSE_FORMULA.findall(formula): filtered = fkey.split('|') initial_key = fkey[:] @@ -1796,29 +1864,7 @@ def get_generated_id(key, item): for filtr in filtered[1:]: if filtr in FORMULA_FILTERS: filters.append(FORMULA_FILTERS[filtr]) - if fkey.startswith('settings__'): - dct[fkey] = getattr(settings, fkey[len('settings__'):]) or '' - continue - obj = item - for k in fkey.split('__'): - try: - obj = getattr(obj, k) - except ObjectDoesNotExist: - obj = None - if hasattr(obj, 'all') and hasattr(obj, 'count'): # query manager - if not obj.count(): - break - obj = obj.all()[0] - elif callable(obj): - obj = obj() - if obj is None: - break - if obj is None: - dct[initial_key] = '' - else: - dct[initial_key] = str(obj) - for filtr in filters: - dct[initial_key] = filtr(dct[initial_key]) + _update_gen_id_dct(item, dct, initial_key, fkey, filters=filters) values = formula.format(**dct).split('||') value = values[0] for filtr in values[1:]: diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py index 056385918..8efcaa9ee 100644 --- a/ishtar_common/views_item.py +++ b/ishtar_common/views_item.py @@ -34,8 +34,9 @@ from weasyprint.fonts import FontConfiguration from ishtar_common.utils import check_model_access_control, CSV_OPTIONS, \ get_all_field_names, Round, PRIVATE_FIELDS -from ishtar_common.models import HistoryError, get_current_profile, \ +from ishtar_common.models import get_current_profile, \ GeneralType, SearchAltName +from ishtar_common.models_common import HistoryError from .menus import Menu from . import models |