Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 565
awidgery Avatar asked Mar 26 '15 14:03

awidgery


People also ask

What are httprequest and httpresponse objects in Django?

Each view is responsible for returning an HttpResponse object. This document explains the APIs for HttpRequest and HttpResponse objects, which are defined in the django.http module. All attributes should be considered read-only, unless stated otherwise. A string representing the scheme of the request ( http or https usually).

What attributes and methods are available in request in REST framework?

As REST framework's Request extends Django's HttpRequest, all the other standard attributes and methods are also available. For example the request.META and request.session dictionaries are available as normal.

What is the difference between request and user in Django?

request.user typically returns an instance of django.contrib.auth.models.User, although the behavior depends on the authentication policy being used. If the request is unauthenticated the default value of request.user is an instance of django.contrib.auth.models.AnonymousUser. For more details see the authentication documentation.

What is the use of request stream in Django?

request.stream returns a stream representing the content of the request body. You won't typically need to directly access the request's content, as you'll normally rely on REST framework's default request parsing behavior. As REST framework's Request extends Django's HttpRequest, all the other standard attributes and methods are also available.


1 Answers

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', ...]
    
like image 152
Kracekumar Avatar answered Oct 20 '22 20:10

Kracekumar