DRF - SerializerMethodField - django

I've an API view as below:-
class ProfileAPI(generics.RetrieveAPIView):
serializer_class = ProfileSerializer
def get_object(self):
try:
return Profile.objects.get(user=self.request.user)
except:
return None
# I don't raise NotFound here for a reason.
# I don't want a 404 response here, but a custom HTML response, explained below.
class ProfileSerializer(serializers.ModelSerializer):
html = serializers.SerializerMethodField()
def get_html(self, obj):
# some custom HTML response based on whether the user obj is `None` or not.
if not obj:
return NOT_LOGGED_IN_HTML
return CUSTOM_HTML
class Meta(object):
model = Profile
fields = ('html',)
Now when the user is logged-in, I get the html key in the response. However, when the user is None (logged-out), I get an empty response. Why? and how can I rectify it?

As far as I can understand from implementation of retrieve and data method, you need to pass an instance of Profile to populate data. I would approach like this:
class ProfileAPI(generics.RetrieveAPIView):
serializer_class = ProfileSerializer
def get_object(self):
try:
return Profile.objects.get(user=self.request.user)
except:
return Profile() # empty object instance
class ProfileSerializer(serializers.ModelSerializer):
html = serializers.SerializerMethodField()
def get_html(self, obj):
if obj and obj.pk:
return CUSTOM_HTML
return NOT_LOGGED_IN_HTML
class Meta(object):
model = Profile
fields = ('html',)

Related

KeyError: 'user_id' when using PUT request

