Testing custom action on a viewset in Django Rest Framework - django

I have defined the following custome action for my ViewSet Agenda:
class AgendaViewSet(viewsets.ModelViewSet):
"""
A simple viewset to retrieve all the Agendas
"""
queryset = Agenda.objects.all()
serializer_class = AgendaSerializer
#action(detail=False, methods=['GET'])
def get_user_agenda(self, request, pk=None):
print('here1')
id = request.GET.get("id_user")
if not id:
return Response("No id in the request.", status=400)
id = int(id)
user = User.objects.filter(pk=id)
if not user:
return Response("No existant user with the given id.", status=400)
response = self.queryset.filter(UserRef__in=user)
if not response:
return Response("No existant Agenda.", status=400)
serializer = AgendaSerializer(response, many=True)
return Response(serializer.data)
Here, I'd like to unit-test my custom action named "get_user_agenda".
However, when I'm testing, the debug output("here1") doesn't show up, and it always returns 200 as a status_code.
Here's my test:
def test_GetUserAgenda(self):
request_url = f'Agenda/get_user_agenda/'
view = AgendaViewSet.as_view(actions={'get': 'retrieve'})
request = self.factory.get(request_url, {'id_user': 15})
response = view(request)
self.assertEqual(response.status_code, 400)
Note that:
self.factory = APIRequestFactory()
Am I missing something?
Sincerely,

You will have to use the method name of the custom action and not retrieve so:
view = AgendaViewSet.as_view(actions={'get': 'get_user_agenda'})

You have to specify request url
#action(detail=False, methods=['GET'], url_path='get_user_agenda')
def get_user_agenda(self, request, pk=None):
And in my opinion it would be better to use detail=True, and get pk from url.
For example: 'Agenda/pk_here/get_user_agenda/'

Related

Django api update/create request from test case never fired the related methods in serializer

