SlugRelatedField查询集

时间:2015-06-04 00:22:51

标签: django-rest-framework

我正在努力弄清楚SlugRelatedField的查询集。 我的数据是这样的,我有一堆属于Object的{​​{1}}个实例。项目有一个独特的“顶级”ProjectObject只有在Object以下才能使用相同的名称。

Project

如果我只想找到属于正确的class Object(models.Model): project = models.ForeignKey('Project', null=False, related_name='objs') name = models.TextField(null=False, db_index=True) .... class Meta: index_together = unique_together = ('project', 'name') class Project(models.Model): user = models.ForeignKey(get_user_model(), null=False, related_name='+') name = models.TextField(null=False) top = models.OneToOneField(Object, null=True, related_name='+') .... class ObjectSerializer(NonNullSerializer): class Meta: model = Object fields = ('name',) class ProjectSerializer(NonNullSerializer): objs = ObjectSerializer(many=True, required=False) top = serializers.SlugRelatedField(slug_field='name', queryset=Object.objects.filter(????)) class Meta: model = Project fields = ('id', 'name', 'objs', 'top') 的{​​{1}},那么top的查询集会是什么样的?换句话说,如何反序化:

Object

2 个答案:

答案 0 :(得分:2)

我有一个解决方案可以解决我的问题,我将在这里解释一下。

问题,抽象:

假设我有一个Foo层次结构作为顶级对象,每个对象与多个Bar s关联:

class Foo(Model):
    pass

class Bar(Model):
    bar_text = CharField()
    foo = ForeignKey(Foo, related_name='bars')

然后我可以将SlugRelatedField简单地用于Foo的只读序列化,我指的是序列化器:

class FooSerializer(ModelSerializer):
     bars = serializers.SlugRelatedField(slug_field='bar_text', 
                                         many=True, read_only=True)
     class Meta:
         model = Foo
         fields = ('bars',)

将生成如下序列化:

{ 'bars' : [<bar_text>, <bar_text>, ...] }

但是,这是只读的。为了允许写入,我必须在任何方法之外提供queryset类属性。问题是,因为我们有Foo->Bar层次结构,所以我们不会知道查询集在任何请求之外的内容。我们希望能够覆盖get_queryset()方法,但似乎没有方法存在。所以我们无法使用SlugRelatedField。我们可以修复什么可怕的黑客方式?

我的解决方案:

首先,将@property添加到Foo模型并将此属性放在序列化程序中:

models.py

class Foo(Model):
    @property
    def bar_texts(self):
        return [bar.bar_text for bar in self.bars.all()]

serializers.py

class FooSerializer(ModelSerializer):
     class Meta:
         model = Foo
         fields = ('bar_texts',)

这允许条形文本像以前一样被序列化,但是我们仍然无法写入(我们可以尝试 - 框架不会拒绝它但是在尝试保存bar_texts时会遇到异常Foo)的属性

所以,hacky部分 - 在perform_create()列表视图中修复Foo

class FooList:
    def perform_create(self, serializer):
        # The serializer contains the bar_text field, which we want, but doesn't correspond
        # to a writeable attribute of Foo. Extract the strings and save the Foo. Use pop with a default arg in case bar_texts isn't in the serialized data
        bar_texts = serializer.validated_data.pop('bar_texts', [])

        # Save the Foo object; it currently has no Bars associated with it
        foo = serializer.save()

        # Now add the Bars to the database
        for bar_text in bar_texts:
            foo.bars.create(bar_text=bar_text)

我希望这是有道理的。它肯定适合我,但我可以找到任何明显的错误

答案 1 :(得分:1)

当我回到这里时,我只是在重新审视自己关于这个主题的问题,所以这是实现这一目标的一种方式(我认为)。

class ObjectSerializer(NonNullSerializer):
    class Meta:
        model = Object
        fields = ('name',)

class TopSerializerField(SlugRelatedField):
    def get_queryset(self):
        queryset = self.queryset
        if hasattr(self.root, 'project_id'):
            queryset = queryset.filter(project_id=project_id)
        return queryset

class ProjectSerializer(NonNullSerializer):

    def __init__(self, *args, **kwargs):
        self.project_id = kwargs.pop('project_id')
        super().__init__(*args, **kwargs)

    # I've needed this workaround for some cases...
    # def __new__(cls, *args, **kwargs):
    #    """When `many=True` is provided then we need to attach the project_id attribute to the ListSerializer instance"""
    #    project_id = kwargs.get('project_id')
    #    serializer = super(ProjectSerializer, cls).__new__(cls, *args, **kwargs)
    #    setattr(serializer, 'project_id', project_id)
    #    return serializer

    objs = ObjectSerializer(many=True, required=False)
    top = TopSerializerField(slug_field='name', queryset=Object.objects.all())

    class Meta:
        model = Project
        fields = ('id', 'name', 'objs', 'top')

当你去反序列化数据时,它会搜索属于序列化程序中定义的正确项目的对象。