django-rest-framework "This field is required" 在 POST
django-rest-framework "This field is required" on POST
每当我 POST 我的 django-rest-framework (DRF) 端点时,我都会收到 "HTTP 400 Bad Request" {"offeror_organization":["This field is required."]}
响应。但是,鉴于下面的 curl 示例,我明确指定了一个值。
无论内容类型如何(application/json、application/x-www-form-urlencoded、multipart/form-data)都会发生这种情况。它唯一有效的时间是当我使用 DRF Web 界面上的“HTML 表单”(相对于“原始数据”)选项卡提交时。
有一些类似的 SO 帖子(例如 this and ),但是 none 的解决方案似乎对我有用。
型号:
class OrganizationManager(models.Manager):
def get_by_natural_key(self, offeror_organization):
return self.get(offeror_organization=offeror_organization)
class Organization(models.Model):
idorganization = models.AutoField(primary_key=True)
offeror_organization = models.CharField(max_length=250, null=False, blank=False, verbose_name='Offeror Organization')
created_at = models.DateTimeField(auto_now_add=True, null=False)
updated_at = models.DateTimeField(auto_now=True, null=False)
objects = OrganizationManager()
def natural_key(self):
return "%s" % (self.offeror_organization)
def __str__(self):
return self.offeror_organization
序列化器:
class OrganizationSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Organization
fields = ['offeror_organization']
# I've tried both with and without a create function
def create(self, validated_data):
organization_data = validated_data.pop('offeror_organization', None)
if organization_data:
organization = Organization.objects.get_or_create(**organization_data)[0]
validated_data['offeror_organization'] = organization
views/api.py:
from webapp.models import Organization
from webapp.serializers import OrganizationSerializer
from rest_framework import viewsets
class OrganizationViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all().order_by('offeror_organization')
serializer_class = OrganizationSerializer
urls.py:
from django.urls import include, path
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'organization', views.OrganizationViewSet)
urlpatterns = [
...
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
卷曲命令:
curl -X POST -H 'Content-Type: application/json' -d '{"offeror_organization":"Test2"}' 10.101.10.228:29000/webapp/api/organization/
settings.py 中间件:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'csp.middleware.CSPMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware'
]
settings.py REST_FRAMEWORK
# currently have all API authentication disabled while troubleshooting this issue
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [],
'DEFAULT_PERMISSION_CLASSES': [],
}
就我而言,解决此问题需要围绕几个不同的实施约束进行“操作”。
nginx + uWSGI 套接字 + Django REMOTE_USER 身份验证:
正如这个 post 的 comments/chat 中提到的,我在我的 Django 应用程序前面有一个 nginx 代理和一个 uWSGI 应用程序服务器。由于我依赖 REMOTE_USER Authentication,我的 uwsgi/nginx 配置 必须 使用 uWSGI 套接字(相对于 http),这样我就可以通过 REMOTE_USER nginx 到 Django 作为环境变量。使用 http(与 nginx proxy_pass 耦合)时,虽然 proxy_pass 可以设置 headers 或 cookie,但这些似乎无法转换为 Django(需要环境变量)。
我认为在尝试 POST 到使用 uWSGI 套接字服务的 Django/DRF 应用程序时存在一些问题。根据 uWSGI Things to know (best practices)、"TL/DR:如果你打算将 uWSGI 直接暴露给 public,如果你想在网络服务器后面代理它,请使用 --http使用后端的 http,使用 --http-socket". 在我的例子中,同时拥有一个网络应用程序和一个 DRF-based API (我希望其他服务和系统交谈),我都需要!作为(希望是临时的)解决方法,我目前正在生成 两个 uWSGI 进程——一个使用 --socket
,一个使用 --http
(对于 API POST 调用)。如果您 POST 在使用 ---socket 时,您可能会收到来自 DRF 的空响应错误。
顺便说一句,我最初看到了在 uWSGI 套接字上利用 uwsgi_curl(从 uwsgi_tools)到 POST 的一些“承诺”(这导致了“field is required”错误(相对于 Empty Response 错误),但那时我开始 运行 我的第二期...
POST 嵌套 application/json w/同步文件上传: post 中引用的“组织”模型主要是 proof-of-concept,因为它是我的 Django 应用程序中最不复杂的模型。实际上,我需要 post 到具有嵌套序列化的更复杂的模型,因为该模型包含其他模型的外键。但这完全是 do-able 与 DRF。 Except 在我的例子中,我的模型属性之一是 FileUpload 字段。正如其他 SO 问题(如 this 一个)所述,尝试 POST 嵌套(即不是“扁平”) [=49] 也存在一些问题=] 在单个请求中上传文件。虽然我一直无法完全理解问题所在(至少在我的案例中使用 drf_writable_nested.serializers.WritableNestedModelSerializer
),但我通过编写自己的自定义序列化程序 (serializers.Serializer) 简化了问题 at-hand,这我可以避免嵌套 JSON objects(比如 { "offeror_organization": {"offeror_organization: "Test"}} 在我的 POST 请求中。这已修复我的问题。
使用自定义序列化程序来缓解嵌套 JSON + 文件上传问题,我敢打赌 uwsgi_curl POST 会起作用。尽管外部客户端 systems/services 仅限于使用该 Python 包。无论如何,我会在尝试后更新我的答案。感谢@Michael 的评论并帮助我走上正确的“道路”。
我有相同的设置(nginx + gunicorn + django + rest-framework + drf-writeable-nested)但我可以找出包含 multipart/form-data
的 POST 请求的有效格式:
需要这样:
json:
{
'name': 'test-name',
'files': [
{
'file': 'test-file-1'
},
{
'file': 'test-file-2'
},
...
],
...
}
必须格式化为:
表格数据:
name: test-name
files[0]file: test-file-1
files[1]file: test-file-2
...
有些库会在嵌套列表的括号后使用点,这会导致 This field is required
错误。甚至列表括号之后的另一个括号也会导致相同的错误。
这是错误的:
files[0].file
这也是错误的:
files[0][file]
我的示例假定以下 Django-类:
# views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.parsers import MultiPartParser, FormParser
from .serializers import YourCustomModelSerializer
class YourCustomModelViewSet(ModelViewSet):
queryset = YourCustomModel.objects.all()
parser_classes = [FormParser, MultiPartParser]
permission_classes = []
serializer_class = YourCustomModelSerializer
# serializers.py
from rest_framework.serializers import ModelSerializer
from drf_writable_nested.serializers import WritableNestedModelSerializer
from .models import YourCustomModel, File
class FileSerializer(ModelSerializer):
class Meta:
model = File
fields = ['file']
class YourCustomModelSerializer(WritableNestedModelSerializer):
# Reverse foreign key
files = FileSerializer(many=True)
class Meta:
model = YourCustomModel
read_only_fields = ('id', )
fields = [
'id',
'name',
'files'
]
# models.py
from django.db import models
class File(models.Model):
file = models.FileField()
class YourCustomModel(models.Model):
name = models.CharField(max_length=200)
我使用以下 javascript/typescript 前端代码将我的 json 数据打包到 FormData 请求中:
const requestBody = {
name: 'test-name',
files: [
{ file: file1 }, // File object
{ file: file2 }, // File object
{ file: file3 } // File object
]
}
// # use your own code to serialize the above javascript dict/json/object
// into form-data object (I used the library https://www.npmjs.com/package/object-to-formdata but I had to write a patch for the exact reason described above: files[0].file is not correctly parsed by Django and files[0][file] also won't work, therefore I slightly changed the library code so that it will format my object to files[0]file for every nested item:
// 2. prepare a serializer-library to convert the dict into a special form which can be parsed by django.
const options = {
indices: true,
allowEmptyArrays: true,
useDotOrBracketsOrNothingForNestedObjects: 2 // Option 0: DOT-Notation, 1: Brackets, 2: Nothing (this option is from my custom patch)
}
// use npx patch:
// (I patched this serialize library and the patch is somewhere stored as a file in this project)
const formData = serialize(
requestBody,
options
)
// 3. upload the data
api.post(url, formData)
每当我 POST 我的 django-rest-framework (DRF) 端点时,我都会收到 "HTTP 400 Bad Request" {"offeror_organization":["This field is required."]}
响应。但是,鉴于下面的 curl 示例,我明确指定了一个值。
无论内容类型如何(application/json、application/x-www-form-urlencoded、multipart/form-data)都会发生这种情况。它唯一有效的时间是当我使用 DRF Web 界面上的“HTML 表单”(相对于“原始数据”)选项卡提交时。
有一些类似的 SO 帖子(例如 this and
型号:
class OrganizationManager(models.Manager):
def get_by_natural_key(self, offeror_organization):
return self.get(offeror_organization=offeror_organization)
class Organization(models.Model):
idorganization = models.AutoField(primary_key=True)
offeror_organization = models.CharField(max_length=250, null=False, blank=False, verbose_name='Offeror Organization')
created_at = models.DateTimeField(auto_now_add=True, null=False)
updated_at = models.DateTimeField(auto_now=True, null=False)
objects = OrganizationManager()
def natural_key(self):
return "%s" % (self.offeror_organization)
def __str__(self):
return self.offeror_organization
序列化器:
class OrganizationSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Organization
fields = ['offeror_organization']
# I've tried both with and without a create function
def create(self, validated_data):
organization_data = validated_data.pop('offeror_organization', None)
if organization_data:
organization = Organization.objects.get_or_create(**organization_data)[0]
validated_data['offeror_organization'] = organization
views/api.py:
from webapp.models import Organization
from webapp.serializers import OrganizationSerializer
from rest_framework import viewsets
class OrganizationViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all().order_by('offeror_organization')
serializer_class = OrganizationSerializer
urls.py:
from django.urls import include, path
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'organization', views.OrganizationViewSet)
urlpatterns = [
...
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
卷曲命令:
curl -X POST -H 'Content-Type: application/json' -d '{"offeror_organization":"Test2"}' 10.101.10.228:29000/webapp/api/organization/
settings.py 中间件:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'csp.middleware.CSPMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware'
]
settings.py REST_FRAMEWORK
# currently have all API authentication disabled while troubleshooting this issue
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [],
'DEFAULT_PERMISSION_CLASSES': [],
}
就我而言,解决此问题需要围绕几个不同的实施约束进行“操作”。
nginx + uWSGI 套接字 + Django REMOTE_USER 身份验证: 正如这个 post 的 comments/chat 中提到的,我在我的 Django 应用程序前面有一个 nginx 代理和一个 uWSGI 应用程序服务器。由于我依赖 REMOTE_USER Authentication,我的 uwsgi/nginx 配置 必须 使用 uWSGI 套接字(相对于 http),这样我就可以通过 REMOTE_USER nginx 到 Django 作为环境变量。使用 http(与 nginx proxy_pass 耦合)时,虽然 proxy_pass 可以设置 headers 或 cookie,但这些似乎无法转换为 Django(需要环境变量)。
我认为在尝试 POST 到使用 uWSGI 套接字服务的 Django/DRF 应用程序时存在一些问题。根据 uWSGI Things to know (best practices)、"TL/DR:如果你打算将 uWSGI 直接暴露给 public,如果你想在网络服务器后面代理它,请使用 --http使用后端的 http,使用 --http-socket". 在我的例子中,同时拥有一个网络应用程序和一个 DRF-based API (我希望其他服务和系统交谈),我都需要!作为(希望是临时的)解决方法,我目前正在生成 两个 uWSGI 进程——一个使用 --socket
,一个使用 --http
(对于 API POST 调用)。如果您 POST 在使用 ---socket 时,您可能会收到来自 DRF 的空响应错误。
顺便说一句,我最初看到了在 uWSGI 套接字上利用 uwsgi_curl(从 uwsgi_tools)到 POST 的一些“承诺”(这导致了“field is required”错误(相对于 Empty Response 错误),但那时我开始 运行 我的第二期...
POST 嵌套 application/json w/同步文件上传: post 中引用的“组织”模型主要是 proof-of-concept,因为它是我的 Django 应用程序中最不复杂的模型。实际上,我需要 post 到具有嵌套序列化的更复杂的模型,因为该模型包含其他模型的外键。但这完全是 do-able 与 DRF。 Except 在我的例子中,我的模型属性之一是 FileUpload 字段。正如其他 SO 问题(如 this 一个)所述,尝试 POST 嵌套(即不是“扁平”) [=49] 也存在一些问题=] 在单个请求中上传文件。虽然我一直无法完全理解问题所在(至少在我的案例中使用 drf_writable_nested.serializers.WritableNestedModelSerializer
),但我通过编写自己的自定义序列化程序 (serializers.Serializer) 简化了问题 at-hand,这我可以避免嵌套 JSON objects(比如 { "offeror_organization": {"offeror_organization: "Test"}} 在我的 POST 请求中。这已修复我的问题。
使用自定义序列化程序来缓解嵌套 JSON + 文件上传问题,我敢打赌 uwsgi_curl POST 会起作用。尽管外部客户端 systems/services 仅限于使用该 Python 包。无论如何,我会在尝试后更新我的答案。感谢@Michael 的评论并帮助我走上正确的“道路”。
我有相同的设置(nginx + gunicorn + django + rest-framework + drf-writeable-nested)但我可以找出包含 multipart/form-data
的 POST 请求的有效格式:
需要这样:
json:
{
'name': 'test-name',
'files': [
{
'file': 'test-file-1'
},
{
'file': 'test-file-2'
},
...
],
...
}
必须格式化为:
表格数据:
name: test-name
files[0]file: test-file-1
files[1]file: test-file-2
...
有些库会在嵌套列表的括号后使用点,这会导致 This field is required
错误。甚至列表括号之后的另一个括号也会导致相同的错误。
这是错误的:
files[0].file
这也是错误的:
files[0][file]
我的示例假定以下 Django-类:
# views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.parsers import MultiPartParser, FormParser
from .serializers import YourCustomModelSerializer
class YourCustomModelViewSet(ModelViewSet):
queryset = YourCustomModel.objects.all()
parser_classes = [FormParser, MultiPartParser]
permission_classes = []
serializer_class = YourCustomModelSerializer
# serializers.py
from rest_framework.serializers import ModelSerializer
from drf_writable_nested.serializers import WritableNestedModelSerializer
from .models import YourCustomModel, File
class FileSerializer(ModelSerializer):
class Meta:
model = File
fields = ['file']
class YourCustomModelSerializer(WritableNestedModelSerializer):
# Reverse foreign key
files = FileSerializer(many=True)
class Meta:
model = YourCustomModel
read_only_fields = ('id', )
fields = [
'id',
'name',
'files'
]
# models.py
from django.db import models
class File(models.Model):
file = models.FileField()
class YourCustomModel(models.Model):
name = models.CharField(max_length=200)
我使用以下 javascript/typescript 前端代码将我的 json 数据打包到 FormData 请求中:
const requestBody = {
name: 'test-name',
files: [
{ file: file1 }, // File object
{ file: file2 }, // File object
{ file: file3 } // File object
]
}
// # use your own code to serialize the above javascript dict/json/object
// into form-data object (I used the library https://www.npmjs.com/package/object-to-formdata but I had to write a patch for the exact reason described above: files[0].file is not correctly parsed by Django and files[0][file] also won't work, therefore I slightly changed the library code so that it will format my object to files[0]file for every nested item:
// 2. prepare a serializer-library to convert the dict into a special form which can be parsed by django.
const options = {
indices: true,
allowEmptyArrays: true,
useDotOrBracketsOrNothingForNestedObjects: 2 // Option 0: DOT-Notation, 1: Brackets, 2: Nothing (this option is from my custom patch)
}
// use npx patch:
// (I patched this serialize library and the patch is somewhere stored as a file in this project)
const formData = serialize(
requestBody,
options
)
// 3. upload the data
api.post(url, formData)