You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django REST Framework接收POST JSON及Gspread写入问题排查

问题分析与解决方案

先帮你拆解两个核心问题的根源,再给你一套完整可运行的实现方案:


第一个问题:JSONDecodeError 报错

你测试代码里的写法犯了一个DRF测试的常见误区:当使用APIClient.post()时,传入json参数不需要手动用json.dumps()序列化,也不用手动设置Content-Type头。Django的测试客户端会自动帮你完成序列化和请求头设置。

你原来的测试代码:

response = self.client.post('/trainings/', json=json.dumps(self.training_json_body), headers=headers, format="json")

这里的问题是,你把已经序列化的JSON字符串再次传给json参数,导致客户端对它做了二次转义,最终发送的是一个不合法的JSON格式数据,直接触发了解析错误。

修正后的测试代码:

response = self.client.post('/trainings/', json=self.training_json_body)

直接把Python字典传给json参数即可,其他冗余的json.dumps、headers和format参数都可以删掉。


第二个问题:request.data.get() 返回 None

这个问题本质还是请求数据的传递格式不匹配。结合你的代码来看,要么是测试请求的结构和视图预期不一致,要么是视图没有正确解析数据。

比如你第一个版本的视图里试图取received_json_data['content'],说明你可能原本期望前端传递的JSON是嵌套在content字段里的,但你的测试training_json_body是平级字段结构,两者不匹配;后来改成平级取字段,但测试请求的写法错误导致数据没传对。


完整可运行示例

下面是整合了DRF、gspread的完整实现,包含视图、测试和异常处理:

1. views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import gspread
from oauth2client.service_account import ServiceAccountCredentials

# 初始化gspread连接(建议把这部分抽离到单独的工具类/settings中)
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds = ServiceAccountCredentials.from_json_keyfile_name('你的服务账号密钥文件.json', scope)
client = gspread.authorize(creds)
spreadsheet = client.open('你的谷歌表格名称')  # 替换成你的表格名

class TrainingList(APIView):
    def post(self, request, format=None):
        # 先校验必填字段是否齐全
        required_fields = [
            "date", "days", "firstname", "lastname", 
            "team", "training", "company", "city", 
            "cost", "invoice", "info"
        ]
        missing_fields = [field for field in required_fields if field not in request.data]
        
        if missing_fields:
            return Response(
                {"error": f"缺少必填字段: {', '.join(missing_fields)}"},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 获取指定工作表(默认用'Data')
        sheet_name = request.GET.get('sheet', 'Data')
        try:
            worksheet = spreadsheet.worksheet(sheet_name)
        except gspread.WorksheetNotFound:
            return Response(
                {"error": f"工作表 {sheet_name} 不存在"},
                status=status.HTTP_404_NOT_FOUND
            )
        
        # 组装要插入的行数据
        row_data = [
            request.data["date"],
            request.data["days"],
            request.data["firstname"],
            request.data["lastname"],
            request.data["team"],
            request.data["training"],
            request.data["company"],
            request.data["city"],
            request.data["cost"],
            request.data["invoice"],
            request.data["info"]
        ]
        
        # 插入到表格末尾(如果要插入到指定位置,用worksheet.insert_row(row_data, 行号))
        worksheet.append_row(row_data)
        
        return Response(request.data, status=status.HTTP_201_CREATED)

2. tests.py

from django.test import TestCase
from rest_framework import status
from rest_framework.test import APIClient

class TrainingAPITest(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.training_json_body = {
            "date": "1/1/2018",
            "days": 1,
            "firstname": "Data",
            "lastname": "Data",
            "team": "Data",
            "training": "Data",
            "company": "Data",
            "city": "Data",
            "cost": 1,
            "invoice": "Data",
            "info": "Data"
        }
    
    def test_post_training_returns_201_when_valid(self):
        """合法格式的POST请求应返回201并插入数据"""
        response = self.client.post('/trainings/', json=self.training_json_body)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data, self.training_json_body)
    
    def test_post_training_returns_400_when_missing_fields(self):
        """缺少必填字段的POST请求应返回400"""
        incomplete_body = self.training_json_body.copy()
        del incomplete_body["date"]
        response = self.client.post('/trainings/', json=incomplete_body)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

关键注意事项

  1. 数据结构一致性:如果你的前端实际传递的是嵌套在content字段里的JSON(比如{"content": {...}}),需要同步修改视图代码(先取request.data.get("content"))和测试的training_json_body结构。
  2. DRF的request.data:request.data是DRF封装的解析后数据,会自动处理JSON、表单等多种格式,比手动解析request.body更可靠。
  3. gspread异常处理:添加工作表不存在的捕获逻辑,避免程序直接崩溃。
  4. 测试简化:利用APIClient的json参数自动处理序列化和请求头,不要手动重复操作。

按照这个示例调整后,应该就能解决你遇到的所有问题了。

内容的提问来源于stack exchange,提问作者Yannick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:23:46