diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2023-09-15 18:46:41 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-04-16 16:38:32 +0200 |
commit | 5ec78fa85dbb6b7ec7d286827b6e32f82c489b3e (patch) | |
tree | f4f98c3ef9923df42ff579fbc9b71c807f26716e | |
parent | b1c4e814bdb7ded9314b8d5337fa5841c737d32d (diff) | |
download | Ishtar-5ec78fa85dbb6b7ec7d286827b6e32f82c489b3e.tar.bz2 Ishtar-5ec78fa85dbb6b7ec7d286827b6e32f82c489b3e.zip |
♻️ refactoring and optimisation: manage defaults like pre_importer_values - add an explicit field for required value to apply defaults
-rw-r--r-- | ishtar_common/data_importer.py | 38 | ||||
-rw-r--r-- | ishtar_common/migrations/0230_auto_20230918_1655.py (renamed from ishtar_common/migrations/0230_auto_20230912_1832.py) | 12 | ||||
-rw-r--r-- | ishtar_common/migrations/0231_default_mandatory_keys.py | 38 | ||||
-rw-r--r-- | ishtar_common/models_imports.py | 77 |
4 files changed, 126 insertions, 39 deletions
diff --git a/ishtar_common/data_importer.py b/ishtar_common/data_importer.py index d5e6f966c..8e3c41e62 100644 --- a/ishtar_common/data_importer.py +++ b/ishtar_common/data_importer.py @@ -746,7 +746,6 @@ class Importer(object): UNICITY_KEYS = [] # if set only models inside this list can be created MODEL_CREATION_LIMIT = [] - EXTRA_DEFAULTS = {} DEFAULTS = {} PRE_IMPORT_VALUES = {} # values from a form before the import ERRORS = { @@ -834,12 +833,6 @@ class Importer(object): if import_instance and import_instance.imported_images: self.archive = import_instance.imported_images self._defaults = self.DEFAULTS.copy() - # EXTRA_DEFAULTS are for multiple inheritance - if self.EXTRA_DEFAULTS: - for k in self.EXTRA_DEFAULTS: - if k not in self._defaults: - self._defaults[k] = {} - self._defaults[k].update(self.EXTRA_DEFAULTS[k]) self._pre_import_values = self.PRE_IMPORT_VALUES.copy() self.history_modifier = history_modifier self.output = output @@ -1158,6 +1151,20 @@ class Importer(object): except: # nosec pass + data = update_data(self._pre_import_values, data) + # put default values only if relevant + for mandatory_keys, defaults in self._defaults: + test_dict = data.copy() + nok = False + for k in mandatory_keys: # test have keys + if k not in test_dict: + nok = True + break + test_dict = test_dict[k] + if nok or not any(1 for k in test_dict if test_dict[k]): # test empty dict + continue + data = update_data(defaults, data) + self.validity.append(c_row) if not self.c_errors and (idx_col + 1) < self.min_col_number: self.c_errors = True @@ -1188,7 +1195,6 @@ class Importer(object): default_srs = profile.srs if profile.srs else None if "geodata" in data: - geodata = copy.deepcopy(self._defaults.get(("geodata",), {})) geodata.update(data.pop("geodata")) if default_srs and not [ 1 for k in geodata if k.startswith("spatial_reference_system") and @@ -1197,7 +1203,6 @@ class Importer(object): geodata["spatial_reference_system"] = default_srs if "main_geodata" in data: - main_geodata = copy.deepcopy(self._defaults.get(("main_geodata",), {})) main_geodata.update(data.pop("main_geodata")) if default_srs and not [ 1 for k in main_geodata if k.startswith("spatial_reference_system") @@ -1538,7 +1543,6 @@ class Importer(object): concat=concats[idx], concat_str=concat_str[idx], ) - data = update_data(self._pre_import_values, data) c_row.append(" ; ".join([v for v in c_values])) return data @@ -1582,15 +1586,6 @@ class Importer(object): # contruct many dict for each values default_dict = {} - # # get default values - p = [attribute] - if c_path: - p = list(c_path) + p - p = tuple(p) - if p in self._defaults: - for k in self._defaults[p]: - default_dict[k] = self._defaults[p][k] - # # init with simple values that will be duplicated for key in val.keys(): if type(val[key]) not in (list, tuple): @@ -1896,15 +1891,10 @@ class Importer(object): elif type(create_dict[k]) == File: create_dict[k] = copy.copy(data[k]) - # default values path = tuple(path) defaults = {} if hasattr(cls, "get_import_defaults"): defaults = cls.get_import_defaults() - if path in self._defaults: - for k in self._defaults[path]: - if (k not in data or not data[k]) and self._defaults[path][k]: - defaults[k] = self._defaults[path][k] if "history_modifier" in create_dict: defaults.update({"history_modifier": create_dict.pop("history_modifier")}) diff --git a/ishtar_common/migrations/0230_auto_20230912_1832.py b/ishtar_common/migrations/0230_auto_20230918_1655.py index 649e0dccb..76fba744c 100644 --- a/ishtar_common/migrations/0230_auto_20230912_1832.py +++ b/ishtar_common/migrations/0230_auto_20230918_1655.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.24 on 2023-09-12 18:32 +# Generated by Django 2.2.24 on 2023-09-18 16:55 import django.core.validators from django.db import migrations, models @@ -41,6 +41,11 @@ class Migration(migrations.Migration): 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'), @@ -71,6 +76,11 @@ class Migration(migrations.Migration): 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'), diff --git a/ishtar_common/migrations/0231_default_mandatory_keys.py b/ishtar_common/migrations/0231_default_mandatory_keys.py new file mode 100644 index 000000000..4c5e2ea35 --- /dev/null +++ b/ishtar_common/migrations/0231_default_mandatory_keys.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.24 on 2023-09-18 17:05 + +from django.db import migrations + +EXCLUDE_LIST = [ + "-", + "auto_external_id", + "spatial_reference_system", + "public_domain", +] + +FULL_COPY_LIST = [ + "scientist__attached_to", +] + + +def migrate(apps, __): + ImporterDefault = apps.get_model('ishtar_common', 'ImporterDefault') + for default in ImporterDefault.objects.all(): + if default.target not in EXCLUDE_LIST: + req = default.target + if req not in FULL_COPY_LIST: + req = req.split("__")[0] + if req.endswith("_type") or req.endswith("_types"): + continue + default.required_fields = req + default.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0230_auto_20230918_1655'), + ] + + operations = [ + migrations.RunPython(migrate), + ] diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py index c03dd208b..85b859a5d 100644 --- a/ishtar_common/models_imports.py +++ b/ishtar_common/models_imports.py @@ -288,14 +288,23 @@ class ImporterType(models.Model): uno.save_calc(calc, dest_filename) return dest_filename + def get_default_values(self) -> list: + """ + Get list of 2-tuple to manage default values. + The first item is a tuple of mandatory fields for the values to be relevant. Empty if the + default values always applies. + The second item is the dict of default values. + """ + return [(default.mandatory_keys, default.values) for default in self.defaults.all()] + def get_importer_class(self, import_instance=None): - OBJECT_CLS = import_class(self.associated_models.klass) - DEFAULTS = dict( - [(default.keys, default.values) for default in self.defaults.all()] - ) - PRE_IMPORT_VALUES = {} + object_cls = import_class(self.associated_models.klass) + pre_import_values = {} if import_instance: - PRE_IMPORT_VALUES = import_instance.pre_import_values + pre_import_values = import_instance.pre_import_values + defaults = import_instance.default_values + else: + defaults = self.get_default_values() LINE_FORMAT = [] LINE_EXPORT_FORMAT = [] idx = 0 @@ -356,10 +365,10 @@ class ImporterType(models.Model): for modls in self.created_models.all(): MODEL_CREATION_LIMIT.append(import_class(modls.klass)) args = { - "OBJECT_CLS": OBJECT_CLS, + "OBJECT_CLS": object_cls, "DESC": self.description, - "DEFAULTS": DEFAULTS, - "PRE_IMPORT_VALUES": PRE_IMPORT_VALUES, + "DEFAULTS": defaults, + "PRE_IMPORT_VALUES": pre_import_values, "LINE_FORMAT": LINE_FORMAT, "UNICITY_KEYS": UNICITY_KEYS, "LINE_EXPORT_FORMAT": LINE_EXPORT_FORMAT, @@ -502,7 +511,19 @@ class ImporterDefault(models.Model): importer_type = models.ForeignKey( ImporterType, related_name="defaults", on_delete=models.CASCADE ) - target = models.CharField("Target", max_length=500) + target = models.CharField( + _("Target"), max_length=500, + help_text=_('The target of the default values. Can be set to empty with "-". ' + 'Use the "__" notation to pass between models.') + ) + required_fields = models.CharField( + _("Required fields"), max_length=500, 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." + ) + ) class Meta: verbose_name = _("Importer - Default") @@ -531,13 +552,23 @@ class ImporterDefault(models.Model): ) @property + def mandatory_keys(self): + return tuple(k for k in self.required_fields.split("__") if k) + + @property def values(self): values = {} for default_value in self.default_values.all(): + targets = list(self.keys) target = default_value.target - if target == "-": - target = "" - values[target.split("__")[0]] = default_value.get_value() + if target != "-": + targets += target.split("__") + value = default_value.value + # explicit key or id is not set - try to get the value + if targets[-1] not in ("srid", "txt_idx", "slug", "id", "pk"): + value = default_value.get_value() + dct = generate_dict_from_list(targets, value) + values = update_data(values, dct) return values @@ -602,7 +633,7 @@ class ImporterDefaultValues(models.Model): if target not in fields: return field = fields[target] - if target in ("srid", "txt_idx"): + if target in ("srid", "txt_idx", "slug"): try: return parent_model.objects.get(**{target: self.value}) except (ValueError, parent_model.DoesNotExist): @@ -1639,6 +1670,17 @@ class Import(BaseImport): return errors @property + def default_values(self) -> dict: + """ + Get DB default values for this importer. Cached in memory. + :return: defaults values as a dict + """ + if hasattr(self, "_default_values"): + return self._default_values + self._default_values = self.importer_type.get_default_values() + return self._default_values + + @property def has_pre_import_form(self) -> bool: """ Check if a pre-import form is available @@ -1655,6 +1697,12 @@ class Import(BaseImport): @property def pre_import_values(self) -> dict: + """ + Get DB pre import values for this importer. Cached in memory. + :return: pre import as a dict + """ + if hasattr(self, "_pre_import_values"): + return self._pre_import_values values = {} for column in self.importer_type.columns.filter(col_number__lte=0): q = ImportColumnValue.objects.filter(column=column, import_item=self) @@ -1697,6 +1745,7 @@ class Import(BaseImport): keys = dup.field_name.split("__") dct = generate_dict_from_list(keys, value) values = update_data(values, dct) + self._pre_import_values = values return values def get_number_of_lines(self): |