Skip to content

Commit

Permalink
Merge pull request #1706 from lucasmarchd01/issue-1492
Browse files Browse the repository at this point in the history
Implement proofread filter functionality to Browse Chants page
  • Loading branch information
lucasmarchd01 authored Dec 2, 2024
2 parents e4dddb8 + 41f5ba7 commit dbcdbd3
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 3 deletions.
31 changes: 31 additions & 0 deletions django/cantusdb_project/main_app/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
(False, "Partial inventory"),
)

# Define choices for Chant model's
# various proofreading fields: manuscript_full_text_std_proofread,
# manuscript_full_text_proofread, volpiano_proofread
PROOFREAD_CHOICES = [
(None, "Any"),
(True, "Yes"),
(False, "No"),
]


class NameModelChoiceField(forms.ModelChoiceField):
"""
Expand Down Expand Up @@ -520,6 +529,28 @@ class Meta:
)


class SourceBrowseChantsProofreadForm(forms.Form):
manuscript_full_text_std_proofread = forms.ChoiceField(
label="Full text as in Source (standardized spelling) proofread",
choices=PROOFREAD_CHOICES,
widget=forms.RadioSelect,
required=False,
)
manuscript_full_text_proofread = forms.ChoiceField(
label="Full text as in Source (source spelling) proofread",
choices=PROOFREAD_CHOICES,
widget=forms.RadioSelect,
required=False,
)

volpiano_proofread = forms.ChoiceField(
label="Volpiano proofread",
choices=PROOFREAD_CHOICES,
widget=forms.RadioSelect,
required=False,
)


class SequenceEditForm(forms.ModelForm):
class Meta:
model = Sequence
Expand Down
26 changes: 26 additions & 0 deletions django/cantusdb_project/main_app/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,39 @@ def user_can_proofread_chant(user: User, chant: Chant) -> bool:
return False

source_id = chant.source.id
user_can_proofread_src = user_can_proofread_source(user, chant.source)

user_is_assigned_to_source: bool = user.sources_user_can_edit.filter( # noqa
id=source_id
).exists()

user_is_project_manager: bool = user.groups.filter(name="project manager").exists()
user_is_editor: bool = user.groups.filter(name="editor").exists()

return user_can_proofread_src and (
user_is_project_manager or (user_is_editor and user_is_assigned_to_source)
)


def user_can_proofread_source(user: User, source: Source) -> bool:
"""
Checks if the user can access the proofreading page of a given Source.
Used in SourceBrowseChantsView.
"""
if user.is_superuser:
return True

if user.is_anonymous:
return False

source_id = source.id
user_is_assigned_to_source: bool = user.sources_user_can_edit.filter(
id=source_id
).exists()

user_is_project_manager: bool = user.groups.filter(name="project manager").exists()
user_is_editor: bool = user.groups.filter(name="editor").exists()

return user_is_project_manager or (user_is_editor and user_is_assigned_to_source)


Expand Down
15 changes: 15 additions & 0 deletions django/cantusdb_project/main_app/templates/browse_chants.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ <h3>Browse Chants</h3>
</select>
</div>
</div>
{% if user_can_proofread_source %}
<fieldset>
{% for field in proofread_filter_form %}
<div class="filter-group">
<label class="small"><b>{{ field.label }}:</b></label>
{% for radio in field %}
<label for="{{ radio.id_for_label }}" style="margin-right: 10px;">
{{ radio.choice_label }}
<span class="radio">{{ radio.tag }}</span>
</label>
{% endfor %}
</div>
{% endfor %}
</fieldset>
{% endif %}

</form>
{% with exists_on_cantus_ultimus=source.exists_on_cantus_ultimus %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,9 @@ def test_chant_with_volpiano_with_no_incipit(self):

def test_proofread_chant(self):
chant = make_fake_chant(
manuscript_full_text_std_spelling="lorem ipsum", folio="001r"
manuscript_full_text_std_spelling="lorem ipsum",
folio="001r",
manuscript_full_text_std_proofread=False,
)
folio = chant.folio
c_sequence = chant.c_sequence
Expand Down
76 changes: 76 additions & 0 deletions django/cantusdb_project/main_app/tests/test_views/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,82 @@ def test_search_full_text_std_spelling(self):
)
self.assertIn(chant, response.context["chants"])

def test_search_proofread(self):
cantus_segment = make_fake_segment(id=4063)
source = make_fake_source(segment=cantus_segment)
chant_std_proofread = make_fake_chant(
source=source,
manuscript_full_text_std_proofread=True,
manuscript_full_text_proofread=False,
volpiano_proofread=False,
)
chant_ft_proofread = make_fake_chant(
source=source,
manuscript_full_text_std_proofread=False,
manuscript_full_text_proofread=True,
volpiano_proofread=False,
)
chant_volpiano_proofread = make_fake_chant(
source=source,
manuscript_full_text_std_proofread=False,
manuscript_full_text_proofread=False,
volpiano_proofread=True,
)
response = self.client.get(
reverse("browse-chants", args=[source.id]),
)
self.assertIn(chant_std_proofread, response.context["chants"])
self.assertIn(chant_ft_proofread, response.context["chants"])
self.assertIn(chant_volpiano_proofread, response.context["chants"])

response = self.client.get(
reverse("browse-chants", args=[source.id]),
{"manuscript_full_text_std_proofread": True},
)
self.assertIn(chant_std_proofread, response.context["chants"])
self.assertNotIn(chant_ft_proofread, response.context["chants"])
self.assertNotIn(chant_volpiano_proofread, response.context["chants"])

response = self.client.get(
reverse("browse-chants", args=[source.id]),
{"manuscript_full_text_std_proofread": False},
)
self.assertNotIn(chant_std_proofread, response.context["chants"])
self.assertIn(chant_ft_proofread, response.context["chants"])
self.assertIn(chant_volpiano_proofread, response.context["chants"])

response = self.client.get(
reverse("browse-chants", args=[source.id]),
{"manuscript_full_text_proofread": True},
)
self.assertNotIn(chant_std_proofread, response.context["chants"])
self.assertIn(chant_ft_proofread, response.context["chants"])
self.assertNotIn(chant_volpiano_proofread, response.context["chants"])

response = self.client.get(
reverse("browse-chants", args=[source.id]),
{"manuscript_full_text_proofread": False},
)
self.assertIn(chant_std_proofread, response.context["chants"])
self.assertNotIn(chant_ft_proofread, response.context["chants"])
self.assertIn(chant_volpiano_proofread, response.context["chants"])

response = self.client.get(
reverse("browse-chants", args=[source.id]),
{"volpiano_proofread": True},
)
self.assertNotIn(chant_std_proofread, response.context["chants"])
self.assertNotIn(chant_ft_proofread, response.context["chants"])
self.assertIn(chant_volpiano_proofread, response.context["chants"])

response = self.client.get(
reverse("browse-chants", args=[source.id]),
{"volpiano_proofread": False},
)
self.assertIn(chant_std_proofread, response.context["chants"])
self.assertIn(chant_ft_proofread, response.context["chants"])
self.assertNotIn(chant_volpiano_proofread, response.context["chants"])

def test_context_source(self):
cantus_segment = make_fake_segment(id=4063)
source = make_fake_source(segment=cantus_segment)
Expand Down
40 changes: 38 additions & 2 deletions django/cantusdb_project/main_app/views/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
TemplateView,
)

from main_app.forms import SourceCreateForm, SourceEditForm
from main_app.forms import (
SourceCreateForm,
SourceEditForm,
SourceBrowseChantsProofreadForm,
)
from main_app.models import (
Century,
Chant,
Expand All @@ -34,6 +38,7 @@
user_can_edit_source,
user_can_view_source,
user_can_manage_source_editors,
user_can_proofread_source,
)
from main_app.views.chant import (
get_feast_selector_options,
Expand All @@ -54,6 +59,10 @@ class SourceBrowseChantsView(ListView):
``search_text``: Filters by text of Chant
``genre``: Filters by genre of Chant
``folio``: Filters by folio of Chant
``manuscript_full_text_proofread``: Filters by chants that have their full text proofread
``manuscript_full_text_std_proofread``: Filters by chants that have their standardized
spelling full text proofread
``volpiano_proofread``: Filters by chants that have their volpiano proofread
"""

