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

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

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

问题

我有以下的Django模型

  1. class Component(models.Model):
  2. parent = models.ForeignKey(
  3. "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
  4. )
  5. vessel = models.ForeignKey(
  6. Vessel, on_delete=models.CASCADE, related_name="components"
  7. )
  8. name = models.CharField(max_length=100)
  9. manufacturer = models.CharField(max_length=255, null=True, blank=True)
  10. model = models.CharField(max_length=255, null=True, blank=True)
  11. type = models.CharField(max_length=255, null=True, blank=True)
  12. serial_number = models.CharField(max_length=255, null=True, blank=True)
  13. supplier = models.CharField(max_length=255, null=True, blank=True)
  14. description = models.TextField(null=True, blank=True)
  15. image = models.ImageField(upload_to="component_images", blank=True, null=True)
  16. def __str__(self):
  17. return self.name

视图集如下

  1. class ComponentViewSet(viewsets.ModelViewSet):
  2. serializer_class = ComponentSerializer
  3. def get_queryset(self):
  4. queryset = Component.objects.all()
  5. vessel_id = self.kwargs.get("vessel_id", None)
  6. if vessel_id is not None:
  7. queryset = queryset.filter(vessel_id=vessel_id)
  8. queryset = queryset.filter(Q(parent=None) | Q(parent__isnull=True))
  9. return queryset
  10. def retrieve(self, request, pk=None, vessel_id=None):
  11. queryset = Component.objects.all()
  12. component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
  13. serializer = ComponentSerializer(component)
  14. return Response(serializer.data)
  15. def update(self, request, pk=None, vessel_id=None, partial=True):
  16. queryset = Component.objects.all()
  17. component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
  18. serializer = ComponentSerializer(component, data=request.data, partial=partial)
  19. if serializer.is_valid():
  20. serializer.save()
  21. return Response(serializer.data)
  22. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  23. @action(detail=True, methods=["delete"])
  24. def delete_component(self, request, pk=None, vessel_id=None):
  25. queryset = Component.objects.all()
  26. component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
  27. # Recursively delete all children of the component
  28. self._delete_children(component)
  29. # Delete the component itself
  30. component.delete()
  31. return Response(status=status.HTTP_204_NO_CONTENT)
  32. def _delete_children(self, component):
  33. children = component.children.all()
  34. for child in children:
  35. self._delete_children(child)
  36. child.delete()

