Django REST API集成DataTables Editor创建功能报400错误求助
问题描述
在Django REST API中集成DataTables Editor时,数据表格可正常显示,但点击创建按钮返回"POST /dashboard/api/investigator/ HTTP/1.1" 400 160错误。400状态码表示请求无效,需排查错误原因,同时说明返回信息中160的含义。
相关代码与请求信息
urls.py
from django.urls import path, include from . import views from rest_framework import routers from .views import StudyList, StudyDetail, InvestigatorView app_name = 'dashboard' router = routers.DefaultRouter() router.register('study', views.StudyView) router.register('investigator', views.InvestigatorView) router.register('lead', views.LeadView) router.register('institution', views.InstitutionView) router.register('site', views.SiteView) investigator_list = InvestigatorView.as_view({ 'get': 'list', 'post': 'create' }) investigator_detail = InvestigatorView.as_view({ 'get': 'retrieve', 'PUT': 'update', 'patch': 'partial_update', 'delete': 'destroy' }) urlpatterns = [ path('', views.datatables, name='datatable'), path('investigator/', views.datatables_investigator, name='datatables_investigator'), path('api/', include(router.urls)), path('api/investigator/', investigator_list, name='InvestigatorList'), path('api/investigator/<int:pk>', investigator_detail, name='InvestigatorDetail'), path('api/study/', StudyList.as_view(), name='StudyList'), path('api/study/<int:pk>/', StudyDetail.as_view(), name='StudyDetail') ]
datatables_investigator.html
{% extends 'base_datatables_editor.html' %} {% block content %} <h1 style="text-align: center; padding-top: 1em; padding-bottom: 1em;">Investigators</h1> <div class="row"> <div class="col-sm-12"> <table id="InvestigatorDataTable" class="table table-striped table-bordered" style="width:100%"> <thead> <tr> <th scope="col">First Name</th> <th scope="col">Last Name</th> <th scope="col">Name</th> <th scope="col">Email</th> <th scope="col">Institution</th> </tr> </thead> </table> </div> </div> {% endblock content %} {% block extra_js %} <script> var editor; $(document).ready(function() { editor = new $.fn.dataTable.Editor( { ajax: { create: { type: "POST", url: 'http://127.0.0.1:8000/dashboard/api/investigator/' contentType: 'application/json', data: function ( d ) { return JSON.stringify( d ); }, edit: { type: "PUT", url: "http://127.0.0.1:8000/dashboard/api/investigator/_id_" }, remove: { type: "DELETE", url: "http://127.0.0.1:8000/dashboard/api/investigator/_id_" }}, table: '#InvestigatorDataTable', data: "symbol", defaultContent: "", contentType: 'application/json', csrfmiddlewaretoken: window.CSRF_TOKEN, table: '#InvestigatorDataTable', idSrc: "id", dataSrc: "data", fields: [ { label: "ID:", name: "id" }, { label: "Last Name:", name: "last_name" }, { label: "First Name:", name: "first_name" }, { label: "Email:", name: "email" }, { label: "Institution:", name: "institution", ///type: "select", ///placeholder: "Select an institution" }, { label: "Created At:", name: "created_at", type: "datetime", def: function () { return new Date(); }, displayFormat: 'YYYY-MM-DD HH:mm:ss.SSSS' ///2022-05-19T17:59:46.167754Z }] }); $('#InvestigatorDataTable').DataTable({ serverSide: true, ajax: 'http://127.0.0.1:8000/dashboard/api/investigator/?format=datatables', dom: 'Bfrtrip', columns: [ {data: "last_name", }, {data: "first_name",}, {data: null, render: function ( data, type, row ) { return data.first_name + " " + data.last_name }}, {data: "email", orderable: false}, {data: "institution.name", name: "institution.name", orderable: true}, ], columnDefs: [ { targets: [2, 3, 4,], visible: true}, { targets: '_all', visible: false } ], select: true, buttons: [ { extend: "create", editor: editor }, { extend: "edit", editor: editor }, { extend: "remove", editor: editor } ] }); }); </script> {% endblock %}
serializers.py
from rest_framework import serializers from dashboard.models import Study, Investigator, Lead, Institution, Site class InstitutionSerializer(serializers.ModelSerializer): class Meta: model = Institution fields = ('name', 'dept') class InvestigatorSerializer(WritableNestedModelSerializer, serializers.ModelSerializer): institution = InstitutionSerializer class Meta: model = Investigator fields = ('__all__') depth = 1 datatables_always_serialize = ('id',)
views.py
from django.shortcuts import render, redirect from .models import Study, Investigator, Lead, Institution, Site from .forms import StudyForm from django.views.decorators.csrf import csrf_exempt from rest_framework import viewsets, generics from rest_framework.permissions import AllowAny from .serializers import StudySerializer, InvestigatorSerializer, LeadSerializer, InstitutionSerializer, SiteSerializer # Datatable Views @csrf_exempt#@csrf_protect#@ensure_csrf_cookie def datatables(request): return render(request, 'datatables.html') def datatables_investigator(request): return render(request, 'datatables_investigator.html') #API Views class InvestigatorView(viewsets.ModelViewSet): queryset = Investigator.objects.all() serializer_class = InvestigatorSerializer permission_classes = [AllowAny] class InstitutionView(viewsets.ModelViewSet): queryset = Institution.objects.all() serializer_class = InstitutionSerializer
models.py
class Investigator(models.Model): id = models.AutoField(unique=True, primary_key=True) last_name = models.CharField(max_length=50) first_name = models.CharField(max_length=50) email = models.CharField(max_length=50) created_at = models.DateTimeField(auto_now_add=True) institution = models.ForeignKey('Institution', models.DO_NOTHING, ) class Meta: managed = True db_table = 'investigator' def __str__(self): return str(self.last_name)
发送的请求内容
{"data":{"0":{"last_name":"test2","first_name":"test2","email":"test2","institution":"1"}},"action":"create"}
问题解析与修复方案
关于返回信息中的160
160是响应内容的字节大小,不是错误码,仅表示后端返回的错误提示内容长度,无需关注这个数字,重点排查400状态码对应的具体请求无效原因。
核心错误点与修复
Serializer嵌套配置错误
InvestigatorSerializer中institution = InstitutionSerializer缺少括号,应为institution = InstitutionSerializer();且当前配置为嵌套序列化,后端会期望接收Institution的完整字段(name、dept),但前端仅传递了机构ID,导致序列化失败。
修复方案:区分读写逻辑,查询时返回嵌套数据,创建/更新时接收机构ID:class InvestigatorSerializer(serializers.ModelSerializer): class Meta: model = Investigator fields = '__all__' datatables_always_serialize = ('id',) def to_representation(self, instance): # 查询时返回嵌套机构信息 representation = super().to_representation(instance) representation['institution'] = InstitutionSerializer(instance.institution).data return representationDataTables Editor请求格式与Django REST不兼容
DataTables Editor默认发送的请求包含外层data和action结构,但Django REST ModelViewSet期望直接接收模型字段的JSON对象。
修复方案:前端调整请求数据格式:create: { type: "POST", url: '/dashboard/api/investigator/', headers: { 'X-CSRFToken': window.CSRF_TOKEN }, contentType: 'application/json', data: function (d) { // 提取有效数据并转换机构ID为数字 let payload = Object.values(d.data)[0]; payload.institution = parseInt(payload.institution); return JSON.stringify(payload); }, success: function(json) { // 适配Editor的返回格式要求 return { data: [json] }; } }URL路由冲突
同时注册了router的investigator路由和手动定义的investigator_list,导致路由匹配混乱。ModelViewSet已包含所有CRUD动作,无需重复定义。
修复方案:删除手动定义的investigator_list和investigator_detail,保留router路由:urlpatterns = [ path('', views.datatables, name='datatable'), path('investigator/', views.datatables_investigator, name='datatables_investigator'), path('api/', include(router.urls)), path('api/study/', StudyList.as_view(), name='StudyList'), path('api/study/<int:pk>/', StudyDetail.as_view(), name='StudyDetail') ]不必要字段提交
前端提交了id和created_at字段,但id为AutoField由后端自动生成,created_at为auto_now_add=True无需前端传递,会导致序列化错误。
修复方案:在Editor的fields配置中移除这两个字段。CSRF令牌处理错误
前端未将CSRF令牌放入请求头,Django默认会校验CSRF令牌导致请求被拒绝。
修复方案:在ajax请求中添加X-CSRFToken请求头(如上述前端代码示例)。
内容的提问来源于stack exchange,提问作者Hamza Jamal

