Skip to content

Commit

Permalink
feat(chant views): catch invalid text errors
Browse files Browse the repository at this point in the history
Modifies chant create, edit, and detail views to prevent and catch text syllabification errors. Modifies edit syllabification view for the same.

Invalidates chant text fields if they error on syllabification.

Catches errors for texts with error rather than propagating to a server error.
  • Loading branch information
dchiller committed Oct 4, 2024
1 parent 3907d42 commit 192a164
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 78 deletions.
107 changes: 76 additions & 31 deletions django/cantusdb_project/main_app/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
from django import forms
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.contrib.admin.widgets import (
FilteredSelectMultiple,
)
from django.forms.widgets import CheckboxSelectMultiple
from dal import autocomplete
from volpiano_display_utilities.cantus_text_syllabification import syllabify_text
from volpiano_display_utilities.latin_word_syllabification import LatinError
from .models import (
Chant,
Service,
Expand All @@ -22,13 +31,6 @@
SelectWidget,
CheckboxWidget,
)
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.contrib.admin.widgets import (
FilteredSelectMultiple,
)
from django.forms.widgets import CheckboxSelectMultiple
from dal import autocomplete

# ModelForm allows to build a form directly from a model
# see https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/
Expand Down Expand Up @@ -71,6 +73,40 @@ def label_from_instance(self, obj):
widget = CheckboxSelectMultiple()


class CantusDBLatinField(forms.CharField):
"""
A custom CharField for chant text fields. Validates that the text
can be syllabified (essentially, that it does not have any improper
characters).
"""

def validate(self, value):
super().validate(value)
if value:
try:
syllabify_text(value)
except LatinError as err:
raise forms.ValidationError(str(err))
except ValueError as exc:
raise forms.ValidationError("Invalid characters in text.") from exc


class CantusDBSyllabifiedLatinField(forms.CharField):
"""
A custom CharField for chant syllabified text fields. Validates that the text
can be syllabified (essentially, that it does not have any improper
characters).
"""

def validate(self, value):
super().validate(value)
if value:
try:
syllabify_text(value, text_presyllabified=True)
except ValueError as exc:
raise forms.ValidationError("Invalid characters in text.") from exc


class ChantCreateForm(forms.ModelForm):
class Meta:
model = Chant
Expand Down Expand Up @@ -125,8 +161,8 @@ class Meta:
"finalis": TextInputWidget(),
"extra": TextInputWidget(),
"chant_range": VolpianoInputWidget(),
# manuscript_full_text_std_spelling: defined below (required)
"manuscript_full_text": TextAreaWidget(),
# manuscript_full_text_std_spelling: defined below (required & special field)
# "manuscript_full_text": defined below (special field)
"volpiano": VolpianoAreaWidget(),
"image_link": TextInputWidget(),
"melody_id": TextInputWidget(),
Expand All @@ -153,14 +189,18 @@ class Meta:
help_text="Each folio starts with '1'.",
)

