Django: HttpResponse in RetrieveAPIView giving an AttributeError - django

I'm trying to create API for liking a post. This works for liking a post
but I get error when adding the return HttpResponse/Response statements gives an error:
AttributeError at /api/posts/like/4/
'HttpResponse' object has no attribute 'model'
Request Method: GET
Request URL: http://127.0.0.1:8000/api/posts/like/4/
Django Version: 2.0.7
views.py
class LikeDetailAPIView(RetrieveAPIView):
serializer_class = PostSerializer
def get_queryset(self):
user = self.request.user
post_id = self.kwargs['pk']
like = Like(post=Post.objects.get(id=post_id), user=user)
user_like = Like.objects.filter(post=post_id, user=user)
if user_like.exists():
user_like.delete()
content = {'message': 'unliked'}
return Response(content, status=status.HTTP_202_ACCEPTED)
else:
like.save()
content = {'message': 'like'}
return Response(content, status=status.HTTP_202_ACCEPTED)
urls.py
url(r'^like/(?P<pk>.+)/$',LikeDetailAPIView.as_view(), name='likeapi'),
What I am intending to do is return a success message.
serializer.py
class LikeListSerializer(serializers.ModelSerializer):
class Meta:
model = Like
fields = [
'user',
'post',
'time',
]
Any links that I can refer to?

From the official DRF doc,
get_queryset(self) method returns the queryset that should be used for list views, and that
should be used as the base for lookups in detail views. Defaults
to returning the queryset specified by the queryset attribute.
From your comments, I understood that, you need to show some response when you access the API (detail-view).
Since you are using RetrieveAPIView class, you have to override retrieve() method, as
from rest_framework.generics import RetrieveAPIView
class LikeDetailAPIView(RetrieveAPIView):
serializer_class = PostSerializer
def get_queryset(self):
return Like.objects.filter(post=self.kwargs['pk'], user=self.request.user)
def retrieve(self, request, *args, **kwargs):
if self.get_queryset().exists():
self.get_queryset().delete()
content = {'message': 'unliked'}
return Response(content, status=status.HTTP_202_ACCEPTED)
like = Like(post=Post.objects.get(id=post_id), user=user)
like.save()
content = {'message': 'like'}
return Response(content, status=status.HTTP_202_ACCEPTED)
Now you can access the endpoint, /api/posts/like/4/ by HTTP GET method

Related

Django Page not found at /auth1/Timings/2/

I know that there is something wrong with my urls. But I'm unable to figure it out.
models.py
class Restaraunt(models.Model):
name=models.CharField(max_length=50,blank=True,null=True)
class Schedule(models.Model):
restaraunt=models.ForeignKey(Restaraunt, on_delete=models.CASCADE,related_name='restaraunt_name')
#days=models.CharField(choices=DAYS,max_length=255)
opening_time=models.TimeField(auto_now=False,auto_now_add=False)
closing_time=models.TimeField(auto_now=False,auto_now_add=False)
def __str__(self):
return str(self.restaraunt)
class Restarasunt(viewsets.ViewSet):
def create(self,request):
try:
name=request.data.get('name')
if not name:
return Response({"message": "name is rerquired!","success":False},
status=status.HTTP_200_OK )
res_obj=Restaraunt()
res_obj.name=name
print(res_obj.name)
res_obj.save()
return Response("Restaurant addedd successfully")
except Exception as error:
traceback.print_exc()
return Response({"message":str(error),"success":False},status = status.HTTP_200_OK)
class ScheduleViewSet(viewsets.ViewSet):
def create(self,request,pk):
try:
res_obj=Restaraunt.objects.filter(pk=pk)
print('hie',res_obj)
data=request.data
opening_time=data.get('opening_time')
closing_time=data.get('closing_time')
sce_obj=Schedule()
sce_obj.opening_time=opening_time
sce_obj.closing_time=closing_time
sce_obj.restaraunt=res_obj
sce_obj.save()
return Response("")
except Exception as error:
traceback.print_exc()
return Response({"message":str(error),"success":False},status = status.HTTP_200_OK)
URLS.PY
from rest_framework.routers import DefaultRouter
from auth1 import views
router=DefaultRouter()
router.register(r'retaraunt', views.Restarasunt, basename='Restarasunt')
router.register(r'Timings', views.ScheduleViewSet, basename='ScheduleViewSet')
urlpatterns = router.urls
As showed in the documentation you need to add retrieve method for your class
class UserViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
I don't think that the create view is meant to be used with "pk". Can you try getting the "pk" value from request.data and use it to get the Restaraunt object

How to write a test for a custom action on a viewset in Django Rest Framework

I am new to Django, and Django Rest Framework.
I would like to know how to go about testing custom actions. For example, assume we have the following code from the DRF tutorials
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset that provides the standard actions
"""
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail=True, methods=['post', 'put'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.data)
if serializer.is_valid():
user.set_password(serializer.data['password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
How would I go about calling this view in a test in DRF.
def test_password_set(self):
user = User.objects.create(name="Joe")
factory = APIRequestFactory()
request_url = f'users/{user.id}/set_password/'
request = factory.post(request_url)
view = UserViewSet.as_view({'put': 'update'})
response = view(request)
self.assertEqual(response.status_code, 200)
That code gives me the error below
AssertionError: 405 != 200
which means that the given method is not allowed.
Could anyone help me figure out what the error could be?
You've made a mistake here
request = factory.post(request_url)
It should be put:
request = factory.put(request_url)
And also, you should add the data into the request body. In this line, you're trying to get data from the request object.
serializer = PasswordSerializer(data=request.data)
This data depends on the PasswordSerializer; But for example, it should be something like this (it should contain all parameters of the serializer):
request = factory.put(request_url, {'password': 'sTr0ngPass'})

DRF - SerializerMethodField

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',)

Receiving a KeyError for 'request' in DRF serializer when using detail_route in my ViewSet

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})

DjangoRestFramework - How to filter ViewSet's object list based on end-users input?

This is my ViewSet:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
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 {
'request': self.request,
'format': self.format_kwarg,
'view': self,
'location': self.request.user.userextended.location
}
#detail_route(methods=['post'], permission_classes=[IsFromLocationOrReadOnly])
def like(self, request, pk=None):
post = self.get_object()
post.usersVoted.add(request.user)
return Response(status=status.HTTP_204_NO_CONTENT)
This is my router / urls.py:
router = routers.DefaultRouter()
router.register(r'posts', views.PostViewSet)
So when I go to /posts I get a list of all posts. What I want is to be able to allow the end-user to go to a specific URL like so: /posts/username and when he does, I want to give him all the posts of that specific username (the filtering will be simple. Something along these lines:
queryset = Post.objects.filter(username=usernameProvidedByTheURL)
How do I go about doing this? Is it possible using DRF?
In your url:
url(r'^/post/(?P<username>\w+)/?$', PostViewSet.as_view({'get': 'list'})),
Then in your PostViewSet, overwrite the get_queryset() method to filter the data by username
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.order_by('-createdAt')
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
UPDATE
If you want to keep /post/ endpoint to retrieve all post. Then you need to create an extra view to handle /post/username
class PostsListByUsername(generics.ListAPIView):
serializer_class = PostSerializer
def get_queryset(self):
username = self.kwargs['username']
return Post.objects.filter(username=username)
Then in your urls.py
url(r'^/post/(?P<username>\w+)/?$', PostsListByUsername.as_view()),
Note:
In your get_serializer_context method, you don't need to return request, format and view. DRF will append it for you.
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'location': self.request.user.userextended.location
}