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

Advertisements

I have the following Django model

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

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("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()

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 = "__all__"

this is my simple react form to create a component

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;

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
: 
['This field is required.']
[[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.

>Solution :

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 read_only=… to prevent to use these in the RecursiveField:

class ComponentSerializer(serializers.ModelSerializer):
    children = RecursiveField(many=True, read_only=True, required=False)
    image = ImageSerializerField(required=False)

    class Meta:
        model = Component
        fields = '__all__'

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 [GitHub], which proxies most attributes [GitHub].

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.

class ComponentViewSet(viewsets.ModelViewSet):
    queryset = Component.objects.all()
    serializer_class = ComponentSerializer

    def get_queryset(self, *args, **kwargs):
        queryset = super().get_queryset(*args, **kwargs)
        vessel_id = self.kwargs.get('vessel_id')
        if vessel_id is not None:
            queryset = queryset.filter(vessel_id=vessel_id)
        return queryset.filter(parent=None)

that is all we need.

Leave a ReplyCancel reply