I'm using Django 1.11 and I'm having a issue with path parameters.
I'm getting an error like this for all requests involving path parameters.
Error:
TypeError at /posts/2
get() missing 1 required positional argument: 'pk'
urls.py
...
url(r'^posts',PostView.as_view()),
url(r'^posts/<int:pk>/',PostView.as_view()),
...
views.py
..
#-------- API for CRUD -----------------#
class PostView(APIView):
permission_classes = (IsAuthenticated,)
def get_object(self,pk,user_id):
try:
return Post.objects.get(pk=pk,user_id=user_id)
except Post.DoesNotExist:
raise Http404
def get(self,request,pk):
post = Post.objects.get(user=request.user.id)
serializer = PostSerializer(post)
return Response({"success":True,"data":serializer.data},status=status.HTTP_200_OK)
def put(self, request, pk):
post = self.get_object(pk,request.user.id)
serializer = PostSerializer(post, data=request.data)
if serializer.is_valid():
serializer.save(user=request.user.id)
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def post(self,request):
params = request.data
params['user'] = request.user.id
serializer = PostSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
saved_data = self.perform_create(serializer)
post = PostSerializer(saved_data)
return Response({"success":True, "message":"Post Added Successfully","data":post.data}, status=status.HTTP_201_CREATED)
def perform_create(self, serializer):
return serializer.save()
...
url example :
GET : localhost:8000/posts/2
Can someone tell me how to pass positional parameters.
In above code you are using user_id, pk as positional arguments in request methods and view method get_object. pk and user_id is available across the view. you can use them like self.kwargs.get('pk'), self.request.user.pk.
Please check the code below for implementation details.
class PostView(APIView):
permission_classes = (IsAuthenticated,)
def get_object(self):
pk = self.kwargs.get('pk')
user_id = self.request.id
try:
return Post.objects.get(pk=pk,user_id=user_id)
except Post.DoesNotExist:
raise Http404
def get(self,request, *args, **kwargs):
# ...
pass
def put(self, request, *args, **kwargs):
# ...
pass
def post(self,request, *args, **kwargs):
# ...
pass
Your get and post signatures only accept a self and request parameter, whereas these here should include the pk parameter of your URL. Since you defined the same view for a URL without the pk parameter, you should make these parameters optional (so add a default value, in case these are missing). Like:
class PostView(APIView):
permission_classes = (IsAuthenticated,)
def get_object(self,pk,user_id):
# ...
pass
def get(self,request, pk=None):
# ...
pass
def put(self, request, pk=None):
# ...
pass
def post(self,request, pk=None):
# ...
pass
An alternative is to provide kwargs in the URL with the missing parameter, like:
path(r'posts',PostView.as_view(), kwargs={'pk': None}),
path(r'posts/<int:pk>/',PostView.as_view())
or for pre-django-2.0 installations, you need to define this as a regex:
url(r'^posts/$',PostView.as_view(), kwargs={'pk': None}),
url(r'^posts/(?P<pk>[0-9]+)/',PostView.as_view())
Related
Right now I'm overriding the whole retrieve and update function. I want to override only that part, it does not ask for the pk value. Thanks
View.py
class EmployeeView(generics.RetrieveUpdateAPIView):
permission_classes = [EmployeePermission]
serializer_class = EmployeeSerializers
def retrieve(self, request, *args, **kwargs):
employee = Employee.objects.get(user=self.request.user)
serializer = EmployeeSerializers(employee)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
employee_user = Employee.objects.get(user=self.request.user)
serializer = EmployeeSerializers(employee_user, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)strong text
urls.py
path('viewEmployee/', views.EmployeeView.as_view()),
what you need is to override the get_queryset method, and write your custom filtering, then django will take care of otherthings.
class EmployeeView(generics.RetrieveUpdateAPIView):
queryset = Employee.objects.all()
permission_classes = [EmployeePermission]
serializer_class = EmployeeSerializers
def get_queryset(self):
return super().get_queryset().filter(
user=self.request.user
)
I am very new to Python and Django. I have this app that returns 4 different types of transport routes (In the code I only showed two, cause they basically are the same...).
These 4 views use the same class-based views, but only the models' names are different. As they all return the same functionality(get, post, put and delete) I ended up repeating the same code over and over again.
Is there any way I can refactor it simpler?
Any help is appreciated! Thank you :)
views.py
********* tube view ***********
class TubeListView(APIView):
def get(self, _request, format=None):
tubeRoutes = TubeRoute.objects.all()
serialized_with_user = NestedTubeRouteSerializer(tubeRoutes, many=True)
return Response(serialized_with_user.data)
def post(self, request, format=None):
request.data['traveler'] = request.user.id
serializer = TubeRouteSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
class TubeDetailView(APIView):
def get(self, _request, pk, format=None):
tubeRoute = TubeRoute.objects.get(pk=pk)
serialized_with_user = NestedTubeRouteSerializer(tubeRoute)
return Response(serialized_with_user.data)
def put(self, request, pk, format=None):
request.data['traveler'] = request.user.id
tubeRoute = self.get_object(pk)
if tubeRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
updated_serializer = TubeRouteSerializer(tubeRoute)
if updated_serializer.is_valid():
updated_serializer.save()
return Response(updated_serializer.data, status=status.HTTP_200_OK)
return Response(updated_serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
def delete(self, request, pk, format=None):
tubeRoute = self.get_object(pk)
if tubeRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
tubeRoute.delete()
return Response(status=status.HTTP_200_OK)
********* bus view ***********
class BusListView(APIView):
def get(self, _request, format=None):
busRoutes = BusRoute.objects.all()
serialized_with_user = NestedBusRouteSerializer(busRoutes, many=True)
return Response(serialized_with_user.data)
def post(self, request, format=None):
request.data['traveler'] = request.user.id
serializer = BusRouteSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
class BusDetailView(APIView):
def get(self, _request, pk, format=None):
busRoute = BusRoute.objects.get(pk=pk)
serialized_with_user = NestedBusRouteSerializer(busRoute)
return Response(serialized_with_user.data)
def put(self, request, pk, format=None):
request.data['traveler'] = request.user.id
busRoute = self.get_object(pk)
if busRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
updated_serializer = BusRouteSerializer(busRoute)
if updated_serializer.is_valid():
updated_serializer.save()
return Response(updated_serializer.data, status=status.HTTP_200_OK)
return Response(updated_serializer.errors, status=status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE)
def delete(self, request, pk, format=None):
busRoute = self.get_object(pk)
if busRoute.owner.id != request.user.id:
return Response(status=status.HTTP_401_UNAUTHORIZED)
busRoute.delete()
return Response(status=status.HTTP_200_OK)
You should take a look to these class based views in DRF.
For instance, the following code should be enough to replace your first TubeListView:
from rest_framework import generics
class TubeListView(generics.ListCreateAPIView):
queryset = TubeRoute.objects.all()
serializer_class = NestedTubeRouteSerializer
def post(self, request, *args, **kwargs):
request.data['traveler'] = request.user.id
return super().post(self, request, *args, **kwargs)
If you don't need any special behavior, you don't have to redefine get, post, etc methods. But if you need to change data, for instance in your POST method, you can do your stuff and then call the usual behavior of the superclass with super().post(self, request, *args, **kwargs)
I would recommend checking Viewsets in Django Rest Framework, specifically ModelViewset.
The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy() which coincide with the following:
get ----> .retrieve()
list ----> .list()
post ----> .create()
patch ----> .partial_update()
put ----> .update()
delete ----> .destroy()
Let me provide a case using the Bus Views
The Modelviewset form would be:
class BusViewset(ModelViewset):
queryset = BusRoute.objects.all()
serializer_class = NestedBusRouteSerializer
def get_queryset(self):
return self.queryset.filter(
owner__id=self.request.user.id
)
For the permission checks you implemented, Django Rest Framework has a Permissions System that takes care of that. For you own use case, a custom permission would be sufficient.
In view, the test does not take the request.data as an element of DRF.
I have this view:
class ChangePasswordView(UpdateAPIView):
serializer_class = ChangePasswordSerializer
model = UserInfo
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
return Response(status=200)
def put(self, request, *args, **kwargs):
self.object = self.request.user
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
if not self.object.check_password(serializer.data.get("old_password")):
return Response(
{"old_password": ["Wrong password"]},
status=400
)
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
return Response("Success", status=200)
return Response(serializer.errors, status=400)
and I have this test method:
def test_change_password(self):
client = APIClient()
self.client.post('/api/user/login/', self.data, follow=True)
self.client.login(username="testuser", password="secret")
request = client.put('/api/user/change_password/', self.new_data)
self.assertEqual(
request, '<Response status_code=200, "application/json">'
)
where data:
def setUp(self):
self.data = {
'username': 'testuser',
'password': 'secret'
}
self.new_data = {
'old_password': 'secret',
'new_password': 'other_secret'
}
User.objects.create_user(**self.data)
Why does the test stop at reqest.data in serializer = self.get_serializer(data=request.data)?
I guess that you are getting the user wrong.
This might solve your issue:
from rest_framework import status
class ChangePasswordView(UpdateAPIView):
serializer_class = ChangePasswordSerializer
model = UserInfo
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
return Response(status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs):
user = request.user
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
if not self.object.check_password(serializer.data.get("old_password")):
msg = _("Wrong old password.")
raise ValidationError(msg)
user.set_password(serializer.data.get("new_password"))
user.save()
return Response("Success", status=status.HTTP_200_OK)
msg = _('Something went wrong while reading the passwords')
raise ValidationError(msg)
I am using the imported status from rest_framework in order to handle the error codes more easily.
Is there any specific reason you are using put? I have changed it to post. When making this API call (change password), you should use post.
You can easily get the user with request.user and then use the variable user inside put function to set the password and save it.
Regarding the test case, this could be a help and/or work for you. If it doesn't work, you might need to change it a bit.
from rest_framework import status
def test_change_password(self):
client = APIClient()
self.client.post('/api/user/login/', self.data, follow=True)
self.client.login(username="testuser", password="secret")
response = self.post('/api/user/change_password/', self.new_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
This is my code in Views.py
class NotificationsViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = Notifications.objects.all()
serializer_class = NotificationsSerializer
filter_fields = ('status','task','survey_type',)
def put(self, request, pk, format=None):
notifications = self.get_object(pk)
serializer = NotificationsSerializer(notifications, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
notifications = self.get_object(pk)
notifications.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
When i try to use method PUT i got error put() takes at least 3 arguments (2 given). What is wrong with my code?
def put(self, request, pk, format=None):
It takes 3 arguments at the minimum. Like:
self.put(request, pk)
self variable needs to be passed.
Alternatively,
put(self, request, pk)
Or 4 arguments at the max,
self.put(request, pk, format)
That's probably because you're doing a PUT on the set entry point where the pk isn't defined. Make it optional:
def put(self, request, pk=None, format=None):
Note that there's no point in using viewset if you're overriding put.
I am using django rest framework. I need to pass some extra context value in response but not getting extra_value in response.
class ResultRowView(generics.ListAPIView):
serializer_class = ResultRowSerializer
permission_classes = (AccountPermission, )
def get_serializer(self, *args, **kwargs):
context = {'extra_value': 5000}
return self.serializer_class(*args, context=context, **kwargs)
def get_queryset(self):
qs = ResultRow.objects.none()
pk = self.kwargs.get('pk', None)
try:
route = IncomingRoute.objects.get(account=self.request.user.account, pk=pk)
qs = route.app_module.rows.all()
except Exception, e:
print 'result_row_query: ', e
return qs
What is missing here ?
you can do this by overriding the list method like:
def list(self, request, *args, **kwargs):
response = super(ResultRowView, self).list(request, args, kwargs)
response.data[ 'extra_value' ] = 5000
return response
You will have to override the get_context_data method and then add context to it
def get_context_data(self, **kwargs):
context = super(PropertyListView, self).get_context_data(**kwargs)
context['some_key'] = some_value
return context