diff --git a/django/cantusdb_project/main_app/admin/__init__.py b/django/cantusdb_project/main_app/admin/__init__.py index 15f8792fb..6839ad2d8 100644 --- a/django/cantusdb_project/main_app/admin/__init__.py +++ b/django/cantusdb_project/main_app/admin/__init__.py @@ -10,3 +10,5 @@ from main_app.admin.segment import SegmentAdmin from main_app.admin.sequence import SequenceAdmin from main_app.admin.source import SourceAdmin +from main_app.admin.institution import InstitutionAdmin +from main_app.admin.institution_identifier import InstitutionIdentifierAdmin diff --git a/django/cantusdb_project/main_app/admin/institution.py b/django/cantusdb_project/main_app/admin/institution.py index f1523a262..71d9c9d48 100644 --- a/django/cantusdb_project/main_app/admin/institution.py +++ b/django/cantusdb_project/main_app/admin/institution.py @@ -1,5 +1,25 @@ from django.contrib import admin +from main_app.admin.base_admin import BaseModelAdmin +from main_app.models import Institution, InstitutionIdentifier -class InstitutionAdmin(admin.ModelAdmin): - pass + +class InstitutionIdentifierInline(admin.TabularInline): + model = InstitutionIdentifier + extra = 0 + exclude = ["created_by", "last_updated_by"] + + +@admin.register(Institution) +class InstitutionAdmin(BaseModelAdmin): + list_display = ("name", "siglum", "get_city_region", "country") + search_fields = ("name", "siglum", "city", "region", "alternate_names") + list_filter = ("city",) + inlines = (InstitutionIdentifierInline,) + + def get_city_region(self, obj) -> str: + city: str = obj.city if obj.city else "[No city]" + region: str = f"({obj.region})" if obj.region else "" + return f"{city} {region}" + + get_city_region.short_description = "City" diff --git a/django/cantusdb_project/main_app/admin/institution_identifier.py b/django/cantusdb_project/main_app/admin/institution_identifier.py index f0d89c07b..6eb1288c9 100644 --- a/django/cantusdb_project/main_app/admin/institution_identifier.py +++ b/django/cantusdb_project/main_app/admin/institution_identifier.py @@ -1,5 +1,10 @@ from django.contrib import admin +from main_app.admin.base_admin import BaseModelAdmin +from main_app.models import InstitutionIdentifier -class InstitutionIdentifierAdmin(admin.ModelAdmin): - pass + +@admin.register(InstitutionIdentifier) +class InstitutionIdentifierAdmin(BaseModelAdmin): + list_display = ('identifier', 'identifier_type') + raw_id_fields = ("institution",) diff --git a/django/cantusdb_project/main_app/identifiers.py b/django/cantusdb_project/main_app/identifiers.py new file mode 100644 index 000000000..4ab27e298 --- /dev/null +++ b/django/cantusdb_project/main_app/identifiers.py @@ -0,0 +1,26 @@ +class ExternalIdentifiers: + RISM = 1 + VIAF = 2 + WIKIDATA = 3 + GND = 4 + BNF = 5 + LC = 6 + + +IDENTIFIER_TYPES = ( + (ExternalIdentifiers.RISM, "RISM Online"), + (ExternalIdentifiers.VIAF, "VIAF"), + (ExternalIdentifiers.WIKIDATA, "Wikidata"), + (ExternalIdentifiers.GND, "GND (Gemeinsame Normdatei)"), + (ExternalIdentifiers.BNF, "Bibliothèque national de France"), + (ExternalIdentifiers.LC, "Library of Congress"), +) + +TYPE_PREFIX = { + ExternalIdentifiers.RISM: ("rism", "https://rism.online/"), + ExternalIdentifiers.VIAF: ("viaf", "https://viaf.org/viaf/"), + ExternalIdentifiers.WIKIDATA: ("wkp", "https://www.wikidata.org/wiki/"), + ExternalIdentifiers.GND: ("dnb", "https://d-nb.info/gnd/"), + ExternalIdentifiers.BNF: ("bnf", "https://catalogue.bnf.fr/ark:/12148/cb"), + ExternalIdentifiers.LC: ("lc", "https://id.loc.gov/authorities/"), +} diff --git a/django/cantusdb_project/main_app/migrations/0013_institution.py b/django/cantusdb_project/main_app/migrations/0013_institution.py new file mode 100644 index 000000000..b206c5342 --- /dev/null +++ b/django/cantusdb_project/main_app/migrations/0013_institution.py @@ -0,0 +1,67 @@ +# Generated by Django 4.1.6 on 2024-06-06 12:06 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("main_app", "0012_alter_source_date_alter_source_title"), + ] + + operations = [ + migrations.CreateModel( + name="Institution", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "date_created", + models.DateTimeField( + auto_now_add=True, help_text="The date this entry was created" + ), + ), + ( + "date_updated", + models.DateTimeField( + auto_now=True, help_text="The date this entry was updated" + ), + ), + ("name", models.CharField(default="s.n.", max_length=255)), + ("siglum", models.CharField(default="XX-Nn", max_length=32)), + ("city", models.CharField(blank=True, max_length=64, null=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_created_by_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "last_updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_last_updated_by_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/django/cantusdb_project/main_app/migrations/0014_institutionidentifier.py b/django/cantusdb_project/main_app/migrations/0014_institutionidentifier.py new file mode 100644 index 000000000..f544fd859 --- /dev/null +++ b/django/cantusdb_project/main_app/migrations/0014_institutionidentifier.py @@ -0,0 +1,92 @@ +# Generated by Django 4.1.6 on 2024-06-06 12:16 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("main_app", "0013_institution"), + ] + + operations = [ + migrations.CreateModel( + name="InstitutionIdentifier", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "date_created", + models.DateTimeField( + auto_now_add=True, help_text="The date this entry was created" + ), + ), + ( + "date_updated", + models.DateTimeField( + auto_now=True, help_text="The date this entry was updated" + ), + ), + ( + "identifier", + models.CharField( + help_text="Do not provide the full URL here; only the identifier.", + max_length=512, + ), + ), + ( + "identifier_type", + models.IntegerField( + choices=[ + (1, "RISM Online"), + (2, "VIAF"), + (3, "Wikidata"), + (4, "GND (Gemeinsame Normdatei)"), + (5, "Bibliothèque national de France"), + (6, "Library of Congress"), + ] + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_created_by_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "institution", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="identifiers", + to="main_app.institution", + ), + ), + ( + "last_updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_last_updated_by_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/django/cantusdb_project/main_app/migrations/0015_source_holding_institution.py b/django/cantusdb_project/main_app/migrations/0015_source_holding_institution.py new file mode 100644 index 000000000..2de78ae33 --- /dev/null +++ b/django/cantusdb_project/main_app/migrations/0015_source_holding_institution.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.11 on 2024-06-06 13:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("main_app", "0014_institutionidentifier"), + ] + + operations = [ + migrations.AddField( + model_name="source", + name="holding_institution", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="main_app.institution", + ), + ), + ] diff --git a/django/cantusdb_project/main_app/migrations/0016_institution_alternate_names_institution_region_squashed_0017_institution_country_alter_institution_city.py b/django/cantusdb_project/main_app/migrations/0016_institution_alternate_names_institution_region_squashed_0017_institution_country_alter_institution_city.py new file mode 100644 index 000000000..15e17ec59 --- /dev/null +++ b/django/cantusdb_project/main_app/migrations/0016_institution_alternate_names_institution_region_squashed_0017_institution_country_alter_institution_city.py @@ -0,0 +1,51 @@ +# Generated by Django 4.1.6 on 2024-06-11 09:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + replaces = [ + ("main_app", "0016_institution_alternate_names_institution_region"), + ("main_app", "0017_institution_country_alter_institution_city"), + ] + + dependencies = [ + ("main_app", "0015_source_holding_institution"), + ] + + operations = [ + migrations.AddField( + model_name="institution", + name="alternate_names", + field=models.TextField( + blank=True, + help_text="Enter alternate names on separate lines.", + null=True, + ), + ), + migrations.AddField( + model_name="institution", + name="region", + field=models.CharField( + blank=True, + help_text='Province / State / Canton / County. Used to disambiguate cities, e.g., "London (Ontario)".', + max_length=64, + null=True, + ), + ), + migrations.AddField( + model_name="institution", + name="country", + field=models.CharField(default="s.l.", max_length=64), + ), + migrations.AlterField( + model_name="institution", + name="city", + field=models.CharField( + blank=True, + help_text="City / Town / Village / Settlement", + max_length=64, + null=True, + ), + ), + ] diff --git a/django/cantusdb_project/main_app/migrations/0018_institution_former_sigla.py b/django/cantusdb_project/main_app/migrations/0018_institution_former_sigla.py new file mode 100644 index 000000000..cdd13afe9 --- /dev/null +++ b/django/cantusdb_project/main_app/migrations/0018_institution_former_sigla.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.6 on 2024-06-11 15:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "main_app", + "0016_institution_alternate_names_institution_region_squashed_0017_institution_country_alter_institution_city", + ), + ] + + operations = [ + migrations.AddField( + model_name="institution", + name="former_sigla", + field=models.TextField( + blank=True, help_text="Enter former sigla on separate lines.", null=True + ), + ), + ] diff --git a/django/cantusdb_project/main_app/models/__init__.py b/django/cantusdb_project/main_app/models/__init__.py index 67d135611..9959354e9 100644 --- a/django/cantusdb_project/main_app/models/__init__.py +++ b/django/cantusdb_project/main_app/models/__init__.py @@ -11,3 +11,5 @@ from main_app.models.sequence import Sequence from main_app.models.rism_siglum import RismSiglum from main_app.models.source import Source +from main_app.models.institution import Institution +from main_app.models.institution_identifier import InstitutionIdentifier diff --git a/django/cantusdb_project/main_app/models/institution.py b/django/cantusdb_project/main_app/models/institution.py new file mode 100644 index 000000000..02fd1f19e --- /dev/null +++ b/django/cantusdb_project/main_app/models/institution.py @@ -0,0 +1,29 @@ +from django.db import models + +from main_app.models import BaseModel + + +region_help_text = """Province / State / Canton / County. Used to disambiguate cities, e.g., "London (Ontario)".""" +city_help_text = """City / Town / Village / Settlement""" + + +class Institution(BaseModel): + name = models.CharField(max_length=255, default="s.n.") + siglum = models.CharField(max_length=32, default="XX-Nn") + city = models.CharField( + max_length=64, blank=True, null=True, help_text=city_help_text + ) + region = models.CharField( + max_length=64, blank=True, null=True, help_text=region_help_text + ) + country = models.CharField(max_length=64, default="s.l.") + alternate_names = models.TextField( + blank=True, null=True, help_text="Enter alternate names on separate lines." + ) + former_sigla = models.TextField( + blank=True, null=True, help_text="Enter former sigla on separate lines." + ) + + def __str__(self) -> str: + sigl: str = f"({self.siglum})" if self.siglum else "" + return f"{self.name} {sigl}" diff --git a/django/cantusdb_project/main_app/models/institution_identifier.py b/django/cantusdb_project/main_app/models/institution_identifier.py new file mode 100644 index 000000000..6e0c4df85 --- /dev/null +++ b/django/cantusdb_project/main_app/models/institution_identifier.py @@ -0,0 +1,33 @@ +from django.db import models + +from main_app.identifiers import IDENTIFIER_TYPES, TYPE_PREFIX +from main_app.models import BaseModel + + +class InstitutionIdentifier(BaseModel): + identifier = models.CharField( + max_length=512, + help_text="Do not provide the full URL here; only the identifier.", + ) + identifier_type = models.IntegerField(choices=IDENTIFIER_TYPES) + institution = models.ForeignKey( + "Institution", related_name="identifiers", on_delete=models.CASCADE + ) + + def __str__(self): + return f"{self.identifier_prefix}:{self.identifier}" + + @property + def identifier_label(self) -> str: + d: dict[int, str] = dict(IDENTIFIER_TYPES) + return d[self.identifier_type] + + @property + def identifier_prefix(self) -> str: + (pfx, _) = TYPE_PREFIX[self.identifier_type] + return pfx + + @property + def identifier_url(self) -> str: + (_, url) = TYPE_PREFIX[self.identifier_type] + return f"{url}{self.identifier}" diff --git a/django/cantusdb_project/main_app/models/source.py b/django/cantusdb_project/main_app/models/source.py index 6d0efe68b..e49363306 100644 --- a/django/cantusdb_project/main_app/models/source.py +++ b/django/cantusdb_project/main_app/models/source.py @@ -44,6 +44,12 @@ class Source(BaseModel): null=True, blank=True, ) + holding_institution = models.ForeignKey( + "Institution", + on_delete=models.PROTECT, + null=True, + blank=True, + ) provenance = models.ForeignKey( "Provenance", on_delete=models.PROTECT,