awidgery
awidgery

Reputation: 1916

Django Request Framework 'ManyRelatedField' object has no attribute 'queryset' when modifying queryset in get_fields

I get the following error on GET /api/stories/169/ in StorySerializer, noted below in a comment:

AttributeError at /api/stories/169/
'ManyRelatedField' object has no attribute 'queryset'

Upon inspection of the object, I discovered that if I change the line from...

fields['feature'].queryset = fields['feature'].queryset.filter(user=user)

to

fields['photos'].child_relation.queryset = fields['photos'].child_relation.queryset.filter(user=user)

...it seems to work. But this approach is undocumented, and I'm sure isn't the right way to do it.

I have a these models:

class Story(CommonInfo):
    user = models.ForeignKey(User)
    text = models.TextField(max_length=5000,blank=True)
    feature = models.ForeignKey("Feature", blank=True, null=True)
    tags = models.ManyToManyField("Tag")

class Feature(CommonInfo):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=50)

class Photo(CommonInfo):
    user = models.ForeignKey(User)
    image = ImageField(upload_to='photos')
    story = models.ForeignKey("Story", blank=True, null=True, related_name='photos', on_delete=models.SET_NULL)

And a StorySerializer:

class StorySerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.CharField(read_only=True) 
    comments = serializers.HyperlinkedRelatedField(read_only=True, view_name='comment-detail', many=True)

    def get_fields(self, *args, **kwargs):
        user = self.context['request'].user
        fields = super(StorySerializer, self).get_fields(*args, **kwargs)

        ## Restrict the options that the user can pick to the Features
        ## and Photos that they own
        # This line works:
        fields['feature'].queryset = fields['feature'].queryset.filter(user=user)

        # This line throws the error:           
        fields['photos'].queryset = fields['photos'].queryset.filter(user=user)

        return fields

    class Meta:
        model = Story
        fields = ('url', 'user', 'text', 'comments', 'photos', 'feature', 'tags')

What am I doing wrong? I feel like it's something to do with the direction of the ForeignKey relationships.

Upvotes: 16

Views: 3766

Answers (3)

Kracekumar
Kracekumar

Reputation: 20419

Two important points.

  1. When a queryset is filtered from the view for a user, all the foreign key objects retrieval will yield only the objects for the particular user. So no need to filter for the user inside get_fields.

    class StoryList(generics.ListAPIView):
        serializer_class = StorySerializer
    
         def get_queryset(self):
             # consider there is login check before code is reaching here
             # since this filtered by the user and any susbquent 
             # foreign key objects will belong only to this user
             return Story.objects.filter(user=self.request.user)
    
  2. Once the filtering for a user happens, then you can use another serializer or SerializerMethodField to construct the data accordingly. The below code should work for your case.

     class UserSerializer(serializers.HyperlinkedModelSerializer):
         class Meta:
             # Allow only url and id
             fields = ['id', 'url']
             extra_kwargs = {'url': {'view_name': 'user-detail'}}
    
     class FeatureSerializer(serializers.HyperlinkedModelSerializer):
         class Meta:
             fields = ['id', 'url']
             extra_kwargs = {'url': {'view_name': 'feature-detail'}}
    
     class PhotoSerializer(serializers.HyperlinkedModelSerializer):
         class Meta:
             fields = ['id', 'url']
             extra_kwargs = {'url': {'view_name': 'photo-detail'}}
    
    class StorySerializer(serializers.HyperlinkedModelSerializer):
        user = UserSerializer(read_only=True) 
        comments = serializers.HyperlinkedRelatedField(read_only=True, 
                               view_name='comment-detail', many=True)
        # this work because of related names
        features = FeatureSerializers(many=True)
        photos = PhotoSerializers(many=True)
        # add tags serializer as well
        text = serializers.CharField()
    
        class Meta:
             fields = ['id', 'users', 'photos', 'features', ...]
    

Upvotes: 1

auvipy
auvipy

Reputation: 1208

Instead of overriding get_fields method, you can try drf-writable-nested package, this will help to serialize nested relationships much easier and hand orm fields directions better.

Upvotes: 0

faruk
faruk

Reputation: 151

you can user django-filter

i think it not practical solution to edit get in serialiser

Upvotes: 0

Related Questions