Django REST framework: сбой десериализации внешнего ключа (многие-ко-многим) - PullRequest
3 голосов
/ 06 февраля 2020

У меня есть следующая модель:

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

Я отправляю следующее JSON с HTTP POST:

{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).

I может читать данные с помощью HTTP GET, но не может писать с помощью POST. Как мне создать правильный сериализатор, чтобы он работал?

Также, когда я пытался отправить JSON, как показано ниже, он не работает:

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

Ответы [ 3 ]

2 голосов
/ 06 февраля 2020

Поскольку event и serialnr являются ForeignKeys на EventHost, вам необходимо отправить данные, которые уже существуют, поэтому я предлагаю использовать PrimaryKeyRelatedField таким образом, вы получите подтверждение, если отправите идентификатор, который не существует в вашем база данных.

Вам нужно будет отправить данные следующим образом: {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__'
0 голосов
/ 06 февраля 2020

Я удалил read_only = True параметры из EventSerializer и HostSerializer и включил отладку, чтобы увидеть, что не так, и теперь, что делает мой запрос HTTP POST он пытается создать новый хост и новое событие. Это не то, что мне нужно. Мне нужно просто сослаться на событие и хост, чтобы создать новую запись в таблице Eventhost.

Просто чтобы сделать заметку. Когда я добавляю новый экземпляр Eventhost с оболочкой django, она работает:

>>> 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
0 голосов
/ 06 февраля 2020

Это происходит потому, что вы делаете EventSerializer и HostSerializer, сериализатором передаваемых значений как read_only, что означает, что они работают только при действии retrieve, а не create

* 1007 Чтобы решить эту проблему, просто удалите read_only kwarg из вашего EventHostSerializer

, как показано ниже:

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

    class Meta:
        model = EventHost
        fields = '__all__'
...