如果相关字段尚不存在,请创建相关字段

时间:2018-01-21 20:45:40

标签: python django rest django-rest-framework

我有以下两个Django模型类(名称已被更改以保护不那么无辜):

class Foo(models.Model)
    foo = models.CharField(max_length=25)


class Bar(models.Model):
    foo = models.ForeignKey(Foo)

每个模型的Django Rest Framework序列化器:

class FooSerializer(serializers.ModelSerializer):
    class Meta:
        model = Foo
        fields = ('foo',)


class BarSerializer(serializers.ModelSerializer):
    class Meta:
        model = Bar
        fields = ('foo',)

路线:

urlpatterns = [
    url(r'^foo/', ModelListView.as_view(model_class=Foo, serializer_class=FooSerializer), name='foo'),
    url(r'^bar/', ModelListView.as_view(model_class=Bar, serializer_class=BarSerializer), name='Bar'),
]

查看:

class ModelListView(ListCreateAPIView):
    model_class = None
    serializer_class = None

    def create(self, request, *args, **kwargs):
        data = JSONParser().parse(request)
        serializer = self.serializer_class(data=data, many=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

为了说明/bar路由,Bar模型及其序列化器的用法,我写了一个测试。我希望能够指定字段foo的字符串值。 POST请求应使用Foo的现有实例(如果已存在)。否则它应该创建它。

class BarTest(UnitTest):
    def setUp(self):
        self.model_class = Bar
        self.fields = [{'foo': 'foo1'},
                       {'foo': 'foo2'}]
        self.url = reverse('bar')

    def test_post(self):
        data = JSONRenderer().render(self.fields)
        response = self.client.post(self.url, data=data, content_type='application/json')
        self.assertEqual(HTTP_201_CREATED, response.status_code)
        result = response.json()
        self.assertEqual(self.fields, result)

我在Django REST Framework文档中阅读了Serializer Relations。似乎StringRelatedFieldSlugRelatedField可能适合我的用例。我不明白这两者之间的区别,也不了解如何使用它们来获得我想要的行为。其中任何一个都可以用于这些目的吗?如果是这样,我该如何使用它们?如果没有,有哪些替代解决方案?

2 个答案:

答案 0 :(得分:1)

根据我的个人经验,我发现很多时候使用通用DRF视图和自动序列化器可能有点过于严格。如果您使用SlugRelatedField,它将尝试使用给定参数查找Foo项,如果尚不存在,则会失败。

顺便说一句,StringRelatedFieldSlugRelatedField之间的区别在于前者是只读的,并且总是返回对象的字符串表示,而后者更灵活,可以用于写同样。

要实现您想要的目标,您可以尝试以下内容:

from rest_framework.generics import ListCreateAPIView

# don't need to subclass from ModelSerializer because we won't use
# its `create`/`update` method or automatic field generation
class CreateBarSerializer(serializers.Serializer):
    foo = serializers.CharField()


class BarListView(ListCreateAPIView):
    model_class = Bar
    serializer_class = BarSerializer

    def create(self, request, *args, **kwargs):
        data = JSONParser().parse(request)
        serializer = CreateBarSerializer(data=data, many=True)
        if serializer.is_valid():
            validated_data = serializer.validated_data
            # create a Foo with given name if it doesn't exist yet
            foo = Foo.objects.get_or_create(
                foo=validated_data['foo']
            )
            Bar.objects.create(foo=foo)
            return Response(serializer.data, status=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

答案 1 :(得分:0)

从@ dukebody的回答中,我了解了Model.objects.get_or_create()。对序列化器的一些进一步研究表明,我可以覆盖to_internal_value()

类BarSerializer(serializers.ModelSerializer):     类Meta:         model = Bar         fields =(' foo',)

def to_internal_value(self, data):
    Foo.objects.get_or_create(foo=data['foo'])
    return super().to_internal_value(data)

显然,我原始问题中的代码是人为的。在实际项目中,Bar对应项还有几个字段,因此该解决方案利用ModelSerializer来管理原始字段。