В Django, как мне создать сериализатор, который будет автоматически генерировать первичный ключ для члена моей модели? - PullRequest
0 голосов
/ 17 июня 2020

Я использую Django 3, структуру Django REST и Python 3.7. Имею следующие модели. Обратите внимание, что второй зависит от первого ...

class ContactMethod(models.Model):
    class ContactTypes(models.TextChoices):
        EMAIL = 'EMAIL', _('Email')
        PHONE = 'PHONE', _('Phone')

    type = models.CharField(
        null=False,
        max_length=5,
        choices=ContactTypes.choices,
    )
    phone = PhoneNumberField(null=True)
    email = models.EmailField(null=True)

    class Meta:
        unique_together = ('phone', 'email',)

...

class Coop(models.Model):
    objects = CoopManager()
    name = models.CharField(max_length=250, null=False)
    types = models.ManyToManyField(CoopType)
    addresses = models.ManyToManyField(Address)
    enabled = models.BooleanField(default=True, null=False)
    phone = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_phone')
    email = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_email')
    web_site = models.TextField()

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

class ContactMethodSerializer(serializers.ModelSerializer):

    class Meta:
        model = ContactMethod
        fields = ['type', 'phone', 'email']

    def create(self, validated_data):
        contact_method = ContactMethod.objects.create(**validated_data)
        return contact_method

    def to_internal_value(self, data):
        if type(data) == dict:
            contatcmethod, created = CoopType.objects.create(**data)
            # Replace the dict with the ID of the newly obtained object
            data = contactmethod.pk
        return super().to_internal_value(data)
...
class CoopSerializer(serializers.ModelSerializer):
    types = CoopTypeSerializer(many=True)
    addresses = AddressTypeField(many=True)

    class Meta:
        model = Coop
        fields = ['id', 'name', 'types', 'addresses', 'phone', 'enabled', 'email', 'web_site']

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['types'] = CoopTypeSerializer(instance.types.all(), many=True).data
        rep['addresses'] = AddressSerializer(instance.addresses.all(), many=True).data
        return rep

    def create(self, validated_data):
        #"""
        #Create and return a new `Snippet` instance, given the validated data.
        #"""

        coop_types = validated_data.pop('types', {})
        instance = super().create(validated_data)
        for item in coop_types:
            coop_type, _ = CoopType.objects.get_or_create(name=item['name'])  
            instance.types.add(coop_type)

        return instance

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

def test_coop_create(self):
    """ Test coop serizlizer model """
    name = "Test 8899"
    coop_type_name = "Library"
    street = "222 W. Merchandise Mart Plaza, Suite 1212"
    city = "Chicago"
    postal_code = "60654"
    enabled = True
    postal_code = "60654"
    email = EmailContactMethodFactory()
    phone = PhoneContactMethodFactory()
    web_site = "http://www.1871.com"
    state = StateFactory()
    serializer_data = {
        "name": name,
        "types": [
            {"name": coop_type_name}
        ],
        "addresses": [{
            "formatted": street,
            "locality": {
                "name": city,
                "postal_code": postal_code,
                "state_id": state.id
            }
        }],
        "enabled": enabled,
        "phone": {
          "phone": phone
        },
        "email": {
          "email": email
        },
        "web_site": web_site
    }

    serializer = CoopSerializer(data=serializer_data)
    serializer.is_valid()
    assert serializer.is_valid(), serializer.errors
    coop = serializer.save()
    assert coop.name == name
    type_count = 0
    for coop_type in coop.types.all():
        assert coop_type.name == coop_type_name
        type_count = type_count + 1
    assert type_count == 1
    assert coop.addresses.first().locality.name == city
    assert coop.addresses.first().locality.postal_code == postal_code
    assert coop.addresses.first().locality.state.id == state.id
    assert coop.enabled == enabled
    assert coop.phone.phone == phone
    assert coop.email.email == email
    assert coop.web_site == web_site

, но это приводит к ошибке ниже

AssertionError: {'phone': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')], 'email': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')]}

Какой правильный способ настроить мой сериализатор для создания внешних ключей без указать идентификатор?

Изменить: Репозиторий GitHub:

https://github.com/chicommons/maps/tree/master/web

1 Ответ

1 голос
/ 17 июня 2020

По умолчанию rest-framework сериализует отношения (phone, email) с первичными ключами.

Любые отношения, такие как внешние ключи в модели, будут сопоставлены с PrimaryKeyRelatedField. Обратные отношения не включены по умолчанию, если они явно не включены, как указано в документации по отношениям сериализатора.

источник: https://www.django-rest-framework.org/api-guide/serializers/#modelserializer

Обычно вы создаете ContactMethod объекты, один для name (например, с id = 1) и второй для email (id = 2), перед созданием Coop и включением их идентификаторов в нашу полезную нагрузку. Таким образом, это будет выглядеть как

{
    // ...
    "phone": 1, 
    "email": 2,
    // ...
}

В вашем случае вам нужно создать ContactMethod при создании Coop. Вам нужно изменить CoopSerializer, чтобы принимать полезные данные, сериализованные ContactMethodEmailSerializer в email поле и ContactMethodPhoneSerializer в phone поле.

class ContactMethodPhoneSerializer(serializers.ModelSerializer):
    class Meta:
        model = ContactMethod
        fields = ['type', 'phone']
        read_only_fields = ['type']
        extra_kwargs = {'type': {'default': 'PHONE'}}


class ContactMethodEmailSerializer(serializers.ModelSerializer):
    class Meta:
        model = ContactMethod
        fields = ['type', 'email']
        read_only_fields = ['type']
        extra_kwargs = {'type': {'default': 'EMAIL'}}


class CoopSerializer(serializers.ModelSerializer):
    types = CoopTypeSerializer(many=True)
    addresses = AddressTypeField(many=True)
    phone = ContactMethodPhoneSerializer()
    email = ContactMethodEmailSerializer()

С этими сериализаторами ваша тестовая полезная нагрузка должна быть принята.

В методе CoopSerializer.create вам необходимо обработать создание ContactMethod, аналогично тому, что вы сделали с CoopType, вы можете следовать примеру в документации: https://www.django-rest-framework.org/api-guide/serializers/#writable -nested-views

...