序列化器如下

  1. class ImageSerializerField(serializers.Field):
  2. def to_representation(self, value):
  3. if not value:
  4. return None
  5. if settings.MEDIA_URL in value.url:
  6. return (
  7. settings.BASE_URL
  8. + settings.MEDIA_URL
  9. + value.url[len(settings.MEDIA_URL) :]
  10. )
  11. return value.url
  12. def to_internal_value(self, data):
  13. return data
  14. class RecursiveField(serializers.Serializer):
  15. def to_representation(self, value):
  16. serializer = self.parent.parent.__class__(value, context=self.context)
  17. return serializer.data
  18. class ComponentSerializer(serializers.ModelSerializer):
  19. children = RecursiveField(many=True)
  20. image = ImageSerializerField()
  21. class Meta:
  22. model = Component
  23. fields = "__all__"

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

  1. import React, { useState } from "react";
  2. import { api } from "../../../../../userAuth/auth";
  3. import { useParams } from "react-router";
  4. const ComponentData = () => {
  5. const { vessel_id } = useParams();
  6. const [formData, setFormData] = useState({
  7. vessel: vessel_id,
  8. name: "",
  9. manufacturer: "",
  10. model: "",
  11. type: "",
  12. serial_number: "",
  13. supplier: "",
  14. description: "",
  15. image: null,
  16. });
  17. const handleChange = (e) => {
  18. const { name, value } = e.target;
  19. setFormData((prevFormData) => ({
  20. ...prevFormData,
  21. [name]: value,
  22. }));
  23. };
  24. const handleImageChange = (e) => {
  25. const file = e.target.files[0];
  26. setFormData((prevFormData) => ({
  27. ...prevFormData,
  28. image: file,
  29. }));
  30. };
  31. const handleSubmit = async (e) => {
  32. e.preventDefault();
  33. try {
  34. const { children, ...data } = formData; // Exclude the 'children' field
  35. const response = await api.post(
  36. `/maintenance/${vessel_id}/components/`,
  37. data
  38. );
  39. // TODO: Handle successful creation (e.g., show success message, redirect, etc.)
  40. } catch (error) {
  41. console.error("Error creating component:", error);
  42. // TODO: Handle error (e.g., show error message, etc.)
  43. }
  44. };
  45. return (
  46. <form onSubmit={handleSubmit}>
  47. <label>
  48. Name:
  49. <input
  50. type="text"
  51. name="name"
  52. value={formData.name}
  53. onChange={handleChange}
  54. required
  55. />
  56. </label>
  57. <br />
  58. <label>
  59. Manufacturer:
  60. <input
  61. type="text"
  62. name="manufacturer"
  63. value={formData.manufacturer}
  64. onChange={handleChange}
  65. />
  66. </label>
  67. <br />
  68. <label>
  69. Model:
  70. <input
  71. type="text"
  72. name="model"
  73. value={formData.model}
  74. onChange={handleChange}
  75. />
  76. </label>
  77. <br />
  78. <label>
  79. Type:
  80. <input
  81. type="text"
  82. name="type"
  83. value={formData.type}
  84. onChange={handleChange}
  85. />
  86. </label>
  87. <br />
  88. <label>
  89. Serial Number:
  90. <input
  91. type="text"
  92. name="serial_number"
  93. value={formData.serial_number}
  94. onChange={handleChange}
  95. />
  96. </label>
  97. <br />
  98. <label>
  99. Supplier:
  100. <input
  101. type="text"
  102. name="supplier"
  103. value={formData.supplier}
  104. onChange={handleChange}
  105. />
  106. </label>
  107. <br />
  108. <label>
  109. Description:
  110. <textarea
  111. name="description"
  112. value={formData.description}
  113. onChange={handleChange}
  114. />
  115. </label>
  116. <br />
  117. <label>
  118. Image:
  119. <input
  120. type="file"
  121. name="image"
  122. accept="image/*"
  123. onChange={handleImageChange}
  124. />
  125. </label>
  126. <br />
  127. <button type="submit">Create Component</button>
  128. </form>
  129. );
  130. };
  131. export default ComponentData;

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

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

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

英文:

I have the following Django model

  1. class Component(models.Model):
  2. parent = models.ForeignKey(
  3. &quot;self&quot;, on_delete=models.CASCADE, null=True, blank=True, related_name=&quot;children&quot;
  4. )
  5. vessel = models.ForeignKey(
  6. Vessel, on_delete=models.CASCADE, related_name=&quot;components&quot;
  7. )
  8. name = models.CharField(max_length=100)
  9. manufacturer = models.CharField(max_length=255, null=True, blank=True)
  10. model = models.CharField(max_length=255, null=True, blank=True)
  11. type = models.CharField(max_length=255, null=True, blank=True)
  12. serial_number = models.CharField(max_length=255, null=True, blank=True)
  13. supplier = models.CharField(max_length=255, null=True, blank=True)
  14. description = models.TextField(null=True, blank=True)
  15. image = models.ImageField(upload_to=&quot;component_images&quot;, blank=True, null=True)
  16. def __str__(self):
  17. return self.name

