diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2023-07-29 13:49:11 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-04-16 16:37:12 +0200 |
commit | 3cf74dc30fee9347720b8e2072d185aeab40e6f8 (patch) | |
tree | 6c66ccf4981290c20b8d034c9415a59c71b4631e | |
parent | b6651d3cafc1b7079db67140bf1e3746e919c73b (diff) | |
download | Ishtar-3cf74dc30fee9347720b8e2072d185aeab40e6f8.tar.bz2 Ishtar-3cf74dc30fee9347720b8e2072d185aeab40e6f8.zip |
✨ Imports groups: models, admin
-rw-r--r-- | ishtar_common/admin.py | 19 | ||||
-rw-r--r-- | ishtar_common/migrations/0230_auto_20230729_1345.py | 78 | ||||
-rw-r--r-- | ishtar_common/models.py | 4 | ||||
-rw-r--r-- | ishtar_common/models_imports.py | 117 |
4 files changed, 192 insertions, 26 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index 764dd2624..2b706f10a 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -1666,6 +1666,25 @@ class ImporterTypeAdmin(ImportJSONActionAdmin): prepopulated_fields = {"slug": ("name",)} +class ImporterGroupImporterInline(admin.TabularInline): + model = models.ImporterGroupImporter + extra = 3 + + +@admin.register(models.ImporterGroup, site=admin_site) +class ImporterTypeAdmin(admin.ModelAdmin): + list_display = ("name", "importer_types_label", "available") + actions = [ + change_value("available", True, _("Make available")), + change_value("available", False, _("Make unavailable")), + ] + list_filter = ["available"] + search_fields = ["name"] + prepopulated_fields = {"slug": ("name",)} + inlines = [ImporterGroupImporterInline] + + + class RegexpAdmin(admin.ModelAdmin): list_display = ("name", "regexp", "description") diff --git a/ishtar_common/migrations/0230_auto_20230729_1345.py b/ishtar_common/migrations/0230_auto_20230729_1345.py new file mode 100644 index 000000000..8d09fe16c --- /dev/null +++ b/ishtar_common/migrations/0230_auto_20230729_1345.py @@ -0,0 +1,78 @@ +# Generated by Django 2.2.24 on 2023-07-29 13:45 + +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.AddField( + model_name='importertype', + name='is_import', + field=models.BooleanField(default=False, verbose_name='Can be import'), + ), + 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.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 images (zip 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={ + 'abstract': False, + }, + ), + 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={ + 'ordering': ('group', 'order'), + }, + ), + ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index b011e541f..6cc31216c 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -109,6 +109,8 @@ from ishtar_common.model_merging import merge_model_objects from ishtar_common.models_imports import ( ImporterModel, ImporterType, + ImporterGroup, + ImporterGroupImporter, ImporterDefault, ImporterDefaultValues, ImporterColumn, @@ -174,6 +176,8 @@ from ishtar_common.models_common import ( __all__ = [ "ImporterModel", "ImporterType", + "ImporterGroup", + "ImporterGroupImporter", "ImporterDefault", "ImporterDefaultValues", "ImporterColumn", diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py index e91a94868..457d3ff4e 100644 --- a/ishtar_common/models_imports.py +++ b/ishtar_common/models_imports.py @@ -42,7 +42,7 @@ from django.contrib.gis.geos.error import GEOSException from django.core.exceptions import ValidationError, SuspiciousOperation from django.core.files import File from django.core.files.base import ContentFile -from django.core.validators import validate_comma_separated_integer_list +from django.core.validators import validate_comma_separated_integer_list, MinValueValidator from django.db.models.base import ModelBase from django.db.models.signals import pre_delete from django.template.defaultfilters import slugify @@ -129,11 +129,6 @@ class ImporterModel(models.Model): return (self.klass,) -class ImporterTypeManager(models.Manager): - def get_by_natural_key(self, slug): - return self.get(slug=slug) - - IMPORT_TYPES = ( ("tab", _("Table")), ("gis", _("GIS")), @@ -155,6 +150,10 @@ class ImporterType(models.Model): type = models.CharField( _("Type"), max_length=3, choices=IMPORT_TYPES, default="tab" ) + tab_number = models.PositiveIntegerField( + _("Tab number"), default=1, validators=[MinValueValidator(1)], + help_text=_("When using an Excel or Calc file choose the tab number. Keep it to 1 by default.") + ) layer_name = models.CharField( _("Layer name"), max_length=200, @@ -184,6 +183,7 @@ class ImporterType(models.Model): related_name="importer_type_created", ) is_template = models.BooleanField(_("Can be exported"), default=False) + is_import = models.BooleanField(_("Can be import"), default=False) unicity_keys = models.CharField( _('Unicity keys (separator ";")'), blank=True, null=True, max_length=500, help_text=_("Mandatory for update importer. Set to key that identify items " @@ -191,7 +191,7 @@ class ImporterType(models.Model): "1 key.") ) available = models.BooleanField(_("Available"), default=True) - objects = ImporterTypeManager() + objects = SlugModelManager() SERIALIZATION_EXCLUDE = ["users"] class Meta: @@ -377,13 +377,47 @@ class ImporterType(models.Model): col_names.append(formater.label) return cols, col_names - def save(self, *args, **kwargs): if not self.slug: self.slug = create_slug(ImporterType, self.name) return super(ImporterType, self).save(*args, **kwargs) +class ImporterGroup(models.Model): + name = models.CharField(_("Name"), max_length=200) + slug = models.SlugField(_("Slug"), unique=True, max_length=100) + description = models.TextField( + _("Description"), blank=True, default="" + ) + available = models.BooleanField(_("Available"), default=True) + + class Meta: + verbose_name = _("Importer - Group") + verbose_name_plural = _("Importer - Groups") + ordering = ("name",) + ADMIN_SECTION = _("Imports") + objects = SlugModelManager() + + def natural_key(self): + return self.slug, + + def __str__(self): + return self.name + + @property + def importer_types_label(self) -> str: + return " ; ".join([imp.importer_type.name for imp in self.importer_types.all()]) + + +class ImporterGroupImporter(models.Model): + group = models.ForeignKey(ImporterGroup, on_delete=models.CASCADE, related_name="importer_types") + importer_type = models.ForeignKey(ImporterType, on_delete=models.CASCADE, related_name="groups") + order = models.PositiveIntegerField(_("Order"), default=10, validators=[MinValueValidator(1)]) + + class Meta: + ordering = ("group", "order") + + def get_associated_model(parent_model, keys): model = None if isinstance(parent_model, str): @@ -1133,6 +1167,21 @@ IMPORT_STATE = ( ) IMPORT_STATE_DCT = dict(IMPORT_STATE) + +IMPORT_GROUP_STATE = ( + ("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")), +) + +IMPORT_GROUP_STATE_DCT = dict(IMPORT_STATE) + ENCODINGS = [ (settings.ENCODING, settings.ENCODING), (settings.ALT_ENCODING, settings.ALT_ENCODING), @@ -1192,13 +1241,11 @@ IMPORT_GEOMETRY = { } -class Import(models.Model): +class BaseImport(models.Model): user = models.ForeignKey( "IshtarUser", blank=True, null=True, on_delete=models.SET_NULL ) name = models.CharField(_("Name"), max_length=500, null=True) - importer_type = models.ForeignKey(ImporterType, on_delete=models.CASCADE, - verbose_name=_("Importer type")) imported_file = models.FileField( _("Imported file"), upload_to="upload/imports/%Y/%m/", @@ -1215,15 +1262,6 @@ class Import(models.Model): max_length=220, help_text=max_size_help(), ) - associated_group = models.ForeignKey( - TargetKeyGroup, - blank=True, - null=True, - on_delete=models.SET_NULL, - help_text=_( - "If a group is selected, target key saved in this group " "will be used." - ), - ) encoding = models.CharField( _("Encoding"), choices=ENCODINGS, default="utf-8", max_length=15, help_text=_("Only required for CSV file"), @@ -1243,6 +1281,39 @@ class Import(models.Model): default=1, help_text=_("Number of header lines in your file (can be 0 and should be 0 for geopackage or Shapefile)."), ) + creation_date = models.DateTimeField( + _("Creation date"), auto_now_add=True, blank=True, null=True + ) + end_date = models.DateTimeField( + _("End date"), auto_now_add=True, blank=True, null=True, editable=False + ) + + class Meta: + abstract = True + + +class ImportGroup(BaseImport): + importer_type = models.ForeignKey(ImporterGroup, on_delete=models.CASCADE, + verbose_name=_("Importer group type")) + current_import = models.PositiveIntegerField(_("Current import"), blank=True, null=True) + state = models.CharField( + _("State"), max_length=2, choices=IMPORT_GROUP_STATE, default="C" + ) + + +class Import(BaseImport): + importer_type = models.ForeignKey(ImporterType, on_delete=models.CASCADE, + verbose_name=_("Importer type")) + # TODO - associated_group: relevant? + associated_group = models.ForeignKey( + TargetKeyGroup, + blank=True, + null=True, + on_delete=models.SET_NULL, + help_text=_( + "If a group is selected, target key saved in this group will be used." + ), + ) error_file = models.FileField( _("Error file"), upload_to="upload/imports/%Y/%m/", @@ -1283,12 +1354,6 @@ class Import(models.Model): default=False, help_text=_("If set to true, do not overload existing values."), ) - creation_date = models.DateTimeField( - _("Creation date"), auto_now_add=True, blank=True, null=True - ) - end_date = models.DateTimeField( - _("End date"), auto_now_add=True, blank=True, null=True, editable=False - ) seconds_remaining = models.IntegerField( _("Remaining seconds"), blank=True, null=True, editable=False ) |