Recently started working with Django REST framework. I have a user table and another table that stores some relevant data for each user. I've set up POST/GET/DELETE methods fine, but I can't get the method for perform_update working - I keep getting a KeyError at /api/sdgdata/1
'user_id' error in Postman when I attempt a put request for a user. Plz see code below:
Models:
class TestDataTwo(models.Model):
user = models.ForeignKey("auth.User", related_name="testdatatwo", on_delete=models.CASCADE)
test_name = models.CharField(max_length=1024, null=True, default="N/A")
Serializers:
class TestDataTwoSerializer(serializers.ModelSerializer):
class Meta:
model = TestDataTwo
fields = (
"id",
"test_name",
)
def update(self, instance, validated_data):
# get user id from validated data:
user_id = validated_data.pop('user_id')
# get user:
user = User.objects.get(id=user_id)
# set user on instance:
instance.user = user
instance.save()
# continue with update method:
super().update(instance, validated_data)
Views:
class TestDataTwoViewSet(ModelViewSet):
queryset = TestDataTwo.objects.all().order_by('id')
serializer_class = TestDataTwoSerializer
paginator = None
# CREATE NEW TESTDATATWO ROW FOR USER
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# GET ALL ENTRIES OF TESTDATATWO FOR SPECIFIC USER, EXCEPT IF SUPERUSER, THEN RETURN ALL
def get_queryset(self):
# if self.request.user.is_superuser:
# return self.queryset
# else:
return self.queryset.filter(user=self.request.user)
def perform_update(self, serializer):
instance = self.get_object()
serializer.save(user=self.request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
# DELETE TESTDATATWO ID
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
Postman GET:
Postman PUT:
I've tried a number of variations on the perform_update method, but I am guessing I am missing a reference to the user's user_id somehow...Appreciate any help.
This error is caused by validated_data.pop('user_id'), since user_id was not defined as a field on the serializer nor was it passed in the request data. It seems you are aiming to save the current user as the user field of the TestDataTwo instance being updated.
In that case you don't need to override update, since saving the user based on self.request.user on a TestDataTwo instance is already done by the serializer's save here:
def perform_update(self, serializer):
serializer.save(user=self.request.user)
This means that you don't have to do any processing in the serializer anymore, so it can be just:
class TestDataTwoSerializer(serializers.ModelSerializer):
class Meta:
model = TestDataTwo
fields = ("id", "test_name",)
# No need to override update for saving self.request.user to `TestDataTwo`'s user
For more information on this functionality, you can have a read here..[DRF-docs-Passing-additional-attributes-to-save].

Django Rest Framework KeyError 'request'

i have my serializer like this
class PublicacionSerializer(serializers.ModelSerializer):
usuario = UserSerializer2()
likeado = serializers.SerializerMethodField()
class Meta:
model = Publicacion
fields = ('id','usuario', 'likeado')
def get_likeado(self, obj):
user = self.context['request'].user
try:
like = Like.objects.get(publicacion=obj, usuario=user)
return like.id
except Like.DoesNotExist:
return False
so i use that seriaizer in another one:
class EstadoSerializer(serializers.ModelSerializer):
publicacion = PublicacionSerializer(read_only=True)
in views.py i have
class ModificarEstadoMixin(object):
queryset = Estado.objects.all()
serializer_class = EstadoSerializer
class ModificarEstadoDetail(ModificarEstadoMixin, RetrieveUpdateAPIView):
permission_classes = (permissions.IsAuthenticated,
CanModifEstado,)
pass
when i access to the url for know if an user has liked to a post i got a KeyError 'request' in code line
user = self.context['request'].user
anyone knows how to solve it?
When you call that serializer, you have to pass context from view like
MySerializer(context={'request': request})

django rest add data to serializer when saving

I want to do the following:
models.py
class MyModel(TimeStampedModel, models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
serializers.py
class MyModelSerializerCreate(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = (
'name',
)
And I would like to add as owner the current user in request.user.
Currently I am adding this in my view directly by uptading request.data with user and then pass the updated data to my serializer.
data = request.data
# Add owner to data
data["owner"] = request.user.pk
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
I would like to do this in my serializer directly but can't find a way to properly do it because it looks like data validation to me. Is this a good idea ? Should I keep this logic in my views or move it to my serializer ?
You can get a user from serializer context:
self.context['request'].user
It is passed from a method get_serializer_context which originally created in a GenericAPIView:
class GenericAPIView(APIView):
....
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
Inside a serializer you need to override a create method:
class MyModelSerializerCreate(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('name', )
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super(MyModelSerializerCreate, self).create(validated_data)
You could also override an update and delete methods if you need some special interactions with the User model.
Unfortunatly I dont have the reputation points to comment on #ivan-Semochkin post above, but should the last line not be:
return super(MyModelSerializerCreate, self).create(validated_data)
The solution from Ivan Semochkin did not work for me, as it never entered into the create() method of the serializer. As request.data field is immutable, you need to copy it and then extend it.
from django.http import HttpRequest
from rest_framework.request import Request
class MyModelViewSet(ModelViewSet):
def _extend_request(self, request):
data = request.POST.copy()
data['owner'] = request.user
request_extended = Request(HttpRequest())
request_extended._full_data = data
def create(self, request, *args, **kwargs):
request_extended = self._extend_request(request)
return super().create(request_extended, *args, **kwargs)

How to using django-taggit in DRF

I am having trouble to serialize the tags from django-taggit.
I followed the instruction from here, but it is outdated.
Here is what I did:
views.py
class TagsSerializer(serializers.WritableField):
def from_native(self, data):
if type(data) is not list:
raise ParseError("expected a list of data")
return data
def to_native(self, obj):
if type(obj) is not list:
return [tag.name for tag in obj.all()]
return obj
I got this error :
'module' object has no attribute 'WritableField
Apparently the WritableField is deprecated.
I am using django 1.8, DRF 3.2 and django-taggit-0.17.
I would use the TaggableManager to update the tags, with a custom ListField to handle the serialisation of tags. Then you can use the serializer create/update methods to set the tags.
serializers.py
class TagSerializerField(serializers.ListField):
child = serializers.CharField()
def to_representation(self, data):
return list(data.values_list('name', flat=True))
class TagSerializer(serializers.ModelSerializer):
tags = TagSerializerField()
def create(self, validated_data):
tags = validated_data.pop('tags')
instance = super(TagSerializer, self).create(validated_data)
instance.tags.set(*tags)
return instance
or you can use the perform_create/perform_update hooks in the view.
views.py
class TagView(APIView):
queryset = Tag.objects.all()
serializer_class = TagSerializer
def perform_create(self, serializer):
instance = serializer.save()
if 'tags' in self.request.DATA:
instance.tags.set(*self.request.DATA['tags'])

Pass request context to serializer from Viewset in Django Rest Framework

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