and the viewset looks like this

  1. class ComponentViewSet(viewsets.ModelViewSet):
  2. serializer_class = ComponentSerializer
  3. def get_queryset(self):
  4. queryset = Component.objects.all()
  5. vessel_id = self.kwargs.get(&quot;vessel_id&quot;, None)
  6. if vessel_id is not None:
  7. queryset = queryset.filter(vessel_id=vessel_id)
  8. queryset = queryset.filter(Q(parent=None) | Q(parent__isnull=True))
  9. return queryset
  10. def retrieve(self, request, pk=None, vessel_id=None):
  11. queryset = Component.objects.all()
  12. component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
  13. serializer = ComponentSerializer(component)
  14. return Response(serializer.data)
  15. def update(self, request, pk=None, vessel_id=None, partial=True):
  16. queryset = Component.objects.all()
  17. component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
  18. serializer = ComponentSerializer(component, data=request.data, partial=partial)
  19. if serializer.is_valid():
  20. serializer.save()
  21. return Response(serializer.data)
  22. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  23. @action(detail=True, methods=[&quot;delete&quot;])
  24. def delete_component(self, request, pk=None, vessel_id=None):
  25. queryset = Component.objects.all()
  26. component = get_object_or_404(queryset, pk=pk, vessel_id=vessel_id)
  27. # Recursively delete all children of the component
  28. self._delete_children(component)
  29. # Delete the component itself
  30. component.delete()
  31. return Response(status=status.HTTP_204_NO_CONTENT)
  32. def _delete_children(self, component):
  33. children = component.children.all()
  34. for child in children:
  35. self._delete_children(child)
  36. child.delete()

The serializer:

  1. class ImageSerializerField(serializers.Field):
  2. def to_representation(self, value):
  3. if not value:
  4. return None
  5. if settings.MEDIA_URL in value.url:
  6. return (
  7. settings.BASE_URL
  8. + settings.MEDIA_URL
  9. + value.url[len(settings.MEDIA_URL) :]
  10. )
  11. return value.url
  12. def to_internal_value(self, data):
  13. return data
  14. class RecursiveField(serializers.Serializer):
  15. def to_representation(self, value):
  16. serializer = self.parent.parent.__class__(value, context=self.context)
  17. return serializer.data
  18. class ComponentSerializer(serializers.ModelSerializer):
  19. children = RecursiveField(many=True)
  20. image = ImageSerializerField()
  21. class Meta:
  22. model = Component
  23. fields = &quot;__all__&quot;

