我正在努力弄清楚SlugRelatedField的查询集。
我的数据是这样的,我有一堆属于Object
的{{1}}个实例。项目有一个独特的“顶级”Project
。 Object
只有在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
答案 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')
当你去反序列化数据时,它会搜索属于序列化程序中定义的正确项目的对象。