diff options
| author | Étienne Loks <etienne.loks@iggdrasil.net> | 2026-03-10 17:26:54 +0100 |
|---|---|---|
| committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2026-03-25 15:10:32 +0100 |
| commit | 14f2a0a368a00a217b5657c0ed903be897d58294 (patch) | |
| tree | b9e1e1aa11afc6687e73854e175441b0f3b3e3e2 | |
| parent | e79327d84c8e18b83e8528b707d064a89259b1eb (diff) | |
| download | Ishtar-14f2a0a368a00a217b5657c0ed903be897d58294.tar.bz2 Ishtar-14f2a0a368a00a217b5657c0ed903be897d58294.zip | |
✨ qualified biographical note - models
| -rw-r--r-- | ishtar_common/admin.py | 15 | ||||
| -rw-r--r-- | ishtar_common/migrations/0274_qualifiedbiographicalnote.py | 78 | ||||
| -rw-r--r-- | ishtar_common/models.py | 80 |
3 files changed, 173 insertions, 0 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index b8cae9fa8..8f9d4d0a9 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -1053,6 +1053,20 @@ class BiographicalNoteAdmin(admin.ModelAdmin): ) +@admin.register(models.QualifiedBiographicalNote, site=admin_site) +class QualifiedBiographicalNoteAdmin(admin.ModelAdmin): + list_display = ("biographical_note", "qualification_type") + autocomplete_fields = ["biographical_note"] + model = models.QualifiedBiographicalNote + exclude = ("search_vector", "uuid", "cached_label") + search_fields = ( + "biographical_note__denomination", + "biographical_note__last_name", + "biographical_note__first_name", + "biographical_note__slug", + ) + + @admin.register(models.GDPRLog, site=admin_site) class GDPRLogAdmin(admin.ModelAdmin): list_display = ("user", "date", "ip", "routable_ip", "activity") @@ -1685,6 +1699,7 @@ general_models = [ models.LicenseType, models.PersonType, models.ShootingAngle, + models.QualifiedBiographicalNoteType, models_common.GeoProviderType, models_common.GeoDataType, models_common.GeoOriginType, diff --git a/ishtar_common/migrations/0274_qualifiedbiographicalnote.py b/ishtar_common/migrations/0274_qualifiedbiographicalnote.py new file mode 100644 index 000000000..4713937cc --- /dev/null +++ b/ishtar_common/migrations/0274_qualifiedbiographicalnote.py @@ -0,0 +1,78 @@ +# Generated by Django 4.2.19 on 2026-03-24 15:39 + +from django.conf import settings +import django.contrib.postgres.search +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import ishtar_common.models +import ishtar_common.models_common +import re +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ishtar_common', '0273_searchcache'), + ] + + operations = [ + migrations.AlterField( + model_name='documenttemplate', + name='export_format', + field=models.CharField(blank=True, choices=[('docx', 'DOCX'), ('html', 'HTML'), ('pdf', 'PDF'), ('xlsx', 'XLSX')], default='', max_length=4, verbose_name='Export format'), + ), + migrations.CreateModel( + name='QualifiedBiographicalNoteType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.TextField(verbose_name='Label')), + ('txt_idx', models.TextField(help_text='The slug is the standardized version of the name. It contains only lowercase letters, numbers and hyphens. Each slug must be unique.', unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9_]+\\Z'), 'Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.', 'invalid')], verbose_name='Textual ID')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('available', models.BooleanField(default=True, verbose_name='Available')), + ('order', models.IntegerField(default=1, verbose_name='Order')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ishtar_common.qualifiedbiographicalnotetype', verbose_name='Parent')), + ], + options={ + 'verbose_name': 'Qualification type', + 'verbose_name_plural': 'Qualification types', + 'ordering': ['order', 'label'], + }, + bases=(ishtar_common.models_common.Cached, models.Model), + ), + migrations.CreateModel( + name='QualifiedBiographicalNote', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('search_vector', django.contrib.postgres.search.SearchVectorField(blank=True, help_text='Auto filled at save', null=True, verbose_name='Search vector')), + ('timestamp_geo', models.IntegerField(blank=True, null=True, verbose_name='Timestamp geo')), + ('timestamp_label', models.IntegerField(blank=True, null=True, verbose_name='Timestamp label')), + ('data', models.JSONField(blank=True, default=dict)), + ('last_modified', models.DateTimeField(blank=True, default=django.utils.timezone.now)), + ('created', models.DateTimeField(blank=True, default=django.utils.timezone.now)), + ('history_m2m', models.JSONField(blank=True, default=dict)), + ('need_update', models.BooleanField(default=False, verbose_name='Need update')), + ('locked', models.BooleanField(default=False, verbose_name='Item locked for edition')), + ('uuid', models.UUIDField(default=uuid.uuid4)), + ('cached_label', models.TextField(blank=True, db_index=True, default='', verbose_name='Cached name')), + ('biographical_note', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='qualified_biographicalnote', to='ishtar_common.biographicalnote', verbose_name='Biographical note')), + ('history_creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('history_modifier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Last editor')), + ('imports', models.ManyToManyField(blank=True, related_name='imported_%(app_label)s_%(class)s', to='ishtar_common.import', verbose_name='Created by imports')), + ('imports_updated', models.ManyToManyField(blank=True, related_name='import_updated_%(app_label)s_%(class)s', to='ishtar_common.import', verbose_name='Updated by imports')), + ('ishtar_users', models.ManyToManyField(blank=True, related_name='%(class)s_associated', to='ishtar_common.ishtaruser')), + ('lock_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Locked by')), + ('qualification_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='ishtar_common.qualifiedbiographicalnotetype', verbose_name='Qualification type')), + ], + options={ + 'verbose_name': 'Qualified biographical note', + 'verbose_name_plural': 'Qualified biographical notes', + 'ordering': ('qualification_type__order', 'biographical_note__denomination'), + 'permissions': (('view_own_qualifiedbiographicalnote', 'Can view own Qualified biographical note'), ('change_own_qualifiedbiographicalnote', 'Can change own Qualified biographical note'), ('delete_own_qualifiedbiographicalnote', 'Can delete own Qualified biographical note')), + }, + bases=(ishtar_common.models_common.StatisticItem, ishtar_common.models_common.TemplateItem, models.Model, ishtar_common.models_common.CachedGen, ishtar_common.models_common.FixAssociated, ishtar_common.models.ValueGetter, ishtar_common.models_common.MainItem), + ), + ] diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 65dddc051..90e8a8d8d 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -4667,6 +4667,86 @@ def author_post_save(sender, **kwargs): post_save.connect(author_post_save, sender=Author) +class QualifiedBiographicalNoteType(OrderedHierarchicalType): + order = models.IntegerField(_("Order"), default=10) + + class Meta: + verbose_name = _("Qualification type") + verbose_name_plural = _("Qualification types") + ordering = ["order", "label"] + ADMIN_SECTION = _("Directory") + + +post_save.connect(post_save_cache, sender=QualifiedBiographicalNoteType) +post_delete.connect(post_save_cache, sender=QualifiedBiographicalNoteType) + + +class QualifiedBiographicalNote(BaseHistorizedItem, ValueGetter, MainItem): + SLUG = "qualifiedbiographicalnote" + PARENT_SEARCH_VECTORS = ["biographical_note"] + + uuid = models.UUIDField(default=uuid.uuid4) + biographical_note = models.ForeignKey( + BiographicalNote, + verbose_name=_("Biographical note"), + related_name="qualified_biographicalnote", + on_delete=models.CASCADE, + ) + qualification_type = models.ForeignKey( + QualifiedBiographicalNoteType, verbose_name=_("Qualification type"), + on_delete=models.PROTECT + ) + cached_label = models.TextField( + _("Cached name"), blank=True, default="", db_index=True + ) + objects = UUIDModelManager() + + class Meta: + verbose_name = _("Qualified biographical note") + verbose_name_plural = _("Qualified biographical notes") + ordering = ("qualification_type__order", "biographical_note__denomination") + permissions = ( + ("view_own_qualifiedbiographicalnote", "Can view own Qualified biographical note"), + ("change_own_qualifiedbiographicalnote", "Can change own Qualified biographical note"), + ("delete_own_qualifiedbiographicalnote", "Can delete own Qualified biographical note"), + ) + ADMIN_SECTION = _("Directory") + + def __str__(self): + return self.cached_label or "" + + def natural_key(self): + return (self.uuid,) + + def _generate_cached_label(self): + return f"{self.biographical_note} ({self.qualification_type})" + + def public_representation(self): + return {"type": str(self.qualification_type), + "biographical_note": str(self.biographical_note)} + + def merge(self, item, keep_old=False): + merge_model_objects(self, item, keep_old=keep_old) + + +def qualified_post_save(sender, **kwargs): + if not kwargs.get("instance"): + return + cached_label_changed(sender, **kwargs) + instance = kwargs.get("instance") + q = QualifiedBiographicalNote.objects.filter( + biographical_note=instance.biographical_note, + qualification_type=instance.qualification_type) + if q.count() <= 1: + return + qualified_persons = list(q.all()) + for qualified_person in qualified_persons[1:]: + qualified_persons[0].merge(qualified_person) + + +post_save.connect(qualified_post_save, sender=QualifiedBiographicalNote) + + class SourceType(OrderedHierarchicalType): coins_type = models.CharField( _("COInS export - type"), default="document", max_length=100 |
