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)
关键注意事项
- 数据结构一致性:如果你的前端实际传递的是嵌套在
content字段里的JSON(比如{"content": {...}}),需要同步修改视图代码(先取request.data.get("content"))和测试的training_json_body结构。 - DRF的request.data:
request.data是DRF封装的解析后数据,会自动处理JSON、表单等多种格式,比手动解析request.body更可靠。 - gspread异常处理:添加工作表不存在的捕获逻辑,避免程序直接崩溃。
- 测试简化:利用
APIClient的json参数自动处理序列化和请求头,不要手动重复操作。
按照这个示例调整后,应该就能解决你遇到的所有问题了。
内容的提问来源于stack exchange,提问作者Yannick
相关产品推荐
相关产品推荐

