diff options
| -rw-r--r-- | archaeological_context_records/migrations/0101_squashed.py | 10 | ||||
| -rw-r--r-- | archaeological_files/migrations/0101_squashed.py | 4 | ||||
| -rw-r--r-- | archaeological_finds/migrations/0101_squashed.py | 22 | ||||
| -rw-r--r-- | archaeological_operations/migrations/0101_squashed.py | 27 | ||||
| -rw-r--r-- | archaeological_warehouse/migrations/0101_squashed.py | 10 | ||||
| -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 | 
12 files changed, 329 insertions, 98 deletions
| diff --git a/archaeological_context_records/migrations/0101_squashed.py b/archaeological_context_records/migrations/0101_squashed.py index ee6bc9866..d8b56641a 100644 --- a/archaeological_context_records/migrations/0101_squashed.py +++ b/archaeological_context_records/migrations/0101_squashed.py @@ -10,6 +10,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 @@ -112,7 +113,14 @@ class Migration(migrations.Migration):                  'ordering': ('cached_label',),                  'permissions': (('view_contextrecord', 'Can view all Context Records'), ('view_own_contextrecord', 'Can view own Context Record'), ('add_own_contextrecord', 'Can add own Context Record'), ('change_own_contextrecord', 'Can change own Context Record'), ('delete_own_contextrecord', 'Can delete own Context Record')),              }, -            bases=(ishtar_common.models.BulkUpdatedItem, ishtar_common.models.DocumentItem, ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel), +            bases=(ishtar_common.models.BulkUpdatedItem, +                   ishtar_common.models.DocumentItem, +                   ishtar_common.models.StatisticItem, +                   ishtar_common.models.TemplateItem, +                   ishtar_common.models.OwnPerms, +                   ishtar_common.models.ValueGetter, +                   ishtar_common.models.MainItem, models.Model, +                   ishtar_common.models.CachedGen, ishtar_common.models_common.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel),          ),          migrations.CreateModel(              name='Dating', diff --git a/archaeological_files/migrations/0101_squashed.py b/archaeological_files/migrations/0101_squashed.py index 0eb99aa27..0ef3a49c5 100644 --- a/archaeological_files/migrations/0101_squashed.py +++ b/archaeological_files/migrations/0101_squashed.py @@ -13,6 +13,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 ishtar_common.utils  import re @@ -82,7 +83,8 @@ class Migration(migrations.Migration):                  'ordering': ('cached_label',),                  'permissions': (('view_file', 'Can view all Archaeological files'), ('view_own_file', 'Can view own Archaeological file'), ('add_own_file', 'Can add own Archaeological file'), ('change_own_file', 'Can change own Archaeological file'), ('delete_own_file', 'Can delete own Archaeological file'), ('close_file', 'Can close File')),              }, -            bases=(archaeological_operations.models.ClosedItem, ishtar_common.models.DocumentItem, 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.ShortMenuItem, ishtar_common.models.DashboardFormItem), +            bases=(archaeological_operations.models.ClosedItem, +                   ishtar_common.models.DocumentItem, 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.ShortMenuItem, ishtar_common.models.DashboardFormItem),          ),          migrations.CreateModel(              name='FileType', diff --git a/archaeological_finds/migrations/0101_squashed.py b/archaeological_finds/migrations/0101_squashed.py index 8d4f4040c..7f4df1aa9 100644 --- a/archaeological_finds/migrations/0101_squashed.py +++ b/archaeological_finds/migrations/0101_squashed.py @@ -13,6 +13,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 ishtar_common.utils  import re  import uuid @@ -102,7 +103,8 @@ class Migration(migrations.Migration):                  'verbose_name_plural': 'Base finds',                  'permissions': (('view_basefind', 'Can view all Base finds'), ('view_own_basefind', 'Can view own Base find'), ('add_own_basefind', 'Can add own Base find'), ('change_own_basefind', 'Can change own Base find'), ('delete_own_basefind', 'Can delete own Base find')),              }, -            bases=(ishtar_common.models.BulkUpdatedItem, 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), +            bases=(ishtar_common.models.BulkUpdatedItem, +                   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),          ),          migrations.CreateModel(              name='BatchType', @@ -234,7 +236,8 @@ class Migration(migrations.Migration):                  'ordering': ('cached_label',),                  'permissions': (('view_find', 'Can view all Finds'), ('view_own_find', 'Can view own Find'), ('add_own_find', 'Can add own Find'), ('change_own_find', 'Can change own Find'), ('delete_own_find', 'Can delete own Find')),              }, -            bases=(ishtar_common.models.BulkUpdatedItem, ishtar_common.models.ValueGetter, ishtar_common.models.DocumentItem, 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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.MainItem), +            bases=(ishtar_common.models.BulkUpdatedItem, +                   ishtar_common.models.ValueGetter, ishtar_common.models.DocumentItem, 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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.MainItem),          ),          migrations.CreateModel(              name='FindBasket', @@ -544,7 +547,10 @@ class Migration(migrations.Migration):                  'verbose_name': 'Property',                  'verbose_name_plural': 'Properties',              }, -            bases=(ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate), +            bases=(ishtar_common.models.StatisticItem, +                   ishtar_common.models.TemplateItem, models.Model, +                   ishtar_common.models.CachedGen, +                   ishtar_common.models_common.FixAssociated, ishtar_common.models.CascasdeUpdate),          ),          migrations.CreateModel(              name='RemarkabilityType', @@ -598,7 +604,10 @@ class Migration(migrations.Migration):                  'ordering': ('-year', '-index', '-start_date'),                  'permissions': (('view_treatment', 'Can view all Treatments'), ('view_own_treatment', 'Can view own Treatment'), ('add_own_treatment', 'Can add own Treatment'), ('change_own_treatment', 'Can change own Treatment'), ('delete_own_treatment', 'Can delete own Treatment')),              }, -            bases=(ishtar_common.models.DashboardFormItem, ishtar_common.models.ValueGetter, ishtar_common.models.DocumentItem, 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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.ShortMenuItem), +            bases=(ishtar_common.models.DashboardFormItem, +                   ishtar_common.models.ValueGetter, +                   ishtar_common.models.DocumentItem, +                   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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.ShortMenuItem),          ),          migrations.CreateModel(              name='TreatmentEmergencyType', @@ -656,7 +665,10 @@ class Migration(migrations.Migration):                  'ordering': ('cached_label',),                  'permissions': (('view_treatmentfile', 'Can view all Treatment requests'), ('view_own_treatmentfile', 'Can view own Treatment request'), ('add_own_treatmentfile', 'Can add own Treatment request'), ('change_own_treatmentfile', 'Can change own Treatment request'), ('delete_own_treatmentfile', 'Can delete own Treatment request')),              }, -            bases=(ishtar_common.models.DashboardFormItem, archaeological_operations.models.ClosedItem, ishtar_common.models.DocumentItem, 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.ShortMenuItem), +            bases=(ishtar_common.models.DashboardFormItem, +                   archaeological_operations.models.ClosedItem, +                   ishtar_common.models.DocumentItem, +                   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.ShortMenuItem),          ),          migrations.CreateModel(              name='TreatmentFileType', diff --git a/archaeological_operations/migrations/0101_squashed.py b/archaeological_operations/migrations/0101_squashed.py index c55e7a8b9..7027406b8 100644 --- a/archaeological_operations/migrations/0101_squashed.py +++ b/archaeological_operations/migrations/0101_squashed.py @@ -13,6 +13,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 @@ -87,7 +88,10 @@ class Migration(migrations.Migration):                  'ordering': ('year', 'signature_date', 'index', 'act_type'),                  'permissions': (('view_administrativeact', 'Can view all Administrative acts'), ('view_own_administrativeact', 'Can view own Administrative act'), ('add_own_administrativeact', 'Can add own Administrative act'), ('change_own_administrativeact', 'Can change own Administrative act'), ('delete_own_administrativeact', 'Can delete own Administrative act')),              }, -            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), +            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),          ),          migrations.CreateModel(              name='ArchaeologicalSite', @@ -137,7 +141,10 @@ class Migration(migrations.Migration):                  'verbose_name_plural': 'Archaeological sites',                  'permissions': (('view_archaeologicalsite', 'Can view all Archaeological sites'), ('view_own_archaeologicalsite', 'Can view own Archaeological site'), ('add_own_archaeologicalsite', 'Can add own Archaeological site'), ('change_own_archaeologicalsite', 'Can change own Archaeological site'), ('delete_own_archaeologicalsite', 'Can delete own Archaeological site')),              }, -            bases=(ishtar_common.models.DocumentItem, 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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem), +            bases=(ishtar_common.models.DocumentItem, +                   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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem),          ),          migrations.CreateModel(              name='CulturalAttributionType', @@ -441,7 +448,13 @@ class Migration(migrations.Migration):                  'ordering': ('cached_label',),                  'permissions': (('view_operation', 'Can view all Operations'), ('view_own_operation', 'Can view own Operation'), ('add_own_operation', 'Can add own Operation'), ('change_own_operation', 'Can change own Operation'), ('delete_own_operation', 'Can delete own Operation'), ('close_operation', 'Can close Operation')),              }, -            bases=(archaeological_operations.models.ClosedItem, ishtar_common.models.DocumentItem, ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem, ishtar_common.models.DashboardFormItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel), +            bases=(archaeological_operations.models.ClosedItem, +                   ishtar_common.models.DocumentItem, +                   ishtar_common.models.StatisticItem, +                   ishtar_common.models.TemplateItem, +                   ishtar_common.models.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models.MainItem, ishtar_common.models.DashboardFormItem, models.Model, ishtar_common.models.CachedGen, +                   ishtar_common.models_common.FixAssociated, +                   ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel),          ),          migrations.CreateModel(              name='OperationTypeOld', @@ -494,7 +507,8 @@ class Migration(migrations.Migration):                  'verbose_name_plural': 'Parcels',                  'ordering': ('year', 'section', 'parcel_number'),              }, -            bases=(ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate), +            bases=(ishtar_common.models.StatisticItem, +                   ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models_common.FixAssociated, ishtar_common.models.CascasdeUpdate),          ),          migrations.CreateModel(              name='ParcelOwner', @@ -521,7 +535,10 @@ class Migration(migrations.Migration):                  'verbose_name': 'Parcel owner',                  'verbose_name_plural': 'Parcel owners',              }, -            bases=(ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate), +            bases=(ishtar_common.models.StatisticItem, +                   ishtar_common.models.TemplateItem, models.Model, +                   ishtar_common.models.CachedGen, +                   ishtar_common.models_common.FixAssociated, ishtar_common.models.CascasdeUpdate),          ),          migrations.CreateModel(              name='Period', diff --git a/archaeological_warehouse/migrations/0101_squashed.py b/archaeological_warehouse/migrations/0101_squashed.py index 4a5b39616..77fccef09 100644 --- a/archaeological_warehouse/migrations/0101_squashed.py +++ b/archaeological_warehouse/migrations/0101_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 @@ -46,7 +47,8 @@ class Migration(migrations.Migration):                  'verbose_name_plural': 'Collection',                  'ordering': ('name',),              }, -            bases=(ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate), +            bases=(ishtar_common.models.StatisticItem, +                   ishtar_common.models.TemplateItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models_common.FixAssociated, ishtar_common.models.CascasdeUpdate),          ),          migrations.CreateModel(              name='Container', @@ -90,7 +92,8 @@ class Migration(migrations.Migration):                  'ordering': ('cached_label',),                  'permissions': (('view_container', 'Can view all Containers'), ('view_own_container', 'Can view own Container'), ('add_own_container', 'Can add own Container'), ('change_own_container', 'Can change own Container'), ('delete_own_container', 'Can delete own Container')),              }, -            bases=(ishtar_common.models.DocumentItem, 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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.MainItem), +            bases=(ishtar_common.models.DocumentItem, +                   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.ImageContainerModel, ishtar_common.models.OwnPerms, ishtar_common.models.MainItem),          ),          migrations.CreateModel(              name='ContainerLocalisation', @@ -181,7 +184,8 @@ class Migration(migrations.Migration):                  'verbose_name_plural': 'Warehouses',                  'permissions': (('view_warehouse', 'Can view all Warehouses'), ('view_own_warehouse', 'Can view own Warehouse'), ('add_own_warehouse', 'Can add own Warehouse'), ('change_own_warehouse', 'Can change own Warehouse'), ('delete_own_warehouse', 'Can delete own Warehouse')),              }, -            bases=(ishtar_common.models.StatisticItem, ishtar_common.models.TemplateItem, ishtar_common.models.DocumentItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel, ishtar_common.models.DashboardFormItem, ishtar_common.models.OwnPerms, ishtar_common.models.MainItem), +            bases=(ishtar_common.models.StatisticItem, +                   ishtar_common.models.TemplateItem, ishtar_common.models.DocumentItem, models.Model, ishtar_common.models.CachedGen, ishtar_common.models_common.FixAssociated, ishtar_common.models.CascasdeUpdate, ishtar_common.models.ImageContainerModel, ishtar_common.models.DashboardFormItem, ishtar_common.models.OwnPerms, ishtar_common.models.MainItem),          ),          migrations.CreateModel(              name='WarehouseDivision', 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 | 
