如何在 ModelSerializer 的 create() 中使用嵌套序列化器而不使嵌套序列化器 read_only=False

问题描述 投票:0回答:1

我有点进退两难。任务是接收一个 json,其中包含有关兔子窝和出生兔子的一些信息。我得到了一个带有 Throw 模型的 ThrowInfoSerializer(Throw = Litter,非英语国家)。在 ThrowInfoSerializer 的创建过程中,我想保存一个新的 Litter 和相应的新 Bunnies,因此我调用 new_bunnies = valid_data.pop('new_bunnies') 并希望从 valid_data 中提取新的 Bunny 数据。

问题是:由于新的 Bunny 数据存储在链接到 Bunny 模型的另一个 ModelSerializer 中,并且是 BunnySerializer(many=True, read_only=True) read_only,因此它在 create() 内的 valid_data 中不可用。

但是如果我想让 new_bunnies read_only=False 以使 create() 内的 valid_data 的信息部分成为我收到此错误:

AttributeError: Got AttributeError when attempting to get a value for field `new_bunnies` on serializer `ThrowInfoSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Throw` instance.
Original exception text was: 'Throw' object has no attribute 'new_bunnies'.

这是有道理的,因为 ThrowInfoSerializer 与 Throw 模型相关联。

我无法真正制作另一个序列化器并将 ThrowInfoSerializer 和 BunnySerializers 放在同一级别,因为我仍然需要 ThrowInfoSerializer 的 Throw Info 来保存新的兔子。

Throw = Litter,我们搞砸了翻译

代码如下: 序列化器.py

from rest_framework import serializers
from rest_framework.serializers import Serializer, ModelSerializer, SerializerMethodField

from bunny_service.bunny_utils import getStudBookKeeperOfUser
from bunny_service.models import Throw, Bunny

class BunnySerializer(ModelSerializer):
    sex = serializers.CharField()
    class Meta:
        model = Bunny
        fields = ['sex', 'RID', 'COLID']

class ParentBunnySerializer(ModelSerializer):
    class Meta:
        model = Bunny
        fields = ['RID', 'COLID', 'l_ear', 'r_ear', 'UID_owner']

    def validate(self, attrs):
         # Check if a Bunny with the given attributes exists
        if not Bunny.objects.filter(
                RID=attrs.get('RID'),
                COLID=attrs.get('COLID'),
                l_ear=attrs.get('l_ear'),
                r_ear=attrs.get('r_ear'),
                UID_owner=attrs.get('UID_owner')
        ).exists():
            raise serializers.ValidationError("A bunny with those properties does not exist")
        return attrs


class ThrowInfoSerializer(ModelSerializer):
    count = SerializerMethodField()
    remaining = SerializerMethodField()
    new_bunnies = BunnySerializer(many=True)


    BID_buck = ParentBunnySerializer()
    BID_doe = ParentBunnySerializer()

    class Meta:
        model = Throw
        fields = ['thrown_on', 'covered_on', 'death_count', 'BID_buck', 'BID_doe', 'count', 'remaining', 'new_bunnies']

    def get_count(self, instance):
        return instance.bunny_set.count()

    def get_remaining(self, instance):
        return self.get_count(instance) - instance.death_count

    def create(self, validated_data, **kwargs):
        print(validated_data)
        new_bunnies = validated_data.pop('new_bunnies')

        BID_buck = Bunny.objects.get(**validated_data.pop('BID_buck', None))
        BID_doe = Bunny.objects.get(**validated_data.pop('BID_doe', None))

        validated_data['BID_buck'] = BID_buck
        validated_data['BID_doe'] = BID_doe
        validated_data['UID_stud_book_keeper'] = getStudBookKeeperOfUser(self.context['user'])

        throw = Throw.objects.create(**validated_data)

        print(new_bunnies)
        for bunny in new_bunnies:
            Bunny.objects.create(
                **bunny,
                TID=throw,
                UID_owner=self.context['user'],
                CID=self.context['user'].CID_breeding_club,
                l_ear="TO BE IMPLEMENTED", #TODO: implement
                r_ear="TO BE IMPLEMENTED"
            )

        print(Bunny.objects.all())
        return throw

模型.py

