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

Implement proofread filter functionality to Browse Chants page #1706

Merged
merged 18 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
64055d9
feat: add filtering for proofread fields
lucasmarchd01 Nov 11, 2024
8a26d92
feat: add form for proofread field choices
lucasmarchd01 Nov 11, 2024
522db41
feat: add radioselect widgets for chant proofreading filters
lucasmarchd01 Nov 11, 2024
c7306f2
feat: add permission check to see if user has proofreading permission…
lucasmarchd01 Nov 11, 2024
6985fbb
feat: add javascript for proofreading filters
lucasmarchd01 Nov 11, 2024
d9465dc
docs: update docstring for SourceBrowseChantsView
lucasmarchd01 Nov 11, 2024
8fe2fd5
refactor: fix labels for proofread form
lucasmarchd01 Nov 12, 2024
7166909
test: add tests for proofread filters on source browse chants page
lucasmarchd01 Nov 22, 2024
27a8abb
fix: fix proofread filter bug in SourceBrowseChantsView
lucasmarchd01 Nov 22, 2024
1a3c6a6
test(chant): use boolean value on proofread fields
lucasmarchd01 Nov 22, 2024
b793858
test: fix occasionally failing test in SourceEditChantsViewTest
lucasmarchd01 Nov 22, 2024
a8581ef
fix: fix typing
lucasmarchd01 Dec 2, 2024
94cc2bc
style: add spacing between radio select buttons
lucasmarchd01 Dec 2, 2024
78c402e
refactor: add permission check for user_can_proofread_chant
lucasmarchd01 Dec 2, 2024
5d90272
Merge branch 'issue-1492' of https://github.com/lucasmarchd01/CantusD…
lucasmarchd01 Dec 2, 2024
1ba6f84
Merge branch 'develop' into issue-1492
lucasmarchd01 Dec 2, 2024
3c06d64
Merge branch 'develop' into issue-1492
lucasmarchd01 Dec 2, 2024
41f5ba7
style: black formatting
lucasmarchd01 Dec 2, 2024
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
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:
lucasmarchd01 marked this conversation as resolved.
Show resolved Hide resolved
"""
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 %}
lucasmarchd01 marked this conversation as resolved.
Show resolved Hide resolved
<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);
}
});
Loading