Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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

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 :

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading