Filter Backends Django Rest Framework List API View - python

Excuse me devs, I want to ask about Django generics List API View filter_backends, how can i return none / data not found when the param is wrong or empty?
# My Django Views
class FilterTablePVPlantByPVOwnerId(filters.FilterSet):
id = filters.CharFilter(
field_name='id', lookup_expr='exact')
class Meta:
model = TablePVPlant
fields = ['id']
class PlantListByClientView(generics.ListAPIView):
queryset = TablePVPlant.objects.all()
serializer_class = PlantListSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = FilterTablePVPlantByPVOwnerId
def list(self, request, *args, **kwargs):
if self.request.query_params:
response = super().list(request, *args, **kwargs)
response.data = {'status': 'success',
'data': response.data, 'msg': 'done'}
return response

In both Django and in Django Rest Framework these type of things are dealt with by raising Exceptions:
from rest_framework.exceptions import APIException
class BadParameter(APIException):
status_code = 400
default_detail = 'The parameter x is wrong or empty'
default_code = 'bad_param'
# ...
class PlantListByClientView(generics.ListAPIView):
# ...
def list(self, request, *args, **kwargs):
# ...
if bad_param:
raise BadParameter()
Some parts of the framework will raise exceptions for you, like, for example, serializes rising ValidationError when validation fails.

Related

Django 1.11 get() missing 1 required positional argument: 'pk'

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

How fix test for change password view (Django REST Framework)?

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)

django filter on APIView

I have a APIView class for showing all the rents and posting and delete etc. Now i want search feature so i tried to use DjangoFilterBackend but it is not working. I see in documentation, it has been used with ListAPIView but how can i use it in APIView.
class Rent(APIView):
"""
List all the rents if token is not provided else a token specific rent
"""
serializer_class = RentSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('city', 'place', 'property_category',)
search_fields = ('=city', '=place')
def get(self, request, token=None, format=None):
reply={}
try:
rents = Rental.objects.all()
if token:
rent = Rental.objects.get(token=token)
reply['data'] = self.serializer_class(rent).data
else:
reply['data'] = self.serializer_class(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
when i search the rent with the following parameters in the url, i get all the rents, instead i should get only those rents that lies in city Kathmandu and place koteshwor
http://localhost:8000/api/v1/rents?city=Kathmandu&place=Koteshwor
To use the functionality of DjangoFilterBackend, you could incorporate the filter_queryset method from GenericViewSet, which is the DRF class that inherits from APIView and leads to all specific 'generic' view classes in DRF. It looks like this:
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py
Here If you are using APIView, There is nothing to do with filters.So you have to do like
get_data = request.query_params #or request.GET check both
Then
Rental.objects.filter(city=get_data['city'], place=get_data['place'])
In case someone is wondering how can we integrate django_filters filter_class with api_views:
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticated])
def filter_data(request, format=None):
qs = models.YourModal.objects.all()
filtered_data = filters.YourFilter(request.GET, queryset=qs)
filtered_qs = filtered_data.qs
....
return response.Ok(yourData)
Adding to #ChidG's answer. All you need to do is override the DjangoFilterBackend's filter_queryset method, which is the entry point for the filter, and pass it the instance of your APIView. The important point to note here is you must declare filter_fields or filter_class on the view in order to get the filter to work. Otherwise it just return your queryset unfiltered.
If you're more curious about how this works, the class is located at django_filters.rest_framework.backends.py
In this example, the url would look something like {base_url}/foo?is_active=true
from django_filters.rest_framework import DjangoFilterBackend
class FooFilter(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset, request=request).qs
return queryset
class Foo(APIView):
permission_classes = (AllowAny,)
filter_fields = ('name', 'is_active')
def get(self, request, format=None):
queryset = Foo.objects.all()
ff = FooFilter()
filtered_queryset = ff.filter_queryset(request, queryset, self)
if filtered_queryset.exists():
serializer = FooSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response([], status=status.HTTP_200_OK)

How to add additional fields on the base of inputs in django rest framework mongoengine

