From ff924651b5c54e78526f41df9796bdd8e0e0b60e Mon Sep 17 00:00:00 2001 From: Dylan Hillerbrand Date: Wed, 2 Oct 2024 11:22:24 -0400 Subject: [PATCH 1/8] feat(source model): add source_completeness and production_method fields Updates source create, source edit, and source detail pages. Adds command to populate the `source_completeness` field based on the current contents of the `full_source` field (which can be deleted after this field is populated). --- django/cantusdb_project/main_app/forms.py | 26 +++++------------ .../commands/populate_source_completeness.py | 22 ++++++++++++++ .../main_app/models/source.py | 24 ++++++++++++++- .../main_app/templates/source_create.html | 13 +++++---- .../main_app/templates/source_detail.html | 11 ++++--- .../main_app/templates/source_edit.html | 12 ++++---- .../test_populate_source_completeness.py | 29 +++++++++++++++++++ 7 files changed, 103 insertions(+), 34 deletions(-) create mode 100644 django/cantusdb_project/main_app/management/commands/populate_source_completeness.py create mode 100644 django/cantusdb_project/main_app/tests/test_populate_source_completeness.py diff --git a/django/cantusdb_project/main_app/forms.py b/django/cantusdb_project/main_app/forms.py index 4f65fa883..98ef482d3 100644 --- a/django/cantusdb_project/main_app/forms.py +++ b/django/cantusdb_project/main_app/forms.py @@ -212,6 +212,8 @@ class Meta: "fragmentarium_id", "dact_id", "indexing_notes", + "production_method", + "source_completeness", ] widgets = { # "title": TextInputWidget(), @@ -246,6 +248,8 @@ class Meta: "other_editors": autocomplete.ModelSelect2Multiple( url="all-users-autocomplete" ), + "production_method": SelectWidget(), + "source_completeness": SelectWidget(), } field_classes = { "segment_m2m": CheckboxNameModelMultipleChoiceField, @@ -262,15 +266,6 @@ class Meta: widget=TextInputWidget, ) - TRUE_FALSE_CHOICES_SOURCE = ( - (True, "Full source"), - (False, "Fragment or Fragmented"), - ) - - full_source = forms.ChoiceField(choices=TRUE_FALSE_CHOICES_SOURCE, required=False) - full_source.widget.attrs.update( - {"class": "form-control custom-select custom-select-sm"} - ) TRUE_FALSE_CHOICES_INVEN = ((True, "Complete"), (False, "Incomplete")) complete_inventory = forms.ChoiceField( @@ -413,6 +408,8 @@ class Meta: "full_text_entered_by", "proofreaders", "other_editors", + "production_method", + "source_completeness", ] widgets = { "segment_m2m": CheckboxSelectMultiple(), @@ -446,6 +443,8 @@ class Meta: "other_editors": autocomplete.ModelSelect2Multiple( url="all-users-autocomplete" ), + "production_method": SelectWidget(), + "source_completeness": SelectWidget(), } field_classes = { "segment_m2m": CheckboxNameModelMultipleChoiceField, @@ -462,15 +461,6 @@ class Meta: widget=autocomplete.ModelSelect2(url="holding-autocomplete"), ) - CHOICES_FULL_SOURCE = ( - (None, "None"), - (True, "Full source"), - (False, "Fragment or Fragmented"), - ) - full_source = forms.ChoiceField(choices=CHOICES_FULL_SOURCE, required=False) - full_source.widget.attrs.update( - {"class": "form-control custom-select custom-select-sm"} - ) CHOICES_CURSUS = ( (None, "None"), diff --git a/django/cantusdb_project/main_app/management/commands/populate_source_completeness.py b/django/cantusdb_project/main_app/management/commands/populate_source_completeness.py new file mode 100644 index 000000000..fe7d9e279 --- /dev/null +++ b/django/cantusdb_project/main_app/management/commands/populate_source_completeness.py @@ -0,0 +1,22 @@ +""" +A temporary command to populate the source_completeness field in the Source model, +based on the full_source field. This command will be removed once the source_completeness +is initially populated. +""" + +from django.core.management.base import BaseCommand +from main_app.models import Source + + +class Command(BaseCommand): + def handle(self, *args, **options): + sources = Source.objects.all() + for source in sources: + if source.full_source: + source.source_completeness = ( + source.SourceCompletenessChoices.FULL_SOURCE + ) + else: + source.source_completeness = source.SourceCompletenessChoices.FRAGMENT + source.save() + self.stdout.write(self.style.SUCCESS("Source completeness populated")) diff --git a/django/cantusdb_project/main_app/models/source.py b/django/cantusdb_project/main_app/models/source.py index 98caddc06..5b08f2082 100644 --- a/django/cantusdb_project/main_app/models/source.py +++ b/django/cantusdb_project/main_app/models/source.py @@ -71,6 +71,18 @@ class Source(BaseModel): null=True, help_text="More exact indication of the provenance (if necessary)", ) + + class SourceCompletenessChoices(models.IntegerChoices): + FULL_SOURCE = 1, "Full source" + FRAGMENT = 2, "Fragment/Fragmented" + RECONSTRUCTION = 3, "Reconstruction" + + source_completeness = models.IntegerField( + choices=SourceCompletenessChoices.choices, + default=SourceCompletenessChoices.FULL_SOURCE, + verbose_name="Full Source/Fragment", + ) + full_source = models.BooleanField(blank=True, null=True) date = models.CharField( blank=True, @@ -140,6 +152,16 @@ class Source(BaseModel): blank=False, null=False, default=False ) + class ProductionMethodChoices(models.IntegerChoices): + MANUSCRIPT = 1, "Manuscript" + PRINTED = 2, "Printed" + + production_method = models.IntegerField( + default=ProductionMethodChoices.MANUSCRIPT, + choices=ProductionMethodChoices.choices, + verbose_name="Manuscript/Printed", + ) + # number_of_chants and number_of_melodies are used for rendering the source-list page (perhaps among other places) # they are automatically recalculated in main_app.signals.update_source_chant_count and # main_app.signals.update_source_melody_count every time a chant or sequence is saved or deleted @@ -182,7 +204,7 @@ def short_heading(self) -> str: tt = self.shelfmark if self.shelfmark else self.title title.append(tt) - if not self.full_source: + if self.source_completeness == self.SourceCompletenessChoices.FRAGMENT: title.append("(fragment)") return " ".join(title) diff --git a/django/cantusdb_project/main_app/templates/source_create.html b/django/cantusdb_project/main_app/templates/source_create.html index 62a8d50a3..d15d5f81f 100644 --- a/django/cantusdb_project/main_app/templates/source_create.html +++ b/django/cantusdb_project/main_app/templates/source_create.html @@ -89,19 +89,20 @@

Create Source

- - {{ form.full_source }} -

{{ form.full_source.help_text }}

+ {{ form.source_completeness.label_tag }} + {{ form.source_completeness }}
-
+
{{ form.century.label_tag }} {{ form.century }}
+
+ {{ form.production_method.label_tag }} + {{ form.production_method }} +
diff --git a/django/cantusdb_project/main_app/templates/source_detail.html b/django/cantusdb_project/main_app/templates/source_detail.html index a7eab6478..f22603e44 100644 --- a/django/cantusdb_project/main_app/templates/source_detail.html +++ b/django/cantusdb_project/main_app/templates/source_detail.html @@ -43,6 +43,11 @@

{{ source.heading }}

{% endif %} + {% if source.production_method %} +
Manuscript/Printed
+
{{ source.get_production_method_display }}
+ {% endif %} + {% if source.summary %}
Summary
{{ source.summary }}
@@ -112,10 +117,8 @@

{{ source.heading }}

{{ source.complete_inventory|yesno:"Complete Inventory,Partial Inventory" }}
{% endif %} - {% if source.full_source is not None %} -
Full Source/Fragment
-
{{ source.full_source|yesno:"Full Source,Fragment or Fragmented" }}
- {% endif %} +
Full Source/Fragment
+
{{ source.get_source_completeness_display }}
{% if user.is_authenticated %}
Source Status
diff --git a/django/cantusdb_project/main_app/templates/source_edit.html b/django/cantusdb_project/main_app/templates/source_edit.html index 89d876b07..48a112d99 100644 --- a/django/cantusdb_project/main_app/templates/source_edit.html +++ b/django/cantusdb_project/main_app/templates/source_edit.html @@ -90,18 +90,20 @@

More exact indication of the provenance (if necessary)

- - {{ form.full_source }} + {{ form.source_completeness.label_tag }} + {{ form.source_completeness }}
-
+
{{ form.century.label_tag }} {{ form.century }}
+
+ {{ form.production_method.label_tag }} + {{ form.production_method }} +
diff --git a/django/cantusdb_project/main_app/tests/test_populate_source_completeness.py b/django/cantusdb_project/main_app/tests/test_populate_source_completeness.py new file mode 100644 index 000000000..065bbb2ea --- /dev/null +++ b/django/cantusdb_project/main_app/tests/test_populate_source_completeness.py @@ -0,0 +1,29 @@ +from django.test import TestCase +from django.core.management import call_command + +from main_app.models import Source +from main_app.tests.make_fakes import make_fake_source + + +class TestPopulateSourceCompleteness(TestCase): + def test_populate_source_completeness(self): + # make a few "Full Source" sources + for _ in range(5): + make_fake_source(full_source=True) + # make a few "Fragment" sources + for _ in range(3): + make_fake_source(full_source=False) + # run the command + call_command("populate_source_completeness") + sources = Source.objects.all() + for source in sources: + if source.full_source: + self.assertEqual( + source.source_completeness, + source.SourceCompletenessChoices.FULL_SOURCE, + ) + else: + self.assertEqual( + source.source_completeness, + source.SourceCompletenessChoices.FRAGMENT, + ) From 2a272c87883bdd310ee82e91ed9a8b80f87a7326 Mon Sep 17 00:00:00 2001 From: Dylan Hillerbrand Date: Wed, 2 Oct 2024 11:30:59 -0400 Subject: [PATCH 2/8] refactor(forms): create StyledChoiceField on forms Removes the need to duplicate process of using default SelectWidget on ChoiceFields and then manually changing attributes. --- django/cantusdb_project/main_app/forms.py | 29 ++++++++++------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/django/cantusdb_project/main_app/forms.py b/django/cantusdb_project/main_app/forms.py index 98ef482d3..1bd0760aa 100644 --- a/django/cantusdb_project/main_app/forms.py +++ b/django/cantusdb_project/main_app/forms.py @@ -71,6 +71,15 @@ def label_from_instance(self, obj): widget = CheckboxSelectMultiple() +class StyledChoiceField(forms.ChoiceField): + """ + A custom ChoiceField that uses the custom SelectWidget defined in widgets.py + as its widget (for styling). + """ + + widget = SelectWidget() + + class ChantCreateForm(forms.ModelForm): class Meta: model = Chant @@ -268,12 +277,9 @@ class Meta: TRUE_FALSE_CHOICES_INVEN = ((True, "Complete"), (False, "Incomplete")) - complete_inventory = forms.ChoiceField( + complete_inventory = StyledChoiceField( choices=TRUE_FALSE_CHOICES_INVEN, required=False ) - complete_inventory.widget.attrs.update( - {"class": "form-control custom-select custom-select-sm"} - ) class ChantEditForm(forms.ModelForm): @@ -416,6 +422,7 @@ class Meta: "provenance": autocomplete.ModelSelect2(url="provenance-autocomplete"), "provenance_notes": TextInputWidget(), "date": TextInputWidget(), + "cursus": SelectWidget(), "summary": TextAreaWidget(), "liturgical_occasions": TextAreaWidget(), "description": TextAreaWidget(), @@ -461,23 +468,11 @@ class Meta: widget=autocomplete.ModelSelect2(url="holding-autocomplete"), ) - - CHOICES_CURSUS = ( - (None, "None"), - ("Monastic", "Monastic"), - ("Secular", "Secular"), - ) - cursus = forms.ChoiceField(choices=CHOICES_CURSUS, required=False) - cursus.widget.attrs.update({"class": "form-control custom-select custom-select-sm"}) - CHOICES_COMPLETE_INV = ( (True, "complete inventory"), (False, "partial inventory"), ) - complete_inventory = forms.ChoiceField(choices=CHOICES_COMPLETE_INV, required=False) - complete_inventory.widget.attrs.update( - {"class": "form-control custom-select custom-select-sm"} - ) + complete_inventory = StyledChoiceField(choices=CHOICES_COMPLETE_INV, required=False) class SequenceEditForm(forms.ModelForm): From f646e77ed95f4e02070d12a18f475e0ac56a7c45 Mon Sep 17 00:00:00 2001 From: Dylan Hillerbrand Date: Wed, 2 Oct 2024 11:29:16 -0400 Subject: [PATCH 3/8] feat(source name): add source name to create, edit, and detail pages --- django/cantusdb_project/main_app/forms.py | 9 ++++----- django/cantusdb_project/main_app/models/source.py | 2 +- .../main_app/templates/source_create.html | 7 +++++++ .../main_app/templates/source_detail.html | 4 ++-- .../cantusdb_project/main_app/templates/source_edit.html | 7 +++++++ .../main_app/tests/test_views/test_source.py | 4 ++++ 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/django/cantusdb_project/main_app/forms.py b/django/cantusdb_project/main_app/forms.py index 1bd0760aa..5ae87697b 100644 --- a/django/cantusdb_project/main_app/forms.py +++ b/django/cantusdb_project/main_app/forms.py @@ -200,6 +200,7 @@ class Meta: # "siglum", "holding_institution", "shelfmark", + "name", "segment_m2m", "provenance", "provenance_notes", @@ -228,6 +229,7 @@ class Meta: # "title": TextInputWidget(), # "siglum": TextInputWidget(), "provenance": autocomplete.ModelSelect2(url="provenance-autocomplete"), + "name": TextInputWidget(), "provenance_notes": TextInputWidget(), "date": TextInputWidget(), "cursus": SelectWidget(), @@ -392,6 +394,7 @@ class Meta: # "siglum", "holding_institution", "shelfmark", + "name", "segment_m2m", "provenance", "provenance_notes", @@ -419,6 +422,7 @@ class Meta: ] widgets = { "segment_m2m": CheckboxSelectMultiple(), + "name": TextInputWidget(), "provenance": autocomplete.ModelSelect2(url="provenance-autocomplete"), "provenance_notes": TextInputWidget(), "date": TextInputWidget(), @@ -723,11 +727,6 @@ class Meta: widget=TextInputWidget, ) - name = forms.CharField( - required=False, - widget=TextInputWidget - ) - holding_institution = forms.ModelChoiceField( queryset=Institution.objects.all().order_by("name"), required=True, diff --git a/django/cantusdb_project/main_app/models/source.py b/django/cantusdb_project/main_app/models/source.py index 5b08f2082..18577238f 100644 --- a/django/cantusdb_project/main_app/models/source.py +++ b/django/cantusdb_project/main_app/models/source.py @@ -54,7 +54,7 @@ class Source(BaseModel): max_length=255, blank=True, null=True, - help_text="A colloquial or commonly-used name for the source" + help_text="A colloquial or commonly-used name for the source", ) provenance = models.ForeignKey( "Provenance", diff --git a/django/cantusdb_project/main_app/templates/source_create.html b/django/cantusdb_project/main_app/templates/source_create.html index d15d5f81f..57ce751e9 100644 --- a/django/cantusdb_project/main_app/templates/source_create.html +++ b/django/cantusdb_project/main_app/templates/source_create.html @@ -62,6 +62,13 @@

Create Source

{{ form.shelfmark }}

{{ form.shelfmark.help_text }} +

+ + {{ form.name }} +

+ {{ form.name.help_text|safe }}

diff --git a/django/cantusdb_project/main_app/templates/source_detail.html b/django/cantusdb_project/main_app/templates/source_detail.html index f22603e44..5e4f8e2e3 100644 --- a/django/cantusdb_project/main_app/templates/source_detail.html +++ b/django/cantusdb_project/main_app/templates/source_detail.html @@ -34,9 +34,9 @@

{{ source.heading }}

{% endif %}
+
Cantus Siglum
+
{{ source.short_heading }}{% if source.name %} "{{ source.name }}"{% endif %}
{% if source.holding_institution %} -
Cantus Siglum
-
{{ source.short_heading }}
Holding Institution
{{ source.holding_institution }} diff --git a/django/cantusdb_project/main_app/templates/source_edit.html b/django/cantusdb_project/main_app/templates/source_edit.html index 48a112d99..1cde438b5 100644 --- a/django/cantusdb_project/main_app/templates/source_edit.html +++ b/django/cantusdb_project/main_app/templates/source_edit.html @@ -64,6 +64,13 @@

{{ form.shelfmark }}

{{ form.shelfmark.help_text }} +

+ + {{ form.name }} +

+ {{ form.name.help_text|safe }}

diff --git a/django/cantusdb_project/main_app/tests/test_views/test_source.py b/django/cantusdb_project/main_app/tests/test_views/test_source.py index e7102d4ec..8022998dc 100644 --- a/django/cantusdb_project/main_app/tests/test_views/test_source.py +++ b/django/cantusdb_project/main_app/tests/test_views/test_source.py @@ -60,6 +60,8 @@ def test_create_source(self): { "shelfmark": "test-shelfmark", # shelfmark is a required field "holding_institution": hinst.id, # holding institution is a required field + "source_completeness": "1", # required field + "production_method": "1", # required field }, ) @@ -113,6 +115,8 @@ def test_edit_source(self): { "shelfmark": "test-shelfmark", # shelfmark is a required field, "holding_institution": hinst.id, # holding institution is a required field + "source_completeness": "1", # required field + "production_method": "1", # required field }, ) From d97563c2a9b0a6421781004a69a128da7573ab2f Mon Sep 17 00:00:00 2001 From: Dylan Hillerbrand Date: Wed, 2 Oct 2024 12:17:18 -0400 Subject: [PATCH 4/8] fix(source): make holding_institution optional for sources This commit removes the requirement for a source to have a holding_institution. Tests and forms are updated to account for this. The `migrate_records` command is updated so that sources with un-parseable sigla that are not otherwise accounted for (by virtue of being private collections or prints) do not have institution records made. Note: For the purposes of data migration, no constraint is added to sources to designate when no holding institution is necessary. After migration, these should be modified. Refs: #1631 --- django/cantusdb_project/main_app/forms.py | 25 +++++-------------- .../management/commands/migrate_records.py | 6 +++-- .../main_app/models/institution.py | 4 +-- .../main_app/models/source.py | 18 ++++++++----- .../main_app/templates/source_create.html | 11 ++++---- .../main_app/templates/source_edit.html | 12 +++++---- .../main_app/templates/source_list.html | 13 +++++++--- .../main_app/tests/make_fakes.py | 24 +++++++++++++++--- .../main_app/tests/test_views/test_source.py | 21 ++++++++++------ 9 files changed, 80 insertions(+), 54 deletions(-) diff --git a/django/cantusdb_project/main_app/forms.py b/django/cantusdb_project/main_app/forms.py index 5ae87697b..282622240 100644 --- a/django/cantusdb_project/main_app/forms.py +++ b/django/cantusdb_project/main_app/forms.py @@ -228,6 +228,7 @@ class Meta: widgets = { # "title": TextInputWidget(), # "siglum": TextInputWidget(), + "shelfmark": TextInputWidget(), "provenance": autocomplete.ModelSelect2(url="provenance-autocomplete"), "name": TextInputWidget(), "provenance_notes": TextInputWidget(), @@ -268,13 +269,8 @@ class Meta: holding_institution = forms.ModelChoiceField( queryset=Institution.objects.all(), - required=True, widget=autocomplete.ModelSelect2(url="holding-autocomplete"), - ) - - shelfmark = forms.CharField( - required=True, - widget=TextInputWidget, + required=False, ) TRUE_FALSE_CHOICES_INVEN = ((True, "Complete"), (False, "Incomplete")) @@ -421,6 +417,7 @@ class Meta: "source_completeness", ] widgets = { + "shelfmark": TextInputWidget(), "segment_m2m": CheckboxSelectMultiple(), "name": TextInputWidget(), "provenance": autocomplete.ModelSelect2(url="provenance-autocomplete"), @@ -461,15 +458,10 @@ class Meta: "segment_m2m": CheckboxNameModelMultipleChoiceField, } - shelfmark = forms.CharField( - required=True, - widget=TextInputWidget, - ) - holding_institution = forms.ModelChoiceField( queryset=Institution.objects.all(), - required=True, widget=autocomplete.ModelSelect2(url="holding-autocomplete"), + required=False, ) CHOICES_COMPLETE_INV = ( @@ -722,14 +714,9 @@ class Meta: # help_text="RISM-style siglum + Shelf-mark (e.g. GB-Ob 202).", # ) - shelfmark = forms.CharField( - required=True, - widget=TextInputWidget, - ) - holding_institution = forms.ModelChoiceField( - queryset=Institution.objects.all().order_by("name"), - required=True, + queryset=Institution.objects.all().order_by("siglum"), + required=False, ) provenance = forms.ModelChoiceField( diff --git a/django/cantusdb_project/main_app/management/commands/migrate_records.py b/django/cantusdb_project/main_app/management/commands/migrate_records.py index bfda4e0a6..bc3f26325 100644 --- a/django/cantusdb_project/main_app/management/commands/migrate_records.py +++ b/django/cantusdb_project/main_app/management/commands/migrate_records.py @@ -163,7 +163,7 @@ def handle(self, *args, **options): ) ) institution = print_inst - elif siglum in created_institutions: + elif siglum in created_institutions and source.id not in bad_siglum: print( self.style.SUCCESS( f"Re-using the pre-created institution for {siglum}" @@ -185,7 +185,7 @@ def handle(self, *args, **options): institution.alternate_names = "\n".join(list(deduped_names)) institution.save() - elif siglum not in created_institutions: + elif siglum not in created_institutions and source.id not in bad_siglum: print(self.style.SUCCESS(f"Creating institution record for {siglum}")) iobj = { @@ -229,6 +229,8 @@ def handle(self, *args, **options): created_institutions[siglum] = institution else: + source.shelfmark = shelfmark.strip() + source.save() print( self.style.ERROR( f"Could not determine the holding institution for {source}" diff --git a/django/cantusdb_project/main_app/models/institution.py b/django/cantusdb_project/main_app/models/institution.py index e38450d90..a5d966f1b 100644 --- a/django/cantusdb_project/main_app/models/institution.py +++ b/django/cantusdb_project/main_app/models/institution.py @@ -28,7 +28,7 @@ class Meta: ), ] - name = models.CharField(max_length=255, default="s.n.") + name = models.CharField(max_length=255, default="[No Name]") siglum = models.CharField( verbose_name="RISM Siglum", max_length=32, @@ -50,7 +50,7 @@ class Meta: region = models.CharField( max_length=64, blank=True, null=True, help_text=region_help_text ) - country = models.CharField(max_length=64, default="s.l.") + country = models.CharField(max_length=64, default="[No Country]") alternate_names = models.TextField( blank=True, null=True, help_text="Enter alternate names on separate lines." ) diff --git a/django/cantusdb_project/main_app/models/source.py b/django/cantusdb_project/main_app/models/source.py index 18577238f..037e16769 100644 --- a/django/cantusdb_project/main_app/models/source.py +++ b/django/cantusdb_project/main_app/models/source.py @@ -42,13 +42,18 @@ class Source(BaseModel): holding_institution = models.ForeignKey( "Institution", on_delete=models.PROTECT, - null=False, - blank=False, + null=True, + blank=True, ) shelfmark = models.CharField( max_length=255, blank=False, null=False, + help_text=( + "Primary Cantus Database identifier for the source " + "(e.g. library shelfmark, DACT ID, etc.)" + ), + default="[No Shelfmark]", ) name = models.CharField( max_length=255, @@ -186,9 +191,10 @@ def heading(self) -> str: title.append(city) title.append(f"{holdinst.name},") - tt = self.shelfmark if self.shelfmark else self.title + title.append(self.shelfmark) - title.append(tt) + if self.name: + title.append(f"({self.name})") return " ".join(title) @@ -198,8 +204,8 @@ def short_heading(self) -> str: if holdinst := self.holding_institution: if holdinst.siglum and holdinst.siglum != "XX-NN": title.append(f"{holdinst.siglum}") - elif holdinst.is_private_collector: - title.append("Cantus") + else: + title.append("Cantus") tt = self.shelfmark if self.shelfmark else self.title title.append(tt) diff --git a/django/cantusdb_project/main_app/templates/source_create.html b/django/cantusdb_project/main_app/templates/source_create.html index 57ce751e9..395a373fa 100644 --- a/django/cantusdb_project/main_app/templates/source_create.html +++ b/django/cantusdb_project/main_app/templates/source_create.html @@ -46,12 +46,9 @@

Create Source

{{ form.holding_institution }} -

- {{ form.holding_institution.help_text }} -

@@ -60,8 +57,10 @@

Create Source

Shelfmark:* {{ form.shelfmark }} -

- {{ form.shelfmark.help_text }} +

+ {{ form.shelfmark.help_text|safe }} +

+