Search code examples
djangodjango-rest-frameworkdjango-serializer

How to validate and get nested serializer values in django rest framework?


I am using 2 serializers for validating this request:

{
    "name":"Dade Waste Facility",
    "email":"example@mailinator.com",
    "mobile":"1234567",
    "street":"Street 1",
    "city":"Miami",
    "zipcode":"123456",
    "state":"Florida",
    "material_types": [{
        "name": "Metal",
        "cost_per_ton": 20
    },
    {
        "cost_per_ton": 50,
        "name": "Food"
    }]
}

Django code:

class MaterialTypeSerializer(serializers.Serializer):
    name = serializers.CharField(
        required=True,
        max_length=255,
        error_messages={
            "required": "Material type name is required.",
            "max_length": "Material type name can be maximum 255 characters.",
        },
    )
    cost_per_ton = serializers.IntegerField(
        required=True,
        error_messages={"required": "Material type cost per ton is required.",},
    )

    class Meta:
        fields = "__all__"


class WasteFacilityPostSerializer(serializers.ModelSerializer):
    name = serializers.CharField(
        required=True,
        max_length=255,
        error_messages={
            "required": "Name is required.",
            "max_length": "Name can be maximum 255 characters.",
        },
    )
    contact_person_name = serializers.CharField(
        required=False,
        max_length=255,
        error_messages={
            "max_length": "Contact person name can be maximum 255 characters.",
        },
    )
    email = serializers.EmailField(
        required=False,
        max_length=255,
        error_messages={
            "invalid": "Email address not valid.",
            "max_length": "Email can be maximum 255 characters.",
        },
    )
    mobile = serializers.CharField(
        required=True,
        validators=[
            RegexValidator(
                regex=r"^\d{10,14}$",
                message="Phone number must be entered in format: '+999999999'. Up to 14 digits allowed.",
            )
        ],
        max_length=15,
        error_messages={
            "required": "Mobile is required.",
            "max_length": "Mobile can be maximum 14 digits.",
        },
    )
    street = serializers.CharField(
        required=True,
        max_length=255,
        error_messages={
            "required": "Street is required.",
            "max_length": "Street can be maximum 255 characters.",
        },
    )
    suite = serializers.CharField(
        required=False,
        max_length=255,
        error_messages={"max_length": "Suite can be maximum 255 characters.",},
    )
    city = serializers.CharField(
        required=True,
        max_length=255,
        error_messages={
            "required": "City is required.",
            "max_length": "City can be maximum 255 characters.",
        },
    )
    zipcode = serializers.CharField(
        required=True,
        max_length=10,
        error_messages={
            "required": "Zipcode is required.",
            "max_length": "Zipcode can be maximum 10 digits.",
        },
    )
    state = serializers.CharField(
        required=True,
        max_length=255,
        error_messages={
            "required": "State is required.",
            "max_length": "State can be maximum 255 characters.",
        },
    )

    material_types = MaterialTypeSerializer(
        required=True,
        many=True,
        error_messages={"required": "Material type is required."},
    )

    class Meta:
        model = WasteFacility
        exclude = []

    def validate_name(self, value):
        waste_facility = WasteFacility.objects

        if self.instance:
            waste_facility = waste_facility.exclude(pk=self.instance.pk)

        if waste_facility.filter(name__iexact=value):
            raise serializers.ValidationError(
                "Waste facility with this name already exists."
            )

        return value

    def validate_email(self, value):
        waste_facility = WasteFacility.objects

        if self.instance:
            waste_facility = waste_facility.exclude(pk=self.instance.pk)

        if waste_facility.filter(email__iexact=value):
            raise serializers.ValidationError(
                "Waste facility with this email already exists."
            )

        return value

    def validate_mobile(self, value):
        if validate_phone_number(value) is None:

            waste_facility = WasteFacility.objects

            if self.instance:
                waste_facility = waste_facility.exclude(pk=self.instance.pk)

            if waste_facility.filter(mobile__iexact=value):
                raise serializers.ValidationError(
                    "Waste facility with this phone number already exists."
                )

        return value

    def validate_material_types(self, attrs):
        if len(attrs) == 0:
            raise serializers.ValidationError("At least one material type is required.")
        return attrs

    def create(self, validated_data):
        try:
            with transaction.atomic():
                material_types = validated_data.pop("material_types")

                instance = WasteFacility.objects.create(**validated_data)

                print(material_types)
                for material_type in material_types:
                    for key, value in material_type.items():
                        print(key, value)

                raise Exception("This is a test exception.")
            return instance
        except DatabaseError as database_error:
            return None

If you see the create method of WasteFacilitySerializer you'll see that i am printing material_types property. This is what I see:

[OrderedDict([('name', 'Metal'), ('cost_per_ton', 20)]), OrderedDict([('name', 'Food'), ('cost_per_ton', 50)])]

How to get list of dictionaries instead of ordereddict to that it will be easy to traverse? As I want to sync this data with one of my ManyToMany tables. Is this the right way to do it or there is a better way to implement this?


Solution

  • This is what I did to get dict from list of ordered dict.

    for material_type in material_types:
        material_type = dict(material_type)
    

    Iterated the material_types list of ordered dict and converted them to dict.