在 Django 中,如何创建一个序列化程序来为我的模型成员自动生成主键?

In Django, how do I create a serializer that will auto-generate a primary key for a member of my model?

我正在使用 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

问题是,我不确定如何在不提交主键的情况下创建我的 phone 和电子邮件联系人字段(我希望自动生成)。我创建此测试是为了尝试...

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')]}

设置序列化程序以创建外键而无需指定 ID 的正确方法是什么?

编辑: GitHub 回购:

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

开箱即用,rest-framework 使用主键序列化关系(phoneemail)。

Any relationships such as foreign keys on the model will be mapped to PrimaryKeyRelatedField. Reverse relationships are not included by default unless explicitly included as specified in the serializer relations documentation.

来源:https://www.django-rest-framework.org/api-guide/serializers/#modelserializer

您通常会创建 ContactMethod 个对象,一个用于 name(例如 id=1),第二个用于 email(id=2),然后再创建 [=17] =] 然后将他们的 ID 包含在我们的有效负载中。所以它看起来像

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

在您的情况下,您需要在创建 Coop 时创建 ContactMethod。您需要更改 CoopSerializer 以接受由 email 字段中的 ContactMethodEmailSerializerphone 字段中的 ContactMethodPhoneSerializer 序列化的有效载荷。

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-representations