Django views testing , RequestFactory with data - python

I have this view:
def send_results(request):
print request
if request.is_ajax():
address = request.POST.get('url')
process_data(address)
context = get_all_from_database()
return HttpResponse(json.dumps(context), content_type='application/json')
and I need to test it:
def test_send_results(self):
factory = RequestFactory()
request = factory.get('/views/send_results')
response = send_results(request)
self.assertEqual(response.status_code, 200)
But it always stop with error that in my view 'address' value is referensed before asignment. How to pass it some value ?

If the request.is_ajax() is False then address will not be assigned before process_data(address) is called. If you want to test an AJAX request then you should pass the HTTP_X_REQUESTED_WITH header:
request = factory.get('/views/send_results', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
However you'll still need to fix your view to handle the case when the request is not an AJAX request.

Related

Why POST API request not working from browser but working fine on Postman? Throws AssertionError in browser to call is_valid() before accessing .data

Ok, Here's the problem: I have created a POST API in Django REST framework & I want to add logged-in user to request.data when making POST request & It works perfectly fine when I create post calling this API from Postman, but when I visit the post create API endpoint from browser it throws AssertionError: '''When a serializer is passed a data keyword argument you must call .is_valid() before attempting to access the serialized .data representation.
You should either call .is_valid() first, or access .initial_data'''.
Here's the snippet of my get_serializer method which i use to override & add user to request.data:
class PostCreateView(CreateAPIView):
serializer_class = PostSerializer
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs["context"] = self.get_serializer_context()
request_data = self.request.data.copy()
request_data["user"] = self.request.user.id
kwargs["data"] = request_data
return serializer_class(*args, **kwargs)
def post(self,request):
serializer = self.get_serializer()
serializer.is_valid(raise_exception=True)
serializer.save()
status_code = status.HTTP_201_CREATED
response = {
'success' : 'True',
'status code' : status_code,
'message': 'Post created successfully',
'post_detail': serializer.data,
}
return Response(response, status=status_code)
Update: I have changed my code in post method to pass user initially in a Post instance & then pass it in PostSerializer as: serializer = self.serializer_class(post, data=request.data) & it works on both Postman as well as browser's DRF request. But I'm curious to know why my above code with get_serializer method gave me AssertionError from browser's DRF request? but worked fine when making a POST request from Postman.

Set cookie in GraphQL mutation

I need to update a cookie in GraphQL mutation using graphene and Django.
My first idea was to add cookie to context (which is request) and then set it in middleware.
I have a very simple mutation that looks like that:
class SetWantedCookieMutation(graphene.Mutation):
class Arguments:
wanted_cookie = graphene.String(required=True)
ok = graphene.Boolean(required=True)
def mutate(self, info, wanted_cookie):
# set cookie here
info.context.wanted_cookie = wanted_cookie
return SetWantedCookieMutation(ok=True)
And Django Middleware is that:
class CookieMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if (hasattr(request, 'wanted_cookie')):
response.set_cookie('wanted_cookie', request.wanted_cookie)
return response
But I cannot get wanted_cookie in my CookieMiddleware.
Any ideas how to set cookie in mutation/moddlewere?
Or what are other ways to set cookie through graphene mutation?
One way to do this is to check the operation name in the json of the request:
class CookieMiddleware(MiddlewareMixin):
def generate_cookie(self, user):
# Generate a cookie (probably with the user)
def request_has_cookie_mutation(self, request):
# Returns true if the request's operation is SetWantedCookieMutation
if len(request.body) != 0 and 'operationName' in json.loads(request.body):
return json.loads(request.body)['operationName'] == 'SetWantedCookieMutation'
return False
def process_response(self, request, response):
if (self.request_has_cookie_mutation(request)):
new_cookie = self.generate_cookie(request.user)
response.set_cookie('wanted_cookie', new_cookie)
return response
Note that it looks like you're using a version of Django pre-1.10 and should consider upgrading.

Test Django Forms submitted through Ajax view

I I'm trying to test a view that is used by Ajax. While with the server (manual testing) everything works, when I ran my unit test an error I can't explain appears.
If you take a look at the following lines, where I present my code the most simplified as possible, you will see that although I add a variable to the json dict named something (just as I do in ajax) the field appears as missing in the form.
My Test (tests.py):
def test_edit_profile_user(self):
self.user = User.objects.create_user(
username='user', email='test#awesome.org', password='top_secret')
self.client.login(username=self.user.username, password='top_secret')
data = {'something': 'data'}
response = self.client.post(reverse('edit_something'),
json.dumps(data),
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
print(response.content)
self.assertEqual(response.status_code, 200)
While my Ajax view works, my test gives the following traceback:
b'{"something": ["This field is mandatory."]}'
Failure
Traceback (most recent call last):
File "***\tests.py", line 81, in ***
self.assertEqual(response.status_code, 200)
AssertionError: 400 != 200
What am I doing wrong in the test?
The rest of the code:
My view (views.py):
def edit_something(request):
if request.method == 'POST':
form = CustomForm(request.POST)
if form.is_valid():
form.save()
response_code = 200
response_data = {'something':'updated')
else:
response_code = 400
response_data = form.errors
return JsonResponse(
response_data,
status=response_code,
)
My JS (something.js):
$.ajax({
url : form_endpoint, // the endpoint
type : "POST", // http method
data : {
something: 'data',
},// data sent with the post request
And code to handle the response
I hope I get a better solution, but for now, my solution was to remove content_type and post a dictionary.
In tests.py the code for the var response is:
response = self.client.post(reverse('edit_something'), data)
This makes the view receive the appropriate values, while when I add content_type='application/json' the view request.POST is empty.

get_current_user() returns None after POST request using MultipartPostHandler

I'm trying to programmatically POST rather than using a web form. To do this, I'm using the MultipartPostHander module downloaded from https://pypi.python.org/pypi/MultipartPostHandler/.
It seems to work well (ie. does the same thing that a web form POST request would do), except users.get_current_user() returns None in the POST page handler. In comparison, the same POST page handler returns the user info correctly, if the POST request is submitted by a web form.
Also tried with poster (http://atlee.ca/software/poster/index.html) and had the exact same issue.
Why could this be happening? Thank you in advance.
class first_page_handler(basehandler.BaseHandler):
def get(self):
user = users.get_current_user()
if user:
raw_upload_url = webapp2.uri_for('test_module')
qimage_upload_url = r'http://localhost:8080'+raw_upload_url
params = {'param1': 'param'}
opener = urllib2.build_opener(multipost.MultipartPostHandler)
urllib2.install_opener(opener)
req = urllib2.Request(qimage_upload_url, params)
response = urllib2.urlopen(req) # <<<< POST request submitted
text_response = response.read().strip()
return self.response.write("<html><body><p>"+text_response+"</p></body>
</html>")
else:
login_url = users.create_login_url("/admin")
context = {'login_url': login_url}
self.render_response('admin/login.html', **context)
class post_page_hander(basehandler.BaseHandler):
def post(self):
user = users.get_current_user() # << Returns NONE
return self.response.write("<html><body><p>"+user.email()+"</p></body></html>")
You're not setting any cookies on the request, so the server has no idea what the current user is.
(What you're doing is unusual ... there is probably a better way of doing whatever it is you want to do.)

Django POST request to my view from Pyres worker - CSRF token

I'm using Pyres workers to do some processing of data users enter in a form. Their processing is done by a view on my form, which I make a POST request to, with data including the data to process and a CSRF middleware token for the user. My issue is that this is apparently not enough, as Django still rejects my request with a 403 forbidden.
Relevant code:
Form handler:
def handler(request):
if(request.method == "POST"):
if(request.POST.__contains__("taskdata")):
#valid post of the form
taskdata = escape(request.POST.get("taskdata",""))
t = TaskData(data=taskdata, time_added=timezone.now(), token=request.POST.get("csrfmiddlewaretoken",""))
t.save()
r = ResQ(server="127.0.0.1:6379")
r.enqueue(TaskData, t.id)
return HttpResponse(t.id)
else:
#invalid post of the form
raise Http404
else:
raise Http404
Pyres worker job:
#staticmethod
def perform(taskData_id):
#Get the taskData from this id, test it for tasky stuff
task_data = TaskData.objects.get(pk=taskData_id)
post_data = [('id',task_data.id),('data',task_data.data), ('csrfmiddlewaretoken',task_data.token)] # a sequence of two element tuples
result = urllib2.urlopen('http://127.0.0.1:8000/tasks/nlp/process/', urllib.urlencode(post_data))
content = result.read()
return
View being posted to by that job:
def process(request):
if(request.method == "POST"):
return HttpResponse("HEY, it works!")
if(request.POST.__contains__("data") and request.POST.__contains__("id")):
#valid post to the form by the model
#taskdata = escape(request.POST.get("taskdata",""))
#data = get_times(taskdata)
return HttpResponse("Hey from process!")
#return HttpResponse(json.dumps(data))
else:
#invalid post of the form
raise Http404
else:
raise Http404
What I'm basically trying to do is save some raw data at form submission, along with the CSRF token for it. The workers then send that data + token to a processing view.
Unfortunately, posting the token doesn't seem to be enough.
Does anybody know what the csrf protection actually looks for, and how I can make my Pyres workers compliant?
(Suggested tag: pyres)
I think I see the problem.
The way Django's CSRF protection works is by generating a nonce, then setting a cookie to the value of the nonce, and ensuring the csrfmiddlewaretoken POST value matches the value of the cookie. The rationale is that it makes it a stateless system, which works without any persistent session data.
The problem is that the request you make in the Pyres worker job...
result = urllib2.urlopen('http://127.0.0.1:8000/tasks/nlp/process/',
urllib.urlencode(post_data))
...is coming from the server, not the client, so it won't have the cookie set.
Assuming the /tasks/nlp/process/ URL is protected such that it can only be accessed by the server, then it's probably simplest to make the process() view exempt from CSRF checking with...
#csrf_exempt
def process(request):
...
...otherwise you'll have to manually grab the cookie value in the handler() view, and pass it on to the Pyres worker job.
Update
To ensure the process() method can only be called by the server, one simple way would be to check the request object with something like...
#csrf_exempt
def process(request):
if request.META['REMOTE_ADDR'] != '127.0.0.1':
# Return some error response here.
# 403 is traditional for access denied, but I prefer sending 404
# so 'hackers' can't infer the existence of any 'hidden' URLs
# from the response code
raise Http404
# Now do the thing
....
...although there may be some built-in decorator or somesuch to do this for you.

Categories