Add HTTP_USER_AGENT to Django RequestFactory request? - python

Is there a way to add a user agent string to a RequestFactory request object? I have the following test:
def test_homepage(self):
request = self.factory.get(reverse('home'))
response = views.home_page(request)
self.assertEqual(response.status_code, 200)
The problem is that the home_page view calls a function that requires request.META["HTTP_USER_AGENT"]. As a result, the above test is raising a KeyError because it doesn't know what HTTP_USER_AGENT is. Is there a way to add it to the RF's request object? I know I can add it if I use Django's Client object but I'd prefer not to go this route as I want to eliminate all middleware involvement in my test.
Thank you.

Pass HTTP_USER_AGENT as keyword argument.
request = self.factory.get(reverse('home'), HTTP_USER_AGENT='Mozilla/5.0')
https://docs.djangoproject.com/en/1.5/topics/testing/overview/#django.test.client.Client.get via https://docs.djangoproject.com/en/1.5/topics/testing/advanced/#django.test.client.RequestFactory

Related

Django Rest Framework gives 302 in Unit tests when force_login() on detail view?

I'm using Django Rest Framework to serve an API. I've got a couple tests which work great. To do a post the user needs to be logged in and I also do some checks for the detail view for a logged in user. I do this as follows:
class DeviceTestCase(APITestCase):
USERNAME = "username"
EMAIL = 'a#b.com'
PASSWORD = "password"
def setUp(self):
self.sa_group, _ = Group.objects.get_or_create(name=settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME)
self.authorized_user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASSWORD)
self.sa_group.user_set.add(self.authorized_user)
def test_post(self):
device = DeviceFactory.build()
url = reverse('device-list')
self.client.force_login(self.authorized_user)
response = self.client.post(url, data={'some': 'test', 'data': 'here'}, format='json')
self.client.logout()
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# And some more tests here
def test_detail_logged_in(self):
device = DeviceFactory.create()
url = reverse('device-detail', kwargs={'pk': device.pk})
self.client.force_login(self.authorized_user)
response = self.client.get(url)
self.client.logout()
self.assertEqual(status.HTTP_200_OK, response.status_code, 'Wrong response code for {}'.format(url))
# And some more tests here
The first test works great. It posts the new record and all checks pass. The second test fails though. It gives an error saying
AssertionError: 200 != 302 : Wrong response code for /sa/devices/1/
It turns out the list view redirects the user to the login screen. Why does the first test log the user in perfectly, but does the second test redirect the user to the login screen? Am I missing something?
Here is the view:
class APIAuthGroup(InAuthGroup):
"""
A permission to allow all GETS, but only allow a POST if a user is logged in,
and is a member of the slimme apparaten role inside keycloak.
"""
allowed_group_names = [settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME]
def has_permission(self, request, view):
return request.method in SAFE_METHODS \
or super(APIAuthGroup, self).has_permission(request, view)
class DevicesViewSet(DatapuntViewSetWritable):
"""
A view that will return the devices and makes it possible to post new ones
"""
queryset = Device.objects.all().order_by('id')
serializer_class = DeviceSerializer
serializer_detail_class = DeviceSerializer
http_method_names = ['post', 'list', 'get']
permission_classes = [APIAuthGroup]
Here is why you are getting this error.
Dependent Libraries
I did some searching by Class Names to find which libraries you were using so that I can re-create the problem on my machine. The library causing the problem is the one called keycloak_idc. This library installs another library mozilla_django_oidc which would turn out to be the reason you are getting this.
Why This Library Is Causing The Problem
Inside the README file of this library, it gives you instructions on how to set it up. These are found in this file. Inside these instructions, it instructed you to add the AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = [
'keycloak_oidc.auth.OIDCAuthenticationBackend',
...
]
When you add this authentication backend, all your requests pass through a Middleware defined inside the SessionRefresh class defined inside mozilla_django_oidc/middleware.py. Inside this class, the method process_request() is always called.
The first thing this method does is call the is_refreshable_url() method which always returns False if the request method was POST. Otherwise (when the request method is GET), it will return True.
Now the body of this if condition was as follows.
if not self.is_refreshable_url(request):
LOGGER.debug('request is not refreshable')
return
# lots of stuff in here
return HttpResponseRedirect(redirect_url)
Since this is a middleware, if the request was POST and the return was None, Django would just proceed with actually doing your request. However when the request is GET and the line return HttpResponseRedirect(redirect_url) is triggered instead, Django will not even proceed with calling your view and will return the 302 response immediately.
The Solution
After a couple of hours debugging this, I do not the exact logic behind this middleware or what exactly are you trying to do to provide a concrete solution since this all started based off guess-work but a naive fix can be that you remove the AUTHENTICATION_BACKENDS from your settings file. While I feel that this is not acceptable, maybe you can try using another library that accomplishes what you're trying to do or find an alternative way to do it. Also, maybe you can contact the author and see what they think.
So i guess you have tested this and you get still the same result:
class APIAuthGroup(InAuthGroup):
def has_permission(self, request, view):
return True
Why do you use DeviceFactory.build() in the first test and DeviceFactory.create() in the second?
Maybe a merge of the two can help you:
def test_get(self):
device = DeviceFactory.build()
url = reverse('device-list')
response = self.client.get(url)
self.assertEqual(status.HTTP_200_OK, response.status_code)
Is this a problem with the setUp() method? From what I see, you may be setting self.authorize_user to a user that was already created on the first test.
Instead, I would create the user on each test, making sure that the user doesn't exist already, like so:
user_exists = User.objects.filter(username=self.USERNAME, email=self.EMAIL).exists()
if not user_exists:
self.authorize_user = User.objects.create_user....
That would explain why your first test did pass, why your second didn't, and why #anupam-chaplot's answer didn't reproduce the error.
Your reasoning and code looks ok.
However you are not giving the full code, there must be error you are not seeing.
Suspicious fact
It isn't be default 302 when you are not logged in.
(#login_required, etc redirects but your code doesn't have it)
Your APIAuthGroup permission does allow GET requests for non-logged-in user ( return request.method in SAFE_METHODS), and you are using GET requests (self.client.get(url))
So it means you are not hitting the endpoint that you think you are hitting (your get request is not hitting the DevicesViewSet method)
Or it could be the case you have some global permission / redirect related setting in your settings.py which could be DRF related..
eg :
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Guess
url = reverse('device-detail', kwargs={'pk': device.pk})
might not point to the url you are thinking..
maybe there's another url (/sa/devices/1/) that overrides the viewset's url. (You might have a django view based url)
And I didn't address why you are getting redirected after force_login.
If it's indeed login related redirect, all I can think of is self.authorized_user.refresh_from_db() or refreshing the request ..
I guess some loggin related property (such as session, or request.user) might point to old instance .. (I have no evidence or fact this can happen, but just a hunch) and you better off not logging out/in for every test case)
You should make a seperate settings file for testing and add to the test command --settings=project_name.test_settings, that's how I was told to do.

