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)