Django REST框架:反序列化外键失败(多对多)

时间:2020-02-06 09:03:17

标签: python django python-3.x django-rest-framework deserialization

我有以下模型:

models.py:

class Host(models.Model):
    serialnr = models.IntegerField(primary_key=True)
    ...some other fields...

class Event(models.Model):
    id = models.AutoField(primary_key=True)
    hosts = models.ManyToManyField(Host, through='EventHost')
    ...some other fields...

class EventHost(models.Model):
    serialnr = models.ForeignKey(Host, on_delete=models.PROTECT)
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    ...some other fields...
    class Meta:
        unique_together = ("serialnr", "event")

serializers.py:

class EventSerializer(serializers.ModelSerializer):
    class Meta:
        model = Event
        fields = '__all__'

class HostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Host
        fields = '__all__'

class EventHostSerializer(serializers.ModelSerializer):
    event =  EventSerializer(read_only=True)
    serialnr = HostSerializer(read_only=True)

    class Meta:
        model = EventHost
        fields = '__all__'

views.py

class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.order_by('-date')
    serializer_class = EventSerializer

class HostViewSet(viewsets.ModelViewSet):
    queryset = Host.objects.order_by('-serialnr')
    serializer_class = HostSerializer

class EventHostViewSet(viewsets.ModelViewSet):
    queryset = EventHost.objects.order_by('-start_date')
    serializer_class = EventHostSerializer

我正在使用HTTP POST发送以下JSON:

{event: {id: 4}, serialnr: {serialnr: 1234}, other_filed: 20} 

但不是event_id和serialnr_id未反序列化,如日志中所示:

psycopg2.errors.NotNullViolation: null value in column "event_id" violates not-null constraint
DETAIL:  Failing row contains (12, 20, null, null).

我可以使用HTTP GET读取数据,但不能使用POST写入数据。我应该如何构造适当的序列化程序才能使其正常工作?

同样,当我尝试像下面那样发送JSON时,它也会失败:

{event_id: 4, serialnr_id: 1234, other_filed: 20} 

3 个答案:

答案 0 :(得分:2)

由于eventserialnrEventHost上的ForeignKeys,因此您需要发送已经存在的数据,因此我建议使用PrimaryKeyRelatedField来发送数据,从而获得验证您的数据库中不存在的ID。

您将需要发送以下数据: {event: 4, serialnr: 1234, other_filed: 20}

并将您的序列化器更改为此:

from rest_framework.relations import PrimaryKeyRelatedField


class EventHostSerializer(serializers.ModelSerializer):
    event = PrimaryKeyRelatedField(queryset=Event.objects.all())
    serialnr = PrimaryKeyRelatedField(queryset=Host.objects.all())

    class Meta:
        model = EventHost
        fields = '__all__'

    # add this(if needed) to get event/serialnr representation instead of primary keys 
    # might be usefull for you when you retrieve the object back (in list/retrieve operations)
    def to_representation(self, instance):
        ret = super().to_representation(instance)
        ret['event'] = EventSerializer(context=self.context).to_representation(instance.event)
        ret['serialnr'] = HostSerializer(context=self.context).to_representation(instance.serialnr)
        return ret

以后编辑:

我发现有一个名为django-extra-fields的库,可以更好地完成此任务。

https://github.com/Hipo/drf-extra-fields#presentableprimarykeyrelatedfield

from drf_extra_fields.relations import PresentablePrimaryKeyRelatedField


class EventHostSerializer(serializers.ModelSerializer):
    event = PresentablePrimaryKeyRelatedField(
        queryset=Event.objects.all(), presentation_serializer=EventSerializer
    )
    serialnr = PresentablePrimaryKeyRelatedField(
        queryset=Host.objects.all(), presentation_serializer=HostSerializer
    )

    class Meta:
        model = EventHost
        fields = '__all__'

答案 1 :(得分:0)

发生这种情况是因为您正在制作EventSerializerHostSerializer, 将传递的值序列化为read_only,这意味着它们仅在存在retrieve动作而不是create

时起作用

解决这个问题的方法就是从您的read_only移除EventHostSerializer矮人

类似于以下内容:

class EventHostSerializer(serializers.ModelSerializer):
    event =  EventSerializer() #read_only=True)
    serialnr = HostSerializer() #read_only=True)

    class Meta:
        model = EventHost
        fields = '__all__'

答案 2 :(得分:0)

我从 EventSerializer HostSerializer 中删除了 read_only = True 参数,并打开了调试功能,以查看问题所在以及现在的HTTP POST请求会尝试创建新的主机和新事件。那不是我所需要的。我只需要引用事件并托管在eventhost表中创建新条目。

只是做笔记。当我使用django shell添加新的eventhost实例时,它会起作用:

>>> from myapp.models import Host, Event, EventHost
>>> from myapp.serializers import HostSerializer, EventSerializer, EventHostSerializer
myeventhost = EventHost.objects.create(event_id=4, serialnr_id=1234, otherparam=20)
>>> serializedmyeventhost = EventHostSerializer(myeventhost)
>>> serializedmyeventhost.data
{'id': 29, 'event': OrderedDict([('id', 4), ...etc