Django test Client submitting a form with a POST request

How can I submit a POST request with Django test Client, such that I include form data in it?
In particular, I would like to have something like (inspired by How should I write tests for Forms in Django?):
from django.tests import TestCase
class MyTests(TestCase):
def test_forms(self):
response = self.client.post("/my/form/", {'something':'something'})
My endpoint /my/form has some internal logic to deal with 'something'.
The problem was that when trying to later access request.POST.get('something') I couldn't get anything.
I found a solution so I'm sharing below.
The key was to add content_type to the post method of client, and also urlencode the data.
from urllib import urlencode
...
data = urlencode({"something": "something"})
response = self.client.post("/my/form/", data, content_type="application/x-www-form-urlencoded")
Hope this helps someone!
If you are sending dictionaries on old-django versions using client, you must define the content_type='application/json' because its internal transformation fails to process dictionaries, you also need to send the dictionary like a blob using the json.dumps method. In conclusion, the following must work:
import json
from django.tests import TestCase
class MyTests(TestCase):
def test_forms(self):
response = self.client.post("/my/form/", json.dumps({'something':'something'}), content_type='application/json')
If you provide content_type as application/json, the data is serialized using json.dumps() if it’s a dict, list, or tuple. Serialization is performed with DjangoJSONEncoder by default, and can be overridden by providing a json_encoder argument to Client. This serialization also happens for put(), patch(), and delete() requests.
I have tried unit testing the POST requests in Django using Client(), but I fail to make it work (even with the methods specified above). So here is an alternative approach I take exclusively for the POST requests (using HttpRequest()):
from django.http import HttpRequest
from django.tests import TestCase
from . import views
# If a different test directory is being used to store the test files, replace the dot with the app name
class MyTests(TestCase):
def test_forms(self):
request = HttpRequest()
request.method = 'POST'
request.POST['something'] = 'something'
request.META['HTTP_HOST'] = 'localhost'
response = views.view_function_name(request)
self.assertNotIn(b'Form error message', response.content)
# make more assertions, if needed
Replace the view_function_name() with the actual function name. This function sends a POST request to the view being tested with the form-field 'something' and it's corresponding value. The assertion statements would totally depend on the utility of the test functions, however.
Here are some assertions that may be used:
self.assertEquals(response.status_code, 302):
Make this assertion when the form, upon submission of the POST request, redirects (302 is the status code for redirection). Read more about it here.
self.assertNotIn(b'Form error message', response.content):
Replace 'Form error message' with the error message that the form generates when incorrect details are sent through the request. The test would fail if the test data is incorrect (the text is converted to bytes since HttpResponse().content is a bytes object as well).
If the view function uses the Django Message framework for displaying the form error messages as well, include this before the response:
from django.contrib import messages
...
request._messages = messages.storage.default_storage(request)
If the view function uses Sessions, include this before the response:
from importlib import import_module
from django.conf import settings
...
engine = import_module(settings.SESSION_ENGINE)
session_key = None
request.session = engine.SessionStore(session_key)
Before sending out the request, remember the use of any context-processors that your application may use.
I personally find this method more intuitive (and functional). This seems to cover all possible test cases with regard to HTTP requests and forms as well.
I would also like to suggest that each unit test case could be broken down into separate components for increased coverage and discovering latent bugs in code, instead of clubbing all cases in a single test_forms().
This technique was mentioned by Harry J.W. Percival in his book Test-Driven Development with Python.

