SlugRelatedField queryset

匿名 (未验证) 提交于 2019-12-03 01:22:02

问题:

I am struggling to figure out the queryset for SlugRelatedField. My data is such that I have a bunch of Object instances that belong to a Project. A project has a unique 'top' Object. Objects can have the same name only if they below to different Projects.

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') 

What is my queryset going to look like for top if I want to find only only the one Object that belongs to the correct Project? In other words, how to deserialize this:

[{     'name' : 'Project1',     'objs' : [{         'name': 'One'     }],     'top': 'One' }, {     'name' : 'Project2',     'objs' : [{         'name': 'One'     }],     'top': 'One'     <-- This should point to One under Project2, not One under Project1 }] 

回答1:

I have a solution that solves this problem in my case, which I will try to explain here.

The problem, abstracted:

Suppose I have a hierarchy with Foo as the top-level objects, each associated with several Bars:

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

Then I can use SlugRelatedField trivially for read only serializations of Foo, by which I mean the serializer:

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

will produce serializations like:

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

However, this is read only. To allow writing, I have to provide a queryset class attribute outside of any methods. The problem is, because we have a Foo->Bar hierarchy, we don't know what the queryset is outside of any request. We would like to be able to override a get_queryset() method, but none seems to exist. So we can't use SlugRelatedField. What horribly hacky way can we fix it?

My Solution:

First, add an @property to the Foo model and put this property in the serializer:

In models.py:

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

In serializers.py:

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

This allows for the bar texts to be serialized as before, but we still can't write (we can try - the framework won't reject it but it will hit an exception when trying to save the bar_texts attribute of a Foo)

So, the hacky part - fix perform_create() in the Foo list view.

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) 

I hope that makes sense. It certainly works for me, but I have get to find any glaring bugs with it



回答2:

I was just revisiting my own question on this topic when I was lead back to here, so here's a way of achieving this (I think).

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') 

When you go to deserialize the data, it would search for objects that belong to the correct project defined on the serializer.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!