I am developing an API, using django-rest-framework-mongoengine with MongoDb, I want to append additional fields to the request from the serializer on the base of user inputs, for example If user enters keyword=#rohit49khatri, I want to append two more fields to the request by manipulating keyword, like type=username, username=rohit49khatri
Here's my code:
Serializer
class SocialFeedCreateSerializer(DocumentSerializer):
type = 'username'
class Meta:
model = SocialFeedSearchTerm
fields = [
'keyword',
'type',
]
read_only_fields = [
'type'
]
View
class SocialFeedCreateAPIView(CreateAPIView):
queryset = SocialFeed.objects.all()
serializer_class = SocialFeedCreateSerializer
def perform_create(self, serializer):
print(self.request.POST.get('type'))
But when I print type parameter, It gives None
Please help me save some time. Thanks.
for the additional question: how to get type parameter?
# access it via `django rest framework request`
self.request.data.get('type', None)
# or via `django request`
self.request.request.POST.get('type', None)
for the original question:
situation 1) IMHO for you situation, perform_create can handle it:
def perform_create(self, serializer):
foo = self.request.data.get('foo', None)
bar = extract_bar_from_foo(foo)
serializer.save(
additional_foo='abc',
additional_bar=bar,
)
situation 2) If you need to manipulate it before the data goes to serializer (so that the manipulated data will pass through serializer validation):
class SocialFeedCreateAPIView(CreateAPIView):
queryset = SocialFeed.objects.all()
serializer_class = SocialFeedCreateSerializer
def create(self, request, *args, **kwargs):
# you can check the original snipeet in rest_framework/mixin
# original: serializer = self.get_serializer(data=request.data)
request_data = self.get_create_data() if hasattr(self, 'get_create_data') else request.data
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=status.HTTP_201_CREATED, headers=headers)
def get_create_data(self):
data = self.request.data.copy()
# manipulte your data
data['foo'] = 'foo'
return data
situation 3) If you do need to manipulate the request:
(here's just an example, you can try to find out another place to manipulate the request.)
class SocialFeedCreateAPIView(CreateAPIView):
queryset = SocialFeed.objects.all()
serializer_class = SocialFeedCreateSerializer
def initial(self, request, *args, **kwargs):
# or any other condition you want
if self.request.method.lower() == 'post':
data = self.request.data
# manipulate it
data['foo'] = 'foo'
request._request.POST = data
return super(SocialFeedCreateAPIView, self).initial(request, *args, **kwargs)

Django Rest Framework pagination on GenericViewSet

I have the following GenericViewSet, I am trying to achieve pagination for the viewset, This is my viewset
class UserAccountViewSet(viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
queryset = UserAccount.objects.all()
lookup_field = 'username'
lookup_url_kwarg = "username"
serializer_class = UserAccountSerializer
page_size = 25
page_size_query_param = 'page_size'
max_page_size = 1000
def list(self, request):
queryset = self.queryset
if request.GET.dict():
return Response(status=status.HTTP_501_NOT_IMPLEMENTED)
serializer = UserListSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, **kwargs):
pass
def create(self, request, *args, **kwargs):
pass
def update(self, request, *args, **kwargs):
pass
def destroy(self, request, *args, **kwargs):
pass
this is my configuration,
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '100/day'
}
}
It is not getting paginated, how can I make pagination work with DRF?
thank you.
Since you are overriding list() method and not returning a paginated response, you are not getting paginated response in your API. Instead, you should call super() in list() method as DRF's list() method itself returns a paginated response for generic views or viewsets.
Pagination is only performed automatically if you're using the generic
views or viewsets. If you're using a regular APIView, you'll need to
call into the pagination API yourself to ensure you return a paginated
response.
class UserAccountViewSet(viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
def list(self, request, *args, **kwargs):
if request.GET.dict():
return Response(status=status.HTTP_501_NOT_IMPLEMENTED)
# call 'super()' to get the paginated response
return super(UserAccountViewSet, self).list(request, *args, **kwargs)

Categories

Resources