I have built a marking application which allows a marker to enter both a comment and a mark for a given element of a student’s work. There are 9 mark-comment pairs and I use a generic view and form to allow the marker to enter and save the comment and mark. The mark field in the form is a text field and I have a validation which checks if the value entered is between 0 and 100. This is to assist in the monitoring of the marking process. (Setting a default value of 0 requires the marking monitor to check if every 0 is a real 0 or a consequence of the work not yet being marked.)
I am using CrispyForms 2 and Bootstrap 5 to render the form on the page. The form validation process raises a ValidationError on the mark field if the entered mark is not between 0 and 100. My approach works as expected without AJAX.
To save a marker’s comments and mark to the database, if the marker forgets to press the “enter…” button on the view before navigating to another page, I have tried to use AJAX and “get”. The “get” is called when there is a change to the form. When using AJAX I see the behaviour outlined below.
Behaviour 1
1a - Enter an invalid mark and press the “enter …” button and an error message is generated on the mark field.
1b - Correct the invalid mark and exit the form, but do not use the “enter …” button but click at random on the page. As the form is changed AJAX does its stuff, the error flag is removed and the data are saved to database.
1c - Enter an invalid mark and click at random on the page. As the form is changed AJAX does its stuff and produces the error message seen in 1a.
This is the behaviour I want to see. If however, I do the following steps.
Behaviour 2
2a - Enter an invalid mark and click at random on the page. The mark field boundary is coloured red indicating the field is invalid but no error message text is attached to the invalid field.
2b - Enter an invalid mark and press the “enter …” button and an error message is generated on the mark field.
2c - Enter an invalid mark and click at random on the page. As the form is changed AJAX does its stuff and produces the error message seen in 2b.
I do not seem to be able to generate the error message under “get”, without first pressing the “enter…” button. I have tried appending an HTML tag similar to "<span>error text</span>"
to the <div>
that contains the mark field, but this results in the error message appearing twice.
Any thoughts would be greatly welcomed.
My view, form and AJAX call are below
class AjaxView(LoginRequiredMixin, GetMenuContextViewMixin, FormView):
template_name = "marking/ajaxpage.html"
model = StudentDetail
form_class = AjaxMarkingForm
def dispatch(self, request, *args, **kwargs):
"""Dispatch used to stop marker seeing students that are not on their list."""
s_id = self.kwargs["pk"]
stud = StudentDetail.objects.get(id=s_id)
marker = self.request.user.marker
allowed = stud.checkAllowed(marker)
context = {"mid": stud.pcode.id}
if not allowed:
return render(self.request, "general/403.html", context=context, status=403)
return super(AjaxView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
print(self, "from get", self.get_initial())
form = self.form_class
if (
self.request.headers.get("x-requested-with") == "XMLHttpRequest"
and self.request.method == "GET"
):
form = form(self.request.GET)
if form.is_valid():
# get the student id
s_id = self.kwargs["pk"]
# get the student object associated with the student id from database
stud = StudentDetail.objects.get(id=s_id)
# get the data from the form
text_in = form.cleaned_data["text"]
mark_in = form.cleaned_data["mark"]
text_f = self.kwargs["section"]
mark_f = text_f.replace("text", "mark")
setattr(stud, text_f, text_in)
setattr(stud, mark_f, mark_in)
stud.save()
return render(
request,
self.template_name,
{"form": form},
)
if not form.is_valid():
return JsonResponse(form.errors, status=400)
else:
return render(
request,
self.template_name,
{"form": form(self.get_initial())},
)
def get_initial(self, **kwargs):
initial = super(AjaxView, self).get_initial(**kwargs)
# get the student id
s_id = self.kwargs["pk"]
# get the student object associated with the student id from database
stud = StudentDetail.objects.get(id=s_id)
# get the text field name
text_f = self.kwargs["section"]
# make the associated mark field
mark_f = text_f.replace("text", "mark")
self.stud = stud
self.text_f = text_f
self.mark_f = mark_f
initial["text"] = getattr(stud, text_f)
initial["mark"] = getattr(stud, mark_f)
return initial
def get_context_data(self, **kwargs):
context = super(AjaxView, self).get_context_data(**kwargs)
return context
def form_valid(self, form):
stud = self.stud
stud.markCommSave(form, self)
return super(AjaxView, self).form_valid(form)
def get_success_url(self, **kwargs):
return reverse_lazy(
"project:ajaxview",
args=(self.kwargs["pk"], self.kwargs["section"]),
)
class AjaxMarkingForm(forms.Form):
def __init__(self, *args, **kwargs):
super(AjaxMarkingForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.fields["mark"].label = ""
self.fields["text"].label = False
mark = forms.CharField(max_length=3, required=True)
text = forms.CharField(
required=True,
widget=forms.Textarea(
attrs={
"rows": 4,
"cols": 60,
"placeholder": "Justification for mark",
"label": "",
}
),
)
def clean(self):
cleaned_data = super().clean()
markin = cleaned_data.get("mark")
# check mark is between 0 - 100
StudentDetail.checkMarkValue(markin, "mark", "form")
return cleaned_data
$("#rdform").on("change",function () {
var urlin = window.location.href;
var serializedData = $(this).serialize();
$.ajax({
type: 'GET',
url: urlin,
data: serializedData,
success: function(response) {
$("#id_mark").removeClass("is-invalid");
$("#id_text").removeClass("is-invalid");
},
error: function(response)
{
$("#id_mark").addClass("is-invalid");
},
});
})