From f10b03c55ece933e4277cdf1e7d4acfba9fdd7ed Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Fri, 22 Nov 2024 12:33:44 +0100 Subject: 🗃️ database: exhibition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile.example | 1 + archaeological_finds/admin.py | 20 +++- archaeological_finds/fixtures/initial_data-fr.json | 9 ++ archaeological_finds/ishtar_menu.py | 4 +- archaeological_finds/migrations/0133_exhibition.py | 131 +++++++++++++++++++++ archaeological_finds/models.py | 4 + archaeological_finds/models_treatments.py | 98 +++++++++++++++ archaeological_finds/urls.py | 15 ++- 8 files changed, 274 insertions(+), 8 deletions(-) create mode 100644 archaeological_finds/migrations/0133_exhibition.py diff --git a/Makefile.example b/Makefile.example index c286a877e..59cd303cf 100644 --- a/Makefile.example +++ b/Makefile.example @@ -318,6 +318,7 @@ fixtures_finds: archaeological_finds.batchtype \ archaeological_finds.checkedtype \ archaeological_finds.treatmentfiletype \ + archaeological_finds.exhibitiontype \ archaeological_finds.checkedtype \ archaeological_finds.technicalprocesstype \ archaeological_finds.technicalareatype \ diff --git a/archaeological_finds/admin.py b/archaeological_finds/admin.py index b616b3dc7..669c41ead 100644 --- a/archaeological_finds/admin.py +++ b/archaeological_finds/admin.py @@ -228,21 +228,37 @@ class ConservatoryStateAdmin(GeneralTypeAdmin): @admin.register(models.TreatmentFileType, site=admin_site) -class TreatmentFileType(GeneralTypeAdmin): +class TreatmentFileTypeAdmin(GeneralTypeAdmin): extra_list_display = ["treatment_type"] @admin.register(models.TreatmentState, site=admin_site) -class TreatmentState(GeneralTypeAdmin): +class TreatmentStateAdmin(GeneralTypeAdmin): extra_list_display = ["order", "executed"] +@admin.register(models.Exhibition, site=admin_site) +class ExhibitionAdmin(HistorizedObjectAdmin): + list_display = ('name', 'year', 'reference', 'exhibition_type') + list_filter = ('exhibition_type',) + search_fields = ('name', 'reference') + model = models.Exhibition + autocomplete_fields = HistorizedObjectAdmin.autocomplete_fields + [ + 'in_charge', 'treatment_files' + ] + readonly_fields = HistorizedObjectAdmin.readonly_fields + [ + 'ishtar_users', + ] + exclude = ["documents", "main_image"] + + general_models = [ models.AlterationCauseType, models.AlterationType, models.BatchType, models.CollectionEntryModeType, models.IntegrityType, models.InventoryConformity, models.InventoryMarkingPresence, models.MarkingType, models.MaterialTypeQualityType, models.MuseumCollection, models.ObjectTypeQualityType, models.OriginalReproduction, models.RemarkabilityType, models.TreatmentEmergencyType, models.DiscoveryMethod, + models.ExhibitionType ] for model in general_models: diff --git a/archaeological_finds/fixtures/initial_data-fr.json b/archaeological_finds/fixtures/initial_data-fr.json index ef6af8b9e..b969d15ca 100644 --- a/archaeological_finds/fixtures/initial_data-fr.json +++ b/archaeological_finds/fixtures/initial_data-fr.json @@ -7082,5 +7082,14 @@ "available": true, "order": 30 } +}, +{ + "model": "archaeological_finds.exhibitiontype", + "fields": { + "label": "Exposition", + "txt_idx": "exhibition", + "comment": "", + "available": true + } } ] diff --git a/archaeological_finds/ishtar_menu.py b/archaeological_finds/ishtar_menu.py index 967d3267c..0326932e6 100644 --- a/archaeological_finds/ishtar_menu.py +++ b/archaeological_finds/ishtar_menu.py @@ -143,8 +143,8 @@ MENU_SECTIONS = [ model=models.TreatmentFile, profile_restriction="museum", access_controls=[ - "archaeological_finds.view_treatmentfile", - "archaeological_finds.view_own_treatmentfile" + "archaeological_finds.view_exhibition", + "archaeological_finds.view_own_exhibition" ], ), MenuItem( diff --git a/archaeological_finds/migrations/0133_exhibition.py b/archaeological_finds/migrations/0133_exhibition.py new file mode 100644 index 000000000..a4a29168e --- /dev/null +++ b/archaeological_finds/migrations/0133_exhibition.py @@ -0,0 +1,131 @@ +# Generated by Django 2.2.24 on 2024-11-21 13:05 + +import archaeological_finds.models_treatments +import datetime +from django.conf import settings +import django.contrib.postgres.fields.jsonb +import django.contrib.postgres.indexes +import django.contrib.postgres.search +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import ishtar_common.models +import ishtar_common.models_common +import ishtar_common.utils +import re +import simple_history.models + + +def create_default(apps, __): + ExhibitionType = apps.get_model("archaeological_finds", "ExhibitionType") + ExhibitionType.objects.get_or_create( + txt_idx="exhibition", defaults={"label": "Exposition"} + ) + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ishtar_common', '0258_rename_perm_query'), + ('archaeological_finds', '0132_exhibitions_basket_for_treatment'), + ] + + operations = [ + migrations.CreateModel( + name='ExhibitionType', + 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')), + ], + options={ + 'verbose_name': 'Exhibition type', + 'verbose_name_plural': 'Exhibition types', + 'ordering': ('label',), + }, + bases=(ishtar_common.models_common.Cached, models.Model), + ), + migrations.CreateModel( + name='HistoricalExhibition', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('search_vector', django.contrib.postgres.search.SearchVectorField(blank=True, help_text='Auto filled at save', null=True, verbose_name='Search vector')), + ('data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), + ('last_modified', models.DateTimeField(blank=True, default=datetime.datetime.now)), + ('created', models.DateTimeField(blank=True, default=datetime.datetime.now)), + ('history_m2m', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), + ('locked', models.BooleanField(default=False, verbose_name='Item locked for edition')), + ('qrcode', models.TextField(blank=True, max_length=255, null=True)), + ('name', models.TextField(verbose_name='Name')), + ('year', models.IntegerField(default=ishtar_common.utils.get_current_year, verbose_name='Year')), + ('reference', models.TextField(blank=True, default='-', null=True, verbose_name='Reference')), + ('associated_basket_id', models.PositiveIntegerField(blank=True, help_text='Reference basket', null=True, verbose_name='Basket ID')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField()), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('exhibition_type', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='archaeological_finds.ExhibitionType', verbose_name='Exhibition type')), + ('history_creator', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('history_modifier', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Last editor')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('in_charge', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='ishtar_common.Person', verbose_name='Person in charge')), + ('lock_user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Locked by')), + ('main_image', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='ishtar_common.Document', verbose_name='Main image')), + ], + options={ + 'verbose_name': 'historical Exhibition', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='Exhibition', + 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')), + ('data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), + ('last_modified', models.DateTimeField(blank=True, default=datetime.datetime.now)), + ('created', models.DateTimeField(blank=True, default=datetime.datetime.now)), + ('history_m2m', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), + ('locked', models.BooleanField(default=False, verbose_name='Item locked for edition')), + ('qrcode', models.ImageField(blank=True, max_length=255, null=True, upload_to=ishtar_common.utils.get_image_path)), + ('name', models.TextField(verbose_name='Name')), + ('year', models.IntegerField(default=ishtar_common.utils.get_current_year, verbose_name='Year')), + ('reference', models.TextField(blank=True, default='-', null=True, verbose_name='Reference')), + ('associated_basket_id', models.PositiveIntegerField(blank=True, help_text='Reference basket', null=True, verbose_name='Basket ID')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('documents', models.ManyToManyField(blank=True, related_name='exhibitions', to='ishtar_common.Document', verbose_name='Documents')), + ('exhibition_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='archaeological_finds.ExhibitionType', verbose_name='Exhibition type')), + ('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_archaeological_finds_exhibition', to='ishtar_common.Import', verbose_name='Created by imports')), + ('imports_updated', models.ManyToManyField(blank=True, related_name='import_updated_archaeological_finds_exhibition', to='ishtar_common.Import', verbose_name='Updated by imports')), + ('in_charge', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exhibitions', to='ishtar_common.Person', verbose_name='Person in charge')), + ('ishtar_users', models.ManyToManyField(blank=True, related_name='exhibition_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')), + ('main_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='main_image_exhibitions', to='ishtar_common.Document', verbose_name='Main image')), + ('treatment_files', models.ManyToManyField(blank=True, related_name='exhibitions', to='archaeological_finds.TreatmentFile', verbose_name='Loans')), + ], + options={ + 'verbose_name': 'Exhibition', + 'verbose_name_plural': 'Exhibitions', + 'ordering': ('year', 'name'), + 'permissions': (('view_own_exhibition', 'Can view own Exhibition'), ('change_own_exhibition', 'Can change own Exhibition'), ('delete_own_exhibition', 'Can delete own Exhibition')), + }, + bases=(ishtar_common.models_common.DocumentItem, 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_common.ImageContainerModel, ishtar_common.utils.OwnPerms, ishtar_common.models.ValueGetter, ishtar_common.models_common.MainItem, archaeological_finds.models_treatments.AssociatedFindBasket), + ), + migrations.AddIndex( + model_name='exhibition', + index=django.contrib.postgres.indexes.GinIndex(fields=['data'], name='archaeologi_data_dd22e0_gin'), + ), + migrations.AlterUniqueTogether( + name='exhibition', + unique_together={('year', 'name')}, + ), + migrations.RunPython(create_default), + ] diff --git a/archaeological_finds/models.py b/archaeological_finds/models.py index ce2098d8d..90ca4bffd 100644 --- a/archaeological_finds/models.py +++ b/archaeological_finds/models.py @@ -35,6 +35,8 @@ from archaeological_finds.models_finds import ( ) from archaeological_finds.models_treatments import ( AbsFindTreatments, + Exhibition, + ExhibitionType, Treatment, FindDownstreamTreatments, FindNonModifTreatments, @@ -57,6 +59,8 @@ __all__ = [ "CommunicabilityType", "ConservatoryState", "DiscoveryMethod", + "Exhibition", + "ExhibitionType", "FBulkView", "Find", "FirstBaseFindView", diff --git a/archaeological_finds/models_treatments.py b/archaeological_finds/models_treatments.py index 9a9455ea1..933df0fa8 100644 --- a/archaeological_finds/models_treatments.py +++ b/archaeological_finds/models_treatments.py @@ -1454,3 +1454,101 @@ class TreatmentFile( m2m_changed.connect(document_attached_changed, sender=TreatmentFile.documents.through) post_save.connect(cached_label_changed, sender=TreatmentFile) + + +class ExhibitionType(GeneralType): + class Meta: + verbose_name = _("Exhibition type") + verbose_name_plural = _("Exhibition types") + ordering = ("label",) + ADMIN_SECTION = _("Treatments") + + +class Exhibition( + DocumentItem, + BaseHistorizedItem, + CompleteIdentifierItem, + OwnPerms, + ValueGetter, + MainItem, + AssociatedFindBasket, +): + SLUG = "exhibition" + APP = "archaeological_finds" + MODEL = SLUG + SHOW_URL = "show-exhibition" + DELETE_URL = "delete-exhibition" + TABLE_COLS = ["year", "reference", "name"] + BASE_SEARCH_VECTORS = [ + SearchVectorConfig("exhibition_type__label"), + SearchVectorConfig("reference"), + SearchVectorConfig("name"), + SearchVectorConfig("comment", "local"), + ] + name = models.TextField(_("Name")) + exhibition_type = models.ForeignKey( + ExhibitionType, + verbose_name=_("Exhibition type"), + on_delete=models.PROTECT, + ) + year = models.IntegerField(_("Year"), default=get_current_year) + reference = models.TextField( + _("Reference"), blank=True, null=True, default="-" + ) + in_charge = models.ForeignKey( + Person, + related_name="exhibitions", + verbose_name=_("Person in charge"), + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + # prevent circular imports... + associated_basket_id = models.PositiveIntegerField( + verbose_name=_("Basket ID"), blank=True, null=True, + help_text=_("Reference basket") + ) + comment = models.TextField(_("Comment"), blank=True, default="") + treatment_files = models.ManyToManyField( + TreatmentFile, + related_name="exhibitions", + verbose_name=_("Loans"), + blank=True, + ) + documents = models.ManyToManyField( + Document, + related_name="exhibitions", + verbose_name=_("Documents"), + blank=True, + ) + main_image = models.ForeignKey( + Document, + related_name="main_image_exhibitions", + on_delete=models.SET_NULL, + verbose_name=_("Main image"), + blank=True, + null=True, + ) + timestamp_geo = None + timestamp_label = None + complete_identifier = None + custom_index = None + need_update = None + cached_label = None + history = HistoricalRecords() + + class Meta: + verbose_name = _("Exhibition") + verbose_name_plural = _("Exhibitions") + unique_together = ("year", "name") + permissions = ( + ("view_own_exhibition", "Can view own Exhibition"), + ("change_own_exhibition", "Can change own Exhibition"), + ("delete_own_exhibition", "Can delete own Exhibition"), + ) + ordering = ("year", "name") + indexes = [ + GinIndex(fields=["data"]), + ] + ADMIN_SECTION = _("Treatments") + diff --git a/archaeological_finds/urls.py b/archaeological_finds/urls.py index 2af33537f..467df9770 100644 --- a/archaeological_finds/urls.py +++ b/archaeological_finds/urls.py @@ -442,16 +442,23 @@ urlpatterns = [ path( "exhibition/", check_permissions( - ["archaeological_finds.view_treatmentfile", - "archaeological_finds.view_own_treatmentfile"] + ["archaeological_finds.view_exhibition", + "archaeological_finds.view_own_exhibition"] )(views.exhibition_wizard), name="exhibition-search", ), + path( + "exhibition/create/", + check_permissions( + ["archaeological_finds.add_exhibition"] + )(views.ExhibitionCreateView.as_view()), + name="exhibition-create", + ), path( "exhibition//", check_permissions( - ["archaeological_finds.view_treatmentfile", - "archaeological_finds.view_own_treatmentfile"] + ["archaeological_finds.view_exhibition", + "archaeological_finds.view_own_exhibition"] )(views.exhibition_wizard), name="exhibition-search", ), -- cgit v1.2.3