I have a table of cells, each with a different object instance, and using htmx to update the objects. I've created a CBV that takes the request.post from the htmx and saves the modified object to the db. The htmx does a hx-swap and loads a new <input>
tag into my form, along with some background-color style based on the saved object. I can click on many cells and update several objects this way. This is working as expected and I don't see any errors.
However, the second time that I try to update the same cell/object, I get a 403 error. This error does not show in the browser. The cell just seems unresponsive. I can continue updating other cells. The error shows up in my browser console.
views
asessmentdetail - view that loads template and retrieves all the objects to fill in the table
def assessmentdetail(request, assess_pk, class_pk):
"""List the current grades for the assessment"""
user = request.user
assessment = Assessment.objects.get(id=assess_pk)
classblock = Classroom.objects.get(id=class_pk)
course_pk = classblock.course.pk
objective_list = Objective.objects.all().filter(
assessment=assess_pk).order_by('objective_name')
student_list = Student.objects.all().filter(
classroom=class_pk).order_by('nickname')
gra = Grade.objects.filter(
assessment=assess_pk).filter(cblock=classblock.id)
context = {'objective_list': objective_list}
new_or_update = gra.exists()
grade_report = [str(new_or_update)]
grade_list = []
grade_id_array = []
grade_report.append(len(objective_list))
# print(len(objective_list))
comment_list = []
n = 0
if gra:
for student in student_list:
# grade_report.append(student.nickname)
comms = AssessComment.objects.filter(
student=student, assessment=assessment).first()
if comms:
comment_list.append(comms)
else:
new_c = AssessComment(user=user,
student=student, assessment=assessment, comment="---")
new_c.save()
comment_list.append(new_c)
for obj in objective_list:
if gra.filter(objective=obj, student=student.id).last():
grade_report.append(q.score)
grade_id_array.append(q.id)
grade_list.append(q)
n = n 1
context['grade_report'] = grade_report
context['grade_list'] = grade_list
context['grade_id_array'] = grade_id_array
context['student_list'] = student_list
context['assessment'] = assessment
context['class_pk'] = class_pk
context['assess_pk'] = assess_pk
context['course_pk'] = course_pk
context['comment_list'] = comment_list
context['classblock'] = classblock
)
return render(request, "gradebook/assessment_detail.html", context)
GradeChange - the view that modifies the object via htmx
class GradeChange(SingleObjectMixin, View):
""" view to handle htmx grade change"""
model = Grade
def post(self, request, *args, **kwargs):
grade = self.get_object()
ns = request.POST.get('score')
new_score = ns.upper()
def get_color(grade): # map background color to the score
if grade == "EXT":
convert_code = "rgba(153,102,255,0.4)"
elif grade == "APP " or grade == "PRF ":
convert_code = "rgba(75, 192, 192, 0.7)"
elif grade == "APP" or grade == "PRF":
convert_code = "rgba(75, 192, 192, 0.3)"
elif grade == "DEV":
convert_code = "rgba(255, 205, 86, 0.4)"
elif grade == "EMG" or grade == "BEG":
convert_code = "rgba(225, 99, 132, 0.4)"
else:
convert_code = "rgba(0, 0, 0, 0.1)"
return (convert_code)
score_list = ["EXT", "APP ", "PRF ", "APP", "PRF", "DEV", "BEG", "EMG", "I", "---"]
if new_score in score_list:
grade.score = new_score
grade.save()
grade_score=str(grade.score)
bgcode = get_color(grade.score)
input_string=f'<input type="text" hx-post="{{% url "gradebook:grade-change" { grade.pk} %}}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" style="background-color:{ bgcode }" title="{ grade_score }" name="score" id="input-{{ forloop.counter0 }}" placeholder="{ grade.score }" required>'
else:
bgcode = get_color(grade.score)
input_string=f'<input type="text" hx-post="{{% url "gradebook:grade-change" { grade.pk} %}}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" style="background-color:{ bgcode }; border:solid rgb(255, 0, 0,.5);" title="xxx" name="score" id="input-{{ forloop.counter0 }}" placeholder="{ new_score }" required>'
return HttpResponse(input_string)
template
<div >
<div >
<div >
<h5>{{ assessment.assessment_name }}</h5>
</div>
<div >Class: {{ classblock }}, Assessment Date: {{ assessment.date_created|date:'Y-m-d' }}
</div>
<div id="edit">
<p>Click an item to edit.</p>
</div>
<div >
<a href="{% url 'gradebook:addassessment' course_pk %}"><button type="submit" >Return to Assessment List</button></a>
</div>
<hr/>
</div>
{% if objective_list %}
<div id="create">
<div >
<div>No grades entered yet.</div>
</div>
<div >
<a href="{% url 'gradebook:addgrades' assess_pk class_pk %}"><button >Add Grades</button></a>
</div>
</div>
<div id = "grade-table">
<table >
<thead>
<tr>
<th scope="col">Students</th>
{% for obj in objective_list %}
<th scope="col">{{ obj.objective_name }}</th>
{% endfor %}
<th scope="col">Comments</th>
</tr>
</thead>
<tbody>
<form action="" method="post" >
<div id="hxtarget">test</div>
{% for student in student_list %}
<tr>
<td >{{ student.student_first }} {{ student.student_last }}</td>
{% for g in grade_list %}
{% if g.student.id == student.id %}
<td>
<input type="text" hx-post="{% url 'gradebook:grade-change' g.pk %}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" title={{ g.score }} name="score" id="input-{{ forloop.counter0 }}" placeholder={{ g.score }} required>
</td>
{% endif %}
{% endfor %}
<td>
{% for comms in comment_list %}
{% if comms.student == student %}
<a href="{% url 'gradebook:addcomment' comms.pk assess_pk class_pk %}">{{ comms.comment|truncatewords:10 }}</a>
{% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</form>
</tbody>
</table>
</div>
{% else %}
<p>No objectives. Please add an objective to this assignment.</p>
{% endif %}
</div>
urls
path('assessmentdetail/<uuid:assess_pk>/<uuid:class_pk>/',
views.assessmentdetail, name='assessdetail'),
path('grade-change/<uuid:pk>/', views.GradeChange.as_view(), name='grade-change'),
The error I get in the console is:
Request URL: http://127.0.0.1:8000/gradebook/assessmentdetail/3f4c7422-89d0-442c-a310-46aff8123949/be8ef548-25a7-4793-99d1-ff3cc4166921/{% url
Request Method: POST
Status Code: 404 Not Found```
The url for this page in my browser is http://127.0.0.1:8000/gradebook/assessmentdetail/3f4c7422-89d0-442c-a310-46aff8123949/be8ef548-25a7-4793-99d1-ff3cc4166921/
. I don't know where the {% url
comes from. I don't understand htmx well enough to know why it's calling this url.
CodePudding user response:
The problem is that the response is not HTML but unrendered Django Template code, that's obviously not gonna work on the frontend. You must return pure HTML (or use a frontend template system, but it's not the topic of your question.)
The problematic variable is the input_string
:
input_string=f'<input type="text" hx-post="{{% url "gradebook:grade-change" { grade.pk} %}}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" style="background-color:{ bgcode }" title="{ grade_score }" name="score" id="input-{{ forloop.counter0 }}" placeholder="{ grade.score }" required>'
HTMX expects that this is valid HTML, so the hx-post
attribute will be just: hx-post="{{% url "
. That's because the Django Template's url
is not evaluated on the backend. So there's the origin of the strange {% url
string in your logs, the {
is the URL-encoded version of {
bracket.
The fix is very easy, just use the reverse()
method on the backend:
from django.urls import reverse
input_string=f'<input type="text" hx-post="{reverse("gradebook:grade-change", args=[grade.pk])}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" style="background-color:{ bgcode }" title="{ grade_score }" name="score" placeholder="{ grade.score }" required>'
Note: I have removed the id="input-{{ forloop.counter0 }}"
because it seems unnecessary and requires passing the loop counter from the frontend. It's possible of course with e.g. hx-vals
if you need it.