Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add indexing notes search to chant search page #1576

Merged
merged 4 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion django/cantusdb_project/main_app/templates/chant_search.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,27 @@ <h3>Search Chants</h3>
</select>
</div>
</div>
{% if source %}
<div class="form-row align-items-end">
<div class="form-group m-1 col-12">
<label for="indexingNotesSearch"><small>Indexing notes search</small></label>
</div>
</div>
<div class="form-row align-items-end">
<div class="form-group m-1 col-lg-3 col-sm-3 col-3">
<select class="form-control custom-select custom-select-sm" id="indexingNotesOp" name="indexing_notes_op">
<option selected value="contains">Contains</option>
<option value="starts_with">Starts with</option>
</select>
</div>
<div class="form-group m-1 col-lg col-sm col-">
<input type="text" class="form-control form-control-sm" id="indexingNotesSearch" name="indexing_notes" value="{{ request.GET.indexing_notes }}" />
</div>
</div>
{% endif %}

<div class="form-row align-items-end">
<div class="form-group m-1 col-lg">
<div class="form-group m-2 col-lg">
<button type="submit" class="btn btn-dark btn-sm" id="btn-submit"> Apply </button>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion django/cantusdb_project/main_app/tests/make_fakes.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def make_fake_chant(
next_chant=None,
differentia=None,
project=None,
indexing_notes=None,
) -> Chant:
"""Generates a fake Chant object."""
if source is None:
Expand Down Expand Up @@ -193,6 +194,8 @@ def make_fake_chant(
differentia = make_random_string(2)
if project is None:
project = make_fake_project()
if indexing_notes is None:
indexing_notes = faker.sentence()

chant = Chant.objects.create(
source=source,
Expand Down Expand Up @@ -224,7 +227,7 @@ def make_fake_chant(
cao_concordances=make_random_string(12, "ABCDEFGHIJKLMNOPQRSTUVWXYZ "),
melody_id="m" + make_random_string(8, "0123456789."),
manuscript_syllabized_full_text=manuscript_syllabized_full_text,
indexing_notes=faker.sentence(),
indexing_notes=indexing_notes,
json_info=None,
next_chant=next_chant,
project=project,
Expand Down
49 changes: 49 additions & 0 deletions django/cantusdb_project/main_app/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2363,6 +2363,55 @@ def test_keyword_search_contains(self):
second_context_chant_id = response.context["chants"][1].id
self.assertEqual(chant_3.id, second_context_chant_id)

def test_indexing_notes_search_starts_with(self):
source = make_fake_source()
search_term = "quick"

# We have three chants to make sure the result is only chant 1 where quick is the first word
chant_1 = make_fake_chant(
source=source,
indexing_notes="quick brown fox jumps over the lazy dog",
)
chant_2 = make_fake_chant(
source=source,
indexing_notes="brown fox jumps over the lazy dog",
)
chant_3 = make_fake_chant(
source=source,
indexing_notes="lazy brown fox jumps quick over the dog",
)
response = self.client.get(
reverse("chant-search-ms", args=[source.id]),
{"indexing_notes": search_term, "indexing_notes_op": "starts_with"},
)
self.assertEqual(len(response.context["chants"]), 1)
context_chant_id = response.context["chants"][0].id
self.assertEqual(chant_1.id, context_chant_id)

def test_indexing_notes_search_contains(self):
source = make_fake_source()
search_term = "quick"
chant_1 = make_fake_chant(
source=source,
indexing_notes="Quick brown fox jumps over the lazy dog",
)
chant_2 = make_fake_chant(
source=source,
indexing_notes="brown fox jumps over the lazy dog",
)
chant_3 = make_fake_chant(
source=source,
indexing_notes="lazy brown fox jumps quickly over the dog",
)
response = self.client.get(
reverse("chant-search-ms", args=[source.id]),
{"indexing_notes": search_term, "indexing_notes_op": "contains"},
)
first_context_chant_id = response.context["chants"][0].id
self.assertEqual(chant_1.id, first_context_chant_id)
second_context_chant_id = response.context["chants"][1].id
self.assertEqual(chant_3.id, second_context_chant_id)

def test_keyword_search_searching_all_fields(self):
search_term = "brevity"
includes_search_term = "brevity is the soul of wit"
Expand Down
14 changes: 14 additions & 0 deletions django/cantusdb_project/main_app/views/chant.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,12 @@ def get_context_data(self, **kwargs):
search_melodies = self.request.GET.get("melodies")
if search_melodies:
search_parameters.append(f"melodies={search_melodies}")
search_indexing_notes_op = self.request.GET.get("indexing_notes_op")
if search_indexing_notes_op:
search_parameters.append(f"indexing_notes_op={search_indexing_notes_op}")
search_indexing_notes = self.request.GET.get("indexing_notes")
if search_indexing_notes:
search_parameters.append(f"indexing_notes={search_indexing_notes}")

if search_parameters:
joined_search_parameters = "&".join(search_parameters)
Expand Down Expand Up @@ -724,6 +730,14 @@ def get_queryset(self) -> QuerySet:

keyword_filter = ms_spelling_filter | std_spelling_filter | incipit_filter
queryset = queryset.filter(keyword_filter)
if notes := self.request.GET.get("indexing_notes"):
operation = self.request.GET.get("indexing_notes_op")
# the operation parameter can be "contains" or "starts_with"
if operation == "contains":
indexing_notes_filter = Q(indexing_notes__icontains=notes)
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only question here is about this else, and not doing a check that the operation = "starts_with"... I'm only thinking about this in relation to the kind of thing in #1484. Is this the kind of place you are envisioning checking? Or was your thought about #1484 to do that separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you're right. I completely forgot about that issue. #1484 is concerned with the actual parameter values themselves, for example if a user inputs a string type value for an expected int type. In this case, the expected value can be anything, since we are doing a text search.

I copied this logic from another part of the code and did some testing, I don't think we'll encounter the 500 error here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, great! I'm good to merge

indexing_notes_filter = Q(indexing_notes__istartswith=notes)
queryset = queryset.filter(indexing_notes_filter)
# ordering with the folio string gives wrong order
# old cantus is also not strictly ordered by folio (there are outliers)
# so we order by id for now, which is the order that the chants are entered into the DB
Expand Down
4 changes: 4 additions & 0 deletions django/cantusdb_project/static/js/chant_search.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ window.addEventListener("load", function () {
const melodiesFilter = document.getElementById("melodiesFilter");
const keywordField = document.getElementById("keywordSearch");
const cantusIDField = document.getElementById("cantus_id");
const indexingNotesOp = document.getElementById("indexingNotesOp");

const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("op")) {
Expand All @@ -25,6 +26,9 @@ window.addEventListener("load", function () {
if (urlParams.has("melodies")) {
melodiesFilter.value = urlParams.get("melodies");
}
if (urlParams.has("indexing_notes_op")) {
indexingNotesOp.value = urlParams.get("indexing_notes_op")
}
if (urlParams.has("search_bar")) {
let search_term = urlParams.get("search_bar");
if (containsNoNumerals(search_term)) {
Expand Down
Loading