Django - htmx 的第二个触发器尝试加载意外的 URL 给出 403 错误

Django - second trigger of htmx tries to load an unexpected URL gives a 403 error

我有 table 个单元格,每个单元格都有不同的对象实例,并使用 htmx 更新对象。我创建了一个 CBV,它从 htmx 中获取 request.post 并将修改后的对象保存到数据库中。 htmx 执行 hx-swap 并将新的 <input> 标记加载到我的表单中,以及一些基于保存的对象的背景颜色样式。我可以点击许多单元格并以这种方式更新多个对象。这按预期工作,我没有看到任何错误。

但是,我第二次尝试更新同一个 cell/object 时,出现了 403 错误。此错误不会显示在浏览器中。细胞似乎没有反应。我可以继续更新其他单元格。该错误出现在我的浏览器控制台中。

观看次数

asessmentdetail - 加载模板并检索所有对象以填充 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 - 通过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" class="form-control score" 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" class="form-control score" 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)  

模板

<div class="container ps-4">
  <div class="row">
    <div class="col-2">
      <h5>{{ assessment.assessment_name }}</h5>
    </div>
    <div class="col-4">Class: {{ classblock }}, Assessment Date: {{ assessment.date_created|date:'Y-m-d' }}
    </div>
    <div class="col-2" id="edit">
      <p>Click an item to edit.</p>
    </div>
    <div class="col-4">
      <a href="{% url 'gradebook:addassessment' course_pk %}"><button type="submit" class="btn btn-secondary">Return to Assessment List</button></a>
    </div>
    <hr/>
  </div>
  {% if objective_list %}
  <div class="row" id="create">
    <div class="col-md-3">
      <div>No grades entered yet.</div>
    </div>
    <div class="col-md-3">
      <a href="{% url 'gradebook:addgrades' assess_pk class_pk %}"><button class="btn btn-primary">Add Grades</button></a>
    </div>
  </div>
  <div class="table-responsive" id = "grade-table">
    <table class="table table-bordered table-sm">
      <thead>
        <tr>
          <th class="col-3" scope="col">Students</th>
          {% for obj in objective_list %}
          <th class="col-2" scope="col">{{ obj.objective_name }}</th>
          {% endfor %}
          <th scope="col">Comments</th>
        </tr>
      </thead>
      <tbody>
        <form action="" method="post" class="form-group">
        <div id="hxtarget">test</div>
        {% for student in student_list %}
        <tr>
          <td >{{ student.student_first }}&nbsp;{{ 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" class="form-control score" 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 class="grade-comment" 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'),

我在控制台中得到的错误是:

Request URL: http://127.0.0.1:8000/gradebook/assessmentdetail/3f4c7422-89d0-442c-a310-46aff8123949/be8ef548-25a7-4793-99d1-ff3cc4166921/%7B%%20url
Request Method: POST
Status Code: 404 Not Found```

在我的浏览器中此页面的 url 是 http://127.0.0.1:8000/gradebook/assessmentdetail/3f4c7422-89d0-442c-a310-46aff8123949/be8ef548-25a7-4793-99d1-ff3cc4166921/。我不知道 %7B%%20url 来自哪里。我对 htmx 的理解不够深入,不知道为什么要这样调用 url.

问题是响应不是 HTML 而是未呈现的 Django 模板代码,这显然不能在前端运行。您必须 return 纯 HTML(或使用前端模板系统,但这不是您问题的主题。)

有问题的变量是 input_string:

input_string=f'<input type="text" hx-post="{{% url "gradebook:grade-change" { grade.pk} %}}" hx-swap="outerHTML" hx-trigger="keyup delay:2s" class="form-control score" style="background-color:{ bgcode }" title="{ grade_score }" name="score" id="input-{{ forloop.counter0 }}" placeholder="{ grade.score }" required>'

HTMX 认为这是有效的 HTML,因此 hx-post 属性将只是:hx-post="{{% url "。那是因为 Django 模板的 url 没有在后端进行评估。所以你的日志中有奇怪的 %7B%%20url 字符串的来源,%7B{ 括号的 URL-encoded 版本。

修复非常简单,只需要在后端使用reverse()方法:

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" class="form-control score" style="background-color:{ bgcode }" title="{ grade_score }" name="score" placeholder="{ grade.score }" required>'

注意:我已经删除了 id="input-{{ forloop.counter0 }}",因为它似乎没有必要并且需要从前端传递循环计数器。这当然是可能的,例如hx-vals如果你需要的话。