Can not make SerializerMethodField() work - django

So I have this view, I passed the request through the context to the serializer so I can use it to get the user
def create(self, request, *args, **kwargs):
""" Handle member creation from invitation code. """
serializer = AddMemberSerializer(
data=request.data,
context={'circle': self.circle, 'request': request}
)
serializer.is_valid(raise_exception=True)
member = serializer.save()
data = self.get_serializer(member).data
return Response(data, status=status.HTTP_201_CREATED)
In the serializer I did this but it does not work,I get "KeyError: 'user'", I ran a debugger and when I tried to call the get_user method it says "TypeError: get_user() missing 1 required positional argument: 'obj'"
user = serializers.SerializerMethodField()
def get_user(self, obj):
request = self.context.get('request', None)
if request:
return request.user
So what am I missing? I looked up other implementations of this field and none of them seem very different of mine, so I would really apreciate it if someone explains to me why it is not working.
Also if there is a more efective way to get the user into a field (Need it to run a user_validate method on it)

Try:
user = serializers.SerializerMethodField()
def get_user(self, obj):
return obj.user_id

Related

I get TypeError: Response.__init__() got an unexpected keyword argument 'errors' when trying to send POST request

I have this view that creates a post when sending a POST request to the endpoint.
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
queryset = Post.objects.all()
permission_classes = [IsAuthorOrReadOnly]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
user = request.user
if serializer.is_valid():
serializer.save(author=user)
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(errors=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Response doesn't have a keyword argument of errors. Instead, just use data since serializer.errors is just a JSON/dictionary:
In your last line of code:
return Response(serializer.errors, status=status. HTTP_400_BAD_REQUEST)

I am getting a None type for my request in serializer after Patch

I am making a test that partial updates or patch some fields in my Product model. I should be enable to update any fields in my Product model. In my serializer, I have a url builder from the request object. However, request is not passed on my serializer or it is a None type. Thus, I am getting an error: AttributeError: 'NoneType' object has no attribute 'build_absolute_uri'
How do I pass a request to my serializer?
def test_patch_products(self):
data = {
'description': 'This is an updated description.',
'type': 'New'
}
...
...
response = self.client.patch('/data/products/1/',
data=data, format='json')
In my view:
def partial_update(self, request, pk=None):
product= get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product,
data=self.request.data, partial=True,
)
if serializer.is_valid():
serializer.save(uploaded_by=self.request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
In my serializer:
class ProductSerializer(serializers.ModelSerializer):
uploaded_by= serializers.StringRelatedField()
thumbnail = serializers.SerializerMethodField()
class Meta:
model = CaptureGroup
fields = ('__all__')
def get_thumbnail(self, object):
request = self.context.get('request') # request is None here
thumbnail = object.thumbnail.url
return request.build_absolute_uri(thumbnail) # here is the url builder
In your view, you can pass some additional context to your serializer. That's useful to manipulate your request.
# views.py
def partial_update(self, request, pk=None):
product= get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product,
data=request.data, partial=True,
context={'request': request}, # you're missing this line
)
# ...
And then you can manipulate it your serializer with self.context.get('request') but I see you already got that part in your code.
In case you would like more information, here is the link to DRF documentation about this.

Django rest framework add extra context in response

