class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.CharField()
field2 = serializers.SerializerMethodField('get_awesome_user')
def get_current_user(self):
request = self.context.get("request")
if request and hasattr(request, "user"):
return request.user
return None
def get_awesome_user(self, obj):
user = self.get_current_user()
## use this user object, do some stuff and return the value
return ...
My api(which uses authentication_classes and permission_classes) is using this serializer and the get_current_user function always returns None. when I debug it, I found that self.context is empty dictionary, i.e {}. to be double sure I also printed self.context.keys(), still it's empty list.
I followed this thread.
Get current user in Model Serializer
PS: I'm using djangorestframework==3.3.3, Django==1.9.1
EDIT: adding viewset code
class MyModelViewSet(viewsets.ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication, TokenAuthentication)
permission_classes = (IsAuthenticated,)
def list(self, *args, **kwargs):
queryset = MyModel.objects.all()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = MyModelSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = MyModelSerializer(queryset, many=True)
return Response(serializer.data)
How do you create serializer in your viewset's list() method? You should call
serializer = self.get_serializer(data=request.data)
to get your serializer context filled automatically as it is done in default implementation of this method in DRF mixins., but I have a feeling that you're just creating it manually, like this:
serializer = MyModelSerializer(instance)
So, to fix this, you should either call get_serializer(), or pass extra context argument to serializer constructor:
serializer = MyModelSerializer(instance, context={'request': request, ...})
Related
I can't find examples of using patch to update a partial view in rest framework and it isn't computing for me. Here is my code:
class ArworkIsSold(generics.RetrieveUpdateAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = ArtworkSerializer
queryset = Artwork.objects.all()
def partial_update(self, request, pk=None):
data = {sold:True,forSale:False}
serializer = ArtworkSerializer(context={'request': request},data=data, partial=True)
serializer.is_valid()
serializer.save()
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
However, it doesn't update and I get this error:
NameError: name 'sold' is not defined
My model does have sold and I am trying to just set the data in the view instead of sending it in from the ajax request. I just want to hit a view and have it update two fields.
You can't use undefined variable as a dictionary key. Use strings as keys and then pass dictionary as "data" parameter:
class ArworkIsSold(generics.RetrieveUpdateAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = ArtworkSerializer
queryset = Artwork.objects.all()
def partial_update(self, request, pk=None):
data = {'sold':True, 'forSale':False}
serializer = ArtworkSerializer(context={'request': request},data=data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
this my Django view
class CreateForeignTableView(CreateAPIView):
"""
create foreign_table finally not difference a normal table ??
"""
serializer_class = CreateForiegnTableSerializer
queryset = None
lookup_url_kwarg = 'foreign_server_id'
I want get lookup_url_kwarg in my create serializer function
Simple you can override create method to achieve this.
def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={
'request': request,
'id': self.kwargs.get(self.lookup_url_kwarg)})
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
In the serializer, you can use id = self.context['id']
You can override get_serializer_context method to achieve this.
class CreateForeignTableView(CreateAPIView):
"""
create foreign_table finally not difference a normal table ??
"""
serializer_class = CreateForiegnTableSerializer
queryset = None
lookup_url_kwarg = 'foreign_server_id'
def get_serializer_context(self):
context = super(CreateForeignTableView, self).get_serializer_context()
context.update({
"foreign_server_id": self.kwargs.get(self.lookup_url_kwarg)
})
return context
In the serializer you can use self.context.get("foreign_server_id", "") to get foreign_server_id.
You can follow this post to know farther.
You only need to access the serializer context. GenericApiView sets the view itself into the serializer context, so you may access lookup_url_kwarg like this:
def create(self, validated_data):
my_url_kwarg = self.context['view'].lookup_url_kwarg
I'm using Django rest framework, Here is my serializers.py for social app:
class SocialPostSerializer(serializers.ModelSerializer):
likes = serializers.SerializerMethodField() # define field
class Meta:
model = SocialPost
def get_likes(self, obj):
post_id = obj.id
#I get post_like from django-redis
post_like = get_redis_connection("default")
likes = post_like.get("post"+":"+str(post_id))
if likes == None:
return 0
else:
likes = likes.decode('utf-8')
return likes
With the code above, I got what I need from the API.
Since 'likes' doesn't exist in my database(Mysql here), I can't using order_by('likes') to sort the data with django ORM
I follow the doc here ListCreateAPIView which lead me to override list(): (I had override create() and get_queryset() before)
from operator import itemgetter
class PostList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostAllSerializer
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
#problem here
serializer.data = sorted(serializer.data, key=itemgetter(serializer.data['likes']))
return Response(serializer.data)
def create(self, request, *args, **kwargs):
act_id = request.data.get('act')
act = Act.objects.get(pk=act_id)
if act.act_type == 0:
if request.user != act.user:
return Response(status=403)
return super().create(request, args, kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_queryset(self):
queryset = Post.objects.all().order_by('-post_create_time')
act_id = self.request.query_params.get('act_id', None)
post_author = self.request.query_params.get('post_author', None)
if act_id is not None:
queryset = queryset.filter(act=act_id)
if post_author is not None:
queryset = queryset.filter(user__user_name=post_author)
return queryset
Nothing happened, What is wired is even when I uncomment
return Response(serializer.data)
Still nothing happened, Which part is wrong?
Another question is when I wanna add some extra data like 'question' when I use django FBV:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': 'some data I wanna add'})
Is it possible to add the data in serializer.data? For example, I wanna display user_id from which user request this api:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
user_id = request.user.id #how can I add the user_id into response data?
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
I look around the docs here Including extra context.
serializer = AccountSerializer(account, context={'request': request})
I don't really understand how to add data in it.
"Since 'likes' doesn't exist in my database(Mysql here), I can't using order_by('likes') to sort the data with django ORM"
You wont be able to sort the results using django ORM as the likes field is not a part of your DB table. The only way to do it is to sort the serializer.data that you get in your view. In your case serializer.data will be a list of dictionary, you can use sort command for list and sort on likes using lambda.
One thing to take care here is as you will be doing the sort by loading the data in memory, make sure that you dont load a lot of data. Have a check on memory utilization.
"Another question is when I wanna add some extra data like 'question' when I use django FBV"
I did'nt understand what is needed here.
This is my ViewSet:
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsOwnerDeleteOrReadOnly)
def get_queryset(self):
return Post.objects.filter(location=self.request.user.userextended.location)
def perform_create(self, serializer):
serializer.save(owner=self.request.user, location=self.request.user.userextended.location)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'format': self.format_kwarg,
'view': self,
'location': self.request.user.userextended.location
}
#detail_route(methods=['post'], permission_classes=[IsAuthenticated, IsFromLocation])
def like(self, request, pk=None):
post = self.get_object()
post.usersVoted.add(request.user)
return Response(status=status.HTTP_204_NO_CONTENT)
#detail_route(methods=['get'], permission_classes=[IsAuthenticated, ValidPostPkInKwargs, IsFromPostLocation])
def replies(self, request, pk=None):
post = self.get_object()
replies = post.postreply_set.all()
serializer = PostReplySerializer(replies, many=True)
return Response(serializer.data)
And this is my PostReplySerializer:
class PostReplySerializer(serializers.ModelSerializer):
owner = serializers.SlugRelatedField(slug_field='username', read_only=True)
voted = serializers.SerializerMethodField()
def get_voted(self, obj):
return self.context['request'].user in obj.usersVoted.all()
class Meta:
model = PostReply
fields = ('id', 'owner', 'post', 'voted', 'location')
The error points to the line
return self.context['request'].user in obj.usersVoted.all()
and says:
KeyError at /URL/20/replies/
'request'
Any idea why DRF says 'request' is a key error even though (from my understanding) it should be automatically in self.context?
Note that PostViewSet works perfectly fine for all other requests (if I get a Post, get a list of posts etc.). It just doesn't work for replies.
It's not in self.context because you have overridden get_serializer_context. request object is attached to context via this method. Just add request: self.request in your return statement of get_serializer_context that would solve the problem. Take a look at default implementation of get_serializer_context here https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/generics.py to understand more. Hope it helps..
EDIT
Since you are using different serializer(PostReplySerializer) in detail_route, you need to create serializer instance like serializer = PostReplySerializer(replies, many=True, context={'request': self.request})
I have a case where the values for a serializer field depend on the identity of the currently logged in user. I have seen how to add the user to the context when initializing a serializer, but I am not sure how to do this when using a ViewSet, as you only supply the serializer class and not the actual serializer instance.
Basically I would like to know how to go from:
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
to:
class myModelSerializer(serializers.ModelSerializer):
uploaded_by = serializers.SerializerMethodField()
special_field = serializers.SerializerMethodField()
class Meta:
model = myModel
def get_special_field(self, obj):
if self.context['request'].user.has_perm('something.add_something'):
return something
Sorry if it wasn't clear, from the DOCs:
Adding Extra Context
Which says to do
serializer = AccountSerializer(account, context={'request': request})
serializer.data
But I am not sure how to do that automatically from the viewset, as I only can change the serializer class, and not the serializer instance itself.
GenericViewSet has the get_serializer_context method which will let you update context:
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = MyModelSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({"request": self.request})
return context
For Python 2.7, use context = super(MyModelViewSet, self).get_serializer_context()
For Function based views you can pass request or user as follows:
serializer = ProductSerializer(context = {"request": request}, data=request.data)
Your Serializer may look like:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id"]
def create(self, validated_data):
user = self.context["request"].user
print(f"User is: {user}")
Feel free to inform if there is any better way to do this.
just use get_serializer() in your viewsets
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Return parent context in overrided function get_serializer_context will make it easy to access request and its data.
class myModelViewSet(ModelViewSet):
queryset = myModel.objects.all()
permission_classes = [DjangoModelPermissions]
serializer_class = myModelSerializer
def get_serializer_context(self):
"""
pass request attribute to serializer
"""
context = super(myModelViewSet, self).get_serializer_context()
return context
This is very stable as every time we request viewset, it returns context as well.
the values for a serializer field depend on the identity of the currently logged in user
This is how I handle such cases in my ModelViewSet:
def perform_create(self, serializer):
user = self.request.user
if user.username == 'myuser':
serializer.data['myfield'] = 'something'
serializer.save()
Simply add this 2 line method in your class and you are good to go.
def get_serializer_context(self):
return {'request': self.request}
since the posted answers had partial correctness, summarizing here in the interest of completeness.
override get_serializer_context..AND
use get_serializer in your views instead of manually calling the serializer