How i can send the UPDATE or CREATE request from my test case? When i run my test case the create/update methods never fired in serializer..
What i misunderstand in Django philosophy?
Can someone suggest what i have to do?
For example:
#View
class filesPairView (viewsets.ModelViewSet):
serializer_class = filesPairViewSerializer
def create(self, request):
...
return Response(status=status.HTTP_201_CREATED)
#Serializer
class filesPairViewSerializer(serializers.ModelSerializer):
class Meta:
...
def create(self, validated_data):
print ("filesPairViewSerializer CREATE")
def update(self, validated_data):
print ("filesPairViewSerializer UPDATE")
#Test case
class filesPairViewTestCase(APITestCase):
def test_barmi(self):
print("test_barmi")
url = ('http://127.0.0.1:8000/api/filesPairView/')
data ={
#some valid data
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
#urls
router.register(r'filesPairView', views.filesPairView )
Do the following:
#Test case
from django.urls import reverse
class filesPairViewTestCase(APITestCase):
def test_barmi(self):
print("test_barmi")
url = reverse('filespairview-list')
data ={
#some valid data
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
#urls
router.register(r'filesPairView', views.filesPairView, basename='filespairview')
Notice that the trick here is use the basename which is normally in singular and has the following result based on REST:
reverse('filespairview-list') -> /filesPairView/ - GET - List all
reverse('filespairview-list') -> /filesPairView/ - POST - Create
reverse('filespairview-detail') -> /filesPairView/{int:pk}/ - GET - Get one
reverse('filespairview-detail') -> /filesPairView/{int:pk}/ - POST - Update one
reverse('filespairview-detail') -> /filesPairView/{int:pk}/ - PATCH - Update partial

Django REST Framework - Testing POST request

I'm currently struggling to make this current unit-test pass:
def test_markNotifications(self):
request_url = f'Notifications/mark_notifications/'
view = NotificationsViewSet.as_view(actions={'post': 'mark_notifications'})
request = self.factory.post(request_url)
request.POST = {'id_notifs': "1"}
force_authenticate(request, user=self.user)
response = view(request)
self.assertEqual(response.status_code, 200)
Here's the associated view:
#action(detail=False, methods=['POST'])
def mark_notifications(self, request, pk=None):
"""
Put Notifications as already read.
"""
id_notifs = request.POST.get("id_notifs")
if not id_notifs:
return Response("Missing parameters.", status=400)
id_notifs = str(id_notifs).split(",")
print(id_notifs)
for id in id_notifs:
notif = Notification.objects.filter(pk=id).first()
if not notif:
return Response("No existant notification with the given id.", status=400)
notif.isRead = True
notif.save()
return Response("Notifications have been marked as read.", status=200)
The problem is that even though I'm passing "id_notifs" through the request in test, I'm getting None when I do id_notifs = request.POST.get("id_notifs").
It seems that the id_notifs I'm passing in the POST request are neither in the body and the form-data. In this context, I have no idea on how to access them.
Looking forward some help, thanks.

DRF Viewset test method

I have added a method to my viewset as follows:
class CustomImageViewSet(viewsets.ModelViewSet):
queryset = CustomImage.objects.all()
serializer_class = CustomImageSerializer
lookup_field = 'id'
#action(detail=True, methods=['get'], url_path='sepia/')
def sepia(self, request, id):
# do something
data = image_to_string(image)
return HttpResponse(data, content_type="image/png", status=status.HTTP_200_OK)
Since it is not a default or overridden request method, I am not sure how can I proceed writing a test for it. Any suggestions?
You're not clear on what the test should test but you can test the response status_code for example like this:
def test_sepia_api():
api_client = APIClient()
response = api_client.get(path="{path_to_your_api}/sepia/")
assert response.status_code == 200
I noticed you were using pytest. I'll assume you've got pytest-django too then (it really does make everything easier). I like using request factory since it's generally faster if you've got authentication needs.
def test_me(self, user, rf):
view = CustomImageViewSet()
request = rf.get("")
request.user = user # If you need authentication
view.request = request
response = view.sepia(request, 123)
assert response.data == BLAH

Django rest-framework ListCreateAPIView doesn't return the full path for the file

I just started using Django rest-framework, and I wanted the JSON response to return the URL for the file stored in the server, I used generics.ListCreateAPIView class
class PeopleList(generics.ListCreateAPIView):
queryset = People.objects.all().order_by('registration_time')
serializer_class = PeopleSerializer
and it worked actually! it returned a full path clickable URL:
{
"id": 1,
...
"profile": "http://127.0.0.1:8000/media/profile_image/IMG_20190826_105834_vwKLf10.jpg",
...
},
But then I had to use the list function because I needed the request object.
class PeopleList(generics.ListCreateAPIView):
queryset = People.objects.all().order_by('registration_time')
serializer_class = PeopleSerializer
def list(self, request, *args, **kwargs):
if self.request.user.is_authenticated:
queryset = People.objects.all().order_by('registration_time')
serializer_class = PeopleSerializer(queryset, many=True)
print(serializer_class.data)
return Response(serializer_class.data)
else:
content = {'Authentication Error': 'Please Login!'}
return Response(content, status.HTTP_405_METHOD_NOT_ALLOWED)
Now the response only contains the path without the domain:
{
"id": 1,
...
"profile": "/media/profile_image/IMG_20190826_105834_vwKLf10.jpg",
...
},
so the question is, How can I return the whole URL?
Add request to the context passed to your serializer:
serializer_class = PeopleSerializer(queryset, many=True, context={'request':request})
Use Adel's answer. If you ever need to access the absolute URI, then request.build_absolute_uri() should suffice.

Django Rest Framework get params inside has_permission

I'm filtering real estates queryset dependent on user status and district (last one with GET param).
In views.py I have this:
class RealEstateView(APIView):
serializer_class = RealEstateSerializer
permission_classes = [RealEstatePermission]
def get(self, request):
district = self.request.query_params.get('pk')
if district:
serializer = RealEstateSerializer(RealEstate.objects.filter(owner_id=district), many=True)
else:
serializer = RealEstateSerializer(RealEstate.objects.all(), many=True)
return Response(serializer.data)
If user is superuser, he have access to all information. If user in not superuser, he can get access only to real estates from district which he is responsible. If user is responsible to district with id=1, but sends a get param with id=2, I need to raise an exception. But the problem is I don't know how to get access to get parameter in has_permission function. Doing this inside views get function seems not good idea.
I already tried request.resolver_match.kwargs.get('id') and view.kwargs.get('id'), both of them are empty.
in permissions.py:
class RealEstatePermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
if request.user.is_staff:
return True
## HERE I need something like request.user.district.id == kwargs('id')
if request.user.role == 'district_municipality':
return True
Using Django 3.0.5 and DRF 3.11.0.
Thank you for your help.
To get access to get parametersfrom url query you can use GET dict.
Example
url:
/district?id=2
access:
district_id = request.GET['id']
You can use this as well:
Url:
/district?id=2
Access:
district_id = view.kwargs['id']