django test RequestFactory cannot get route parameter to work - python

I have a problem that I don't know how to make it work.
urls.py:
urlpatterns = [
url(r'athletes/search$', SearchAthletes.as_view()),
url(r'athletes/([0-9]+)$', ViewAthlete.as_view())
]
views.py:
class ViewAthlete(APIView):
def get(self, request, id, format=None):
athlete = Athlete.objects.get(id=id)
serializer = AthleteSerializer(athlete)
return Response(serializer.data)
test.py:
def test_view_athlete(self):
tmp = Athlete.objects.order_by('?')[0]
request = self.factory.get('/_api/v1/athletes/' + str(tmp.id))
request.user = AnonymousUser()
response = ViewAthlete.as_view()(request)
self.assertEquals(response.data.id, tmp.id)
I keep getting the following error:
Traceback (most recent call last):
File "/tests.py", line 44, in test_view_athlete
response = ViewAthlete.as_view()(request)
File "/venv/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/venv/lib/python3.5/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/venv/lib/python3.5/site-packages/rest_framework/views.py", line 474, in dispatch
response = self.handle_exception(exc)
File "/venv/lib/python3.5/site-packages/rest_framework/views.py", line 471, in dispatch
response = handler(request, *args, **kwargs)
TypeError: get() missing 1 required positional argument: 'id'
To my understanding, the problem is that, there is no id parameter passed to get function of ViewAthelete view class. What is the reason for this? In development environment (not testing), it displays the data but testing environment doesn't recognize the arguments from the route.

AFAIK urlpatterns are considered when testing through the full django request stack, e.g.: through django.test.Client, using it's get/post methods
When testing your view directly (MyView.as_view()(request)) the whole url resolver logic is bypassed, and then the args/kwargs need to be supplied by the caller (e.g.: MyView.as_view()(request, 'arg1', 'arg2', id='34'))

As zsepi says, your URLs aren't used here. To avoid repeating the arguments, rather than calling the view directly you could use the test client to "call" the URL: another advantage of doing this is that the middleware runs, so you don't need to assign the user attribute separately.
response = self.client.get('/_api/v1/athletes/' + str(tmp.id))

Related

Django / Django Rest Framework ModelViewSet: __init__() takes 1 positional argument but 2 were given

I'm trying to create a custom "list" under OptionViewSet but It's giving me an error.
class OptionViewSet(viewsets.ModelViewSet):
serializer_class = OptionSerializer
queryset = Option.objects.all()
def list(self, request):
queryset = Option.objects.all()
serializer = OptionSerializer(queryset, many=True)
return Response(serializer.data)
Error
__init__() takes 1 positional argument but 2 were given
This works fine:
class OptionViewSet(viewsets.ModelViewSet):
serializer_class = OptionSerializer
queryset = Option.objects.all()
urls.py
path('api/shipping/', include((shipping_routes.urls, 'shipping'), namespace='shipping')),
routes.py
routes.register(r'option', OptionViewSet, basename='option')
serializer
class OptionSerializer(serializers.ModelSerializer):
class Meta:
model = Option
fields = ['id', 'extra', 'name']
full traceback
Traceback (most recent call last):
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\viewsets.py", line 125, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
raise exc
File "C:\Users\Simon\.virtualenvs\django_backend-ZmgaAA1F\lib\site-packages\rest_framework\views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "C:\Users\Simon\Documents\GitHub\crud-react-django\django_backend\shipping\views.py", line 18, in list
return Response(serializer.data)
Exception Type: TypeError at /api/shipping/option/
Exception Value: __init__() takes 1 positional argument but 2 were given
I had the wrong Response import. Imported it from "requests" instead of "rest_framework.response".
In the url.py, in the the specific path from urlpatters[] you need to call your views.OptionViewSet.as_view() as a middle parameter between the URL path and the name reference:
path('api/shipping/', include((shipping_routes.urls, 'shipping'), namespace='shipping')),
Django has very good documentation and you can read more about the Django-Rest-Framework's ViewSets here:
https://www.django-rest-framework.org/api-guide/viewsets/
edit: From the exception stack trace we can see that url.py is refering to a problem with /api/shipping/option/ which essentially is the routes.py's record with basename='option'
Seems like you might need to adjust routes.py from
routes.register(r'option', OptionViewSet, basename='option')
to
routes.register(r'option', OptionViewSet.as_view(), basename='option')
Can you please give it a go and let us know how it goes!

