Публикация отношения "один ко многим"

Я пытаюсь предоставить API для моей модели Django через структуру Django REST.

У меня есть объект Observation. Наблюдение может содержать несколько наблюдаемых вещей. Поэтому я представил это так:

class Observation(models.Model):

    photo_file = models.ImageField( upload_to=img_dir,   blank=True, null=True )
    titestamp = models.DateTimeField(blank=True, null=True)
    latitude = models.FloatField()
    longitude = models.FloatField()


class ObservedThing(models.Model):
    thing = models.ForeignKey(Thing) # the thing being observed
    observation = models.ForeignKey(Observation, related_name='observed_thing')
    value = models.FloatField()

Насколько я понимаю, это отношение один ко многим.

Теперь у меня есть представление API:

class ObsvList(generics.ListCreateAPIView):
    """
    API endpoint that represents a list of observations.
    """
    model = Observation
    serializer_class = ObsvSerializer

и соответствующий сериализатор:

class ObsvSerializer(serializers.ModelSerializer):

    observed_thing = serializers.PrimaryKeyRelatedField(many=True)

    class Meta:
        model = Observation

Что мне нужно сделать, чтобы иметь возможность опубликовать наблюдение с несколькими обнаруженными вещами? Я не могу разобраться. Большое спасибо.


person gozzilli    schedule 04.03.2013    source источник


Ответы (3)


(ответ более или менее скопирован из другого похожий, но менее понятный вопрос)

Для создания нескольких связанных объектов в одном POST требуются доступные для записи вложенные сериализаторы, которые пока недоступны.

Полная поддержка — это работа в процессе, но пока (хакерское) решение состоит в том, чтобы переопределить метод create в представлении в каждом случае:

class FooListCreateView(ListCreateAPIView):
    model = Foo
    serializer_class = FooSerializer

    def create(self, request, *args, **kwargs):
        data=request.DATA

        f = Foo.objects.create()

        # ... create nested objects from request data ...  

        # ...
        return Response(serializer.data, 
                        status=status.HTTP_201_CREATED,
                        headers=headers)

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

Другой вариант — создать связанные объекты Observation по отдельности с помощью отдельных POST и использовать PrimaryKeyRelatedField или HyperlinkedRelatedField, чтобы сделать ассоциации в окончательном ObservedThing POST.

person Rob Agar    schedule 05.03.2013
comment
Ага, это то, что я искал. Большое спасибо, ответ принят. - person gozzilli; 05.03.2013

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

#parent model class
class Parent(models.Model):

    id = models.AutoField(primary_key=True)
    field = models.CharField(max_length=45)

    class Meta:
        managed = False
        db_table = 'parent'

затем дочерний класс:

#child model class
class Child(models.Model):

    id = models.AutoField(primary_key=True)
    field = models.CharField(max_length=45)
    parent = models.ForeignKey(Parent, related_name='children')

    class Meta:
        managed = False
        db_table = 'child'

Мне пришлось определить сериализаторы, так как я не хотел создавать URL-адрес, доступный для маршрутизатора, для прямого управления дочерними объектами, но я хотел создать их через ModelViewSet родительского ModelViewSet, это то, что мне нужно:

class ChildSerializer(serializers.ModelSerializer):
    class Meta:
        model = Child
        read_only_fields = ('id',)

class ParentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        read_only_fields = ('id',)

class ParentSerializerNested(ParentSerializer):
    children = ChildSerializer(many=True)

Затем я был готов создать ModelViewSet, переопределив/расширив миксины создания/обновления и сделав его универсальным, чтобы повторно использовать его для других случаев:

class ParentChildViewSet(viewsets.ModelViewSet):

    def create(self, request, *args, **kwargs):
        serializer = self.serializer_parent(data=request.DATA,
                                            files=request.FILES)

        try:
            if serializer.is_valid():
                with transaction.commit_on_success():
                    self.pre_save(serializer.object)
                    parent = serializer.save(force_insert=True)
                    self.post_save(parent, created=True)

                    # need to insert children records
                    for child in request.DATA[self.child_field]:
                        child[self.parent_field] = parent.id
                        child_record = self.serializer_child(data=child)
                        if child_record.is_valid():
                            child_record.save(force_insert=True)
                        else:
                            raise ValidationError('Child validation failed')

                    headers = self.get_success_headers(serializer.data)

                    serializer.data[self.child_field] = self.serializer_child(
                        self.model_child.objects.filter(
                            **{self.parent_field: parent.id}).all(),
                            many=True).data
                    return Response(serializer.data,
                                    status=status.HTTP_201_CREATED,
                                    headers=headers)
        except ValidationError:
            pass
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Поэтому я могу повторно использовать его для каждого случая вложенных отношений, который у меня есть в моем приложении, например:

class ParentViewSet(ParentChildViewSet):
    child_field = 'children'
    parent_field = 'parent'
    model = Parent
    model_child = Child
    serializer_class = ParentSerializerNested
    serializer_parent = ParentSerializer
    serializer_child = ChildSerializer

И, наконец, маршрутизация:

router = routers.DefaultRouter()
router.register(r'parents', ParentViewSet)

Работает как часы!

person gigaDIE    schedule 22.05.2014
comment
Спасибо за ответ, но здесь вы используете ModelViewSet , я думаю, было бы круче, если бы вы использовали genericView. - person Lutaaya Huzaifah Idris; 23.07.2020

thing = models.ManyToManyField('Thing')

вам нужно использовать отношение «многие ко многим» для создания временной таблицы, в которой будут храниться ключи и автоматически связываться данные.

person Mayank Saxena    schedule 05.03.2013
comment
Спасибо, но моя промежуточная таблица ObservedThing. ManyToManyField работает только в том случае, если у вас нет никакой другой информации, связанной с отношениями. Что я могу сделать, так это иметь thing = models.ManyToManyField(Thing, through='ObservedThing'), но это все еще не решает мою первоначальную проблему. - person gozzilli; 05.03.2013