字段是必填的,当它没有被定义为这样时

huangapple go评论79阅读模式
英文:

Field is required, when it's not defined as so

问题

我有以下的Django模型

class Component(models.Model):
    parent = models.ForeignKey(
        "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
    )
    vessel = models.ForeignKey(
        Vessel, on_delete=models.CASCADE, related_name="components"
    )
    name = models.CharField(max_length=100)
    manufacturer = models.CharField(max_length=255, null=True, blank=True)
    model = models.CharField(max_length=255, null=True, blank=True)
    type = models.CharField(max_length=255, null=True, blank=True)
    serial_number = models.CharField(max_length=255, null=True, blank=True)
    supplier = models.CharField(max_length=255, null=True, blank=True)
    description = models.TextField(null=True, blank=True)
    image = models.ImageField(upload_to="component_images", blank=True, null=True)

    def __str__(self):
        return self.name

视图集如下

class ComponentViewSet(viewsets.ModelViewSet):
    serializer_class = ComponentSerializer

    def get_queryset(self):
        queryset = Component.objects.all()
        vessel_id = self.kwargs.get("vessel_id", None)
        if vessel_id is not None:
            queryset = queryset.filter(vessel_id=vessel_id)
        queryset = queryset.filter(Q(parent=None) | Q(parent__isnull=True))
        return queryset

    def retrieve(self, request, pk=None, vessel_id=None):
        queryset = Component.objects.all()
        component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
        serializer = ComponentSerializer(component)
        return Response(serializer.data)

    def update(self, request, pk=None, vessel_id=None, partial=True):
        queryset = Component.objects.all()
        component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
        serializer = ComponentSerializer(component, data=request.data, partial=partial)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, methods=["delete"])
    def delete_component(self, request, pk=None, vessel_id=None):
        queryset = Component.objects.all()
        component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)

        # Recursively delete all children of the component
        self._delete_children(component)

        # Delete the component itself
        component.delete()

        return Response(status=status.HTTP_204_NO_CONTENT)

    def _delete_children(self, component):
        children = component.children.all()
        for child in children:
            self._delete_children(child)
            child.delete()

序列化器如下

class ImageSerializerField(serializers.Field):
    def to_representation(self, value):
        if not value:
            return None
        if settings.MEDIA_URL in value.url:
            return (
                settings.BASE_URL
                + settings.MEDIA_URL
                + value.url[len(settings.MEDIA_URL) :]
            )
        return value.url

    def to_internal_value(self, data):
        return data


class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data


class ComponentSerializer(serializers.ModelSerializer):
    children = RecursiveField(many=True)
    image = ImageSerializerField()

    class Meta:
        model = Component
        fields = "__all__"

这是我创建组件的简单React表单

import React, { useState } from "react";
import { api } from "../../../../../userAuth/auth";
import { useParams } from "react-router";