Getting "Exception: You cannot access body after reading from request's data stream" while working with DRF and django-revproxy

I'm using django-revproxy and Django REST Framework in my project. And I'm exposing an API where users get analytics about their chatbots, and it works as the following:
The user send requests the analytics from the Django project
Django project, checks if the user is authenticated and owns that chatbot
if True it contacts another external service.
My urls.py:
# urls.py
urlpatterns = (
url(r'^analytics/(?P<path>.*)$', api.AnalyticsFunctionsProxyView.as_view()),
)
And in my view:
# views.py
from rest_framework.authentication import TokenAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from revproxy.views import ProxyView
from .permissions import HasChatBotPermission
...
class AnalyticsFunctionsProxyView(ProxyView):
upstream = settings.ANALYTICS_FAAS_URL
def parse_body(self, request):
if isinstance(request, rest_framework.request.Request):
return request.data
return super(AnalyticsFunctionsProxyView, self).parse_body(request)
#classmethod
def as_view(cls, *args, **kwargs):
view = super(AnalyticsFunctionsProxyView, cls).as_view(*args, **kwargs)
view = permission_classes((IsAuthenticated, HasChatBotPermission,))(view)
view = authentication_classes([TokenAuthentication, JSONWebTokenAuthentication])(view)
view = api_view(['GET', 'POST'])(view)
return view
And my HasChatBotPermission permissions
#permissions.py
class HasChatBotPermission(permissions.BasePermission):
def has_permission(self, request, view):
try:
bot_name = request.data.get('name')
user = request.user
self.message = 'Permission denied for {}'.format(name)
return ChatBot.objects.filter(user=user, project_name=project_id).exists()
except Exception:
self.message = 'Permission denied, no project_id was defined!'
return False
When the view is called it raises this exception:
Traceback (most recent call last):
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/request.py", line 379, in __getattribute__
return super(Request, self).__getattribute__(attr)
AttributeError: 'Request' object has no attribute 'body'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/exception.py", line 42, in inner
response = get_response(request)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/fcmam5/anaconda3/lib/python3.6/contextlib.py", line 52, in inner
return func(*args, **kwds)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 477, in dispatch
response = self.handle_exception(exc)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 437, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/views.py", line 474, in dispatch
response = handler(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/decorators.py", line 52, in handler
return func(*args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/revproxy/views.py", line 204, in dispatch
proxy_response = self._created_proxy_response(request, path)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/revproxy/views.py", line 139, in _created_proxy_response
request_payload = request.body
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/rest_framework/request.py", line 383, in __getattribute__
return getattr(self._request, attr)
File "/home/fcmam5/dela3a/env/lib/python3.6/site-packages/django/http/request.py", line 264, in body
raise RawPostDataException("You cannot access body after reading from request's data stream")
django.http.request.RawPostDataException: You cannot access body after reading from request's data stream
The issue is caused by this line bot_name = request.data.get('name') in my permissions.py, when I pass a string directly it passes without any problems.
My question is:
How can I access the request body without having this error? Why I'm having this error?
Is there a better solution for checking the user permission with Django revproxy.
This is my first question in Stackoverflow, sorry if my question is not clea, and for my poor English :)
You're having this error because django-revproxy attempts to read the raw request body so it can create a proxy request to the upstream server.
However, with Django's (and WSGI's, and buffering) semantics, this isn't possible once you've accessed the request body as anything but a raw stream, which you do when you request.data.get('name'). This parses the request body as JSON, HTTP multipart, whatever, depending on DRF's request negotiation configuration, and consumes the stream.
There are two ways you could get around this, as far as I see:
pass bot_name somewhere else than the body; a query string parameter, an HTTP header, part of the URL, for instance, so you don't need to access the body, or
make the backend request yourself using requests instead of reverse-proxying (which is basically the same thing, but with added magic to try and copy the request through as-is).