Django's URL Dispatcher not extracting kwargs

I have configured url patterns a ton of times in Django before.
Suddenly it's not working and I have no idea why...
I'm using Django REST Framework, and I'm testing the API using Django's RequestFactory.
This is my URL pattern:
url(r'^samples/(?P<sha256>\w+)/?$', endpoints.SampleAPI.as_view())
This is how I tested the GET request:
from django.test import RequestFactory
factory = RequestFactory()
request = factory.get('/samples/somehash')
response = endpoints.SampleAPI.as_view()(request)
This is what I'm trying to do:
class SampleAPI(APIView):
def get(self, request: Request, *args, **kwargs) -> Response:
sha256 = self.kwargs.get('sha256', None)
The URL gets resolved and I go into the method, but for some reason, sha256 is always None (The kwargs dictionary is always empty!).
Weird thing is that I did the exact same thing in 2 of the other services I'm developing (only another name for the keyword argument), and they work.
Do you guys see anything I'm missing here?
EDIT: Updated the post to show how I tested the urls
OK, It's a stupid mistake that I didn't think about.
I shouldn't have used RequestFactory in my tests, because the arguments do not get passed to the view this way.
The correct way is using Django's test Client:
from django.test import Client
client = Client()
response = client.get('/samples/somehash')
Sorry for the newbie mistake here.

Send user data in python requests module

I am unable to send the user details along with requests module i had to hard code the user details in the data payload to identify the user.
full_url = ''.join(['http://', get_current_site(request).domain, '/am/reply'])
data = {
'agent_type':'trigger',
'input':platform,
'userid':request.user.id ####==>> had to send userid like this
}
a = requests.get(full_url,params=data)
Is there way to send all general request data using requests.?
And moreover the requests url the destination view i have implemented
def index(request):
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('login'))
And request.user.id is none when url is reached through requests module
In general how should i validate a request when using requests module
Django uses request and response objects to pass state through the system.
When a page is requested, Django creates an HttpRequest object that contains metadata about the request. Then Django loads the appropriate view, passing the HttpRequest as the first argument to the view function. Each view is responsible for returning an HttpResponse object.
Some of the middleware included in Django’s contrib apps set attributes on the request. If you don’t see the attribute on a request, be sure the appropriate middleware class like authenticationmiddleware,sessionmiddleware.
Following piece of code will give the user.id if and only if the user is authenticated.
def myview(request):
if request.user.is_authenticated:
print request.user.id
else:
... # Do something else.
https://docs.djangoproject.com/en/1.10/ref/request-response/
If I understood your question correctly, You are getting request in one view, and then making a call to other view using requests module. It that case the request object in index view will be totally different because that request was sent from your server where application works, not from user. You can only get data in index view using request.GET.get("userid") and so on. And then if you will need user info, just fetch it again from database using userid. Passing request object to other view using requests library is not possible.

What's the best way to handle many paths on a python Tornado server?

class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/auth/login", AuthLoginHandler),
(r"/auth/logout", AuthLogoutHandler),
(r"/auth/register",RegisterHandler),
(r'/user/([a-z\d.]{5,})/?',UserHandler),
(r'/user/([a-z\d.]{5,})/friends',UserFriendHandler),
(r'/user/([a-z\d.]{5,})/status',StatusHandler),
(r'/user/([a-z\d.]{5,})/wall',WallHandler),
(r'/actions/respond_friend',FriendActionHandler),
]
This is my handlers array. This is likely to grow even more. My question involves the line:
(r'/actions/respond_friend',FriendActionHandler),
I plan on using this line to respond to friend requests via post, with the friends username as a post parameter. How would I retrieve this parameter when I send the post (where is it "stored")? Does it make more sense to do this:
(r'/user/([a-z\d.]{5,})/actions/respond_friend',FriendActionHandler),
You can use get_argument to get a request param inside of your post method
username = self.get_argument('username', None)
I don't know if it makes sense to pass the username through the url. If you are it would be duplicating it to post the username as well.
If a url param doesn't match the regex I believe tornado just raises a 404. So if you need more fine grained control over what errors are raised, it could make more sense to just post the username in the post body

Categories

Resources