diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2025-06-16 11:44:16 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2025-06-17 11:55:46 +0200 |
commit | e8d66d978a10f6fd03321cd4c387721489ded27d (patch) | |
tree | 6c6246c11477c0956e05cefe2bca15f4985e7f2d | |
parent | 329c62eae9ede8ae4908a82e22f6ebc611586182 (diff) | |
download | Ishtar-e8d66d978a10f6fd03321cd4c387721489ded27d.tar.bz2 Ishtar-e8d66d978a10f6fd03321cd4c387721489ded27d.zip |
🗃️ Media exporter: database migration
-rw-r--r-- | ishtar_common/migrations/0263_media_exporter.py | 36 | ||||
-rw-r--r-- | ishtar_common/models_common.py | 4 | ||||
-rw-r--r-- | ishtar_common/models_imports.py | 107 |
3 files changed, 145 insertions, 2 deletions
diff --git a/ishtar_common/migrations/0263_media_exporter.py b/ishtar_common/migrations/0263_media_exporter.py new file mode 100644 index 000000000..2776b1271 --- /dev/null +++ b/ishtar_common/migrations/0263_media_exporter.py @@ -0,0 +1,36 @@ +# Generated by Django 2.2.24 on 2025-06-16 15:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('ishtar_common', '0262_migrate_custom_form_slug'), + ] + + operations = [ + migrations.CreateModel( + name='MediaExporter', + 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')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ('files_to_export', models.CharField(choices=[('I', 'Images'), ('F', 'Associated files'), ('A', 'All')], default='I', max_length=1, verbose_name='Files to export')), + ('thumbnail_for_images', models.BooleanField(default=False, verbose_name='Use thumbnails for images')), + ('cascade', models.BooleanField(default=False, help_text='Export media of all attached items. For instance, for a context record, get all media attached to the attached finds.', verbose_name='Cascade export')), + ('query', models.TextField(blank=True, default='', help_text="Use 'text' query used in Ishtar search input. Can be left empty to export all.", verbose_name='Filter query')), + ('naming', models.TextField(blank=True, default='', help_text='Can use jinja template logic to have conditionnal naming. If left empty, each media will be named with incremented numbers.', verbose_name='Naming')), + ('associated_model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='media_exporters', to='ishtar_common.ImporterModel', verbose_name='Associated model')), + ('profile_types', models.ManyToManyField(blank=True, help_text='Limit to user with theses profiles', to='ishtar_common.ProfileType', verbose_name='Profile types')), + ('users', models.ManyToManyField(blank=True, help_text='Limit to theses users', to='ishtar_common.IshtarUser', verbose_name='Users')), + ], + options={ + 'verbose_name': 'Media exporter', + 'verbose_name_plural': 'Media exporters', + 'ordering': ('name',), + }, + ), + ] diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index bb786d48d..0c22b8e7f 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -3168,8 +3168,8 @@ class PermissionQuery(models.Model): name = models.CharField(_("Name"), max_length=200) slug = models.SlugField(_("Slug"), unique=True) request = models.TextField( - _("Request"), blank=True, null=False, default="", - help_text=_("Use 'text' request used in Ishtar search input") + _("Query"), blank=True, null=False, default="", + help_text=_("Use 'text' query used in Ishtar search input") ) active = models.BooleanField(_("Active"), default=True) include_associated_items = models.BooleanField( diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py index 55bbd1906..3d4bf632b 100644 --- a/ishtar_common/models_imports.py +++ b/ishtar_common/models_imports.py @@ -140,6 +140,9 @@ class ImporterModel(models.Model): def natural_key(self): return (self.klass,) + def get_class(self): + return import_class(self.klass) + IMPORT_TYPES = ( ("tab", _("Table")), @@ -3005,3 +3008,107 @@ class ItemKey(models.Model): linked_to_group.boolean = True linked_to_group.short_description = _("Linked to group") + + +MEDIA_EXPORT_TYPES = ( + ("I", _("Images")), + ("F", _("Associated files")), + ("A", _("All")), +) + + +class MediaExporter(models.Model): + """ + Settings to export all media attached to an item + """ + + name = models.CharField(_("Name"), max_length=200) + slug = models.SlugField(_("Slug"), unique=True, max_length=100) + available = models.BooleanField(_("Available"), default=True) + profile_types = models.ManyToManyField( + "ProfileType", + verbose_name=_("Profile types"), blank=True, + help_text=_("Limit to user with theses profiles") + ) + users = models.ManyToManyField( + "IshtarUser", verbose_name=_("Users"), blank=True, + help_text=_("Limit to theses users") + ) + associated_model = models.ForeignKey( + ImporterModel, + verbose_name=_("Associated model"), + on_delete=models.CASCADE, + related_name="media_exporters", + ) + files_to_export = models.CharField(_("Files to export"), max_length=1, + choices=MEDIA_EXPORT_TYPES, default="I") + cascade = models.BooleanField( + _("Cascade export"), + default=False, + help_text=_("Export media of all attached items. For instance, for a context " + "record, get all media attached to the attached finds.")) + query = models.TextField( + _("Filter query"), blank=True, null=False, default="", + help_text=_( + "Use 'text' query used in Ishtar search input. Can be left empty " + "to export all." + ) + ) + naming = models.TextField( + _("Naming"), blank=True, null=False, default="", + help_text=_( + "Can use jinja template logic to have conditionnal naming. If left empty, " + "each media will be named with incremented numbers." + ) + ) + + class Meta: + verbose_name = _("Media exporter") + verbose_name_plural = _("Media exporters") + ordering = ("name",) + ADMIN_SECTION = _("Documents") + + def __str__(self): + return f"{self.associated_model} - {self.name}" + + @classmethod + def get_available_query(cls, ishtar_user_id): + q = Q(users=None, profile_types=None) + q |= Q(users__pk=ishtar_user_id) + q |= Q( + profile_types__user_profiles__person__ishtaruser__pk=ishtar_user_id + ) + return cls.objects.filter(q) + + @classmethod + def get_available(cls, model, request): + if not request or not request.user or not hasattr(request.user, "ishtaruser")\ + or not request.user.ishtaruser: + return [] + model_klass = f"{model.__module__}.{model.__name__}" + model_klasses = [model_klass] + # manage compatibility with alternate notation for finds and treatments + if "models_finds" in model_klass: + model_klasses.append(model_klass.replace("models_finds", "models")) + elif "models_treatments" in model_klass: + model_klasses.append(model_klass.replace("models_treatments", "models")) + return list( + cls.get_available_query(request.user.ishtaruser.pk).filter( + associated_model__klass__in=model_klasses + ).order_by("name").all() + ) + + def is_available(self, request): + if not request or not request.user or not hasattr(request.user, "ishtaruser")\ + or not request.user.ishtaruser or not self.available: + return False + # no limitation + if not self.profile_types.count() and not self.users.count(): + return True + user_id = request.user.ishtaruser.pk + if self.users.filter(pk=user_id).exists(): + return True + if self.profile_types.filter( + user_profiles__person__ishtaruser__pk=user_id).exists(): + return True + return False |