Skip to content

Commit

Permalink
Merge pull request #1739 from DDMAL/staging
Browse files Browse the repository at this point in the history
Merge `staging` into `production`, 05 Feb 2025
  • Loading branch information
dchiller authored Feb 5, 2025
2 parents 0122510 + 7eaad52 commit ad6e3c8
Show file tree
Hide file tree
Showing 109 changed files with 7,166 additions and 5,335 deletions.
11 changes: 5 additions & 6 deletions django/cantusdb_project/articles/templates/article_detail.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
{% extends "base_page_with_side_cards.html" %}
{% load helper_tags %} {# for recent_articles #}
{% load helper_tags %}
{# for recent_articles #}
{% block title %}
<title>{{ article.title }} | Cantus Database</title>
<title>{{ article.title }} | Cantus Database</title>
{% endblock %}

{% block uppersidebar %}
<div class="search-bar mb-3">
<div class="search-bar mb-3 position-relative">
{% include "global_search_bar.html" %}
</div>
{% endblock %}

{% block maincontent %}
<h3>
{{ article.title }}
Expand All @@ -36,4 +35,4 @@ <h3>
{% recent_articles %}
</div>
</div>
{% endblock %}
{% endblock %}
39 changes: 21 additions & 18 deletions django/cantusdb_project/articles/templates/article_list.html
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
{% extends "base_page_with_side_cards.html" %}
{% load helper_tags %} {# for recent_articles #}\
{% load helper_tags %}
{# for recent_articles #}\
{% block title %}
<title>What's New | Cantus Database</title>
<title>What's New | Cantus Database</title>
{% endblock %}
{% block uppersidebar %}
<div class="search-bar mb-3">
{% include "global_search_bar.html" %}
</div>
<div class="search-bar mb-3 position-relative">
{% include "global_search_bar.html" %}
</div>
{% endblock %}
{% block maincontent %}
<h3>What's New</h3>
<h3>
What's New
</h3>
{% for article in articles %}
<div class="row">
<div class="col">
<small>{{ article.date_created|date:"D, m/d/Y - H:i" }}</small>
<h4>
<a href="{% url 'article-detail' article.id %}">{{ article.title }}</a>
</h4>
<div class="container">
<small>
{{ article.body.html|safe|truncatechars_html:3000 }}
</small>
<div class="row">
<div class="col">
<small>{{ article.date_created|date:"D, m/d/Y - H:i" }}</small>
<h4>
<a href="{% url 'article-detail' article.id %}">{{ article.title }}</a>
</h4>
<div class="container">
<small>
{{ article.body.html|safe|truncatechars_html:3000 }}
</small>
</div>
</div>
</div>
</div>
{% endfor %}
{% include "pagination.html" %}
{% endblock %}
Expand All @@ -36,4 +39,4 @@ <h4>
{% recent_articles %}
</div>
</div>
{% endblock %}
{% endblock %}
8 changes: 2 additions & 6 deletions django/cantusdb_project/cantusindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,8 @@ def get_suggested_fulltext(cantus_id: str) -> Optional[str]:
# mostly, in case of a timeout within get_json_from_ci_api
return None

try:
suggested_fulltext = json_response["info"]["field_full_text"]
except KeyError:
return None

return suggested_fulltext
info: Optional[dict] = json_response.get("info", {}) or {}
return info.get("field_full_text")


def get_merged_cantus_ids() -> Optional[list[Optional[dict]]]:
Expand Down
6 changes: 4 additions & 2 deletions django/cantusdb_project/main_app/admin/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class IdentifiersInline(admin.TabularInline):
extra = 0

def get_queryset(self, request):
return super().get_queryset(request).select_related("source__holding_institution")
return (
super().get_queryset(request).select_related("source__holding_institution")
)


@admin.register(Source)
Expand All @@ -38,7 +40,7 @@ class SourceAdmin(BaseModelAdmin):
"id",
"provenance_notes",
"name",
"identifiers__identifier"
"identifiers__identifier",
)
readonly_fields = (
("title", "siglum")
Expand Down
157 changes: 132 additions & 25 deletions django/cantusdb_project/main_app/forms.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import Optional, Any

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 django.forms.widgets import CheckboxSelectMultiple, HiddenInput
from dal import autocomplete # type: ignore[import-untyped]
from volpiano_display_utilities.cantus_text_syllabification import syllabify_text
from volpiano_display_utilities.latin_word_syllabification import LatinError
from .models import (
Expand Down Expand Up @@ -42,6 +44,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 @@ -159,6 +170,7 @@ class Meta:
"incipit_of_refrain",
"later_addition",
"rubrics",
"source",
]
# the widgets dictionary is ignored for a model field with a non-empty
# choices attribute. In this case, you must override the form field to
Expand Down Expand Up @@ -228,17 +240,23 @@ class Meta:
help_text="Select the project (if any) that the chant belongs to.",
)

# automatically computed fields
# source and incipit are mandatory fields in model,
# but have to be optional in the form, otherwise the field validation won't pass
source = forms.ModelChoiceField(
queryset=Source.objects.all().order_by("title"),
required=False,
error_messages={
"invalid_choice": "This source does not exist, please switch to a different source."
},
)
incipit = forms.CharField(required=False)
def clean(self) -> dict[str, Any]:
"""
Provide custom clean method that ensures the created chant does
not duplicate the folio and c_sequence of an already-existing chant.
"""
# Call super().clean() to ensure that the form's built-in validation
# is run before our custom validation.
super().clean()
folio = self.cleaned_data["folio"]
c_sequence = self.cleaned_data["c_sequence"]
source = self.cleaned_data["source"]
if source.chant_set.filter(folio=folio, c_sequence=c_sequence):
raise forms.ValidationError(
"Chant with the same sequence and folio already exists in this source.",
code="duplicate-folio-sequence",
)
return self.cleaned_data


class SourceCreateForm(forms.ModelForm):
Expand Down Expand Up @@ -277,6 +295,9 @@ class Meta:
widgets = {
# "title": TextInputWidget(),
# "siglum": TextInputWidget(),
"holding_institution": autocomplete.ModelSelect2(
url="holding-autocomplete"
),
"shelfmark": TextInputWidget(),
"provenance": autocomplete.ModelSelect2(url="provenance-autocomplete"),
"name": TextInputWidget(),
Expand Down Expand Up @@ -316,12 +337,6 @@ class Meta:
"segment_m2m": CheckboxNameModelMultipleChoiceField,
}

holding_institution = forms.ModelChoiceField(
queryset=Institution.objects.all(),
widget=autocomplete.ModelSelect2(url="holding-autocomplete"),
required=False,
)

complete_inventory = StyledChoiceField(
choices=COMPLETE_INVENTORY_FORM_CHOICES, required=False
)
Expand Down Expand Up @@ -363,6 +378,7 @@ class Meta:
"incipit_of_refrain",
"later_addition",
"rubrics",
"source",
]
widgets = {
# manuscript_full_text_std_spelling: defined below (required) & special field
Expand Down Expand Up @@ -404,7 +420,7 @@ class Meta:
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,
required=False,
)

manuscript_full_text = CantusDBLatinField(
Expand Down Expand Up @@ -432,6 +448,45 @@ class Meta:
required=False,
)

def clean_manuscript_full_text_std_spelling(self) -> Optional[str]:
"""
Provide a custom validation function for the
manuscript_full_text_std_spelling field to ensure that
if it initially contained text, it cannot be made blank.
"""
if (
self["manuscript_full_text_std_spelling"].initial
and not self["manuscript_full_text_std_spelling"].data
):
raise forms.ValidationError(
"This field cannot be blank for this chant.",
code="txt-req-prev-existing",
)
entered_text: str = self["manuscript_full_text_std_spelling"].data
return entered_text

def clean(self) -> dict[str, Any]:
"""
Custom validation to ensure that the edited chant does not duplicate
the folio and c_sequence of an already-existing chant.
"""
# Call super().clean() to ensure that the form's built-in validation
# is run before our custom validation.
super().clean()
folio = self.cleaned_data["folio"]
c_sequence = self.cleaned_data["c_sequence"]
source = self.cleaned_data["source"]
if (
source.chant_set.exclude(id=self.instance.id)
.filter(folio=folio, c_sequence=c_sequence)
.exists()
):
raise forms.ValidationError(
"A chant with this folio and sequence already exists.",
code="duplicate-folio-sequence",
)
return self.cleaned_data


class SourceEditForm(forms.ModelForm):
class Meta:
Expand Down Expand Up @@ -468,6 +523,9 @@ class Meta:
"source_completeness",
]
widgets = {
"holding_institution": autocomplete.ModelSelect2(
url="holding-autocomplete"
),
"shelfmark": TextInputWidget(),
"segment_m2m": CheckboxSelectMultiple(),
"name": TextInputWidget(),
Expand Down Expand Up @@ -509,14 +567,30 @@ class Meta:
"segment_m2m": CheckboxNameModelMultipleChoiceField,
}

holding_institution = forms.ModelChoiceField(
queryset=Institution.objects.all(),
widget=autocomplete.ModelSelect2(url="holding-autocomplete"),
complete_inventory = StyledChoiceField(
choices=COMPLETE_INVENTORY_FORM_CHOICES, required=False
)


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,
)

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


Expand Down Expand Up @@ -860,3 +934,36 @@ class Meta:
'using <a href="../password/">this form</a>.'
)
)


class ImageLinkForm(forms.Form):
"""
Subclass of Django's Form class that creates the form we use for
adding image links to chants in a source.
Initialize the Form with a field for every folio in the source,
passed as the "initial" parameter, which is a dictionary with a key
for every folio and a blank value.
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
initial = kwargs.get("initial")
if initial:
for folio in initial:
self.fields[folio] = forms.CharField(
widget=HiddenInput(attrs={"class": "img-link-input"}),
required=False,
)

def save(self, source: Source) -> None:
"""
Save the image links to the database.
Args:
source: The source to which the image links belong.
"""
cleaned_data = self.cleaned_data
for folio, image_link in cleaned_data.items():
if image_link != "":
source.chant_set.filter(folio=folio).update(image_link=image_link)
Loading

0 comments on commit ad6e3c8

Please sign in to comment.