diff --git a/django/cantusdb_project/cantusdb/urls.py b/django/cantusdb_project/cantusdb/urls.py index e39f736f6..0179b5e5d 100644 --- a/django/cantusdb_project/cantusdb/urls.py +++ b/django/cantusdb_project/cantusdb/urls.py @@ -13,6 +13,7 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.contrib import admin from django.urls import path from django.urls import include diff --git a/django/cantusdb_project/main_app/models/base_model.py b/django/cantusdb_project/main_app/models/base_model.py index dca07ac08..f64b44ff7 100644 --- a/django/cantusdb_project/main_app/models/base_model.py +++ b/django/cantusdb_project/main_app/models/base_model.py @@ -1,4 +1,5 @@ """Defines a BaseModel to be extended by all other models""" + from typing import List from django.db import models from django.urls import reverse diff --git a/django/cantusdb_project/main_app/tests/make_fakes.py b/django/cantusdb_project/main_app/tests/make_fakes.py index 94ada55e4..47177eb57 100644 --- a/django/cantusdb_project/main_app/tests/make_fakes.py +++ b/django/cantusdb_project/main_app/tests/make_fakes.py @@ -1,4 +1,5 @@ """Functions to make fake objects to be used for testing""" + import random from faker import Faker diff --git a/django/cantusdb_project/main_app/tests/test_views.py b/django/cantusdb_project/main_app/tests/test_views.py index b4f7f3af8..c4402bd6f 100644 --- a/django/cantusdb_project/main_app/tests/test_views.py +++ b/django/cantusdb_project/main_app/tests/test_views.py @@ -2886,7 +2886,7 @@ def test_volpiano_signal(self): "c_sequence": "1", # liquescents, to be converted to lowercase # vv v v v v v v - "volpiano": "9abcdefg)A-B1C2D3E4F5G67?. yiz" + "volpiano": "9abcdefg)A-B1C2D3E4F5G67?. yiz", # ^ ^ ^ ^ ^ ^ ^^^^^^^^ # clefs, accidentals, etc., to be deleted }, @@ -2909,6 +2909,52 @@ def test_volpiano_signal(self): self.assertEqual(chant_2.volpiano, "abacadaeafagahaja") 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 + source: Source = make_fake_source() + folio: str = "001r" + sequence: int = 1 + feast: Feast = make_fake_feast() + office: Office = make_fake_office() + image_link: str = "https://www.youtube.com/watch?v=9bZkp7q19f0" + self.client.post( + reverse("chant-create", args=[source.id]), + { + "manuscript_full_text_std_spelling": "this is a bog standard manuscript textful spelling", + "folio": folio, + "c_sequence": str(sequence), + "feast": feast.id, + "office": office.id, + "image_link": image_link, + }, + ) + + # when we request the Chant Create page, the same folio, feast, office and image_link should + # be preselected, and c_sequence should be incremented by 1. + response = self.client.get( + reverse("chant-create", args=[source.id]), + ) + + observed_initial_folio: int = response.context["form"].initial["folio"] + with self.subTest(subtest="test initial value of folio field"): + self.assertEqual(observed_initial_folio, folio) + + observed_initial_feast: int = response.context["form"].initial["feast"] + 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_sequence: int = response.context["form"].initial["c_sequence"] + with self.subTest(subtest="test initial value of c_sequence field"): + self.assertEqual(observed_initial_sequence, sequence + 1) + + observed_initial_image: int = response.context["form"].initial["image_link"] + with self.subTest(subtest="test initial value of image_link field"): + self.assertEqual(observed_initial_image, image_link) + class ChantDeleteViewTest(TestCase): @classmethod @@ -3033,7 +3079,7 @@ def test_volpiano_signal(self): "c_sequence": "1", # liquescents, to be converted to lowercase # vv v v v v v v - "volpiano": "9abcdefg)A-B1C2D3E4F5G67?. yiz" + "volpiano": "9abcdefg)A-B1C2D3E4F5G67?. yiz", # ^ ^ ^ ^ ^ ^ ^^^^^^^^ # clefs, accidentals, etc., to be deleted }, @@ -4510,7 +4556,9 @@ def test_dd_column(self): response = self.client.get(reverse("source-inventory", args=[source.id])) html: str = str(response.content) self.assertIn(diff_id, html) - expected_html_substring: str = f'' + expected_html_substring: str = ( + f'' + ) self.assertIn(expected_html_substring, html) def test_redirect_with_source_parameter(self): @@ -5180,9 +5228,16 @@ def test_url(self): def test_content(self): NUM_CHANTS = 5 - source = make_fake_source(published=True) + source_siglum = "SourceSiglum" + chant_siglum = "ChantSiglum" # OldCantus chants/sequences had a "siglum" + # field, which would sometimes get out of date when the chant's source's siglum + # was updated. We keep the chant siglum field around to ensure no data is + # inadvertently lost, but we need to ensure it is never displayed publicly. + source = make_fake_source(published=True, siglum=source_siglum) for _ in range(NUM_CHANTS): - make_fake_chant(source=source) + chant = make_fake_chant(source=source) + chant.siglum = chant_siglum + chant.save() response = self.client.get(reverse("csv-export", args=[source.id])) content = response.content.decode("utf-8") split_content = list(csv.reader(content.splitlines(), delimiter=",")) @@ -5213,11 +5268,21 @@ def test_content(self): "node_id", ] for t in expected_column_titles: - self.assertIn(t, header) - - self.assertEqual(len(rows), NUM_CHANTS) - for row in rows: - self.assertEqual(len(header), len(row)) + with self.subTest(expected_column=t): + self.assertIn(t, header) + with self.subTest(subtest="ensure a row exists for each chant"): + self.assertEqual(len(rows), NUM_CHANTS) + with self.subTest( + subtest="ensure all rows have the same number of columns as the header" + ): + for row in rows: + self.assertEqual(len(header), len(row)) + with self.subTest( + "ensure we only ever display chants' sources' sigla, and never the " + "value stored in chants' siglum fields" + ): + for row in rows: + self.assertEqual(row[0], source_siglum) def test_published_vs_unpublished(self): published_source = make_fake_source(published=True) @@ -5243,12 +5308,18 @@ def test_csv_export_on_source_with_sequences(self): split_content = list(csv.reader(content.splitlines(), delimiter=",")) header, rows = split_content[0], split_content[1:] - self.assertEqual(len(rows), NUM_SEQUENCES) - for row in rows: - self.assertEqual(len(header), len(row)) - self.assertNotEqual( - row[3], "" - ) # ensure that the .s_sequence field is being written to the "sequence" column + with self.subTest(subtest="ensure a row exists for each sequence"): + self.assertEqual(len(rows), NUM_SEQUENCES) + with self.subTest( + subtest="ensure all rows have the same number of columns as the header" + ): + for row in rows: + self.assertEqual(len(header), len(row)) + with self.subTest( + subtest="ensure .s_sequence field is being written to the 'sequence' column" + ): + for row in rows: + self.assertNotEqual(row[3], "") class ChangePasswordViewTest(TestCase): diff --git a/django/cantusdb_project/main_app/urls.py b/django/cantusdb_project/main_app/urls.py index 7c1ed6039..7661db2f7 100644 --- a/django/cantusdb_project/main_app/urls.py +++ b/django/cantusdb_project/main_app/urls.py @@ -391,11 +391,6 @@ views.csv_export_redirect_from_old_path, name="csv-export-old-path", ), - path( - "ajax/concordance/", - views.ajax_concordance_list, - name="ajax-concordance", - ), # content overview (for project managers) path( "content-overview/", diff --git a/django/cantusdb_project/main_app/views/chant.py b/django/cantusdb_project/main_app/views/chant.py index bd17143fc..4d7324ea3 100644 --- a/django/cantusdb_project/main_app/views/chant.py +++ b/django/cantusdb_project/main_app/views/chant.py @@ -805,6 +805,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_seq = ( latest_chant.c_sequence if latest_chant.c_sequence is not None else 0 ) @@ -812,6 +813,7 @@ def get_initial(self): return { "folio": latest_folio, "feast": latest_feast, + "office": latest_office, "c_sequence": latest_seq + 1, "image_link": latest_image, } diff --git a/django/cantusdb_project/main_app/views/views.py b/django/cantusdb_project/main_app/views/views.py index 54a40fbcc..9768551ad 100644 --- a/django/cantusdb_project/main_app/views/views.py +++ b/django/cantusdb_project/main_app/views/views.py @@ -69,61 +69,6 @@ def items_count(request): return render(request, "items_count.html", context) -def ajax_concordance_list(request, cantus_id): - """ - Function-based view responding to the AJAX call for concordance list on the chant detail page, - accessed with ``chants/``, click on "Display concordances of this chant" - - Args: - cantus_id (str): The Cantus ID of the requested concordances group - - Returns: - JsonResponse: A response to the AJAX call, to be unpacked by the frontend js code - """ - chants = Chant.objects.filter(cantus_id=cantus_id) - seqs = Sequence.objects.filter(cantus_id=cantus_id) - - display_unpublished = request.user.is_authenticated - if not display_unpublished: - chants = chants.filter(source__published=True) - seqs = seqs.filter(source__published=True) - - if seqs: - chants = chants.union(seqs).order_by("siglum", "folio") - else: - chants = chants.order_by("siglum", "folio") - # queryset(list of dictionaries) - concordance_values = chants.values( - "siglum", - "folio", - "incipit", - "office__name", - "genre__name", - "position", - "feast__name", - "mode", - "image_link", - ) - - concordances = list(concordance_values) - for i, concordance in enumerate(concordances): - # some chants do not have a source - # for those chants, do not return source link - if chants[i].source: - concordance["source_link"] = chants[i].source.get_absolute_url() - if chants[i].search_vector: - concordance["chant_link"] = chants[i].get_absolute_url() - else: - concordance["chant_link"] = reverse("sequence-detail", args=[chants[i].id]) - concordance["db"] = "CD" - - concordance_count = len(concordances) - return JsonResponse( - {"concordances": concordances, "concordance_count": concordance_count}, - safe=True, - ) - - def ajax_melody_list(request, cantus_id) -> JsonResponse: """ Function-based view responding to the AJAX call for melody list on the chant detail page, @@ -235,6 +180,7 @@ def csv_export(request, source_id): "node_id", ] ) + siglum = source.siglum for entry in entries: feast = entry.feast.name if entry.feast else "" office = entry.office.name if entry.office else "" @@ -243,7 +189,7 @@ def csv_export(request, source_id): writer.writerow( [ - entry.siglum, + siglum, entry.marginalia, entry.folio, # if entry has a c_sequence, it's a Chant. If it doesn't, it's a Sequence, so write its s_sequence diff --git a/django/cantusdb_project/requirements.txt b/django/cantusdb_project/requirements.txt index 889947a2c..270ffceac 100644 --- a/django/cantusdb_project/requirements.txt +++ b/django/cantusdb_project/requirements.txt @@ -3,11 +3,11 @@ asgiref==3.6.0 astroid==2.4.2 attrs==19.3.0 backports.zoneinfo==0.2.1 -black==21.10b0 +black==24.3.0 certifi==2023.7.22 chardet==3.0.4 charset-normalizer==2.0.12 -click==7.1.2 +click==8.0.0 coverage==5.3.1 Django==4.2.11 django-autocomplete-light==3.9.4 @@ -36,7 +36,7 @@ sqlparse==0.4.4 text-unidecode==1.3 toml==0.10.1 typed-ast==1.4.1 -typing-extensions==3.10.0.0 +typing-extensions==4.0.1 ujson==5.9.0 urllib3==1.26.18 volpiano-display-utilities @ git+https://github.com/DDMAL/volpiano-display-utilities.git@v1.1.2 diff --git a/scripts/parse_link_checker_output.py b/scripts/parse_link_checker_output.py index 172f84684..178d91109 100644 --- a/scripts/parse_link_checker_output.py +++ b/scripts/parse_link_checker_output.py @@ -1,4 +1,5 @@ """Modules""" + import json import sys from pathlib import Path