Cannot redirect when Django model class is mocked

I have a view here which adds a new List to the database and redirects to the List page. I have get_absolute_url configured in the model class. It seems to works perfectly.
def new_list(request):
form = ItemForm(request.POST)
if form.is_valid():
list_ = List()
list_.owner = request.user
list_.save()
form.save(for_list=list_)
return redirect(list_)
else:
return render(request, 'home.html', {'form': form})
But the problem happens when I try to mock the model class and the form class with patch from unitest.mock
class TestMyLists(TestCase):
#patch('lists.views.List')
#patch('lists.views.ItemForm')
def test_list_owner_is_saved_if_user_is_authenticated(
self, mockItemFormClass, mockListClass
):
user = User.objects.create(email='a#b.com')
self.client.force_login(user)
self.client.post('/lists/new', data={'text': 'new item'})
mock_list = mockListClass.return_value
self.assertEqual(mock_list.owner, user)
When I run the test, I get error like this:
Traceback (most recent call last):
File "/home/sjsakib/.virtualenvs/superlists/lib/python3.6/site-packages/django/core/handlers/exception.py", line 35, in inner
response = get_response(request)
File "/home/sjsakib/.virtualenvs/superlists/lib/python3.6/site-packages/django/core/handlers/base.py", line 128, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/sjsakib/.virtualenvs/superlists/lib/python3.6/site-packages/django/core/handlers/base.py", line 126, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/mnt/BAC4BB93C4BB4FFD/codes/tdd/superlists/lists/views.py", line 36, in new_list
return redirect(list_)
File "/home/sjsakib/.virtualenvs/superlists/lib/python3.6/site-packages/django/shortcuts.py", line 58, in redirect
return redirect_class(resolve_url(to, *args, **kwargs))
File "/home/sjsakib/.virtualenvs/superlists/lib/python3.6/site-packages/django/http/response.py", line 407, in __init__
self['Location'] = iri_to_uri(redirect_to)
File "/home/sjsakib/.virtualenvs/superlists/lib/python3.6/site-packages/django/utils/encoding.py", line 151, in iri_to_uri
return quote(iri, safe="/#%[]=:;$&()+,!?*#'~")
File "/usr/local/lib/python3.6/urllib/parse.py", line 787, in quote
return quote_from_bytes(string, safe)
File "/usr/local/lib/python3.6/urllib/parse.py", line 812, in quote_from_bytes
raise TypeError("quote_from_bytes() expected bytes")
TypeError: quote_from_bytes() expected bytes
It seems like the redirect function will not work with a mock object. How can I fix this?
I'm using Django 2.0.1
I'm learning the same tutorial, and i got the same error, however i find the solution here: Mock() function gives TypeError in django2
The cause is:
Django 2 doesn't support anymore bytestrings in some places so when the views redirect the mock Class List it does as a mock object and the iri_to_uri django function throws an error.
In django 1.11 iri_to_uri forced the iri to a bytes return quote(force_bytes(iri), safe="/#%[]=:;$&()+,!?#'~") instead now is return quote(iri, safe="/#%[]=:;$&()+,!?#'~").
So the solution is to return redirect(str(list_.get_absolute_url())) instead of return redirect(list_) in the lists.views.py
Here is my example:
def new_list(request):
form = ItemForm(data=request.POST)
if form.is_valid():
list_ = List()
list_.owner = request.user
list_.save()
form.save(for_list=list_)
return redirect(str(list_.get_absolute_url()))
else:
return render(request, 'home.html', {"form": form})
Please see my answer here which only changes the testing code, while resulting in the desired production code.
In short,
Patch the redirect
#patch('app_name.views.redirect')
Pass mock_redirect as argument
def test_new_list_redirects_view(self, mock_redirect):
Check that the response is equal to the return_value of the mock_redirect
response = new_list(self.request)
self.assertEqual(response, mock_redirect.return_value)