model = Chant
Expand Down Expand Up @@ -84,8 +93,19 @@ def get_queryset(self):
folio = self.request.GET.get("folio")
search_text = self.request.GET.get("search_text")

# proofread fields filter
manuscript_full_text_proofread = self.request.GET.get(
"manuscript_full_text_proofread"
)
manuscript_full_text_std_proofread = self.request.GET.get(
"manuscript_full_text_std_proofread"
)
volpiano_proofread = self.request.GET.get("volpiano_proofread")

# get all chants in the specified source
chants = source.chant_set.select_related("feast", "service", "genre")
chants: QuerySet[Chant] = 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)
Expand All @@ -100,6 +120,18 @@ def get_queryset(self):
| Q(incipit__icontains=search_text)
| Q(manuscript_full_text__icontains=search_text)
)
# Apply proofreading filters if they are set
if manuscript_full_text_std_proofread:
chants = chants.filter(
manuscript_full_text_std_proofread=manuscript_full_text_std_proofread
)
if manuscript_full_text_proofread:
chants = chants.filter(
manuscript_full_text_proofread=manuscript_full_text_proofread
)
if volpiano_proofread:
chants = chants.filter(volpiano_proofread=volpiano_proofread)

return chants.order_by("folio", "c_sequence")

def get_context_data(self, **kwargs):
Expand Down Expand Up @@ -134,6 +166,7 @@ def get_context_data(self, **kwargs):

