diff --git a/django/cantusdb_project/main_app/admin/__init__.py b/django/cantusdb_project/main_app/admin/__init__.py index 6c0fac619..49a75893d 100644 --- a/django/cantusdb_project/main_app/admin/__init__.py +++ b/django/cantusdb_project/main_app/admin/__init__.py @@ -4,7 +4,7 @@ from main_app.admin.feast import FeastAdmin from main_app.admin.genre import GenreAdmin from main_app.admin.notation import NotationAdmin -from main_app.admin.office import OfficeAdmin +from main_app.admin.service import ServiceAdmin from main_app.admin.provenance import ProvenanceAdmin from main_app.admin.segment import SegmentAdmin from main_app.admin.sequence import SequenceAdmin diff --git a/django/cantusdb_project/main_app/admin/chant.py b/django/cantusdb_project/main_app/admin/chant.py index 425ca9e12..373b59916 100644 --- a/django/cantusdb_project/main_app/admin/chant.py +++ b/django/cantusdb_project/main_app/admin/chant.py @@ -1,10 +1,20 @@ from django.contrib import admin from main_app.admin.base_admin import EXCLUDE, READ_ONLY, BaseModelAdmin +from main_app.admin.filters import InputFilter from main_app.forms import AdminChantForm from main_app.models import Chant +class SourceKeyFilter(InputFilter): + parameter_name = "source_id" + title = "Source ID" + + def queryset(self, request, queryset): + if self.value(): + return queryset.filter(source_id=self.value()) + + @admin.register(Chant) class ChantAdmin(BaseModelAdmin): @@ -12,7 +22,7 @@ def get_queryset(self, request): return ( super() .get_queryset(request) - .select_related("source__holding_institution", "genre", "office") + .select_related("source__holding_institution", "genre", "service") ) @admin.display(description="Source Siglum") @@ -30,13 +40,15 @@ def get_source_siglum(self, obj): "incipit", "cantus_id", "id", + "source__holding_institution__siglum" ) readonly_fields = READ_ONLY + ("incipit",) list_filter = ( + SourceKeyFilter, "genre", - "office", + "service", ) exclude = EXCLUDE + ( "col1", diff --git a/django/cantusdb_project/main_app/admin/office.py b/django/cantusdb_project/main_app/admin/office.py deleted file mode 100644 index d31d56534..000000000 --- a/django/cantusdb_project/main_app/admin/office.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.contrib import admin - -from main_app.admin.base_admin import BaseModelAdmin -from main_app.forms import AdminOfficeForm -from main_app.models import Office - - -@admin.register(Office) -class OfficeAdmin(BaseModelAdmin): - search_fields = ("name",) - form = AdminOfficeForm diff --git a/django/cantusdb_project/main_app/admin/sequence.py b/django/cantusdb_project/main_app/admin/sequence.py index 1f2f0909f..252e1515f 100644 --- a/django/cantusdb_project/main_app/admin/sequence.py +++ b/django/cantusdb_project/main_app/admin/sequence.py @@ -11,7 +11,7 @@ def get_queryset(self, request): return ( super() .get_queryset(request) - .select_related("source__holding_institution", "genre", "office") + .select_related("source__holding_institution", "genre", "service") ) @admin.display(description="Source Siglum") @@ -34,7 +34,7 @@ def get_source_siglum(self, obj): list_display = ("incipit", "get_source_siglum", "genre") list_filter = ( "genre", - "office", + "service", ) raw_id_fields = ( "source", diff --git a/django/cantusdb_project/main_app/admin/service.py b/django/cantusdb_project/main_app/admin/service.py new file mode 100644 index 000000000..77ad37858 --- /dev/null +++ b/django/cantusdb_project/main_app/admin/service.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from main_app.admin.base_admin import BaseModelAdmin +from main_app.forms import AdminServiceForm +from main_app.models import Service + + +@admin.register(Service) +class ServiceAdmin(BaseModelAdmin): + search_fields = ("name",) + form = AdminServiceForm diff --git a/django/cantusdb_project/main_app/forms.py b/django/cantusdb_project/main_app/forms.py index 9e4807f43..e1f6e11f8 100644 --- a/django/cantusdb_project/main_app/forms.py +++ b/django/cantusdb_project/main_app/forms.py @@ -2,7 +2,7 @@ from django.contrib.auth.forms import ReadOnlyPasswordHashField from .models import ( Chant, - Office, + Service, Genre, Notation, Feast, @@ -37,9 +37,9 @@ class NameModelChoiceField(forms.ModelChoiceField): """ A custom ModelChoiceField that overrides the label_from_instance method to display the object's name attribute instead of str(object). - This field is specifically designed for handling genre and office objects. + This field is specifically designed for handling genre and service objects. Rather than displaying the name along with its description, sometimes we - only want the shorthand notation for the genre and office objects. + only want the shorthand notation for the genre and service objects. (Eg. [AV] Antiphon verse --> AV) """ @@ -78,7 +78,7 @@ class Meta: "marginalia", "folio", "c_sequence", - "office", + "service", "genre", "position", "cantus_id", @@ -113,7 +113,7 @@ class Meta: "marginalia": TextInputWidget(), # folio: defined below (required) # c_sequence: defined below (required) - "office": autocomplete.ModelSelect2(url="office-autocomplete"), + "service": autocomplete.ModelSelect2(url="service-autocomplete"), "genre": autocomplete.ModelSelect2(url="genre-autocomplete"), "position": TextInputWidget(), "cantus_id": TextInputWidget(), @@ -284,7 +284,7 @@ class Meta: "folio", "c_sequence", "feast", - "office", + "service", "genre", "position", "cantus_id", @@ -318,7 +318,7 @@ class Meta: # folio: defined below (required) # c_sequence: defined below (required) "feast": autocomplete.ModelSelect2(url="feast-autocomplete"), - "office": autocomplete.ModelSelect2(url="office-autocomplete"), + "service": autocomplete.ModelSelect2(url="service-autocomplete"), "genre": autocomplete.ModelSelect2(url="genre-autocomplete"), "position": TextInputWidget(), "cantus_id": TextInputWidget(), @@ -591,10 +591,10 @@ class Meta: label="Sequence", ) - # We use NameModelChoiceField here so the dropdown list of office/mass displays the name + # We use NameModelChoiceField here so the dropdown list of service/mass displays the name # instead of [name] + description - office = NameModelChoiceField( - queryset=Office.objects.all().order_by("name"), + service = NameModelChoiceField( + queryset=Service.objects.all().order_by("name"), required=False, ) # We use NameModelChoiceField here so the dropdown list of genres displays the name @@ -639,9 +639,9 @@ class Meta: name.widget.attrs.update({"style": "width: 400px;"}) -class AdminOfficeForm(forms.ModelForm): +class AdminServiceForm(forms.ModelForm): class Meta: - model = Office + model = Service fields = "__all__" name = forms.CharField(required=True, widget=TextInputWidget) @@ -678,10 +678,10 @@ class Meta: "chant_range": VolpianoAreaWidget(), } - # We use NameModelChoiceField here so the dropdown list of office/mass displays the name + # We use NameModelChoiceField here so the dropdown list of service/mass displays the name # instead of [name] + description - office = NameModelChoiceField( - queryset=Office.objects.all().order_by("name"), + service = NameModelChoiceField( + queryset=Service.objects.all().order_by("name"), required=False, ) # We use NameModelChoiceField here so the dropdown list of genres displays the name diff --git a/django/cantusdb_project/main_app/management/commands/update_cached_concordances.py b/django/cantusdb_project/main_app/management/commands/update_cached_concordances.py index 47015a58b..7c0fb2ebc 100644 --- a/django/cantusdb_project/main_app/management/commands/update_cached_concordances.py +++ b/django/cantusdb_project/main_app/management/commands/update_cached_concordances.py @@ -62,7 +62,7 @@ def get_concordances() -> list[dict]: "source", "feast", "genre", - "office", + "service", ).values( "id", "source_id", @@ -72,7 +72,7 @@ def get_concordances() -> list[dict]: "incipit", "feast__name", "genre__name", - "office__name", + "service__name", "position", "cantus_id", "image_link", @@ -112,7 +112,7 @@ def make_chant_dict(chant: dict) -> dict: "incipit": chant["incipit"], "feast": chant["feast__name"], "genre": chant["genre__name"], - "office": chant["office__name"], + "service": chant["service__name"], "position": chant["position"], "cantus_id": chant["cantus_id"], "image": chant["image_link"], diff --git a/django/cantusdb_project/main_app/migrations/0027_rename_office_service_rename_office_chant_service_and_more.py b/django/cantusdb_project/main_app/migrations/0027_rename_office_service_rename_office_chant_service_and_more.py new file mode 100644 index 000000000..01120ca14 --- /dev/null +++ b/django/cantusdb_project/main_app/migrations/0027_rename_office_service_rename_office_chant_service_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.11 on 2024-08-07 17:10 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("main_app", "0026_source_segment_m2m"), + ] + + operations = [ + migrations.RenameModel( + old_name="Office", + new_name="Service", + ), + migrations.RenameField( + model_name="chant", + old_name="office", + new_name="service", + ), + migrations.RenameField( + model_name="sequence", + old_name="office", + new_name="service", + ), + ] diff --git a/django/cantusdb_project/main_app/models/__init__.py b/django/cantusdb_project/main_app/models/__init__.py index ef755c898..ea1149eca 100644 --- a/django/cantusdb_project/main_app/models/__init__.py +++ b/django/cantusdb_project/main_app/models/__init__.py @@ -5,7 +5,7 @@ from main_app.models.feast import Feast from main_app.models.genre import Genre from main_app.models.notation import Notation -from main_app.models.office import Office +from main_app.models.service import Service from main_app.models.provenance import Provenance from main_app.models.segment import Segment from main_app.models.sequence import Sequence diff --git a/django/cantusdb_project/main_app/models/base_chant.py b/django/cantusdb_project/main_app/models/base_chant.py index 7885f538a..ea208a0d1 100644 --- a/django/cantusdb_project/main_app/models/base_chant.py +++ b/django/cantusdb_project/main_app/models/base_chant.py @@ -56,8 +56,8 @@ class Meta: image_link = models.URLField(blank=True, null=True) json_info = models.JSONField(null=True, blank=True) marginalia = models.CharField(max_length=63, null=True, blank=True) - office = models.ForeignKey( - "Office", on_delete=models.PROTECT, null=True, blank=True + service = models.ForeignKey( + "Service", on_delete=models.PROTECT, null=True, blank=True ) position = models.CharField(max_length=63, null=True, blank=True) diff --git a/django/cantusdb_project/main_app/models/chant.py b/django/cantusdb_project/main_app/models/chant.py index 2c5986eb0..4091a2aec 100644 --- a/django/cantusdb_project/main_app/models/chant.py +++ b/django/cantusdb_project/main_app/models/chant.py @@ -28,7 +28,7 @@ def index_components(self) -> dict: source = self.source.title if self.source else None genre = self.genre.name if self.genre else None feast = self.feast.name if self.feast else None - office = self.office.name if self.office else None + service = self.service.name if self.service else None return { "A": ( " ".join( @@ -38,7 +38,7 @@ def index_components(self) -> dict: ) ) ), - "B": (" ".join(filter(None, [genre, feast, office]))), + "B": (" ".join(filter(None, [genre, feast, service]))), } def related_chants_by_cantus_id(self) -> QuerySet: diff --git a/django/cantusdb_project/main_app/models/office.py b/django/cantusdb_project/main_app/models/service.py similarity index 93% rename from django/cantusdb_project/main_app/models/office.py rename to django/cantusdb_project/main_app/models/service.py index a4e2ff87d..34c4d119f 100644 --- a/django/cantusdb_project/main_app/models/office.py +++ b/django/cantusdb_project/main_app/models/service.py @@ -2,7 +2,7 @@ from main_app.models import BaseModel -class Office(BaseModel): +class Service(BaseModel): name = models.CharField(max_length=3) description = models.TextField() diff --git a/django/cantusdb_project/main_app/templates/browse_chants.html b/django/cantusdb_project/main_app/templates/browse_chants.html index dfdb6c020..8b9245520 100644 --- a/django/cantusdb_project/main_app/templates/browse_chants.html +++ b/django/cantusdb_project/main_app/templates/browse_chants.html @@ -99,8 +99,8 @@

Browse Chants

{% endif %} - {% if chant.office %} - {{ chant.office.name|default:"" }} + {% if chant.service %} + {{ chant.service.name|default:"" }} {% endif %} diff --git a/django/cantusdb_project/main_app/templates/chant_create.html b/django/cantusdb_project/main_app/templates/chant_create.html index 7edca7149..5aec5de03 100644 --- a/django/cantusdb_project/main_app/templates/chant_create.html +++ b/django/cantusdb_project/main_app/templates/chant_create.html @@ -70,9 +70,9 @@

Create Chant

- -
{{ form.office }} - ? + +
{{ form.service }} + ?
diff --git a/django/cantusdb_project/main_app/templates/chant_detail.html b/django/cantusdb_project/main_app/templates/chant_detail.html index c885385e7..e83585a83 100644 --- a/django/cantusdb_project/main_app/templates/chant_detail.html +++ b/django/cantusdb_project/main_app/templates/chant_detail.html @@ -77,11 +77,11 @@

{{ chant.incipit }}

{% endif %} - {% if chant.office %} + {% if chant.service %}
Service
- {{ chant.office.name }} + {{ chant.service.name }}
{% endif %} @@ -351,8 +351,8 @@

List of melodies

{{ chant.c_sequence }} - - {{ chant.office.name|default_if_none:"" }} + + {{ chant.service.name|default_if_none:"" }} {{ chant.genre.name|default_if_none:"" }} {{ chant.position|default_if_none:"" }} @@ -385,8 +385,8 @@

List of melodies

{{ chant.c_sequence }} - - {{ chant.office.name|default_if_none:"" }} + + {{ chant.service.name|default_if_none:"" }} {{ chant.genre.name|default_if_none:"" }} {{ chant.position|default_if_none:"" }} @@ -424,8 +424,8 @@

List of melodies

{{ chant.c_sequence }} - - {{ chant.office.name|default_if_none:"" }} + + {{ chant.service.name|default_if_none:"" }} {{ chant.genre.name|default_if_none:"" }} {{ chant.position|default_if_none:"" }} diff --git a/django/cantusdb_project/main_app/templates/chant_edit.html b/django/cantusdb_project/main_app/templates/chant_edit.html index a7751507d..038eef616 100644 --- a/django/cantusdb_project/main_app/templates/chant_edit.html +++ b/django/cantusdb_project/main_app/templates/chant_edit.html @@ -77,8 +77,8 @@
- -
{{ form.office }} + +
{{ form.service }}
@@ -421,8 +421,8 @@

{{ {{ chant.c_sequence }} - - {{ chant.office.name|default_if_none:"" }} + + {{ chant.service.name|default_if_none:"" }} {{ chant.genre.name|default_if_none:"" }} @@ -462,8 +462,8 @@

{{ {{ chant.c_sequence }} - - {{ chant.office.name|default_if_none:"" }} + + {{ chant.service.name|default_if_none:"" }} {{ chant.genre.name|default_if_none:"" }} diff --git a/django/cantusdb_project/main_app/templates/chant_search.html b/django/cantusdb_project/main_app/templates/chant_search.html index a14de48ec..601bd380a 100644 --- a/django/cantusdb_project/main_app/templates/chant_search.html +++ b/django/cantusdb_project/main_app/templates/chant_search.html @@ -54,11 +54,11 @@

Search Chants

- - - {% for office in offices %} - + {% for service in services %} + {% endfor %}
@@ -157,14 +157,14 @@

Search Chants

Feast - {% if order == "office" %} + {% if order == "service" %} {% if sort == "desc" %} -
Service ▼ + Service ▼ {% else %} - Service ▲ + Service ▲ {% endif %} {% else %} - Service + Service {% endif %} @@ -273,8 +273,8 @@

Search Chants

{% endif %} - {% if chant.office %} - {{ chant.office.name }} + {% if chant.service %} + {{ chant.service.name }} {% endif %} diff --git a/django/cantusdb_project/main_app/templates/chant_seq_by_cantus_id.html b/django/cantusdb_project/main_app/templates/chant_seq_by_cantus_id.html index f1a678dfd..19543c760 100644 --- a/django/cantusdb_project/main_app/templates/chant_seq_by_cantus_id.html +++ b/django/cantusdb_project/main_app/templates/chant_seq_by_cantus_id.html @@ -52,9 +52,9 @@

{{ chant.incipit }} {% endif %} - {% if chant.office %} - - {{ chant.office.name }} + {% if chant.service %} + + {{ chant.service.name }} {% endif %} diff --git a/django/cantusdb_project/main_app/templates/full_inventory.html b/django/cantusdb_project/main_app/templates/full_inventory.html index 46b58012c..07ee1827e 100644 --- a/django/cantusdb_project/main_app/templates/full_inventory.html +++ b/django/cantusdb_project/main_app/templates/full_inventory.html @@ -65,8 +65,8 @@

Cantus Inventory: - - {{ chant.office.name|default_if_none:"" }} + + {{ chant.service.name|default_if_none:"" }} diff --git a/django/cantusdb_project/main_app/templates/office_detail.html b/django/cantusdb_project/main_app/templates/service_detail.html similarity index 67% rename from django/cantusdb_project/main_app/templates/office_detail.html rename to django/cantusdb_project/main_app/templates/service_detail.html index 78d4d223c..8c4f14f80 100644 --- a/django/cantusdb_project/main_app/templates/office_detail.html +++ b/django/cantusdb_project/main_app/templates/service_detail.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block title %} - {{ office.name }} | Cantus Database + {{ service.name }} | Cantus Database {% endblock %} {% block content %} @@ -9,7 +9,7 @@ {% include "global_search_bar.html" %} -

{{ office.name }}

-

{{ office.description }}

+

{{ service.name }}

+

{{ service.description }}

{% endblock %} diff --git a/django/cantusdb_project/main_app/templates/office_list.html b/django/cantusdb_project/main_app/templates/service_list.html similarity index 81% rename from django/cantusdb_project/main_app/templates/office_list.html rename to django/cantusdb_project/main_app/templates/service_list.html index 1629ae2d0..19e6871c4 100644 --- a/django/cantusdb_project/main_app/templates/office_list.html +++ b/django/cantusdb_project/main_app/templates/service_list.html @@ -19,12 +19,12 @@

Service Abbreviations

- {% for office in offices %} + {% for service in services %} - {{ office.name }} + {{ service.name }} - {{ office.description }} + {{ service.description }} {% endfor %} diff --git a/django/cantusdb_project/main_app/templates/source_edit.html b/django/cantusdb_project/main_app/templates/source_edit.html index 51bb130be..4f9678726 100644 --- a/django/cantusdb_project/main_app/templates/source_edit.html +++ b/django/cantusdb_project/main_app/templates/source_edit.html @@ -286,7 +286,7 @@

{{ source.siglum }}

- {{ chant.office.name|default:"" }} + {{ chant.service.name|default:"" }}
{{ chant.genre.name|default:"" }}
diff --git a/django/cantusdb_project/main_app/tests/make_fakes.py b/django/cantusdb_project/main_app/tests/make_fakes.py index 6e97c08ca..613fce9c9 100644 --- a/django/cantusdb_project/main_app/tests/make_fakes.py +++ b/django/cantusdb_project/main_app/tests/make_fakes.py @@ -9,7 +9,7 @@ from main_app.models.genre import Genre from main_app.models.institution import Institution from main_app.models.notation import Notation -from main_app.models.office import Office +from main_app.models.service import Service from main_app.models.project import Project from main_app.models.provenance import Provenance from main_app.models.segment import Segment @@ -138,7 +138,7 @@ def make_fake_chant( source=None, marginalia=None, folio=None, - office=None, + service=None, genre=None, position=None, c_sequence=None, @@ -164,8 +164,8 @@ def make_fake_chant( if folio is None: # two digits and one letter folio = faker.bothify("##?") - if office is None: - office = make_fake_office() + if service is None: + service = make_fake_service() if genre is None: genre = make_fake_genre() if position is None: @@ -202,7 +202,7 @@ def make_fake_chant( marginalia=marginalia, folio=folio, c_sequence=c_sequence, - office=office, + service=service, genre=genre, position=position, cantus_id=cantus_id, @@ -282,13 +282,13 @@ def make_fake_notation() -> Notation: return notation -def make_fake_office() -> Office: - """Generates a fake Office object.""" - office = Office.objects.create( +def make_fake_service() -> Service: + """Generates a fake Service object.""" + service = Service.objects.create( name=faker.lexify(text="??"), description=faker.sentence(), ) - return office + return service def make_fake_provenance() -> Provenance: diff --git a/django/cantusdb_project/main_app/tests/test_functions.py b/django/cantusdb_project/main_app/tests/test_functions.py index e4d7ead00..a250d6cd6 100644 --- a/django/cantusdb_project/main_app/tests/test_functions.py +++ b/django/cantusdb_project/main_app/tests/test_functions.py @@ -208,7 +208,7 @@ def test_concordances_structure(self): "incipit", "feast", "genre", - "office", + "service", "position", "cantus_id", "image", @@ -259,7 +259,7 @@ def test_concordances_values(self): ("incipit", chant.incipit), ("feast", chant.feast.name), ("genre", chant.genre.name), - ("office", chant.office.name), + ("service", chant.service.name), ("position", chant.position), ("cantus_id", chant.cantus_id), ("image", chant.image_link), diff --git a/django/cantusdb_project/main_app/tests/test_models.py b/django/cantusdb_project/main_app/tests/test_models.py index bdadf3bbb..91c57392a 100644 --- a/django/cantusdb_project/main_app/tests/test_models.py +++ b/django/cantusdb_project/main_app/tests/test_models.py @@ -6,7 +6,7 @@ Chant, Feast, Genre, - Office, + Service, Sequence, Source, ) @@ -15,7 +15,7 @@ make_fake_chant, make_fake_feast, make_fake_genre, - make_fake_office, + make_fake_service, make_fake_sequence, make_fake_source, ) @@ -82,7 +82,7 @@ def test_index_components(self): "B": ( " ".join( filter( - None, [chant.genre.name, chant.feast.name, chant.office.name] + None, [chant.genre.name, chant.feast.name, chant.service.name] ) ) ), @@ -318,25 +318,25 @@ def test_absolute_url(self): self.assertEqual(genre.get_absolute_url(), absolute_url) -class OfficeModelTest(TestCase): +class ServiceModelTest(TestCase): @classmethod def setUpTestData(cls): - make_fake_office() + make_fake_service() def test_object_string_representation(self): - office = Office.objects.first() - self.assertEqual(str(office), f"[{office.name}] {office.description}") + service = Service.objects.first() + self.assertEqual(str(service), f"[{service.name}] {service.description}") def test_display_name(self): - office = Office.objects.first() - display_name = office.display_name - name_str = office.__str__() + service = Service.objects.first() + display_name = service.display_name + name_str = service.__str__() self.assertEqual(display_name, name_str) def test_absolute_url(self): - office = Office.objects.first() - absolute_url = reverse("office-detail", args=[str(office.id)]) - self.assertEqual(office.get_absolute_url(), absolute_url) + service = Service.objects.first() + absolute_url = reverse("service-detail", args=[str(service.id)]) + self.assertEqual(service.get_absolute_url(), absolute_url) class SequenceModelTest(TestCase): diff --git a/django/cantusdb_project/main_app/tests/test_views.py b/django/cantusdb_project/main_app/tests/test_views.py index 0619b1503..ae156bede 100644 --- a/django/cantusdb_project/main_app/tests/test_views.py +++ b/django/cantusdb_project/main_app/tests/test_views.py @@ -24,7 +24,7 @@ Feast, Genre, Notation, - Office, + Service, Provenance, Segment, Sequence, @@ -38,7 +38,7 @@ make_fake_feast, make_fake_genre, make_fake_notation, - make_fake_office, + make_fake_service, make_fake_provenance, make_fake_segment, make_fake_sequence, @@ -931,12 +931,12 @@ def test_published_vs_unpublished(self): ) self.assertEqual(len(response.context["chants"]), 0) - def test_search_by_office(self): + def test_search_by_service(self): source = make_fake_source(published=True) - office = make_fake_office() - chant = Chant.objects.create(source=source, office=office) - search_term = office.id - response = self.client.get(reverse("chant-search"), {"office": search_term}) + service = make_fake_service() + chant = Chant.objects.create(source=source, service=service) + search_term = service.id + response = self.client.get(reverse("chant-search"), {"service": search_term}) context_chant_id = response.context["chants"][0].id self.assertEqual(chant.id, context_chant_id) @@ -1261,16 +1261,16 @@ def test_order_by_incipit_global_search(self): last_result_incipit = descending_results[1].incipit self.assertEqual(last_result_incipit, chant_1.incipit) - def test_order_by_office(self): - # currently, office sort works by ID rather than by name - office_1 = make_fake_office() - office_2 = make_fake_office() - assert office_1.id < office_2.id + def test_order_by_service(self): + # currently, service sort works by ID rather than by name + service_1 = make_fake_service() + service_2 = make_fake_service() + assert service_1.id < service_2.id chant_1 = make_fake_chant( - office=office_1, manuscript_full_text_std_spelling="hocus" + service=service_1, manuscript_full_text_std_spelling="hocus" ) chant_2 = make_fake_chant( - office=office_2, manuscript_full_text_std_spelling="pocus" + service=service_2, manuscript_full_text_std_spelling="pocus" ) search_term = "ocu" @@ -1280,7 +1280,7 @@ def test_order_by_office(self): { "keyword": search_term, "op": "contains", - "order": "office", + "order": "service", "sort": "asc", }, ) @@ -1295,7 +1295,7 @@ def test_order_by_office(self): { "keyword": search_term, "op": "contains", - "order": "office", + "order": "service", "sort": "desc", }, ) @@ -1305,16 +1305,16 @@ def test_order_by_office(self): last_result_incipit = descending_results[1].incipit self.assertEqual(last_result_incipit, chant_1.incipit) - def test_order_by_office_global_search(self): - # currently, office sort works by ID rather than by name - office_1 = make_fake_office() - office_2 = make_fake_office() - assert office_1.id < office_2.id + def test_order_by_service_global_search(self): + # currently, service sort works by ID rather than by name + service_1 = make_fake_service() + service_2 = make_fake_service() + assert service_1.id < service_2.id chant_1 = make_fake_chant( - office=office_1, manuscript_full_text_std_spelling="fluffy" + service=service_1, manuscript_full_text_std_spelling="fluffy" ) chant_2 = make_fake_chant( - office=office_2, manuscript_full_text_std_spelling="fluster" + service=service_2, manuscript_full_text_std_spelling="fluster" ) search_term = "flu" @@ -1322,7 +1322,7 @@ def test_order_by_office_global_search(self): reverse("chant-search"), { "search_bar": search_term, - "order": "office", + "order": "service", "sort": "asc", }, ) @@ -1336,7 +1336,7 @@ def test_order_by_office_global_search(self): reverse("chant-search"), { "search_bar": search_term, - "order": "office", + "order": "service", "sort": "desc", }, ) @@ -1845,7 +1845,7 @@ def test_column_header_links(self): # these are the 9 column headers users can order by: shelfmark = "glum-01" fulltext = "so it begins" - office = make_fake_office() + service = make_fake_service() genre = make_fake_genre() cantus_id = make_random_string(6, "0123456789") mode = make_random_string(1, "0123456789*?") @@ -1859,7 +1859,7 @@ def test_column_header_links(self): position = make_random_string(1) chant = make_fake_chant( manuscript_full_text_std_spelling=fulltext, - office=office, + service=service, genre=genre, cantus_id=cantus_id, mode=mode, @@ -1885,7 +1885,7 @@ def test_column_header_links(self): query_keys_and_values = { "op": "contains", "keyword": search_term, - "office": office.id, + "service": service.id, "genre": genre.id, "cantus_id": cantus_id, "mode": mode, @@ -1917,7 +1917,7 @@ def test_column_header_links(self): orderings = ( "siglum", "incipit", - "office", + "service", "genre", "cantus_id", "mode", @@ -2030,28 +2030,28 @@ def test_feast_column(self): f'{feast_name}', html ) - def test_office_column(self): + def test_service_column(self): source = make_fake_source(published=True) - office = make_fake_office() - office_name = office.name - office_description = office.description - url = office.get_absolute_url() + service = make_fake_service() + service_name = service.name + service_description = service.description + url = service.get_absolute_url() fulltext = "manuscript full text" search_term = "full" chant = make_fake_chant( source=source, manuscript_full_text_std_spelling=fulltext, - office=office, + service=service, ) response = self.client.get( reverse("chant-search"), {"keyword": search_term, "op": "contains"} ) html = str(response.content) - self.assertIn(office_name, html) - self.assertIn(office_description, html) + self.assertIn(service_name, html) + self.assertIn(service_description, html) self.assertIn(url, html) self.assertIn( - f'{office_name}', html + f'{service_name}', html ) def test_genre_column(self): @@ -2236,13 +2236,13 @@ def test_published_vs_unpublished(self): response = self.client.get(reverse("chant-search-ms", args=[source.id])) self.assertEqual(response.status_code, 403) - def test_search_by_office(self): + def test_search_by_service(self): source = make_fake_source() - office = make_fake_office() - chant = Chant.objects.create(source=source, office=office) - search_term = office.id + service = make_fake_service() + chant = Chant.objects.create(source=source, service=service) + search_term = service.id response = self.client.get( - reverse("chant-search-ms", args=[source.id]), {"office": search_term} + reverse("chant-search-ms", args=[source.id]), {"service": search_term} ) context_chant_id = response.context["chants"][0].id self.assertEqual(chant.id, context_chant_id) @@ -2499,17 +2499,17 @@ def test_order_by_incipit(self): last_result_incipit = descending_results[1].incipit self.assertEqual(last_result_incipit, chant_1.incipit) - def test_order_by_office(self): + def test_order_by_service(self): source = make_fake_source() - # currently, office sort works by ID rather than by name - office_1 = make_fake_office() - office_2 = make_fake_office() - assert office_1.id < office_2.id + # currently, service sort works by ID rather than by name + service_1 = make_fake_service() + service_2 = make_fake_service() + assert service_1.id < service_2.id chant_1 = make_fake_chant( - office=office_1, manuscript_full_text_std_spelling="hocus", source=source + service=service_1, manuscript_full_text_std_spelling="hocus", source=source ) chant_2 = make_fake_chant( - office=office_2, manuscript_full_text_std_spelling="pocus", source=source + service=service_2, manuscript_full_text_std_spelling="pocus", source=source ) search_term = "ocu" @@ -2519,7 +2519,7 @@ def test_order_by_office(self): { "keyword": search_term, "op": "contains", - "order": "office", + "order": "service", "sort": "asc", }, ) @@ -2534,7 +2534,7 @@ def test_order_by_office(self): { "keyword": search_term, "op": "contains", - "order": "office", + "order": "service", "sort": "desc", }, ) @@ -2818,7 +2818,7 @@ def test_column_header_links(self): shelfmark = "glum-01" full_text = "this is a full text that begins with the search term" search_term = "this is a fu" - office = make_fake_office() + service = make_fake_service() genre = make_fake_genre() cantus_id = make_random_string(6, "0123456789") mode = make_random_string(1, "0123456789*?") @@ -2831,7 +2831,7 @@ def test_column_header_links(self): feast = make_fake_feast() position = make_random_string(1) chant = make_fake_chant( - office=office, + service=service, genre=genre, cantus_id=cantus_id, mode=mode, @@ -2856,7 +2856,7 @@ def test_column_header_links(self): query_keys_and_values = { "op": "contains", "keyword": search_term, - "office": office.id, + "service": service.id, "genre": genre.id, "cantus_id": cantus_id, "mode": mode, @@ -2877,7 +2877,7 @@ def test_column_header_links(self): # for each orderable column, check that 'asc' flips to 'desc', and vice versa orderings = ( "incipit", - "office", + "service", "genre", "cantus_id", "mode", @@ -2991,29 +2991,29 @@ def test_feast_column(self): f'{feast_name}', html ) - def test_office_column(self): + def test_service_column(self): source = make_fake_source(published=True) - office = make_fake_office() - office_name = office.name - office_description = office.description - url = office.get_absolute_url() + service = make_fake_service() + service_name = service.name + service_description = service.description + url = service.get_absolute_url() fulltext = "manuscript full text" search_term = "full" chant = make_fake_chant( source=source, manuscript_full_text_std_spelling=fulltext, - office=office, + service=service, ) response = self.client.get( reverse("chant-search-ms", args=[source.id]), {"keyword": search_term, "op": "contains"}, ) html = str(response.content) - self.assertIn(office_name, html) - self.assertIn(office_description, html) + self.assertIn(service_name, html) + self.assertIn(service_description, html) self.assertIn(url, html) self.assertIn( - f'{office_name}', html + f'{service_name}', html ) def test_genre_column(self): @@ -3354,12 +3354,12 @@ def test_volpiano_signal(self): self.assertEqual(chant_2.volpiano_intervals, "1-12-23-34-45-56-67-78-8") def test_initial_values(self): - # create a chant with a known folio, feast, office, c_sequence and image_link + # create a chant with a known folio, feast, service, c_sequence and image_link source: Source = make_fake_source() folio: str = "001r" sequence: int = 1 feast: Feast = make_fake_feast() - office: Office = make_fake_office() + service: Service = make_fake_service() image_link: str = "https://www.youtube.com/watch?v=9bZkp7q19f0" self.client.post( reverse("chant-create", args=[source.id]), @@ -3368,12 +3368,12 @@ def test_initial_values(self): "folio": folio, "c_sequence": str(sequence), "feast": feast.id, - "office": office.id, + "service": service.id, "image_link": image_link, }, ) with patch("requests.get", mock_requests_get): - # when we request the Chant Create page, the same folio, feast, office and image_link should + # when we request the Chant Create page, the same folio, feast, service and image_link should # be preselected, and c_sequence should be incremented by 1. response = self.client.get( reverse("chant-create", args=[source.id]), @@ -3387,9 +3387,9 @@ def test_initial_values(self): with self.subTest(subtest="test initial value of feast feild"): self.assertEqual(observed_initial_feast, feast.id) - observed_initial_office: int = response.context["form"].initial["office"] - with self.subTest(subtest="test initial value of office field"): - self.assertEqual(observed_initial_office, office.id) + observed_initial_service: int = response.context["form"].initial["service"] + with self.subTest(subtest="test initial value of service field"): + self.assertEqual(observed_initial_service, service.id) observed_initial_sequence: int = response.context["form"].initial["c_sequence"] with self.subTest(subtest="test initial value of c_sequence field"): @@ -4145,66 +4145,66 @@ def test_context(self): self.assertEqual(genre, response.context["genre"]) -class OfficeListViewTest(TestCase): - OFFICE_COUNT = 10 +class ServiceListViewTest(TestCase): + SERVICE_COUNT = 10 def setUp(self): - for _ in range(self.OFFICE_COUNT): - make_fake_office() + for _ in range(self.SERVICE_COUNT): + make_fake_service() def test_view_url_path(self): - response = self.client.get("/offices/") + response = self.client.get("/services/") self.assertEqual(response.status_code, 200) def test_view_url_reverse_name(self): - response = self.client.get(reverse("office-list")) + response = self.client.get(reverse("service-list")) self.assertEqual(response.status_code, 200) def test_url_and_templates(self): - response = self.client.get(reverse("office-list")) + response = self.client.get(reverse("service-list")) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "base.html") - self.assertTemplateUsed(response, "office_list.html") + self.assertTemplateUsed(response, "service_list.html") def test_context(self): - response = self.client.get(reverse("office-list")) - offices = response.context["offices"] - # the list view should contain all offices - self.assertEqual(offices.count(), self.OFFICE_COUNT) + response = self.client.get(reverse("service-list")) + services = response.context["services"] + # the list view should contain all services + self.assertEqual(services.count(), self.SERVICE_COUNT) -class OfficeDetailViewTest(TestCase): +class ServiceDetailViewTest(TestCase): def setUp(self): for _ in range(10): - make_fake_office() + make_fake_service() def test_view_url_path(self): - for office in Office.objects.all(): - response = self.client.get(f"/office/{office.id}") + for service in Service.objects.all(): + response = self.client.get(f"/service/{service.id}") self.assertEqual(response.status_code, 200) def test_view_url_reverse_name(self): - for office in Office.objects.all(): - response = self.client.get(reverse("office-detail", args=[office.id])) + for service in Service.objects.all(): + response = self.client.get(reverse("service-detail", args=[service.id])) self.assertEqual(response.status_code, 200) def test_view_context_data(self): - for office in Office.objects.all(): - response = self.client.get(reverse("office-detail", args=[office.id])) - self.assertTrue("office" in response.context) - self.assertEqual(office, response.context["office"]) + for service in Service.objects.all(): + response = self.client.get(reverse("service-detail", args=[service.id])) + self.assertTrue("service" in response.context) + self.assertEqual(service, response.context["service"]) def test_url_and_templates(self): - office = make_fake_office() - response = self.client.get(reverse("office-detail", args=[office.id])) + service = make_fake_service() + response = self.client.get(reverse("service-detail", args=[service.id])) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "base.html") - self.assertTemplateUsed(response, "office_detail.html") + self.assertTemplateUsed(response, "service_detail.html") def test_context(self): - office = make_fake_office() - response = self.client.get(reverse("office-detail", args=[office.id])) - self.assertEqual(office, response.context["office"]) + service = make_fake_service() + response = self.client.get(reverse("service-detail", args=[service.id])) + self.assertEqual(service, response.context["service"]) class ProvenanceDetailViewTest(TestCase): @@ -4956,21 +4956,21 @@ def test_feast_column(self): self.assertIn(feast_name, html) self.assertIn(feast_description, html) - def test_office_column(self): + def test_service_column(self): source = make_fake_source(published=True) - office = make_fake_office() - office_name = office.name - office_description = office.description + service = make_fake_service() + service_name = service.name + service_description = service.description fulltext = "manuscript full text" make_fake_chant( source=source, manuscript_full_text_std_spelling=fulltext, - office=office, + service=service, ) response = self.client.get(reverse("source-inventory", args=[source.id])) html = str(response.content) - self.assertIn(office_name, html) - self.assertIn(office_description, html) + self.assertIn(service_name, html) + self.assertIn(service_description, html) def test_genre_column(self): source = make_fake_source(published=True) @@ -5133,7 +5133,7 @@ def test_json_melody_fields(self): "volpiano", "mode", "feast", - "office", + "service", "genre", "position", "chantlink", @@ -5619,7 +5619,7 @@ def test_structure(self): "incipit": "some string" "feast": "some string" "genre": "some string" - "office": "some string" + "service": "some string" "position": "some string" "cantus_id": "some string" "image": "some string" @@ -5665,7 +5665,7 @@ def test_structure(self): "incipit", "feast", "genre", - "office", + "service", "position", "cantus_id", "image", @@ -5688,7 +5688,7 @@ def test_values(self): "incipit": chant.incipit, "feast": chant.feast.name, "genre": chant.genre.name, - "office": chant.office.name, + "service": chant.service.name, "position": chant.position, "mode": chant.mode, "image": chant.image_link, @@ -5709,7 +5709,7 @@ def test_values(self): chant.incipit = None chant.feast = None chant.genre = None - chant.office = None + chant.service = None chant.position = None chant.mode = None chant.image_link = None @@ -5775,7 +5775,7 @@ def test_content(self): "sequence", "incipit", "feast", - "office", + "service", "genre", "position", "cantus_id", @@ -5898,7 +5898,7 @@ def test_chant_redirect(self): ) expected_url = reverse("chant-detail", args=[example_chant_id]) - self.assertEqual(response_1.status_code, 302) + self.assertEqual(response_1.status_code, 301) self.assertEqual(response_1.url, expected_url) def test_source_redirect(self): @@ -5914,7 +5914,7 @@ def test_source_redirect(self): ) expected_url = reverse("source-detail", args=[example_source_id]) - self.assertEqual(response_1.status_code, 302) + self.assertEqual(response_1.status_code, 301) self.assertEqual(response_1.url, expected_url) def test_sequence_redirect(self): @@ -5929,7 +5929,7 @@ def test_sequence_redirect(self): ) expected_url = reverse("sequence-detail", args=[example_sequence_id]) - self.assertEqual(response_1.status_code, 302) + self.assertEqual(response_1.status_code, 301) self.assertEqual(response_1.url, expected_url) def test_article_redirect(self): @@ -5945,7 +5945,7 @@ def test_article_redirect(self): ) expected_url = reverse("article-detail", args=[example_article_id]) - self.assertEqual(response_1.status_code, 302) + self.assertEqual(response_1.status_code, 301) self.assertEqual(response_1.url, expected_url) def test_indexer_redirect(self): @@ -5962,7 +5962,7 @@ def test_indexer_redirect(self): ) expected_url = reverse("user-detail", args=[example_matching_user_id]) - self.assertEqual(response_1.status_code, 302) + self.assertEqual(response_1.status_code, 301) self.assertEqual(response_1.url, expected_url) def test_bad_redirect(self): @@ -6005,7 +6005,7 @@ def test_indexer_redirect_good(self): ) expected_url = reverse("user-detail", args=[example_matching_user_id]) - self.assertEqual(response_1.status_code, 302) + self.assertEqual(response_1.status_code, 301) self.assertEqual(response_1.url, expected_url) def test_indexer_redirect_bad(self): @@ -6033,7 +6033,7 @@ def test_document_redirects(self): for path in old_document_paths: # each path should redirect to the new path response = self.client.get(path) - self.assertEqual(response.status_code, 302) + self.assertEqual(response.status_code, 301) # In Aug 2023, Jacob struggled to get the following lines to work - # I was getting 404s when I expected 200s. This final step would be nice # to test properly - if a future developer who is cleverer than me can @@ -6382,7 +6382,7 @@ def test_concordance_items(self): expected_items: ItemsView = { "siglum": chant.source.short_heading, "folio": chant.folio, - "office__name": chant.office.name, + "service__name": chant.service.name, "genre__name": chant.genre.name, "position": chant.position, "feast__name": chant.feast.name, diff --git a/django/cantusdb_project/main_app/urls.py b/django/cantusdb_project/main_app/urls.py index 84eed1f92..2d8959982 100644 --- a/django/cantusdb_project/main_app/urls.py +++ b/django/cantusdb_project/main_app/urls.py @@ -27,6 +27,8 @@ redirect_chants, redirect_genre, redirect_office, + redirect_offices, + redirect_office_id, redirect_source_inventory, csv_export_redirect_from_old_path, redirect_search, @@ -62,9 +64,9 @@ from main_app.views.notation import ( NotationDetailView, ) -from main_app.views.office import ( - OfficeListView, - OfficeDetailView, +from main_app.views.service import ( + ServiceListView, + ServiceDetailView, ) from main_app.views.provenance import ( ProvenanceDetailView, @@ -95,7 +97,7 @@ AllUsersAutocomplete, CenturyAutocomplete, FeastAutocomplete, - OfficeAutocomplete, + ServiceAutocomplete, GenreAutocomplete, DifferentiaAutocomplete, ProvenanceAutocomplete, @@ -272,22 +274,32 @@ NotationDetailView.as_view(), name="notation-detail", ), - # office + # service path( - "offices/", - OfficeListView.as_view(), - name="office-list", + "services/", + ServiceListView.as_view(), + name="service-list", ), path( - "office/", - OfficeDetailView.as_view(), - name="office-detail", + "service/", + ServiceDetailView.as_view(), + name="service-detail", ), path( "office/", redirect_office, name="redirect-office", ), + path( + "offices/", + redirect_offices, + name="redirect-office", + ), + path( + "office/", + redirect_office_id, + name="redirect-office-id", + ), # provenance path( "provenance/", @@ -542,9 +554,9 @@ name="provenance-autocomplete", ), path( - "office-autocomplete/", - OfficeAutocomplete.as_view(), - name="office-autocomplete", + "service-autocomplete/", + ServiceAutocomplete.as_view(), + name="service-autocomplete", ), path( "genre-autocomplete/", diff --git a/django/cantusdb_project/main_app/views/api.py b/django/cantusdb_project/main_app/views/api.py index 8c66b2a9c..48f4da16e 100644 --- a/django/cantusdb_project/main_app/views/api.py +++ b/django/cantusdb_project/main_app/views/api.py @@ -41,7 +41,7 @@ def ajax_melody_list(request, cantus_id) -> JsonResponse: """ chants: QuerySet[Chant] = ( Chant.objects.filter(cantus_id=cantus_id) - .select_related("source__holding_institution", "feast", "genre", "office") + .select_related("source__holding_institution", "feast", "genre", "service") .exclude(volpiano=None) .order_by("id") ) @@ -54,7 +54,7 @@ def ajax_melody_list(request, cantus_id) -> JsonResponse: "source__holding_institution__siglum", "source__shelfmark", "folio", - "office__name", + "service__name", "genre__name", "position", "feast__name", @@ -113,7 +113,7 @@ def csv_export(request, source_id): entries = source.sequence_set.order_by("id") else: entries = source.chant_set.order_by("id").select_related( - "feast", "office", "genre" + "feast", "service", "genre" ) response = HttpResponse(content_type="text/csv") @@ -129,7 +129,7 @@ def csv_export(request, source_id): "sequence", "incipit", "feast", - "office", + "service", "genre", "position", "cantus_id", @@ -152,7 +152,7 @@ def csv_export(request, source_id): for entry in entries: feast = entry.feast.name if entry.feast else "" - office = entry.office.name if entry.office else "" + service = entry.service.name if entry.service else "" genre = entry.genre.name if entry.genre else "" diff_db = entry.diff_db.id if entry.diff_db else "" @@ -166,7 +166,7 @@ def csv_export(request, source_id): entry.c_sequence if entry.c_sequence is not None else entry.s_sequence, entry.incipit, feast, - office, + service, genre, entry.position, entry.cantus_id, @@ -327,7 +327,7 @@ def ajax_search_bar(request, search_term): chants = Chant.objects.filter(incipit__istartswith=search_term).order_by("id") chants = chants.select_related( - "source__holding_institution", "genre", "feast", "office" + "source__holding_institution", "genre", "feast", "service" ) display_unpublished: bool = request.user.is_authenticated @@ -346,7 +346,7 @@ def ajax_search_bar(request, search_term): "mode", "source__holding_institution__siglum", "source__shelfmark", - "office__name", + "service__name", "folio", "c_sequence", ) @@ -376,7 +376,7 @@ def json_melody_export(request, cantus_id: str) -> JsonResponse: "volpiano", "mode", "feast__id", - "office__id", + "service__id", "genre__id", "position", ] @@ -421,7 +421,7 @@ def standardize_dict_for_json_melody_export( "volpiano": "volpiano", "mode": "mode", "feast__id": "feast", # <- - "office__id": "office", # <- + "service__id": "service", # <- "genre__id": "genre", # <- "position": "position", } @@ -532,7 +532,7 @@ def build_json_cid_dictionary(chant, request) -> dict: "incipit": chant.incipit if chant.incipit else "", "feast": chant.feast.name if chant.feast else "", "genre": chant.genre.name if chant.genre else "", - "office": chant.office.name if chant.office else "", + "service": chant.service.name if chant.service else "", "position": chant.position if chant.position else "", "cantus_id": chant.cantus_id if chant.cantus_id else "", "image": chant.image_link if chant.image_link else "", diff --git a/django/cantusdb_project/main_app/views/autocomplete.py b/django/cantusdb_project/main_app/views/autocomplete.py index 47c3b8ac2..1e0c1f014 100644 --- a/django/cantusdb_project/main_app/views/autocomplete.py +++ b/django/cantusdb_project/main_app/views/autocomplete.py @@ -7,7 +7,7 @@ Differentia, Feast, Genre, - Office, + Service, Institution, Provenance, ) @@ -66,14 +66,14 @@ def get_queryset(self): return qs -class OfficeAutocomplete(autocomplete.Select2QuerySetView): +class ServiceAutocomplete(autocomplete.Select2QuerySetView): def get_result_label(self, result): return f"{result.name} - {result.description}" def get_queryset(self): if not self.request.user.is_authenticated: - return Office.objects.none() - qs = Office.objects.all().order_by("name") + return Service.objects.none() + qs = Service.objects.all().order_by("name") if self.q: qs = qs.filter( Q(name__istartswith=self.q) | Q(description__icontains=self.q) diff --git a/django/cantusdb_project/main_app/views/chant.py b/django/cantusdb_project/main_app/views/chant.py index f86fae9c8..f5be3d7b7 100644 --- a/django/cantusdb_project/main_app/views/chant.py +++ b/django/cantusdb_project/main_app/views/chant.py @@ -40,7 +40,7 @@ Genre, Source, Sequence, - Office, + Service, ) from main_app.permissions import ( user_can_edit_chants_in_source, @@ -70,9 +70,9 @@ "feast__id", "feast__description", "feast__name", - "office__id", - "office__description", - "office__name", + "service__id", + "service__description", + "service__name", "genre__id", "genre__description", "genre__name", @@ -82,7 +82,7 @@ "id", "genre", "feast", - "office", + "service", "source", "source__holding_institution__siglum", "source__shelfmark", @@ -118,7 +118,7 @@ def get_feast_selector_options(source: Source) -> list[tuple[str, int, str]]: """ folios_feasts_iter: Iterator[tuple[Optional[str], int, str]] = ( source.chant_set.exclude(feast=None) - .select_related("feast", "genre", "office") + .select_related("feast", "genre", "service") .values_list("folio", "feast_id", "feast__name") .order_by("folio", "c_sequence") .iterator() @@ -186,7 +186,7 @@ class ChantDetailView(DetailView): def get_queryset(self) -> QuerySet: qs = super().get_queryset() return qs.select_related( - "source__holding_institution", "office", "genre", "feast" + "source__holding_institution", "service", "genre", "feast" ) def get_context_data(self, **kwargs): @@ -222,7 +222,7 @@ def get_context_data(self, **kwargs): # source navigation section chants_in_source = chant.source.chant_set.select_related( - "source__holding_institution", "feast", "genre", "office" + "source__holding_institution", "feast", "genre", "service" ) context["folios"] = ( chants_in_source.values_list("folio", flat=True) @@ -278,10 +278,10 @@ def dispatch(self, request, *args, **kwargs): def get_queryset(self): chant_set = Chant.objects.filter(cantus_id=self.cantus_id).select_related( - "source__holding_institution", "office", "genre", "feast" + "source__holding_institution", "service", "genre", "feast" ) sequence_set = Sequence.objects.filter(cantus_id=self.cantus_id).select_related( - "source__holding_institution", "office", "genre", "feast" + "source__holding_institution", "service", "genre", "feast" ) display_unpublished = self.request.user.is_authenticated if not display_unpublished: @@ -309,7 +309,7 @@ class ChantSearchView(ListView): If no ``GET`` parameters, returns empty queryset ``GET`` parameters: - ``office``: Filters by Office of Chant + ``service``: Filters by Service of Chant ``genre``: Filters by Genre of Chant ``cantus_id``: Filters by the Cantus ID field of Chant ``mode``: Filters by mode of Chant @@ -329,7 +329,9 @@ def get_context_data(self, **kwargs) -> dict: context = super().get_context_data(**kwargs) # Add to context a QuerySet of dicts with id and name of each Genre context["genres"] = Genre.objects.all().order_by("name").values("id", "name") - context["offices"] = Office.objects.all().order_by("name").values("id", "name") + context["services"] = ( + Service.objects.all().order_by("name").values("id", "name") + ) context["order"] = self.request.GET.get("order") context["sort"] = self.request.GET.get("sort") @@ -346,9 +348,9 @@ def get_context_data(self, **kwargs) -> dict: if search_keyword: search_parameters.append(f"keyword={search_keyword}") context["keyword"] = search_keyword - search_office: Optional[str] = self.request.GET.get("office") - if search_office: - search_parameters.append(f"office={search_office}") + search_service: Optional[str] = self.request.GET.get("service") + if search_service: + search_parameters.append(f"service={search_service}") search_genre: Optional[str] = self.request.GET.get("genre") if search_genre: search_parameters.append(f"genre={search_genre}") @@ -399,10 +401,10 @@ def get_queryset(self) -> QuerySet: sequence_set = Sequence.objects.filter(source__published=True) chant_set = chant_set.select_related( - "source__holding_institution", "feast", "office", "genre" + "source__holding_institution", "feast", "service", "genre" ) sequence_set = sequence_set.select_related( - "source__holding_institution", "feast", "office", "genre" + "source__holding_institution", "feast", "service", "genre" ) search_bar_term_contains_digits = any( @@ -435,8 +437,8 @@ def get_queryset(self) -> QuerySet: # The field names should be keys in the "GET" QueryDict if the search button has been # clicked, even if the user put nothing into the search form and hit "apply" immediately. # In that case, we return all chants + seqs filtered by the search form. - if office_id := self.request.GET.get("office"): - q_obj_filter &= Q(office__id=office_id) + if service_id := self.request.GET.get("service"): + q_obj_filter &= Q(service__id=service_id) if genre_id := self.request.GET.get("genre"): q_obj_filter &= Q(genre__id=int(genre_id)) @@ -467,10 +469,10 @@ def get_queryset(self) -> QuerySet: # Filter the QuerySet with Q object chant_set = chant_set.filter(q_obj_filter).select_related( - "source__holding_institution", "feast", "office", "genre" + "source__holding_institution", "feast", "service", "genre" ) sequence_set = sequence_set.filter(q_obj_filter).select_related( - "source__holding_institution", "feast", "office", "genre" + "source__holding_institution", "feast", "service", "genre" ) # Finally, do keyword searching over the querySet @@ -508,7 +510,7 @@ def get_queryset(self) -> QuerySet: order_param_options = ( "incipit", - "office", + "service", "genre", "cantus_id", "mode", @@ -553,7 +555,7 @@ def get_context_data(self, **kwargs): if self.request.GET.get("source"): context["source"] = Source.objects.get( id=self.request.GET.get("source") - ).select_related("holding_institution", "feast", "office", "genre") + ).select_related("holding_institution", "feast", "service", "genre") return context @@ -567,7 +569,7 @@ class ChantSearchMSView(ListView): If no ``GET`` parameters, returns empty queryset ``GET`` parameters: - ``office``: Filters by the office/mass of Chant + ``service``: Filters by the service/mass of Chant ``genre``: Filters by Genre of Chant ``cantus_id``: Filters by the Cantus ID field of Chant ``mode``: Filters by mode of Chant @@ -594,7 +596,9 @@ def get_context_data(self, **kwargs): context["source"] = source # Add to context a QuerySet of dicts with id and name of each Genre context["genres"] = Genre.objects.all().order_by("name").values("id", "name") - context["offices"] = Office.objects.all().order_by("name").values("id", "name") + context["services"] = ( + Service.objects.all().order_by("name").values("id", "name") + ) context["order"] = self.request.GET.get("order") context["sort"] = self.request.GET.get("sort") # This is searching in a specific source, pass the source into context @@ -608,9 +612,9 @@ def get_context_data(self, **kwargs): search_keyword = self.request.GET.get("keyword") if search_keyword: search_parameters.append(f"keyword={search_keyword}") - search_office = self.request.GET.get("office") - if search_office: - search_parameters.append(f"office={search_office}") + search_service = self.request.GET.get("service") + if search_service: + search_parameters.append(f"service={search_service}") search_genre = self.request.GET.get("genre") if search_genre: search_parameters.append(f"genre={search_genre}") @@ -653,8 +657,8 @@ def get_queryset(self) -> QuerySet: # Create a Q object to filter the QuerySet of Chants q_obj_filter = Q() # For every GET parameter other than incipit, add to the Q object - if office_id := self.request.GET.get("office"): - q_obj_filter &= Q(office__id=office_id) + if service_id := self.request.GET.get("service"): + q_obj_filter &= Q(service__id=service_id) if genre_id := self.request.GET.get("genre"): q_obj_filter &= Q(genre__id=int(genre_id)) @@ -684,7 +688,7 @@ def get_queryset(self) -> QuerySet: "cantus_id", "mode", "feast", - "office", + "service", }: order = order_value elif order_value == "has_fulltext": @@ -707,7 +711,7 @@ def get_queryset(self) -> QuerySet: # Filter the QuerySet with Q object queryset = queryset.select_related( - "source__holding_institution", "feast", "office", "genre" + "source__holding_institution", "feast", "service", "genre" ).filter(q_obj_filter) # Fetch only the values necessary for rendering the template queryset = queryset.only(*ONLY_FIELDS) @@ -790,7 +794,7 @@ def get_initial(self): } latest_folio = latest_chant.folio if latest_chant.folio else "001r" latest_feast = latest_chant.feast.id if latest_chant.feast else "" - latest_office = latest_chant.office.id if latest_chant.office else "" + latest_service = latest_chant.service.id if latest_chant.service else "" latest_seq = ( latest_chant.c_sequence if latest_chant.c_sequence is not None else 0 ) @@ -798,7 +802,7 @@ def get_initial(self): return { "folio": latest_folio, "feast": latest_feast, - "office": latest_office, + "service": latest_service, "c_sequence": latest_seq + 1, "image_link": latest_image, } @@ -822,7 +826,7 @@ def get_suggested_feasts(self): current_feast = latest_chant.feast chants_that_end_current_feast = Chant.objects.filter( is_last_chant_in_feast=True, feast=current_feast - ).select_related("next_chant__feast", "feast", "genre", "office") + ).select_related("next_chant__feast", "feast", "genre", "service") next_chants = [chant.next_chant for chant in chants_that_end_current_feast] next_feasts = [ chant.feast @@ -999,7 +1003,7 @@ def get_queryset(self): # get all chants in the specified source chants = source.chant_set.select_related( - "feast", "office", "genre", "source__holding_institution" + "feast", "service", "genre", "source__holding_institution" ) if not source.chant_set.exists(): # return empty queryset @@ -1050,7 +1054,7 @@ def get_context_data(self, **kwargs): context["source"] = source chants_in_source = source.chant_set.select_related( - "feast", "genre", "office", "source__holding_institution" + "feast", "genre", "service", "source__holding_institution" ) # the following code block is sort of obsolete because if there is no Chant @@ -1103,7 +1107,7 @@ def get_context_data(self, **kwargs): # need to render a list of chants, ordered by c_sequence and grouped by feast context["feasts_current_folio"] = get_chants_with_feasts( self.queryset.select_related( - "feast", "genre", "office", "source__holding_institution" + "feast", "genre", "service", "source__holding_institution" ).order_by("c_sequence") ) diff --git a/django/cantusdb_project/main_app/views/office.py b/django/cantusdb_project/main_app/views/office.py deleted file mode 100644 index 1052faec2..000000000 --- a/django/cantusdb_project/main_app/views/office.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.views.generic import DetailView, ListView - -from main_app.models import Office - - -class OfficeDetailView(DetailView): - model = Office - context_object_name = "office" - template_name = "office_detail.html" - - -class OfficeListView(ListView): - model = Office - queryset = Office.objects.order_by("name") - paginate_by = 100 - context_object_name = "offices" - template_name = "office_list.html" diff --git a/django/cantusdb_project/main_app/views/redirect.py b/django/cantusdb_project/main_app/views/redirect.py index ff4593274..6a6cd0b4c 100644 --- a/django/cantusdb_project/main_app/views/redirect.py +++ b/django/cantusdb_project/main_app/views/redirect.py @@ -16,7 +16,7 @@ def csv_export_redirect_from_old_path(request, source_id): - return redirect(reverse("csv-export", args=[source_id])) + return redirect(reverse("csv-export", args=[source_id]), permanent=True) def redirect_node_url(request, pk: int) -> HttpResponse: @@ -37,12 +37,12 @@ def redirect_node_url(request, pk: int) -> HttpResponse: user_id = get_user_id_from_old_indexer_id(pk) if get_user_id_from_old_indexer_id(pk) is not None: - return redirect("user-detail", user_id) + return redirect("user-detail", user_id, permanent=True) for rec_type, view in NODE_TYPES_AND_VIEWS: if record_exists(rec_type, pk): # if an object is found, a redirect() call to the appropriate view is returned - return redirect(view, pk) + return redirect(view, pk, permanent=True) # if it reaches the end of the types with finding an existing object, a 404 will be returned raise Http404("No record found matching the /node/ query.") @@ -60,14 +60,14 @@ def redirect_indexer(request, pk: int) -> HttpResponse: """ user_id = get_user_id_from_old_indexer_id(pk) if get_user_id_from_old_indexer_id(pk) is not None: - return redirect("user-detail", user_id) + return redirect("user-detail", user_id, permanent=True) raise Http404("No indexer found matching the query.") def redirect_office(request) -> HttpResponse: """ - Redirects from office/ (à la OldCantus) to offices/ (à la NewCantus) + Redirects from office/ (à la OldCantus) to services/ (à la NewCantus) Args: request @@ -75,7 +75,34 @@ def redirect_office(request) -> HttpResponse: Returns: HttpResponse """ - return redirect("office-list") + return redirect("service-list", permanent=True) + + +def redirect_offices(request) -> HttpResponse: + """ + Redirects old URL for offices/ to services/ + + Args: + request + + Returns: + HttpResponse + """ + return redirect("service-list", permanent=True) + + +def redirect_office_id(request, pk: int) -> HttpResponse: + """ + Redirects from the old URL pattern 'office/ to the new URL patern 'service/' + + Args: + request + pk: The ID of the service + + Returns: + HttpResponse + """ + return redirect(reverse("service-detail", args=[pk]), permanent=True) def redirect_genre(request) -> HttpResponse: @@ -88,7 +115,7 @@ def redirect_genre(request) -> HttpResponse: Returns: HttpResponse """ - return redirect("genre-list") + return redirect("genre-list", permanent=True) def redirect_search(request: HttpRequest) -> HttpResponse: @@ -143,7 +170,7 @@ def redirect_documents(request) -> HttpResponse: new_path = mapping[old_path] except KeyError as exc: raise Http404 from exc - return redirect(new_path) + return redirect(new_path, permanent=True) def redirect_chants(request) -> HttpResponse: diff --git a/django/cantusdb_project/main_app/views/service.py b/django/cantusdb_project/main_app/views/service.py new file mode 100644 index 000000000..902175542 --- /dev/null +++ b/django/cantusdb_project/main_app/views/service.py @@ -0,0 +1,17 @@ +from django.views.generic import DetailView, ListView + +from main_app.models import Service + + +class ServiceDetailView(DetailView): + model = Service + context_object_name = "service" + template_name = "service_detail.html" + + +class ServiceListView(ListView): + model = Service + queryset = Service.objects.order_by("name") + paginate_by = 100 + context_object_name = "services" + template_name = "service_list.html" diff --git a/django/cantusdb_project/main_app/views/site_stats.py b/django/cantusdb_project/main_app/views/site_stats.py index fd547b545..a58516856 100644 --- a/django/cantusdb_project/main_app/views/site_stats.py +++ b/django/cantusdb_project/main_app/views/site_stats.py @@ -7,7 +7,7 @@ Sequence, Source, Feast, - Office, + Service, Provenance, Genre, Notation, @@ -60,7 +60,7 @@ def content_overview(request): Chant, Feast, Sequence, - Office, + Service, Provenance, Genre, Notation, diff --git a/django/cantusdb_project/main_app/views/source.py b/django/cantusdb_project/main_app/views/source.py index 208eabec8..3878ba958 100644 --- a/django/cantusdb_project/main_app/views/source.py +++ b/django/cantusdb_project/main_app/views/source.py @@ -82,7 +82,7 @@ def get_queryset(self): search_text = self.request.GET.get("search_text") # get all chants in the specified source - chants = source.chant_set.select_related("feast", "office", "genre") + chants = source.chant_set.select_related("feast", "service", "genre") # filter the chants with optional search params if feast_id: chants = chants.filter(feast__id=feast_id) @@ -186,7 +186,7 @@ def get_context_data(self, **kwargs): if source.segment and source.segment_id == BOWER_SEGMENT_ID: # if this is a sequence source - sequences = source.sequence_set.select_related("genre", "office") + sequences = source.sequence_set.select_related("genre", "service") context["sequences"] = sequences.order_by("s_sequence") context["folios"] = ( sequences.values_list("folio", flat=True).distinct().order_by("folio") @@ -481,7 +481,7 @@ def get_context_data(self, **kwargs): queryset = ( source.chant_set.annotate(record_type=Value("chant")) .order_by("folio", "c_sequence") - .select_related("feast", "office", "genre", "diff_db") + .select_related("feast", "service", "genre", "diff_db") ) context["source"] = source diff --git a/django/cantusdb_project/static/js/chant_detail.js b/django/cantusdb_project/static/js/chant_detail.js index 176c8327c..0a672cb48 100644 --- a/django/cantusdb_project/static/js/chant_detail.js +++ b/django/cantusdb_project/static/js/chant_detail.js @@ -76,7 +76,7 @@ function loadMelodies(cantusId) { const chantCell = newRow.getElementsByTagName("td")[0]; chantCell.innerHTML += '
'; if (chant.folio) { chantCell.innerHTML += `${chant.folio} | ` } else { chantCell.innerHTML += '' } - if (chant.office__name) { chantCell.innerHTML += `${chant.office__name} ` } else { chantCell.innerHTML += '' } + if (chant.service__name) { chantCell.innerHTML += `${chant.service__name} ` } else { chantCell.innerHTML += '' } if (chant.genre__name) { chantCell.innerHTML += `${chant.genre__name} ` } else { chantCell.innerHTML += '' } if (chant.position) { chantCell.innerHTML += `${chant.position}` } else { chantCell.innerHTML += '' } chantCell.innerHTML += '
'; diff --git a/django/cantusdb_project/static/js/chant_search.js b/django/cantusdb_project/static/js/chant_search.js index 9774e2f20..962c8344d 100644 --- a/django/cantusdb_project/static/js/chant_search.js +++ b/django/cantusdb_project/static/js/chant_search.js @@ -6,7 +6,7 @@ window.addEventListener("load", function () { // Make sure the select components keep their values across multiple GET requests // so the user can "drill down" on what they want const opFilter = document.getElementById("opFilter"); - const officeFilter = document.getElementById("officeFilter"); + const serviceFilter = document.getElementById("serviceFilter"); const genreFilter = document.getElementById("genreFilter"); const melodiesFilter = document.getElementById("melodiesFilter"); const keywordField = document.getElementById("keywordSearch"); @@ -17,8 +17,8 @@ window.addEventListener("load", function () { if (urlParams.has("op")) { opFilter.value = urlParams.get("op"); } - if (urlParams.has("office")) { - officeFilter.value = urlParams.get("office"); + if (urlParams.has("service")) { + serviceFilter.value = urlParams.get("service"); } if (urlParams.has("genre")) { genreFilter.value = urlParams.get("genre"); diff --git a/django/cantusdb_project/templates/base.html b/django/cantusdb_project/templates/base.html index b6f1f6b19..3a9faac8e 100644 --- a/django/cantusdb_project/templates/base.html +++ b/django/cantusdb_project/templates/base.html @@ -251,7 +251,7 @@