cache_page in Django 1.8 causes 'str' object does not support item assignment error

I've written a wrapper for Django's cache_page decorator, so that a test function can be passed on to it. By that, e.g. only non-authenticated users get to see a cached version. Here's the wrapper's code:
from functools import wraps
from django.views.decorators.cache import cache_page
from django.utils.decorators import available_attrs
def passes_test_cache(test_func, timeout=None, using=None, key_prefix=None):
def decorator(view_func):
#wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
if test_func(request):
return cache_page(timeout, cache=using, key_prefix=key_prefix)(view_func)(request, *args, **kwargs)
else:
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
It's been working nicely up to Django 1.7.x. However, with the update to Django 1.8 I'm getting the following error:
Internal Server Error: /
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 223, in get_response
response = middleware_method(request, response)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/sessions/middleware.py", line 55, in process_response
httponly=settings.SESSION_COOKIE_HTTPONLY or None)
File "/usr/local/lib/python2.7/dist-packages/django/http/response.py", line 235, in set_cookie
self.cookies[key] = value
TypeError: 'str' object does not support item assignment
It seems something is wrong with the cookies dict, which appears to be a string here. Any idea how to solve this issue?
The way cookies work has been changed in 1.8, but your cached page still returns the 1.7 format. Just clear your cache to fix it.

Error when using Django Rest Framework's APITestCase

I'm trying to run some tests for my Django-Rest-Framework API but am stuck on an error. When I run the following tests, I get the following errors.
Traceback (most recent call last):
File "C:\Users\Bill\SD\DjangoApps\vidapp\startapp\tests.py", line 21, in test_get_user
response = self.client.get('/user/1/')
File "C:\Anaconda\lib\site-packages\django\test\client.py", line 473, in get
response = super(Client, self).get(path, data=data, **extra)
File "C:\Anaconda\lib\site-packages\django\test\client.py", line 280, in get
return self.request(**r)
File "C:\Anaconda\lib\site-packages\rest_framework\test.py", line 143, in request
return super(APIClient, self).request(**kwargs)
File "C:\Anaconda\lib\site-packages\rest_framework\test.py", line 95, in request
request = super(APIRequestFactory, self).request(**kwargs)
File "C:\Anaconda\lib\site-packages\django\test\client.py", line 444, in request
six.reraise(*exc_info)
File "C:\Anaconda\lib\site-packages\django\core\handlers\base.py", line 114, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
TypeError: __init__() takes exactly 1 argument (2 given)
Test Cases:
class UserTestCase(APITestCase):
def setUp(self):
helper.reset_test_db()
def test_get_user(self):
response = self.client.get('/user/1/')
print response.content
self.assertEqual(response.data, {'fname':'Generic','lname':'Name','token':'token1'})
URL Config:
url(r'^user/new/$', 'startapp.views.new_user'),
url(r'^user/1/$', 'startapp.views.get_1'),
Views:
class get_1(APIView):
def get(self, request):
user = db_models.User.objects.get(pk=1)
if(user is not None):
serial_user = serial.UserSerializer(user)
return Response(serial_user.data)
else:
return Response(status.HTTP_404_NOT_FOUND)
I know the view itself works because I tested that separately. The data is definitely present since helper.reset_test_db() puts it there (I know I should be using fixtures but this is for testing so I went with the simple route). The same error occurs for POST and other commands or when I use Django's TestCase instead of APITestCase. While this is my first time using Django's TestCase, I read both the Django and Django rest documents but can't seem to figure out this issue.
The view in your case is a class-based view.
So you have to add it to the urlconfig with as_view:
url(r'^user/1/$', startapp.views.get_1.as_view()),

Categories