user = self.request.user
context["user_can_edit_chant"] = user_can_edit_chants_in_source(user, source)
context["user_can_proofread_source"] = user_can_proofread_source(user, source)

chants_in_source: QuerySet[Chant] = source.chant_set
if chants_in_source.count() == 0:
Expand Down Expand Up @@ -165,6 +198,9 @@ def get_context_data(self, **kwargs):

# the options for the feast selector on the right, same as the source detail page
context["feasts_with_folios"] = get_feast_selector_options(source)
context["proofread_filter_form"] = SourceBrowseChantsProofreadForm(
self.request.GET or None
)
return context


Expand Down
50 changes: 50 additions & 0 deletions django/cantusdb_project/static/js/chant_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ window.addEventListener("load", function () {
const genreFilter = document.getElementById("genreFilter");
const folioFilter = document.getElementById("folioFilter");

// Proofreading filters (radio buttons)
const manuscriptFullTextStdProofread = document.querySelectorAll('input[name="manuscript_full_text_std_proofread"]');
const manuscriptFullTextProofread = document.querySelectorAll('input[name="manuscript_full_text_proofread"]');
const volpianoProofread = document.querySelectorAll('input[name="volpiano_proofread"]');

// Make sure the select components keep their values across multiple GET requests
// so the user can "drill down" on what they want
const urlParams = new URLSearchParams(window.location.search);
Expand All @@ -23,18 +28,42 @@ window.addEventListener("load", function () {
folioFilter.value = urlParams.get("folio");
}

// Set the initial state of proofreading filters based on URL parameters
if (urlParams.has("manuscript_full_text_std_proofread")) {
document.querySelector(`input[name="manuscript_full_text_std_proofread"][value="${urlParams.get("manuscript_full_text_std_proofread")}"]`).checked = true;
}
if (urlParams.has("manuscript_full_text_proofread")) {
document.querySelector(`input[name="manuscript_full_text_proofread"][value="${urlParams.get("manuscript_full_text_proofread")}"]`).checked = true;
}
if (urlParams.has("volpiano_proofread")) {
document.querySelector(`input[name="volpiano_proofread"][value="${urlParams.get("volpiano_proofread")}"]`).checked = true;
}

// Event listeners for the select fields and search input
searchText.addEventListener("change", setSearch);
sourceFilter.addEventListener("change", setSource);
feastFilter.addEventListener("change", setFeastLeft);
feastSelect.addEventListener("change", setFeastRight);
genreFilter.addEventListener("change", setGenre);
folioFilter.addEventListener("change", setFolio);

// Event listeners for the proofreading radio buttons
manuscriptFullTextStdProofread.forEach(radio => {
radio.addEventListener("change", setProofreadingFilter);
});
manuscriptFullTextProofread.forEach(radio => {
radio.addEventListener("change", setProofreadingFilter);
});
volpianoProofread.forEach(radio => {
radio.addEventListener("change", setProofreadingFilter);
});

// functions for the auto-jump of various selectors and input fields on the page
// the folio selector and folio-feast selector on the right half do source-wide filtering
// the feast selector, genre selector, and text search on the left half do folio-wide filtering
var url = new URL(window.location.href);

// Handle text search change
function setSearch() {
const searchTerm = searchText.value;
url.searchParams.set('search_text', searchTerm);
Expand Down Expand Up @@ -86,4 +115,25 @@ window.addEventListener("load", function () {
url.searchParams.set('folio', folio);
window.location.assign(url);
}

// Helper function to update URL parameters
function updateURLParam(name, value) {
if (value === "") {
url.searchParams.delete(name);
} else {
url.searchParams.set(name, value);
}
}

// Handle proofreading filters (radio buttons)
function setProofreadingFilter() {
const stdProofread = document.querySelector('input[name="manuscript_full_text_std_proofread"]:checked')?.value;
const proofread = document.querySelector('input[name="manuscript_full_text_proofread"]:checked')?.value;
const volpianoProof = document.querySelector('input[name="volpiano_proofread"]:checked')?.value;

updateURLParam('manuscript_full_text_std_proofread', stdProofread);
updateURLParam('manuscript_full_text_proofread', proofread);
updateURLParam('volpiano_proofread', volpianoProof);
window.location.assign(url);
}
});

0 comments on commit dbcdbd3

Please sign in to comment.