Как в Django REST Framework обрабатывать поля Many-to-Many в сериализаторах?

Ответ

Для обработки полей ManyToManyField в сериализаторах Django REST Framework (DRF) существует несколько подходов, выбор которых зависит от желаемого представления данных (ID, строковое представление или вложенный объект).

Основные подходы:

  1. PrimaryKeyRelatedField (по ID) Наиболее простой способ. В запросах и ответах для связанных объектов используются их первичные ключи (ID). Это эффективно и минимизирует объем данных.

    from rest_framework import serializers
    from .models import Author, Book
    
    class BookSerializer(serializers.ModelSerializer):
        # При POST/PUT запросе ожидается список ID авторов: [1, 2, 3]
        authors = serializers.PrimaryKeyRelatedField(
            many=True, 
            queryset=Author.objects.all()
        )
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'authors']
  2. SlugRelatedField (по строковому полю) Используется, когда нужно представлять связанные объекты по уникальному текстовому полю (например, username или name) вместо ID.

    class BookSerializer(serializers.ModelSerializer):
        # Ожидается список имен авторов: ["Author Name 1", "Author Name 2"]
        authors = serializers.SlugRelatedField(
            many=True,
            slug_field='name',
            queryset=Author.objects.all()
        )
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'authors']
  3. Вложенный сериализатор (полное представление) Позволяет включить полную информацию о связанных объектах. Удобно для чтения (GET), но усложняет запись (POST/PUT), так как требует специальной обработки.

    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ['id', 'name']
    
    class BookSerializer(serializers.ModelSerializer):
        # В ответе будет полный список объектов авторов
        authors = AuthorSerializer(many=True, read_only=True) # read_only упрощает реализацию
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'authors']

Запись данных (Create/Update)

При использовании вложенных сериализаторов для записи необходимо переопределить методы .create() или .update() в сериализаторе, чтобы вручную обработать сохранение M2M-связей после создания основного объекта.

Ответ 18+ 🔞

Так, слушай, тут народ часто в ступор входит, когда в DRF надо с полями ManyToMany работать. Типа, ну как их, блядь, сериализовать-то? А вариантов, оказывается, дохуя! Выбирай, как тебе удобнее данные представлять: по ID, по имени или вообще вложенным объектом целиком.

Варианты, которые есть:

  1. PrimaryKeyRelatedField (по ID) Самый простой и тупой, как пробка. В запросах и ответах ты будешь видеть только IDшники связанных объектов. Экономно и быстро, но не очень информативно, если хочешь сразу имя увидеть.

    from rest_framework import serializers
    from .models import Author, Book
    
    class BookSerializer(serializers.ModelSerializer):
        # Когда будешь POST/PUT делать, шли сюда список ID авторов: [1, 2, 3]
        authors = serializers.PrimaryKeyRelatedField(
            many=True, 
            queryset=Author.objects.all()
        )
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'authors']
  2. SlugRelatedField (по какому-то текстовому полю) А вот это уже интереснее. Если ID — это для машин, то тут можно показывать людям что-то понятное, например, имя автора. Главное, чтобы поле было уникальным, а то пиздец начнётся.

    class BookSerializer(serializers.ModelSerializer):
        # Теперь ждём список имён: ["Лев Толстой", "Фёдор Достоевский"]
        authors = serializers.SlugRelatedField(
            many=True,
            slug_field='name', # Указываешь, по какому полю цепляться
            queryset=Author.objects.all()
        )
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'authors']
  3. Вложенный сериализатор (полный разворот) Ну а это, блядь, для красоты и полного счастья. В ответе получишь не просто ID или имя, а целый объект автора со всеми его полями. Для чтения (GET) — охуенно. Для записи (POST/PUT) — уже сложнее, потому что DRF сам с этим не справится, придётся руки марать.

    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ['id', 'name']
    
    class BookSerializer(serializers.ModelSerializer):
        # Вуаля! В ответе полный список авторов как объектов
        authors = AuthorSerializer(many=True, read_only=True) # read_only ставим, чтобы не ебаться с записью пока что
    
        class Meta:
            model = Book
            fields = ['id', 'title', 'authors']

Про запись данных (Create/Update) — отдельная песня

Если выбрал вложенный сериализатор и хочешь через него же и создавать/обновлять, то готовься к ручному труду. Придётся переопределять методы .create() и .update() в сериализаторе, потому что после создания основной модели (книги) нужно вручную, блядь, прописывать все эти M2M-связи с авторами. Иначе нихуя не сохранится.