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.
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).
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.
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.
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.
Two important points.
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)
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', ...]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With