diff --git a/django/cantusdb_project/main_app/forms.py b/django/cantusdb_project/main_app/forms.py index 1279c5247..253ab3d02 100644 --- a/django/cantusdb_project/main_app/forms.py +++ b/django/cantusdb_project/main_app/forms.py @@ -170,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 @@ -239,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): diff --git a/django/cantusdb_project/main_app/views/chant.py b/django/cantusdb_project/main_app/views/chant.py index 986c91452..f0f373301 100644 --- a/django/cantusdb_project/main_app/views/chant.py +++ b/django/cantusdb_project/main_app/views/chant.py @@ -880,8 +880,16 @@ def test_func(self): return user_can_edit_chants_in_source(user, self.source) - # if success_url and get_success_url not specified, will direct to chant detail page def get_success_url(self): + """ + Get the incipit of the created chant (generated by a signal) + and display a success message. + """ + self.object.refresh_from_db() + messages.success( + self.request, + "Chant '" + self.object.incipit + "' created successfully!", + ) return reverse("chant-create", args=[self.source.id]) def get_initial(self): @@ -960,53 +968,24 @@ def get_context_data(self, **kwargs: Any) -> dict[Any, Any]: context["suggested_chants"] = suggested_chants return context - def form_valid(self, form): + def get_form_kwargs(self): """ - Validates the new chant. - - Custom validation steps are: - - Check if a chant with the same sequence and folio already exists in the source. - - Compute the chant incipit. - - Adds the "created_by" and "updated_by" fields to the chant. + In the case of a submitted form (there is data in the request), + we copy the data dictionary and add the source id to it. """ - # compute source - form.instance.source = self.source - - # compute incipit, within 30 charactors, keep words complete - words = form.instance.manuscript_full_text_std_spelling.split(" ") - incipit = "" - for word in words: - new_incipit = incipit + word + " " - if len(new_incipit) >= 30: - break - incipit = new_incipit - - form.instance.incipit = incipit.strip(" ") - - # if a chant with the same sequence and folio already exists in the source - if ( - Chant.objects.all() - .filter( - source=self.source, - folio=form.instance.folio, - c_sequence=form.instance.c_sequence, - ) - .exists() - ): - form.add_error( - None, - "Chant with the same sequence and folio already exists in this source.", - ) + kwargs = super().get_form_kwargs() + if "data" in kwargs: + kwargs["data"] = kwargs["data"].copy() + kwargs["data"]["source"] = self.source.id + return kwargs - if form.is_valid(): - form.instance.created_by = self.request.user - form.instance.last_updated_by = self.request.user - messages.success( - self.request, - "Chant '" + form.instance.incipit + "' created successfully!", - ) - return super().form_valid(form) - return super().form_invalid(form) + def form_valid(self, form): + """ + Adds the "created_by" and "updated_by" fields to the chant. + """ + form.instance.created_by = self.request.user + form.instance.last_updated_by = self.request.user + return super().form_valid(form) class ChantDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):