manuscript_full_text_std_spelling = forms.CharField(
manuscript_full_text_std_spelling = CantusDBLatinField(
widget=TextAreaWidget,
help_text=Chant._meta.get_field("manuscript_full_text_std_spelling").help_text,
label="Full text as in Source (standardized spelling)",
required=True,
)

manuscript_full_text = CantusDBLatinField(
widget=TextAreaWidget,
help_text="Manuscript full text with standardized spelling. Enter the words "
"according to the manuscript but normalize their spellings following "
"Classical Latin forms. Use upper-case letters for proper nouns, "
'the first word of each chant, and the first word after "Alleluia" for '
"Mass Alleluias. Punctuation is omitted.",
label="Full text as in Source (source spelling)",
help_text=Chant._meta.get_field("manuscript_full_text").help_text,
required=False,
)

project = SelectWidgetNameModelChoiceField(
Expand Down Expand Up @@ -319,8 +359,8 @@ class Meta:
"rubrics",
]
widgets = {
# manuscript_full_text_std_spelling: defined below (required)
"manuscript_full_text": TextAreaWidget(),
# manuscript_full_text_std_spelling: defined below (required) & special field
# manuscript_full_text: defined below (special field)
"volpiano": VolpianoAreaWidget(),
"marginalia": TextInputWidget(),
# folio: defined below (required)
Expand Down Expand Up @@ -354,14 +394,18 @@ class Meta:
"rubrics": TextInputWidget(),
}

manuscript_full_text_std_spelling = forms.CharField(
manuscript_full_text_std_spelling = CantusDBLatinField(
widget=TextAreaWidget,
help_text=Chant._meta.get_field("manuscript_full_text_std_spelling").help_text,
label="Full text as in Source (standardized spelling)",
required=True,
)

manuscript_full_text = CantusDBLatinField(
widget=TextAreaWidget,
help_text="Manuscript full text with standardized spelling. Enter the words "
"according to the manuscript but normalize their spellings following "
"Classical Latin forms. Use upper-case letters for proper nouns, "
'the first word of each chant, and the first word after "Alleluia" for '
"Mass Alleluias. Punctuation is omitted.",
label="Full text as in Source (source spelling)",
help_text=Chant._meta.get_field("manuscript_full_text").help_text,
required=False,
)

folio = forms.CharField(
Expand Down Expand Up @@ -550,10 +594,14 @@ class Meta:
"manuscript_full_text",
"manuscript_syllabized_full_text",
]
widgets = {
"manuscript_full_text": TextAreaWidget(),
"manuscript_syllabized_full_text": TextAreaWidget(),
}

manuscript_full_text = CantusDBLatinField(
widget=TextAreaWidget, label="Full text as in Source (source spelling)"
)

manuscript_syllabized_full_text = CantusDBSyllabifiedLatinField(
widget=TextAreaWidget, label="Syllabized full text"
)


class AdminCenturyForm(forms.ModelForm):
Expand Down Expand Up @@ -738,10 +786,7 @@ class Meta:
widget=TextInputWidget,
)

name = forms.CharField(
required=False,
widget=TextInputWidget
)
name = forms.CharField(required=False, widget=TextInputWidget)

holding_institution = forms.ModelChoiceField(
queryset=Institution.objects.all().order_by("name"),
Expand Down
5 changes: 2 additions & 3 deletions django/cantusdb_project/main_app/templates/chant_create.html
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ <h3>Create Chant</h3>

<div class="form-row align-items-end">
<div class="form-group m-1 col-lg">
<label for="{{ form.manuscript_full_text_std_spelling.id_for_label }}"><small>Full text as in Source (standardized spelling):<span class="text-danger" title="This field is required">*</span></small></label>
<label for="{{ form.manuscript_full_text_std_spelling.id_for_label }}" class="small">{{ form.manuscript_full_text_std_spelling.label_tag }}<span class="text-danger" title="This field is required">*</span></label>
{{ form.manuscript_full_text_std_spelling }}
<p>
<small class="text-muted">{{ form.manuscript_full_text_std_spelling.help_text }}
Expand All @@ -241,8 +241,7 @@ <h3>Create Chant</h3>

<div class="form-row align-items-end">
<div class="form-group m-1 col-lg">
<label for="{{ form.manuscript_full_text.id_for_label }}"><small>Full text as in Source
(source spelling): </small></label>
<label for="{{ form.manuscript_full_text.id_for_label }}" class="small">{{ form.manuscript_full_text.label_tag }}</label>
{{ form.manuscript_full_text }}
<p class="text-muted" style="line-height: normal">
<small>{{ form.manuscript_full_text.help_text }}
Expand Down
35 changes: 22 additions & 13 deletions django/cantusdb_project/main_app/templates/chant_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<!--Display messages -->
{% for message in messages %}
<div class="alert {{ message.tags }} alert-dismissible" role="alert" >
<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
{{ message }}
</div>
{% endfor %}
Expand Down Expand Up @@ -283,25 +284,33 @@
<p><small>Syllabification is based on saved syllabized text.</small></p>
{% endif %}
<dd>
{% for syl_text, syl_mel in syllabized_text_with_melody %}
<span style="float: left">
<div style="font-family: volpiano; font-size: 36px">{{ syl_mel }}</div>
<!-- "mt" is margin at the top, so that the lowest note in volpiano don't overlap with text -->
<div class="mt-2" style="font-size: 12px; "><pre>{{ syl_text }}</pre></div>
</span>
{% endfor %}
{% if syllabized_text_with_melody %}
{% for syl_text, syl_mel in syllabized_text_with_melody %}
<span style="float: left">
<div style="font-family: volpiano; font-size: 36px">{{ syl_mel }}</div>
<!-- "mt" is margin at the top, so that the lowest note in volpiano don't overlap with text -->
<div class="mt-2" style="font-size: 12px; "><pre>{{ syl_text }}</pre></div>
</span>
{% endfor %}
{% else %}
<p>Error aligning text and melody. Please check text for invalid characters.</p>
{% endif %}
</dd>
</div>
</div>
{% endif %}

<div class="form-row">
<div class="form-group m-1 col-lg-4">
<a href="{% url "source-edit-syllabification" chant.id %}" style="display: inline-block; margin-top:5px;" target="_blank">
<small>Edit syllabification (new window)</small>
</a>
<!-- If there's no syllabized_text_with_melody (either there's no volpiano to align or there's an
error in the text), there's no need for the user to edit the syllabification. -->
{% if syllabized_text_with_melody %}
<div class="form-row">
<div class="form-group m-1 col-lg-4">
<a href="{% url "source-edit-syllabification" chant.id %}" style="display: inline-block; margin-top:5px;" target="_blank">
<small>Edit syllabification (new window)</small>
</a>
</div>
</div>
</div>
{% endif %}

<div class="form-row">
<div class="form-group m-1 col-lg-12">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<!--Display messages -->
{% for message in messages %}
<div class="alert {{ message.tags }} alert-dismissible" role="alert" >
<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>s
{{ message }}
</div>
{% endfor %}
Expand All @@ -37,17 +38,17 @@ <h3>Edit Syllabification</h3>

<div class="form-row">
<div class="form-group m-1 col-lg-12">
<label for="{{ form.manuscript_full_text.id_for_label }}">
<small><b>Manuscript Reading Full Text (MS spelling):</b></small>
<label for="{{ form.manuscript_full_text.id_for_label }}" class="small font-weight-bold">
{{ form.manuscript_full_text.label_tag }}
</label>
{{ form.manuscript_full_text }}
</div>
</div>

<div class="form-row">
<div class="form-group m-1 col-lg-12">
<label for="{{ form.manuscript_syllabized_full_text.id_for_label }}">
<small><b>Syllabized Full Text:</b></small>
<label for="{{ form.manuscript_syllabized_full_text.id_for_label }}" class="small font-weight-bold">
{{ form.manuscript_syllabized_full_text.label_tag }}
</label>
{{ form.manuscript_syllabized_full_text }}
</div>
Expand Down
Loading

0 comments on commit 192a164

Please sign in to comment.