Django Rest Framework ManytoMany Feld Serializing

Модели:

class MaterialAttribute(models.Model):
    """
    Represents a material attribute.

    Attributes:
        name (CharField): The name of the material attribute.
    """

    class Meta:
        verbose_name = "Material Attribute"
        verbose_name_plural = "Material Attributes"
        db_table = "material_attribute"

    name = models.CharField(max_length=255)

    def __str__(self):
        return f"{self.name}"


class MaterialType(models.Model):
    """
    Represents a material type.

    Attributes:
        name (CharField): The name of the material type.
        attributes (ManyToManyField): The attributes of the material type.
    """

    class Meta:
        verbose_name = "Material Type"
        verbose_name_plural = "Material Types"
        db_table = "material_type"

    name = models.CharField(max_length=255)
    attributes = models.ManyToManyField(
        MaterialAttribute, related_name="material_types"
    )

    def __str__(self):
        return f"{self.name}"

Я хочу написать MaterialTypeSerializer таким образом, чтобы для list/retrieave он возвращал Json-представление

{
        "id": 1,
        "name": "Test",
        "attributes": [
            {
                "id": 1,
                "name": "Format"
            },
            {
                "id": 2,
                "name": "Farbe"
            }
        ]
    } 

из аттрибутов и для создания/обновления

{
"name": "Test",
        "attributes": [
            {
                1
            },
            {
                2
            }
        ] 
}

первичный ключ.

class MaterialAttributeSerializer(serializers.ModelSerializer):
    """
    Serializer for the MaterialAttribute model.

    This serializer includes the following fields:
    - id: The unique identifier of the material attribute.
    - name: The name of the material attribute.

    All fields are read-only.
    It is used to serialize minimal material attribute data.
    """

    class Meta:
        model = MaterialAttribute
        fields = ["id", "name"]


class MaterialAttributeManyField(serializers.PrimaryKeyRelatedField):
    
    def to_representation(self, value):
        return MaterialAttributeSerializer(value).data

class MaterialTypeSerializer(serializers.ModelSerializer):
    """
    Serializer for the MaterialType model.

    This serializer includes the following fields:
    - id: The unique identifier of the material type.
    - name: The name of the material type.
    - attributes: The attributes associated with the material type.

    All fields are read-only.
    It is used to serialize minimal material type data.
    """

    attributes = MaterialAttributeManyField(
        queryset=MaterialAttribute.objects.all(), many=True
    )

    class Meta:
        model = MaterialType
        fields = ["id", "name", "attributes"]

Однако это приводит к "нехешируемому типу: 'ReturnDict'"

Я стараюсь не переопределять методы создания и обновления сериализатора, чтобы сохранить простоту кода.

Если кто-то знает чистый способ решить эту проблему, я буду благодарен за это

Чтобы правильно сериализовать ManyToManyField в Django REST Framework (DRF) для операций list/retrieve и create/update, необходимо настроить свои сериализаторы для корректной обработки отношений.

serializers.py

from rest_framework import serializers
from .models import MaterialAttribute, MaterialType

class MaterialAttributeSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = MaterialAttribute
        fields = ["id", "name"]

class MaterialTypeSerializer(serializers.ModelSerializer):
    attributes = serializers.PrimaryKeyRelatedField(queryset=MaterialAttribute.objects.all(), many=True)

    class Meta:
        model = MaterialType
        fields = ["id", "name", "attributes"]

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation['attributes'] = MaterialAttributeSerializer(instance.attributes.all(), many=True).data
        return representation

    def create(self, validated_data):
        attributes_data = validated_data.pop('attributes', [])
        material_type = MaterialType.objects.create(**validated_data)
        for attr_id in attributes_data:
            attribute = MaterialAttribute.objects.get(id=attr_id)
            material_type.attributes.add(attribute)
        return material_type

    def update(self, instance, validated_data):
        attributes_data = validated_data.pop('attributes', [])
        instance.name = validated_data.get('name', instance.name)
        
        instance.attributes.clear()
        for attr_id in attributes_data:
            attribute = MaterialAttribute.objects.get(id=attr_id)
            instance.attributes.add(attribute)
        
        instance.save()
        return instance

Объяснение

MaterialAttributeSerializer:

Этот сериализатор прост, он сериализует экземпляры MaterialAttribute в формат JSON с полями id и name.

MaterialTypeSerializer:

Для списков/получения он переопределяет to_representation, чтобы обеспечить пользовательскую сериализацию, в которой атрибуты представляются в виде списка словарей ({"id": 1, "name": "Format"}). Для создания/обновления он переопределяет методы create и update для обработки отношения ManyToManyField. При создании или обновлении MaterialType вы можете передавать атрибуты в виде списка идентификаторов MaterialAttribute.

Надеюсь, это поможет!

from rest_framework import serializers
from .models import Article, Tag


class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ("id", "name")


class ArticleSerializer(serializers.ModelSerializer):

    tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all())

    class Meta:
        model = Article
        fields = ("id", "title", "tags")
        
    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation["tags"] = TagSerializer(instance.tags.all(), many=True).data
        return representation

Я думаю, что этот пример работает фактически так же.

Вернуться на верх