From a7a0b8e6cf8d67cc50eefe79a65caa93f6059169 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Mon, 2 Oct 2023 16:39:42 +0200 Subject: 🗃️ DB changes to manage user permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0113_contextrecord_imports_updated.py | 4 +- .../migrations/0112_file_imports_updated.py | 4 +- .../migrations/0113_auto_20230922_1443.py | 39 ---- .../migrations/0113_auto_20231002_1617.py | 39 ++++ .../migrations/0113_auto_20230922_1443.py | 39 ---- .../migrations/0113_auto_20231002_1613.py | 39 ++++ .../migrations/0119_auto_20230922_1443.py | 24 --- .../migrations/0119_auto_20231002_1617.py | 24 +++ changelog/fr/changelog_2023-01-25.md | 11 +- .../migrations/0230_auto_20230922_1443.py | 169 --------------- .../migrations/0230_auto_20231002_1613.py | 173 ++++++++++++++++ .../migrations/0231_default_mandatory_keys.py | 2 +- ishtar_common/models_common.py | 221 +------------------- ishtar_common/models_imports.py | 29 ++- ishtar_common/utils.py | 227 ++++++++++++++++++++- 15 files changed, 539 insertions(+), 505 deletions(-) delete mode 100644 archaeological_finds/migrations/0113_auto_20230922_1443.py create mode 100644 archaeological_finds/migrations/0113_auto_20231002_1617.py delete mode 100644 archaeological_operations/migrations/0113_auto_20230922_1443.py create mode 100644 archaeological_operations/migrations/0113_auto_20231002_1613.py delete mode 100644 archaeological_warehouse/migrations/0119_auto_20230922_1443.py create mode 100644 archaeological_warehouse/migrations/0119_auto_20231002_1617.py delete mode 100644 ishtar_common/migrations/0230_auto_20230922_1443.py create mode 100644 ishtar_common/migrations/0230_auto_20231002_1613.py diff --git a/archaeological_context_records/migrations/0113_contextrecord_imports_updated.py b/archaeological_context_records/migrations/0113_contextrecord_imports_updated.py index bda2c3122..97b09dfcb 100644 --- a/archaeological_context_records/migrations/0113_contextrecord_imports_updated.py +++ b/archaeological_context_records/migrations/0113_contextrecord_imports_updated.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.24 on 2023-09-22 14:43 +# Generated by Django 2.2.24 on 2023-10-02 16:13 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('ishtar_common', '0230_auto_20230922_1443'), + ('ishtar_common', '0230_auto_20231002_1613'), ('archaeological_context_records', '0112_migrate_created'), ] diff --git a/archaeological_files/migrations/0112_file_imports_updated.py b/archaeological_files/migrations/0112_file_imports_updated.py index 226f4988a..de242a6ba 100644 --- a/archaeological_files/migrations/0112_file_imports_updated.py +++ b/archaeological_files/migrations/0112_file_imports_updated.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.24 on 2023-09-22 14:43 +# Generated by Django 2.2.24 on 2023-10-02 16:17 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('ishtar_common', '0230_auto_20230922_1443'), + ('ishtar_common', '0230_auto_20231002_1613'), ('archaeological_files', '0111_migrate_created'), ] diff --git a/archaeological_finds/migrations/0113_auto_20230922_1443.py b/archaeological_finds/migrations/0113_auto_20230922_1443.py deleted file mode 100644 index ba2e761f3..000000000 --- a/archaeological_finds/migrations/0113_auto_20230922_1443.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 2.2.24 on 2023-09-22 14:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ishtar_common', '0230_auto_20230922_1443'), - ('archaeological_finds', '0112_migrate_created'), - ] - - operations = [ - migrations.AddField( - model_name='basefind', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_basefind', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='find', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_find', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='property', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_property', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='treatment', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_treatment', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='treatmentfile', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_treatmentfile', to='ishtar_common.Import'), - ), - ] diff --git a/archaeological_finds/migrations/0113_auto_20231002_1617.py b/archaeological_finds/migrations/0113_auto_20231002_1617.py new file mode 100644 index 000000000..000e99de9 --- /dev/null +++ b/archaeological_finds/migrations/0113_auto_20231002_1617.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.24 on 2023-10-02 16:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0230_auto_20231002_1613'), + ('archaeological_finds', '0112_migrate_created'), + ] + + operations = [ + migrations.AddField( + model_name='basefind', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_basefind', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='find', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_find', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='property', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_property', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='treatment', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_treatment', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='treatmentfile', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_treatmentfile', to='ishtar_common.Import'), + ), + ] diff --git a/archaeological_operations/migrations/0113_auto_20230922_1443.py b/archaeological_operations/migrations/0113_auto_20230922_1443.py deleted file mode 100644 index 7070b0c33..000000000 --- a/archaeological_operations/migrations/0113_auto_20230922_1443.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 2.2.24 on 2023-09-22 14:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ishtar_common', '0230_auto_20230922_1443'), - ('archaeological_operations', '0112_migrate_created'), - ] - - operations = [ - migrations.AddField( - model_name='administrativeact', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_administrativeact', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='archaeologicalsite', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_archaeologicalsite', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='operation', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_operation', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='parcel', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_parcel', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='parcelowner', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_parcelowner', to='ishtar_common.Import'), - ), - ] diff --git a/archaeological_operations/migrations/0113_auto_20231002_1613.py b/archaeological_operations/migrations/0113_auto_20231002_1613.py new file mode 100644 index 000000000..eab32be8c --- /dev/null +++ b/archaeological_operations/migrations/0113_auto_20231002_1613.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.24 on 2023-10-02 16:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0230_auto_20231002_1613'), + ('archaeological_operations', '0112_migrate_created'), + ] + + operations = [ + migrations.AddField( + model_name='administrativeact', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_administrativeact', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='archaeologicalsite', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_archaeologicalsite', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='operation', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_operation', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='parcel', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_parcel', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='parcelowner', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_operations_parcelowner', to='ishtar_common.Import'), + ), + ] diff --git a/archaeological_warehouse/migrations/0119_auto_20230922_1443.py b/archaeological_warehouse/migrations/0119_auto_20230922_1443.py deleted file mode 100644 index 43020abc3..000000000 --- a/archaeological_warehouse/migrations/0119_auto_20230922_1443.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.24 on 2023-09-22 14:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ishtar_common', '0230_auto_20230922_1443'), - ('archaeological_warehouse', '0118_auto_20230807_1106'), - ] - - operations = [ - migrations.AddField( - model_name='container', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_warehouse_container', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='warehouse', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_warehouse_warehouse', to='ishtar_common.Import'), - ), - ] diff --git a/archaeological_warehouse/migrations/0119_auto_20231002_1617.py b/archaeological_warehouse/migrations/0119_auto_20231002_1617.py new file mode 100644 index 000000000..b05a88886 --- /dev/null +++ b/archaeological_warehouse/migrations/0119_auto_20231002_1617.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.24 on 2023-10-02 16:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0230_auto_20231002_1613'), + ('archaeological_warehouse', '0118_auto_20230807_1106'), + ] + + operations = [ + migrations.AddField( + model_name='container', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_warehouse_container', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='warehouse', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_archaeological_warehouse_warehouse', to='ishtar_common.Import'), + ), + ] diff --git a/changelog/fr/changelog_2023-01-25.md b/changelog/fr/changelog_2023-01-25.md index 95cc9a36f..82accf4f1 100644 --- a/changelog/fr/changelog_2023-01-25.md +++ b/changelog/fr/changelog_2023-01-25.md @@ -3,12 +3,13 @@ v4.0.XX - 2099-12-31 ### Fonctionnalités/améliorations ### - ajout de formulaire pré-imports -- formulaire d'imports: réorganisation de l'ordre des champs +- formulaire d'imports : réorganisation de l'ordre des champs - table des imports : - - visualisateur CSV intégré - - raffrachissement automatique de l'avancement - - réorganisation des champs - - amélioration de la présentation + - visualisateur CSV intégré + - rafraîchissement automatique de l'avancement + - réorganisation des champs + - amélioration de la présentation + ### Technique ### - relation de mise à jour entre imports et les éléments principaux diff --git a/ishtar_common/migrations/0230_auto_20230922_1443.py b/ishtar_common/migrations/0230_auto_20230922_1443.py deleted file mode 100644 index 7839e0cd0..000000000 --- a/ishtar_common/migrations/0230_auto_20230922_1443.py +++ /dev/null @@ -1,169 +0,0 @@ -# Generated by Django 2.2.24 on 2023-09-22 14:43 - -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('ishtar_common', '0229_auto_20230608_1303'), - ] - - operations = [ - migrations.CreateModel( - name='ImporterGroup', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='Name')), - ('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')), - ('description', models.TextField(blank=True, default='', verbose_name='Description')), - ('available', models.BooleanField(default=True, verbose_name='Available')), - ], - options={ - 'verbose_name': 'Importer - Group', - 'verbose_name_plural': 'Importer - Groups', - 'ordering': ('name',), - }, - ), - migrations.AlterModelOptions( - name='import', - options={'verbose_name': 'Import - Import', 'verbose_name_plural': 'Import - Imports'}, - ), - migrations.AlterModelOptions( - name='itemkey', - options={'verbose_name': 'Import - Item key', 'verbose_name_plural': 'Import - Item keys'}, - ), - migrations.AddField( - model_name='document', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_document', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='geovectordata', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_geovectordata', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='import', - name='next_import', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='imports', to='ishtar_common.Import', verbose_name='Next import'), - ), - migrations.AddField( - model_name='importerdefault', - name='required_fields', - field=models.CharField(blank=True, default='', help_text='Theses defaults values only apply if the designated fields are not empty. Use the "__" notation to pass between models.Leave empty to always apply.', max_length=500, verbose_name='Required fields'), - ), - migrations.AddField( - model_name='importertype', - name='archive_required', - field=models.BooleanField(default=False, verbose_name='Archive required'), - ), - migrations.AddField( - model_name='importertype', - name='is_import', - field=models.BooleanField(default=True, verbose_name='Can be import'), - ), - migrations.AddField( - model_name='importertype', - name='pre_import_message', - field=models.TextField(blank=True, default='', max_length=500, verbose_name='Pre-import form message'), - ), - migrations.AddField( - model_name='importertype', - name='tab_number', - field=models.PositiveIntegerField(default=1, help_text='When using an Excel or Calc file choose the tab number. Keep it to 1 by default.', validators=[django.core.validators.MinValueValidator(1)], verbose_name='Tab number'), - ), - migrations.AddField( - model_name='organization', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_organization', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='person', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_person', to='ishtar_common.Import'), - ), - migrations.AddField( - model_name='town', - name='imports_updated', - field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_town', to='ishtar_common.Import'), - ), - migrations.AlterField( - model_name='import', - name='imported_images', - field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=220, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Associated documents (zip file)'), - ), - migrations.AlterField( - model_name='importercolumn', - name='col_number', - field=models.SmallIntegerField(default=1, help_text='Column number in the table. Put 0 or negative number for pre-importer field.', verbose_name='Column number'), - ), - migrations.AlterField( - model_name='importerdefault', - name='target', - field=models.CharField(help_text='The target of the default values. Can be set to empty with "-". Use the "__" notation to pass between models.', max_length=500, verbose_name='Target'), - ), - migrations.AlterField( - model_name='ishtarsiteprofile', - name='account_naming_style', - field=models.CharField(choices=[('NF', 'name.firstname'), ('FN', 'firstname.name')], default='FN', max_length=2, verbose_name='Naming style for accounts'), - ), - migrations.CreateModel( - name='ImportGroup', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=500, null=True, verbose_name='Name')), - ('imported_file', models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=220, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Imported file')), - ('imported_images', models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=220, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Associated documents (zip file)')), - ('archive_file', models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Archive file')), - ('encoding', models.CharField(choices=[('windows-1252', 'windows-1252'), ('ISO-8859-15', 'ISO-8859-15'), ('utf-8', 'utf-8')], default='utf-8', help_text='Only required for CSV file', max_length=15, verbose_name='Encoding')), - ('csv_sep', models.CharField(choices=[(',', ','), (';', ';'), ('|', '|')], default=',', help_text='Separator for CSV file. Standard is comma but Microsoft Excel do not follow this standard and use semi-colon.', max_length=1, verbose_name='CSV separator')), - ('skip_lines', models.IntegerField(default=1, help_text='Number of header lines in your file (can be 0 and should be 0 for geopackage or Shapefile).', verbose_name='Skip lines')), - ('creation_date', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Creation date')), - ('end_date', models.DateTimeField(auto_now_add=True, null=True, verbose_name='End date')), - ('current_import', models.PositiveIntegerField(blank=True, null=True, verbose_name='Current import')), - ('state', models.CharField(choices=[('C', 'Created'), ('AP', 'Analyse in progress'), ('A', 'Analysed'), ('IQ', 'Import in queue'), ('IP', 'Import in progress'), ('PP', 'Post-processing in progress'), ('FE', 'Finished with errors'), ('F', 'Finished'), ('AC', 'Archived')], default='C', max_length=2, verbose_name='State')), - ('importer_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.ImporterGroup', verbose_name='Importer group type')), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ishtar_common.IshtarUser')), - ], - options={ - 'verbose_name': 'Import - Group', - 'verbose_name_plural': 'Import - Groups', - }, - ), - migrations.AddField( - model_name='import', - name='group', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='imports', to='ishtar_common.ImportGroup', verbose_name='Group'), - ), - migrations.CreateModel( - name='ImporterGroupImporter', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('order', models.PositiveIntegerField(default=10, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Order')), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='importer_types', to='ishtar_common.ImporterGroup')), - ('importer_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='ishtar_common.ImporterType')), - ], - options={ - 'verbose_name': 'Importer - Group <-> Importer', - 'ordering': ('group', 'order'), - 'unique_together': {('group', 'order')}, - }, - ), - migrations.CreateModel( - name='ImportColumnValue', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('value', models.TextField(blank=True, default='')), - ('column', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.ImporterColumn')), - ('import_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.Import')), - ], - options={ - 'verbose_name': 'Import - Pre-import value', - 'verbose_name_plural': 'Import - Pre-import values', - 'unique_together': {('column', 'import_item')}, - }, - ), - ] diff --git a/ishtar_common/migrations/0230_auto_20231002_1613.py b/ishtar_common/migrations/0230_auto_20231002_1613.py new file mode 100644 index 000000000..ec409c5e2 --- /dev/null +++ b/ishtar_common/migrations/0230_auto_20231002_1613.py @@ -0,0 +1,173 @@ +# Generated by Django 2.2.24 on 2023-10-02 16:13 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import ishtar_common.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0229_auto_20230608_1303'), + ] + + operations = [ + migrations.CreateModel( + name='ImporterGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, verbose_name='Name')), + ('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')), + ('description', models.TextField(blank=True, default='', verbose_name='Description')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ('users', models.ManyToManyField(blank=True, to='ishtar_common.IshtarUser', verbose_name='Users')), + ], + options={ + 'verbose_name': 'Importer - Group', + 'verbose_name_plural': 'Importer - Groups', + 'ordering': ('name',), + }, + ), + migrations.AlterModelOptions( + name='import', + options={'permissions': (('view_own_import', 'Can view own Import'), ('add_own_import', 'Can add own Import'), ('change_own_import', 'Can change own Import'), ('delete_own_import', 'Can delete own Import')), 'verbose_name': 'Import - Import', 'verbose_name_plural': 'Import - Imports'}, + ), + migrations.AlterModelOptions( + name='itemkey', + options={'verbose_name': 'Import - Item key', 'verbose_name_plural': 'Import - Item keys'}, + ), + migrations.AddField( + model_name='document', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_document', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='geovectordata', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_geovectordata', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='import', + name='next_import', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='imports', to='ishtar_common.Import', verbose_name='Next import'), + ), + migrations.AddField( + model_name='importerdefault', + name='required_fields', + field=models.CharField(blank=True, default='', help_text='Theses defaults values only apply if the designated fields are not empty. Use the "__" notation to pass between models.Leave empty to always apply.', max_length=500, verbose_name='Required fields'), + ), + migrations.AddField( + model_name='importertype', + name='archive_required', + field=models.BooleanField(default=False, verbose_name='Archive required'), + ), + migrations.AddField( + model_name='importertype', + name='is_import', + field=models.BooleanField(default=True, verbose_name='Can be import'), + ), + migrations.AddField( + model_name='importertype', + name='pre_import_message', + field=models.TextField(blank=True, default='', max_length=500, verbose_name='Pre-import form message'), + ), + migrations.AddField( + model_name='importertype', + name='tab_number', + field=models.PositiveIntegerField(default=1, help_text='When using an Excel or Calc file choose the tab number. Keep it to 1 by default.', validators=[django.core.validators.MinValueValidator(1)], verbose_name='Tab number'), + ), + migrations.AddField( + model_name='organization', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_organization', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='person', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_person', to='ishtar_common.Import'), + ), + migrations.AddField( + model_name='town', + name='imports_updated', + field=models.ManyToManyField(blank=True, related_name='import_updated_ishtar_common_town', to='ishtar_common.Import'), + ), + migrations.AlterField( + model_name='import', + name='imported_images', + field=models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=220, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Associated documents (zip file)'), + ), + migrations.AlterField( + model_name='importercolumn', + name='col_number', + field=models.SmallIntegerField(default=1, help_text='Column number in the table. Put 0 or negative number for pre-importer field.', verbose_name='Column number'), + ), + migrations.AlterField( + model_name='importerdefault', + name='target', + field=models.CharField(help_text='The target of the default values. Can be set to empty with "-". Use the "__" notation to pass between models.', max_length=500, verbose_name='Target'), + ), + migrations.AlterField( + model_name='ishtarsiteprofile', + name='account_naming_style', + field=models.CharField(choices=[('NF', 'name.firstname'), ('FN', 'firstname.name')], default='FN', max_length=2, verbose_name='Naming style for accounts'), + ), + migrations.CreateModel( + name='ImportGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=500, null=True, verbose_name='Name')), + ('imported_file', models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=220, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Imported file')), + ('imported_images', models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=220, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Associated documents (zip file)')), + ('archive_file', models.FileField(blank=True, help_text='La taille maximale supportée pour le fichier est de 100 Mo.', max_length=255, null=True, upload_to='upload/imports/%Y/%m/', verbose_name='Archive file')), + ('encoding', models.CharField(choices=[('windows-1252', 'windows-1252'), ('ISO-8859-15', 'ISO-8859-15'), ('utf-8', 'utf-8')], default='utf-8', help_text='Only required for CSV file', max_length=15, verbose_name='Encoding')), + ('csv_sep', models.CharField(choices=[(',', ','), (';', ';'), ('|', '|')], default=',', help_text='Separator for CSV file. Standard is comma but Microsoft Excel do not follow this standard and use semi-colon.', max_length=1, verbose_name='CSV separator')), + ('skip_lines', models.IntegerField(default=1, help_text='Number of header lines in your file (can be 0 and should be 0 for geopackage or Shapefile).', verbose_name='Skip lines')), + ('creation_date', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Creation date')), + ('end_date', models.DateTimeField(auto_now_add=True, null=True, verbose_name='End date')), + ('current_import', models.PositiveIntegerField(blank=True, null=True, verbose_name='Current import')), + ('state', models.CharField(choices=[('C', 'Created'), ('AP', 'Analyse in progress'), ('A', 'Analysed'), ('IQ', 'Import in queue'), ('IP', 'Import in progress'), ('PP', 'Post-processing in progress'), ('FE', 'Finished with errors'), ('F', 'Finished'), ('AC', 'Archived')], default='C', max_length=2, verbose_name='State')), + ('importer_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.ImporterGroup', verbose_name='Importer group type')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ishtar_common.IshtarUser')), + ], + options={ + 'verbose_name': 'Import - Group', + 'verbose_name_plural': 'Import - Groups', + 'permissions': (('view_own_importgroup', 'Can view own Import Group'), ('add_own_importgroup', 'Can add own Import Group'), ('change_own_importgroup', 'Can change own Import Group'), ('delete_own_importgroup', 'Can delete own Import Group')), + }, + bases=(models.Model, ishtar_common.utils.OwnPerms), + ), + migrations.AddField( + model_name='import', + name='group', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='imports', to='ishtar_common.ImportGroup', verbose_name='Group'), + ), + migrations.CreateModel( + name='ImporterGroupImporter', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order', models.PositiveIntegerField(default=10, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Order')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='importer_types', to='ishtar_common.ImporterGroup')), + ('importer_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='ishtar_common.ImporterType')), + ], + options={ + 'verbose_name': 'Importer - Group <-> Importer', + 'ordering': ('group', 'order'), + 'unique_together': {('group', 'order')}, + }, + ), + migrations.CreateModel( + name='ImportColumnValue', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('value', models.TextField(blank=True, default='')), + ('column', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.ImporterColumn')), + ('import_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ishtar_common.Import')), + ], + options={ + 'verbose_name': 'Import - Pre-import value', + 'verbose_name_plural': 'Import - Pre-import values', + 'unique_together': {('column', 'import_item')}, + }, + ), + ] diff --git a/ishtar_common/migrations/0231_default_mandatory_keys.py b/ishtar_common/migrations/0231_default_mandatory_keys.py index d364f01dc..b510976fb 100644 --- a/ishtar_common/migrations/0231_default_mandatory_keys.py +++ b/ishtar_common/migrations/0231_default_mandatory_keys.py @@ -30,7 +30,7 @@ def migrate(apps, __): class Migration(migrations.Migration): dependencies = [ - ('ishtar_common', '0230_auto_20230922_1443'), + ('ishtar_common', '0230_auto_20231002_1613'), ] operations = [ diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index aa6a329c3..1033be9ed 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -75,6 +75,7 @@ from ishtar_common.utils import ( duplicate_item, get_generated_id, get_current_profile, + OwnPerms ) @@ -1762,226 +1763,6 @@ class LightHistorizedItem(BaseHistorizedItem): return self -class OwnPerms(object): - """ - Manage special permissions for object's owner - """ - - @classmethod - def get_query_owns(cls, ishtaruser): - """ - Query object to get own items - """ - return None # implement for each object - - def can_view(self, request): - if hasattr(self, "LONG_SLUG"): - perm = "view_" + self.LONG_SLUG - else: - perm = "view_" + self.SLUG - return self.can_do(request, perm) - - def can_edit(self, request): - if not getattr(request.user, "ishtaruser", None): - return False - ishtaruser = request.user.ishtaruser - slug = self.LONG_SLUG if hasattr(self, "LONG_SLUG") else self.SLUG - if ishtaruser.has_perm("change_" + slug, session=request.session): - return True - if not ishtaruser.has_perm("change_own_" + slug, session=request.session): - return False - return self.is_own(ishtaruser) - - def can_do(self, request, action_name): - """ - Check permission availability for the current object. - :param request: request object - :param action_name: action name eg: "change_find" - "own" variation is - checked - :return: boolean - """ - if not getattr(request.user, "ishtaruser", None): - return False - splited = action_name.split("_") - action_own_name = splited[0] + "_own_" + "_".join(splited[1:]) - user = request.user - if action_name == "view_findbasket": - action_own_name = "view_own_find" - action_name = "view_find" - return user.ishtaruser.has_right(action_name, request.session) or ( - user.ishtaruser.has_right(action_own_name, request.session) - and self.is_own(user.ishtaruser) - ) - - def is_own(self, user, alt_query_own=None): - """ - Check if the current object is owned by the user - """ - IshtarUser = apps.get_model("ishtar_common", "IshtarUser") - if isinstance(user, IshtarUser): - ishtaruser = user - elif hasattr(user, "ishtaruser"): - ishtaruser = user.ishtaruser - else: - return False - if not alt_query_own: - query = self.get_query_owns(ishtaruser) - else: - query = getattr(self, alt_query_own)(ishtaruser) - if not query: - return False - query &= Q(pk=self.pk) - return self.__class__.objects.filter(query).count() - - @classmethod - def has_item_of(cls, user): - """ - Check if the user own some items - """ - IshtarUser = apps.get_model("ishtar_common", "IshtarUser") - if isinstance(user, IshtarUser): - ishtaruser = user - elif hasattr(user, "ishtaruser"): - ishtaruser = user.ishtaruser - else: - return False - query = cls.get_query_owns(ishtaruser) - if not query: - return False - return cls.objects.filter(query).count() - - @classmethod - def _return_get_owns( - cls, owns, values, get_short_menu_class, label_key="cached_label" - ): - if not owns: - return [] - sorted_values = [] - if hasattr(cls, "BASKET_MODEL"): - owns_len = len(owns) - for idx, item in enumerate(reversed(owns)): - if get_short_menu_class: - item = item[0] - if type(item) == cls.BASKET_MODEL: - basket = owns.pop(owns_len - idx - 1) - sorted_values.append(basket) - sorted_values = list(reversed(sorted_values)) - if not values: - if not get_short_menu_class: - return sorted_values + list( - sorted(owns, key=lambda x: getattr(x, label_key) or "") - ) - return sorted_values + list( - sorted(owns, key=lambda x: getattr(x[0], label_key) or "") - ) - if not get_short_menu_class: - return sorted_values + list(sorted(owns, key=lambda x: x[label_key] or "")) - return sorted_values + list(sorted(owns, key=lambda x: x[0][label_key] or "")) - - @classmethod - def get_owns( - cls, - user, - replace_query=None, - limit=None, - values=None, - get_short_menu_class=False, - menu_filtr=None, - ): - """ - Get Own items - """ - if not replace_query: - replace_query = {} - if hasattr(user, "is_authenticated") and not user.is_authenticated: - returned = cls.objects.filter(pk__isnull=True) - if values: - returned = [] - return returned - IshtarUser = apps.get_model("ishtar_common", "IshtarUser") - if isinstance(user, User): - try: - ishtaruser = IshtarUser.objects.get(user_ptr=user) - except IshtarUser.DoesNotExist: - returned = cls.objects.filter(pk__isnull=True) - if values: - returned = [] - return returned - elif isinstance(user, IshtarUser): - ishtaruser = user - else: - if values: - return [] - return cls.objects.filter(pk__isnull=True) - items = [] - if hasattr(cls, "BASKET_MODEL"): - items = list(cls.BASKET_MODEL.objects.filter(user=ishtaruser).all()) - query = cls.get_query_owns(ishtaruser) - if not query and not replace_query: - returned = cls.objects.filter(pk__isnull=True) - if values: - returned = [] - return returned - if query: - q = cls.objects.filter(query) - else: # replace_query - q = cls.objects.filter(replace_query) - if values: - q = q.values(*values) - if limit: - items += list(q.order_by("-pk")[:limit]) - else: - items += list(q.order_by(*cls._meta.ordering).all()) - if get_short_menu_class: - if values: - if "id" not in values: - raise NotImplementedError( - "Call of get_owns with get_short_menu_class option and" - " no 'id' in values is not implemented" - ) - my_items = [] - for i in items: - if hasattr(cls, "BASKET_MODEL") and type(i) == cls.BASKET_MODEL: - dct = dict([(k, getattr(i, k)) for k in values]) - my_items.append( - (dct, cls.BASKET_MODEL.get_short_menu_class(i.pk)) - ) - else: - my_items.append((i, cls.get_short_menu_class(i["id"]))) - items = my_items - else: - items = [(i, cls.get_short_menu_class(i.pk)) for i in items] - return items - - @classmethod - def _get_query_owns_dicts(cls, ishtaruser): - """ - List of query own dict to construct the query. - Each dict is joined with an AND operator, each dict key, values are - joined with OR operator - """ - return [] - - @classmethod - def _construct_query_own(cls, prefix, dct_list): - q = None - for subquery_dict in dct_list: - subquery = None - for k in subquery_dict: - subsubquery = Q(**{prefix + k: subquery_dict[k]}) - if subquery: - subquery |= subsubquery - else: - subquery = subsubquery - if not subquery: - continue - if q: - q &= subquery - else: - q = subquery - return q - - class DocumentItem: ALT_NAMES = { "documents__image__isnull": SearchAltName( diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py index ea2f0f549..af907d44d 100644 --- a/ishtar_common/models_imports.py +++ b/ishtar_common/models_imports.py @@ -74,7 +74,8 @@ from ishtar_common.utils import ( put_session_message, put_session_var, reverse_coordinates, - update_data + update_data, + OwnPerms ) from ishtar_common.data_importer import ( Importer, @@ -416,6 +417,7 @@ class ImporterGroup(models.Model): _("Description"), blank=True, default="" ) available = models.BooleanField(_("Available"), default=True) + users = models.ManyToManyField("IshtarUser", verbose_name=_("Users"), blank=True) class Meta: verbose_name = _("Importer - Group") @@ -1338,7 +1340,7 @@ IMPORT_GEOMETRY = { } -class BaseImport(models.Model): +class BaseImport(models.Model, OwnPerms): user = models.ForeignKey( "IshtarUser", blank=True, null=True, on_delete=models.SET_NULL ) @@ -1412,6 +1414,15 @@ class BaseImport(models.Model): q = q.filter(user=ishtar_user) return q + @classmethod + def get_query_owns(cls, ishtaruser): + return cls._construct_query_own( + "", + [ + {"importer_type__users__pk": ishtaruser.pk}, + ], + ) + @property def group_prefix(self): return "" @@ -1465,7 +1476,14 @@ class ImportGroup(BaseImport): class Meta: verbose_name = _("Import - Group") verbose_name_plural = _("Import - Groups") + permissions = ( + ("view_own_importgroup", "Can view own Import Group"), + ("add_own_importgroup", "Can add own Import Group"), + ("change_own_importgroup", "Can change own Import Group"), + ("delete_own_importgroup", "Can delete own Import Group"), + ) ADMIN_SECTION = _("Imports") + SLUG = "importgroup" def __str__(self): return f"{self.name} ({self.importer_type.name})" @@ -1848,7 +1866,14 @@ class Import(BaseImport): class Meta: verbose_name = _("Import - Import") verbose_name_plural = _("Import - Imports") + permissions = ( + ("view_own_import", "Can view own Import"), + ("add_own_import", "Can add own Import"), + ("change_own_import", "Can change own Import"), + ("delete_own_import", "Can delete own Import"), + ) ADMIN_SECTION = _("Imports") + SLUG = "import" def __str__(self): return "{} | {}".format(self.name or "-", self.importer_type) diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 4e50c506c..7b708cd76 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -51,7 +51,7 @@ from django import forms from django.apps import apps from django.conf import settings from django.conf.urls import url -from django.contrib.auth.models import Permission +from django.contrib.auth.models import Permission, User from django.contrib.auth.hashers import Argon2PasswordHasher as BaseArgon2PasswordHasher from django.contrib.contenttypes.models import ContentType from django.contrib.gis.geos import GEOSGeometry @@ -64,6 +64,7 @@ from django.core.files.storage import FileSystemStorage from django.core.validators import EMPTY_VALUES from django.urls import reverse from django.db import models +from django.db.models import Q from django.http import HttpResponseRedirect from django.utils.crypto import get_random_string from django.utils.datastructures import MultiValueDict as BaseMultiValueDict @@ -274,6 +275,228 @@ def check_model_access_control(request, model, available_perms=None): return allowed, own +class OwnPerms: + """ + Manage special permissions for object's owner + """ + + @classmethod + def get_query_owns(cls, ishtaruser): + """ + Query object to get own items + """ + return None # implement for each object + + def can_view(self, request): + if hasattr(self, "LONG_SLUG"): + perm = "view_" + self.LONG_SLUG + else: + perm = "view_" + self.SLUG + return self.can_do(request, perm) + + def can_edit(self, request): + if not getattr(request.user, "ishtaruser", None): + return False + ishtaruser = request.user.ishtaruser + slug = self.LONG_SLUG if hasattr(self, "LONG_SLUG") else self.SLUG + if ishtaruser.has_perm("change_" + slug, session=request.session): + return True + if not ishtaruser.has_perm("change_own_" + slug, session=request.session): + return False + return self.is_own(ishtaruser) + + def can_do(self, request, action_name): + """ + Check permission availability for the current object. + :param request: request object + :param action_name: action name eg: "change_find" - "own" variation is + checked + :return: boolean + """ + if not getattr(request.user, "ishtaruser", None): + return False + splited = action_name.split("_") + action_own_name = splited[0] + "_own_" + "_".join(splited[1:]) + user = request.user + if action_name == "view_findbasket": + action_own_name = "view_own_find" + action_name = "view_find" + return user.ishtaruser.has_right(action_name, request.session) or ( + user.ishtaruser.has_right(action_own_name, request.session) + and self.is_own(user.ishtaruser) + ) + + def is_own(self, user, alt_query_own=None): + """ + Check if the current object is owned by the user + """ + IshtarUser = apps.get_model("ishtar_common", "IshtarUser") + if isinstance(user, IshtarUser): + ishtaruser = user + elif hasattr(user, "ishtaruser"): + ishtaruser = user.ishtaruser + else: + return False + if not alt_query_own: + query = self.get_query_owns(ishtaruser) + else: + query = getattr(self, alt_query_own)(ishtaruser) + if not query: + return False + query &= Q(pk=self.pk) + return self.__class__.objects.filter(query).count() + + @classmethod + def has_item_of(cls, user): + """ + Check if the user own some items + """ + IshtarUser = apps.get_model("ishtar_common", "IshtarUser") + if isinstance(user, IshtarUser): + ishtaruser = user + elif hasattr(user, "ishtaruser"): + ishtaruser = user.ishtaruser + else: + return False + query = cls.get_query_owns(ishtaruser) + if not query: + return False + return cls.objects.filter(query).count() + + @classmethod + def _return_get_owns( + cls, owns, values, get_short_menu_class, label_key="cached_label" + ): + if not owns: + return [] + sorted_values = [] + if hasattr(cls, "BASKET_MODEL"): + owns_len = len(owns) + for idx, item in enumerate(reversed(owns)): + if get_short_menu_class: + item = item[0] + if type(item) == cls.BASKET_MODEL: + basket = owns.pop(owns_len - idx - 1) + sorted_values.append(basket) + sorted_values = list(reversed(sorted_values)) + if not values: + if not get_short_menu_class: + return sorted_values + list( + sorted(owns, key=lambda x: getattr(x, label_key) or "") + ) + return sorted_values + list( + sorted(owns, key=lambda x: getattr(x[0], label_key) or "") + ) + if not get_short_menu_class: + return sorted_values + list(sorted(owns, key=lambda x: x[label_key] or "")) + return sorted_values + list(sorted(owns, key=lambda x: x[0][label_key] or "")) + + @classmethod + def get_owns( + cls, + user, + replace_query=None, + limit=None, + values=None, + get_short_menu_class=False, + menu_filtr=None, + ): + """ + Get Own items + """ + if not replace_query: + replace_query = {} + if hasattr(user, "is_authenticated") and not user.is_authenticated: + returned = cls.objects.filter(pk__isnull=True) + if values: + returned = [] + return returned + IshtarUser = apps.get_model("ishtar_common", "IshtarUser") + if isinstance(user, User): + try: + ishtaruser = IshtarUser.objects.get(user_ptr=user) + except IshtarUser.DoesNotExist: + returned = cls.objects.filter(pk__isnull=True) + if values: + returned = [] + return returned + elif isinstance(user, IshtarUser): + ishtaruser = user + else: + if values: + return [] + return cls.objects.filter(pk__isnull=True) + items = [] + if hasattr(cls, "BASKET_MODEL"): + items = list(cls.BASKET_MODEL.objects.filter(user=ishtaruser).all()) + query = cls.get_query_owns(ishtaruser) + if not query and not replace_query: + returned = cls.objects.filter(pk__isnull=True) + if values: + returned = [] + return returned + if query: + q = cls.objects.filter(query) + else: # replace_query + q = cls.objects.filter(replace_query) + if values: + q = q.values(*values) + if limit: + items += list(q.order_by("-pk")[:limit]) + else: + items += list(q.order_by(*cls._meta.ordering).all()) + if get_short_menu_class: + if values: + if "id" not in values: + raise NotImplementedError( + "Call of get_owns with get_short_menu_class option and" + " no 'id' in values is not implemented" + ) + my_items = [] + for i in items: + if hasattr(cls, "BASKET_MODEL") and type(i) == cls.BASKET_MODEL: + dct = dict([(k, getattr(i, k)) for k in values]) + my_items.append( + (dct, cls.BASKET_MODEL.get_short_menu_class(i.pk)) + ) + else: + my_items.append((i, cls.get_short_menu_class(i["id"]))) + items = my_items + else: + items = [(i, cls.get_short_menu_class(i.pk)) for i in items] + return items + + @classmethod + def _get_query_owns_dicts(cls, ishtaruser): + """ + List of query own dict to construct the query. + Each dict is joined with an AND operator, each dict key, values are + joined with OR operator + """ + return [] + + @classmethod + def _construct_query_own(cls, prefix, dct_list): + q = None + for subquery_dict in dct_list: + subquery = None + for k in subquery_dict: + subsubquery = Q(**{prefix + k: subquery_dict[k]}) + if subquery: + subquery |= subsubquery + else: + subquery = subsubquery + if not subquery: + continue + if q: + q &= subquery + else: + q = subquery + return q + + + + def update_data(data, new_data, merge=False): """ Update a data directory taking account of key detail @@ -286,7 +509,7 @@ def update_data(data, new_data, merge=False): if new_data: return new_data return data - if new_data and data_2 != data: + if new_data and new_data != data: return data + " ; " + new_data return data for k in data: -- cgit v1.2.3