const ComponentData = () => {
  const { vessel_id } = useParams();
  const [formData, setFormData] = useState({
    vessel: vessel_id,
    name: "",
    manufacturer: "",
    model: "",
    type: "",
    serial_number: "",
    supplier: "",
    description: "",
    image: null,
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevFormData) => ({
      ...prevFormData,
      [name]: value,
    }));
  };

  const handleImageChange = (e) => {
    const file = e.target.files[0];
    setFormData((prevFormData) => ({
      ...prevFormData,
      image: file,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      const { children, ...data } = formData; // Exclude the 'children' field

      const response = await api.post(
        `/maintenance/${vessel_id}/components/`,
        data
      );

      // TODO: Handle successful creation (e.g., show success message, redirect, etc.)
    } catch (error) {
      console.error("Error creating component:", error);
      // TODO: Handle error (e.g., show error message, etc.)
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
          required
        />
      </label>
      <br />
      <label>
        Manufacturer:
        <input
          type="text"
          name="manufacturer"
          value={formData.manufacturer}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Model:
        <input
          type="text"
          name="model"
          value={formData.model}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Type:
        <input
          type="text"
          name="type"
          value={formData.type}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Serial Number:
        <input
          type="text"
          name="serial_number"
          value={formData.serial_number}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Supplier:
        <input
          type="text"
          name="supplier"
          value={formData.supplier}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Description:
        <textarea
          name="description"
          value={formData.description}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Image:
        <input
          type="file"
          name="image"
          accept="image/*"
          onChange={handleImageChange}
        />
      </label>
      <br />
      <button type="submit">Create Component</button>
    </form>
  );
};

export default ComponentData;

每当我发送POST请求时,我收到Bad Request错误。当我检查请求控制台时,有这个错误信息。

{
  "children": ["This field is required."]
}

我认为它是在谈论递归字段parent,但我已经在我的模型中声明它不是必需的,所以我不确定我漏掉了什么。

英文:

I have the following Django model

class Component(models.Model):
parent = models.ForeignKey(
&quot;self&quot;, on_delete=models.CASCADE, null=True, blank=True, related_name=&quot;children&quot;
)
vessel = models.ForeignKey(
Vessel, on_delete=models.CASCADE, related_name=&quot;components&quot;
)
name = models.CharField(max_length=100)
manufacturer = models.CharField(max_length=255, null=True, blank=True)
model = models.CharField(max_length=255, null=True, blank=True)
type = models.CharField(max_length=255, null=True, blank=True)
serial_number = models.CharField(max_length=255, null=True, blank=True)
supplier = models.CharField(max_length=255, null=True, blank=True)
description = models.TextField(null=True, blank=True)
image = models.ImageField(upload_to=&quot;component_images&quot;, blank=True, null=True)
def __str__(self):
return self.name

and the viewset looks like this

class ComponentViewSet(viewsets.ModelViewSet):
serializer_class = ComponentSerializer
def get_queryset(self):
queryset = Component.objects.all()
vessel_id = self.kwargs.get(&quot;vessel_id&quot;, None)
if vessel_id is not None:
queryset = queryset.filter(vessel_id=vessel_id)
queryset = queryset.filter(Q(parent=None) | Q(parent__isnull=True))
return queryset
def retrieve(self, request, pk=None, vessel_id=None):
queryset = Component.objects.all()
component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
serializer = ComponentSerializer(component)
return Response(serializer.data)
def update(self, request, pk=None, vessel_id=None, partial=True):
queryset = Component.objects.all()
component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
serializer = ComponentSerializer(component, data=request.data, partial=partial)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=[&quot;delete&quot;])
def delete_component(self, request, pk=None, vessel_id=None):
queryset = Component.objects.all()
component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
# Recursively delete all children of the component
self._delete_children(component)
# Delete the component itself
component.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def _delete_children(self, component):
children = component.children.all()
for child in children:
self._delete_children(child)
child.delete()

The serializer:

class ImageSerializerField(serializers.Field):
def to_representation(self, value):
if not value:
return None
if settings.MEDIA_URL in value.url:
return (
settings.BASE_URL
+ settings.MEDIA_URL
+ value.url[len(settings.MEDIA_URL) :]
)
return value.url
def to_internal_value(self, data):
return data
class RecursiveField(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data
class ComponentSerializer(serializers.ModelSerializer):
children = RecursiveField(many=True)
image = ImageSerializerField()
class Meta:
model = Component
fields = &quot;__all__&quot;

This is my simple React form to create a component

import React, { useState } from &quot;react&quot;;
import { api } from &quot;../../../../../userAuth/auth&quot;;
import { useParams } from &quot;react-router&quot;;
const ComponentData = () =&gt; {
const { vessel_id } = useParams();
const [formData, setFormData] = useState({
vessel: vessel_id,
name: &quot;&quot;,
manufacturer: &quot;&quot;,
model: &quot;&quot;,
type: &quot;&quot;,
serial_number: &quot;&quot;,
supplier: &quot;&quot;,
description: &quot;&quot;,
image: null,
});
const handleChange = (e) =&gt; {
const { name, value } = e.target;
setFormData((prevFormData) =&gt; ({
...prevFormData,
[name]: value,
}));
};
const handleImageChange = (e) =&gt; {
const file = e.target.files[0];
setFormData((prevFormData) =&gt; ({
...prevFormData,
image: file,
}));
};
const handleSubmit = async (e) =&gt; {
e.preventDefault();
try {
const { children, ...data } = formData; // Exclude the &#39;children&#39; field
const response = await api.post(
`/maintenance/${vessel_id}/components/`,
data
);
// TODO: Handle successful creation (e.g., show success message, redirect, etc.)
} catch (error) {
console.error(&quot;Error creating component:&quot;, error);
// TODO: Handle error (e.g., show error message, etc.)
}
};
return (
&lt;form onSubmit={handleSubmit}&gt;
&lt;label&gt;
Name:
&lt;input
type=&quot;text&quot;
name=&quot;name&quot;
value={formData.name}
onChange={handleChange}
required
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
Manufacturer:
&lt;input
type=&quot;text&quot;
name=&quot;manufacturer&quot;
value={formData.manufacturer}
onChange={handleChange}
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
Model:
&lt;input
type=&quot;text&quot;
name=&quot;model&quot;
value={formData.model}
onChange={handleChange}
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
Type:
&lt;input
type=&quot;text&quot;
name=&quot;type&quot;
value={formData.type}
onChange={handleChange}
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
Serial Number:
&lt;input
type=&quot;text&quot;
name=&quot;serial_number&quot;
value={formData.serial_number}
onChange={handleChange}
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
Supplier:
&lt;input
type=&quot;text&quot;
name=&quot;supplier&quot;
value={formData.supplier}
onChange={handleChange}
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
Description:
&lt;textarea
name=&quot;description&quot;
value={formData.description}
onChange={handleChange}
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;label&gt;
Image:
&lt;input
type=&quot;file&quot;
name=&quot;image&quot;
accept=&quot;image/*&quot;
onChange={handleImageChange}
/&gt;
&lt;/label&gt;
&lt;br /&gt;
&lt;button type=&quot;submit&quot;&gt;Create Component&lt;/button&gt;
&lt;/form&gt;
);
};
export default ComponentData;

Whenever I send the post request I receive Bad request. When I check the request console inside there is this.

response
: 
config
: 
{transitional: {…}, adapter: Array(2), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …}
data
: 
children
: 
[&#39;This field is required.&#39;]
[[Prototype]]
: 
Object

I assume it's talking about the recursive field parent, but I already stated in my model it's not required so I am not sure what I am missing.

答案1

得分: 1

以下是您要翻译的内容:

"The main problem is the serializer, where your image and children field did not specify if these are required, and if not, by default they are. For the children, if I understand it correctly, these will only be used to read, so add <code>read_only=&hellip;</code> to prevent to use these in the RecursiveField:

<pre><code>class ComponentSerializer(serializers.ModelSerializer):
children = RecursiveField(many=True, <b>read_only=True, required=False</b>)
image = ImageSerializerField(<b>required=False</b>)

class Meta:
model = Component
fields = &#39;__all__&#39;&lt;/code&gt;&lt;/pre&gt;

The implementation for the RecursiveField also likely does not work for all cases, for example without a many=True, this will definitely fail since there is no parent for the parent. You might want to use django-rest-framework-recursive&nbsp;<sup>[GitHub]</sup>, which proxies most attributes&nbsp;<sup>[GitHub]</sup>.

Finally the serializer does way too much. Most of what you do is boilerplate. For example removing all the children and subchildren, etc. will be handled by Django's ORM, and in a more efficient manner. By doing this, you also make it harder later to perform proper authentication, authorization, throttling, etc. The ModelViewSet already has boilerplate logic to get a list, a specific item, etc.

<pre><code>class ComponentViewSet(viewsets.ModelViewSet):
<b>queryset = Component objects.all()</b>
serializer_class = ComponentSerializer

def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
vessel_id = self.kwargs.get(&#39;vessel_id&#39;)
if vessel_id is not None:
queryset = queryset.filter(vessel_id=vessel_id)
return queryset.filter(&lt;b&gt;parent=None&lt;/b&gt;)&lt;/code&gt;&lt;/pre&gt;

that is all we need."

英文:

The main problem is the serializer, where your image and children field did not specify if these are required, and if not, by default they are. For the children, if I understand it correctly, these will only be used to read, so add <code>read_only=&hellip;</code> to prevent to use these in the RecursiveField:

<pre><code>class ComponentSerializer(serializers.ModelSerializer):
children = RecursiveField(many=True, <b>read_only=True, required=False</b>)
image = ImageSerializerField(<b>required=False</b>)

class Meta:
model = Component
fields = &#39;__all__&#39;&lt;/code&gt;&lt;/pre&gt;

The implementation for the RecursiveField also likely does not work for all cases, for example without a many=True, this will definitely fail since there is no parent for the parent. You might want to use django-rest-framework-recursive&nbsp;<sup>[GitHub]</sup>, which proxies most attributes&nbsp;<sup>[GitHub]</sup>.

Finally the serializer does way too much. Most of what you do is boilerplate. For example removing all the children and subchildren, etc. will be handled by Django's ORM, and in a more efficient manner. By doing this, you also make it harder later to perform proper authentication, authorization, throttling, etc. The ModelViewSet already has boilerplate logic to get a list, a specific item, etc.

<pre><code>class ComponentViewSet(viewsets.ModelViewSet):
<b>queryset = Component.objects.all()</b>
serializer_class = ComponentSerializer

def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
vessel_id = self.kwargs.get(&#39;vessel_id&#39;)
if vessel_id is not None:
queryset = queryset.filter(vessel_id=vessel_id)
return queryset.filter(&lt;b&gt;parent=None&lt;/b&gt;)&lt;/code&gt;&lt;/pre&gt;

that is all we need.

huangapple
  • 本文由 发表于 2023年5月28日 17:32:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76350818.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定