class Bunny(models.Model):
    l_ear = models.CharField(max_length=50)
    r_ear = models.CharField(max_length=50)
    sex = models.CharField(max_length=1, choices=[('M', 'Male'), ('F', 'Female')])
    tattooed_on = models.DateTimeField(null=True)

    CID = models.ForeignKey(
        Club,
        on_delete=models.PROTECT,
        blank=False
    )
    UID_owner = models.ForeignKey(
        "auth_service.User",
        on_delete=models.PROTECT,
        blank=False,
        related_name='bunny_owners'
    )
    TID = models.ForeignKey(
        "bunny_service.Throw",
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
    RID = models.ForeignKey(
        "bunny_service.Race",
        on_delete=models.PROTECT,
        blank=False
    )
    COLID = models.ForeignKey(
        "bunny_service.Color",
        on_delete=models.PROTECT,
        blank=False
    )
    UID_tattoo_master = models.ForeignKey(
        "auth_service.User",
        on_delete=models.PROTECT,
        blank=False,
        related_name='bunny_tattoo_master',
        null=True
    )

    def __str__(self):
        return self.l_ear + " / " + self.r_ear


class Throw(models.Model):
    covered_on = models.DateTimeField()
    thrown_on = models.DateTimeField()
    death_count = models.IntegerField()
    reported_on = models.DateTimeField(null=True, blank=True)

    BID_buck = models.ForeignKey(  # buck = Rammler
        Bunny,
        on_delete=models.PROTECT,
        related_name='bunny_buck'
    )

    BID_doe = models.ForeignKey(  # doe = Häsin
        Bunny,
        on_delete=models.PROTECT,
        related_name='bunny_doe'
    )
    UID_stud_book_keeper = models.ForeignKey(
        User,
        on_delete=models.PROTECT,
        blank=False
    )

    def __str__(self):
        return str(self.BID_buck.UID_owner) + " / "+ str(self.thrown_on)


class Color(models.Model):
    name = models.CharField(max_length=100)
    abbreviation = models.CharField(max_length=50)

    def __str__(self):
        return self.name


class Race(models.Model):
    name = models.CharField(max_length=100)
    abbreviation = models.CharField(max_length=50)
    SID = models.ForeignKey(
        "Section",
        on_delete=models.PROTECT,
        blank=False
    )

    def __str__(self):
        return self.name

class User(AbstractUser):
    """
    Custom KEZ User supporting email authentication instead of username
    """
    email = models.EmailField(unique=True, null=False, blank=False)
    username = None

    first_name = models.CharField(max_length=50, null=True, blank=True)
    last_name = models.CharField(max_length=50, null=False, blank=False)
    date_of_birth = models.DateField(null=True, blank=True)
    CID_breeding_club = models.ForeignKey(
        to='club_service.Club',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )

    # Address
    street = models.CharField(max_length=50, null=True, blank=True)
    zip_code = models.PositiveSmallIntegerField(null=True, blank=True)  # TODO: validate length
    city = models.CharField(max_length=50, null=True, blank=True)
    state = models.CharField(max_length=50, null=True, blank=True)
    country = models.CharField(max_length=50, null=True, blank=True)

    telephone = PhoneNumberField(null=True, blank=True)

    is_active = models.BooleanField(default=False)

    objects = UserManager()
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['last_name']


    def __str__(self):
        return self.email

我希望这是有道理的

python django django-rest-framework frameworks backend
1个回答
0
投票

您收到的错误

Original exception text was: 'Throw' object has no attribute 'new_bunnies'.
是因为确实不存在名为
new_bunnies
的字段。因此,要解决此问题,您可以:

  • new_bunnies
    更改为模型上实际存在的字段名称。

  • 定义指定什么字段

    new_bunny
    参考:

new_bunnies = UUIDField(source='the.referenced.field.in.the.model)

如果需要在源字段中导航关系,请用点

.
分隔它们。当然,将
UUIDField
更改为您要接收的数据的字段类型。

  • 或者,如果
    new_bunnies
    不是特定模型字段,您可以像第二个选项一样简单地定义它,但没有
    source
    字段。
new_bunnies = UUIDField()

通过这种方式,您可以为序列化器定义一个字段,尽管它在模型中不存在。但在这种方法中,您将需要一个自定义

create()
函数来处理该新字段发生的情况,因为它不是模型的一部分,并且 django 不知道如何处理它。

希望这对您有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.