I am using a updateapiview to the update my user information. This is my view
class UserUpdateView(generics.UpdateAPIView):
serializer_class = UserUpdateSerializer
def get_queryset(self):
print(self.kwargs['pk'])
return User.objects.filter(pk=self.kwargs['pk'])
def partial_update(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, partial=True)
if self.get_object() != request.user:
return Response({'error': 'permission denied'}, status=status.HTTP_400_BAD_REQUEST)
print(request.data['password'])
request.data['password'] = make_password(request.data['password'])
instance = super(UserUpdateView, self).partial_update(request, *args, **kwargs)
return instance
This is my serializer
class UserUpdateSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
def validate_username(self, username):
if User.objects.filter(username=username):
raise serializers.ValidationError('Username already exists!!')
return username
def validate_email(self, email):
if User.objects.filter(email=email):
raise serializers.ValidationError('Email already exists!!')
return email
class Meta:
model = User
fields = ('pk', 'username', 'password', 'email')
read_only_fields = ('pk',)
I am getting the data updated and returned successfully. But I want to add some field like message with content 'successfully updated' on successful updation of the user profile. I searched if there is any way to perform this but can't find the appropriate way to do it (alike get_context_data method in django). So, is there any way to perform the above task ??
Question 2:
How to prevent the self user ( i.e if has a email sample#gmail.com and if user clicks udpate with the same email, it should not raise an error that username already exists, I guess this can be done with self.instance (but not sure how actually to implement it).
Thanks!
I'm not sure what version of DRF you are using but in the latest 3.9.0 UpdateAPIView uses UpdateModelMixin, which returns Response object instead of instance. So you can modify data before returning.
def partial_update(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, partial=True)
if self.get_object() != request.user:
return Response({'error': 'permission denied'}, status=status.HTTP_400_BAD_REQUEST)
print(request.data['password'])
request.data['password'] = make_password(request.data['password'])
response = super(UserUpdateView, self).partial_update(request, *args, **kwargs)
response.data['message'] = 'Successfully updated'
return response
Regarding your second question you can exclude the current user using self.instance.
def validate_email(self, email):
if User.objects.filter(email=email).exclude(id=self.instance.id).exists():
raise serializers.ValidationError('Email already exists!!')
return email

Serializer.is_valid() raises Queryset error while serializer.is_valid(raise_exception=True) does not

The issue here is easy to fix and the reason I open it is that I don't understand what's happening.
Here's the code from django-rest-auth:
class PasswordResetView(GenericAPIView):
"""
Calls Django Auth PasswordResetForm save method.
Accepts the following POST parameters: email
Returns the success/fail message.
"""
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# Create a serializer with request.data
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"success": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
It's the whole class and it surely works well.
I make a copy with a small change - instead of serializer.is_valid(raise_exception=True) I expand it:
if serializer.is_valid():
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"success": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
else:
return Response(
{"error": "Something's wrong."},
status=status.HTTP_400_BAD_REQUEST
)
When I run it I get
*AssertionError: 'PasswordResetView' should either include a `queryset` attribute, or override the `get_queryset()` method.*
The only difference is in using serializer.is_valid(raise_exception=True) (Ok) or just serializer.is_valid() (fail).
Can somebody explain it to me? It's not the first time I see such an issue.
Of course I can write an empty def get_quesryset(): return None to fix it.
But the original code doesn't have get_queryset or get_object or anything and works fine.
Thanks!
Edit: here's my code, so that you can see it's really the same as original.
class PasswordResetView(GenericAPIView):
"""
Calls Django Auth PasswordResetForm save method.
Accepts the following POST parameters: email
Returns the success/fail message.
"""
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# Create a serializer with request.data
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"success": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
else:
return Response(
{"error": "Something's wrong."},
status=status.HTTP_400_BAD_REQUEST
)

Django Rest Framework object level permission on POST

I want to make sure the request.user can only issue a POST request to create a forum topic in which they are the auther. With PUT and DELETE I'm able to achieve that by using the has_object_permission but with POST I'm not able to do that, I'm guessing because the object hasn't been created yet.
class TopicPermission(IsAuthenticatedOrReadOnly):
"""
Any user should be able to read topics but only authenticated
users should be able to create new topics. An owner or moderator
should be able to update a discussion or delete.
"""
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
# Instance must have an attribute named `author` or moderator
return obj.author == request.user or request.user.forum_moderator
How would I go about verifying request.user == obj.author in POST requests?
I ended up doing the validation in the viewset instead of the serializer:
class TopicViewSet(viewsets.ModelViewSet):
permission_classes = (TopicPermission, )
queryset = Topic.objects.all()
serializer_class = TopicSerializer
def create(self, request, *args, **kwargs):
"""
verify that the POST has the request user as the obj.author
"""
if request.data["author"] == str(request.user.id):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=201, headers=headers)
else:
return Response(status=403)