Создание сериализатора для работы с отношениями модели - PullRequest
0 голосов
/ 05 мая 2020

Я новичок в Django Rest Framework и хотел понять, какова общепринятая практика написания сериализаторов, которые работают с вложенными отношениями.

Скажем, у меня есть модели с именами Client и Invoice ( это просто иллюстративный пример):

class Client(models.Model)
    name = models.CharField(max_length=256)    


class Invoice(models.Model)
    client = models.ForeignKey(Client)
    date = models.DateTimeField()
    amount = models.DecimalField(max_digits=10, decimal_places=3)

Я хочу создать сериализатор для Client, который поддерживает следующие варианты использования:

  1. Создать Client
  2. Когда я создаю Invoice, обращайтесь к Client, используя его id.

Допустим, я использую эту реализацию:

class ClientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Client
        fields = ['id', 'name']


class InvoiceSerializer(serializers.ModelSerializer):
    client = ClientSerializer()

    class Meta:
        model = Invoice
        fields = ['id', 'client', 'date', 'amount']

    def create(self, data):
        client = Client.objects.get(pk=data['client']['id'])
        invoice = Invoice(client=client, 
                          date=datetime.fromisoformat(data['date']),
                          amount=Decimal(data['amount']))
        invoice.save()

With этот код, если я попытаюсь создать Invoice, мне нужно, чтобы объект client в данных POST также содержал name. Не существует конфигурации поля name (read_only=True, write_only=True, required=False), которое позволяет мне создавать и читать Client, а также не требуется при создании Invoice.

Как это решить?

  • Есть ли общепринятая практика, что запрос все равно включает поле name?
  • Можно ли каким-то образом создать такие вложенные модели? /api/Client/<id:client_id>/Invoice
  • Создаем ли мы несколько классов Serializer для каждой модели - один для своего собственного представления, а другой для использования в наборах представлений других моделей?

Спасибо!

1 Ответ

1 голос
/ 05 мая 2020

Это общепринятая практика, но у нее есть свои преимущества и недостатки. Фактическая передовая практика зависит от ваших реальных потребностей. Здесь, как вы предложили, при создании счета вам также необходимо отправить имя клиента в запросе, что не обязательно. Чтобы преодолеть эту потребность, одна возможная практика может быть следующей:

class ClientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Client
        fields = ['id', 'name']


class InvoiceSerializer(serializers.ModelSerializer):
    client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())

    class Meta:
        model = Invoice
        fields = ['id', 'client', 'date', 'amount']

При таком подходе вы включаете только идентификатор клиента в сериализатор. При таком подходе вам нужно будет отправить только идентификатор клиента в реквизите, и вам не нужно писать собственный метод создания в сериализаторе. Недостаток такого подхода: при перечислении счетов у вас нет имени клиента. поэтому, если вам нужно отображать имя клиента при отображении счета-фактуры, нам нужно немного улучшить это решение:

class InvoiceSerializer(serializers.ModelSerializer):
    client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())
    client_details = ClientSerializer(source='client', read_only=True)

    class Meta:
        model = Invoice
        fields = ['id', 'client', 'client_details', 'date', 'amount']

При таком подходе мы добавили поле только для чтения, client_details , который хранит данные в клиентском серилайзере. Поэтому для операций записи мы используем поле client , которое является только идентификатором, а для чтения деталей о клиенте мы используем поле client_details .

Другой подход может быть определение отдельного клиентского сериализатора, который будет использоваться как дочерний сериализатор только в InvoiceSerializer:

class ClientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Client
        fields = ['id', 'name']


class InvoiceClientSerializer(serializers.ModelSerializer):
    name = serializers.CharField(read_only=True)

    class Meta:
        model = Client
        fields = ['id', 'name']

class InvoiceSerializer(serializers.ModelSerializer):
    client = InvoiceClientSerializer()

    class Meta:
        model = Invoice
        fields = ['id', 'client', 'date', 'amount']

    def create(self, data):
        client = Client.objects.get(pk=data['client']['id'])
        invoice = Invoice(client=client, 
                          date=datetime.fromisoformat(data['date']),
                          amount=Decimal(data['amount']))
        invoice.save()

В этом подходе мы определили специальный клиентский сериализатор для использования только в InvoiceSerializer, у которого есть поле имени только для чтения. Таким образом, при создании / обновлении счета-фактуры вам не нужно отправлять имя клиента, но при перечислении счетов-фактур вы получите имя клиента. Преимущество этого подхода перед подходом перед использованием, нам не нужно использовать два отдельных поля для поля клиента для записи и чтения деталей.

Что касается вашего второго вопроса, DRF не поддерживает его из коробки, но вы можете взглянуть на этот пакет, который предоставляет эту функциональность и указан в собственной документации DRF: https://github.com/alanjds/drf-nested-routers

...