This is my simple React form to create a component

  1. import React, { useState } from &quot;react&quot;;
  2. import { api } from &quot;../../../../../userAuth/auth&quot;;
  3. import { useParams } from &quot;react-router&quot;;
  4. const ComponentData = () =&gt; {
  5. const { vessel_id } = useParams();
  6. const [formData, setFormData] = useState({
  7. vessel: vessel_id,
  8. name: &quot;&quot;,
  9. manufacturer: &quot;&quot;,
  10. model: &quot;&quot;,
  11. type: &quot;&quot;,
  12. serial_number: &quot;&quot;,
  13. supplier: &quot;&quot;,
  14. description: &quot;&quot;,
  15. image: null,
  16. });
  17. const handleChange = (e) =&gt; {
  18. const { name, value } = e.target;
  19. setFormData((prevFormData) =&gt; ({
  20. ...prevFormData,
  21. [name]: value,
  22. }));
  23. };
  24. const handleImageChange = (e) =&gt; {
  25. const file = e.target.files[0];
  26. setFormData((prevFormData) =&gt; ({
  27. ...prevFormData,
  28. image: file,
  29. }));
  30. };
  31. const handleSubmit = async (e) =&gt; {
  32. e.preventDefault();
  33. try {
  34. const { children, ...data } = formData; // Exclude the &#39;children&#39; field
  35. const response = await api.post(
  36. `/maintenance/${vessel_id}/components/`,
  37. data
  38. );
  39. // TODO: Handle successful creation (e.g., show success message, redirect, etc.)
  40. } catch (error) {
  41. console.error(&quot;Error creating component:&quot;, error);
  42. // TODO: Handle error (e.g., show error message, etc.)
  43. }
  44. };
  45. return (
  46. &lt;form onSubmit={handleSubmit}&gt;
  47. &lt;label&gt;
  48. Name:
  49. &lt;input
  50. type=&quot;text&quot;
  51. name=&quot;name&quot;
  52. value={formData.name}
  53. onChange={handleChange}
  54. required
  55. /&gt;
  56. &lt;/label&gt;
  57. &lt;br /&gt;
  58. &lt;label&gt;
  59. Manufacturer:
  60. &lt;input
  61. type=&quot;text&quot;
  62. name=&quot;manufacturer&quot;
  63. value={formData.manufacturer}
  64. onChange={handleChange}
  65. /&gt;
  66. &lt;/label&gt;
  67. &lt;br /&gt;
  68. &lt;label&gt;
  69. Model:
  70. &lt;input
  71. type=&quot;text&quot;
  72. name=&quot;model&quot;
  73. value={formData.model}
  74. onChange={handleChange}
  75. /&gt;
  76. &lt;/label&gt;
  77. &lt;br /&gt;
  78. &lt;label&gt;
  79. Type:
  80. &lt;input
  81. type=&quot;text&quot;
  82. name=&quot;type&quot;
  83. value={formData.type}
  84. onChange={handleChange}
  85. /&gt;
  86. &lt;/label&gt;
  87. &lt;br /&gt;
  88. &lt;label&gt;
  89. Serial Number:
  90. &lt;input
  91. type=&quot;text&quot;
  92. name=&quot;serial_number&quot;
  93. value={formData.serial_number}
  94. onChange={handleChange}
  95. /&gt;
  96. &lt;/label&gt;
  97. &lt;br /&gt;
  98. &lt;label&gt;
  99. Supplier:
  100. &lt;input
  101. type=&quot;text&quot;
  102. name=&quot;supplier&quot;
  103. value={formData.supplier}
  104. onChange={handleChange}
  105. /&gt;
  106. &lt;/label&gt;
  107. &lt;br /&gt;
  108. &lt;label&gt;
  109. Description:
  110. &lt;textarea
  111. name=&quot;description&quot;
  112. value={formData.description}
  113. onChange={handleChange}
  114. /&gt;
  115. &lt;/label&gt;
  116. &lt;br /&gt;
  117. &lt;label&gt;
  118. Image:
  119. &lt;input
  120. type=&quot;file&quot;
  121. name=&quot;image&quot;
  122. accept=&quot;image/*&quot;
  123. onChange={handleImageChange}
  124. /&gt;
  125. &lt;/label&gt;
  126. &lt;br /&gt;
  127. &lt;button type=&quot;submit&quot;&gt;Create Component&lt;/button&gt;
  128. &lt;/form&gt;
  129. );
  130. };
  131. export default ComponentData;

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

  1. response
  2. :
  3. config
  4. :
  5. {transitional: {…}, adapter: Array(2), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …}
  6. data
  7. :
  8. children
  9. :
  10. [&#39;This field is required.&#39;]
  11. [[Prototype]]
  12. :
  13. 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>)

  1. class Meta:
  2. model = Component
  3. 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

  1. def get_queryset(self, *args, **kwargs):
  2. queryset = super().get_queryset(*args, **kwargs)
  3. vessel_id = self.kwargs.get(&#39;vessel_id&#39;)
  4. if vessel_id is not None:
  5. queryset = queryset.filter(vessel_id=vessel_id)
  6. 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>)

  1. class Meta:
  2. model = Component
  3. 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

  1. def get_queryset(self, *args, **kwargs):
  2. queryset = super().get_queryset(*args, **kwargs)
  3. vessel_id = self.kwargs.get(&#39;vessel_id&#39;)
  4. if vessel_id is not None:
  5. queryset = queryset.filter(vessel_id=vessel_id)
  6. 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:

确定