Acessing POST field data without a form (REST api) using Django - python

In the django documentation, it says:
HttpRequest.POST
A dictionary-like object containing all given HTTP POST parameters, providing that the request contains form data. See the QueryDict documentation below. If you need to access raw or non-form data posted in the request, access this through the HttpRequest.body attribute instead.
However, the server does not respond to a browser (such as using JS frameworks or a form) but instead a REST api sent by an Anroid/iOS application.
If the client sends fields directly in a POST request, how can I read the data? For example, this (Java + Unirest):
Unirest.post("/path/to/server")
.field("field1", "value2")
.field("field2", "value2");
EDIT: Can I simply read the data usingresponse.POST["field1"], or will I have to do something with request.body?
EDIT 2: So I can simply use request.body as a dictionary-like object similar to request.POST?

As far as I understand the field method from Unirest just uses normal application/x-www-form-urlencoded data like a HTML form. So you should be able to just use response.POST["field1"] like you suggested.

From the docs:
request.data returns the parsed content of the request body. This is
similar to the standard request.POST and request.FILES attributes
except that:
It includes all parsed content, including file and non-file inputs.
It supports parsing the content of HTTP methods other than POST, meaning that you can access the content of PUT and PATCH
requests.
It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming
JSON data in the same way that you handle incoming form data.
Can I simply read the data using response.POST["field1"], or will I
have to do something with request.body?
So I can simply use request.body as a dictionary-like object similar
to request.POST?
An example - From a create method (viewsets):
user = dict(
full_name=request.DATA['full_name'],
password=request.DATA['password'],
email=request.DATA['email'],
personal_number=request.DATA['personal_number'],
user_type=request.DATA['user_type'],
profile_id=request.DATA['profile_id'],
account_id=request.DATA['account_id']
)
Edit 1: In version 3 (latest) - request.DATA has been replaced with request.data:
user = dict(
full_name=request.data['full_name'],
password=request.data['password'],
email=request.data['email'],
personal_number=request.data['personal_number'],
user_type=request.data['user_type'],
profile_id=request.data['profile_id'],
account_id=request.data['account_id']
)

If the api you are interacting with is a sipmle Django Class Based view, you access the data through request.body something like this:
class MyView(View):
def post(self, request):
field1 = request.body.get('field1')
field2 = request.body.get('field2')
... # processing here
In case you are using Django rest framework api, you access the data through request.data:
field1 = request.data.get('field1')
field2 = request.data.get('field2')
NB: If you find request.DATA used somewhere in Internet that's correct too, but it's only valid for old version of DRF, and it's deprecated in favor of request.data in the newer versions.

Related

Django req.POST always returns False

I am testing a shipstation webhook and I can't seem to get data from the POST request they are sending.
Their webhook docs say that their POST request will contain a body that looks like this:
{"resource_url":"https://ssapiX.shipstation.com/orders?storeID=123456&importBatch=1ab23c4d-12ab-1abc-a1bc-a12b12cdabcd","resource_type":"ORDER_NOTIFY"}
To debug the issue, I went into the Firefox and tried to send this:
And got the same result; req.method = 'POST' and req.POST = False
View controller for myNgrokAddress.ngrok.io/bot/shipstation:
#csrf_exempt
def vc(req):
print(req.META) //this works but it looks like meta-data for my browser and not from shipstation
print(req.POST.get('resource_url')) //prints false
print(req.POST) //prints false
return HttpResponse('')
When I go to localhost:4040 (the ngrok inspector) the POST body shows up, so something must be incorrectly configured on my django server.
I set ALLOWED_HOSTS = ['myNgrokAdress.ngrok.io', 'localhost'] in my settings.py. Is there something else I need to do?
What am I missing here?
The problem is with the req.POST method.
From the Django docs :
HttpRequest.POST:
A dictionary-like object containing all given HTTP POST parameters, providing that the request contains form data. See the QueryDict documentation below. If you need to access raw or non-form data posted in the request, access this through the HttpRequest.body attribute instead.
Since the data-type being sent is non-form data, you will need to use req.body instead.

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.

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.

Django-rest-framework: Browsable API can't get POST data via HTML form, but can from Raw data

Well, the problem is simple. If I post my data via the HTML form, a new record is created in the DB but all data passed is null, as shown in the picture.
post response via html form
However, if a use the "raw_data" interface, everything works just fine:
post response via raw data
Code is quite simple:
views.py:
class CollectionList(generics.ListCreateAPIView):
queryset = Collection.objects.all()
serializer_class = CollectionSerializer
serializer.py:
class CollectionSerializer(serializers.ModelSerializer):
class Meta:
model = Collection
fields = ('id','title', 'nlikes')
Any suggestion?
You simply need to upgrade django rest framework to 3.3.1. There was a bug in 3.3.0 that sets all form data to empty values when using the session login.
Edit:
The issue was https://github.com/tomchristie/django-rest-framework/issues/3574

Django, REST: Serialize a text or image file to post via HTTP in JSON

Running:Windows 7, Python 3.3. Django 1.6
Background: I'm created an app in Django using the REST framework, that accepts HTTP 'POST' requests with JSON descriptions of objects in the body and creates records in a SQL databse from those 'POST' requests. Most of the fields of such a database record are either integers, like numOfTests or char fields like result. These fields are deserialized into Django by the REST frameworks serializers.py file, which (to my knowledge) translate the plain json string into an object in Django, who then saves them into the SQL database with the appropriate commands. But in each table record we also need to store a text file, as well as a screenshot, and I haven't figured out how to send these via HTTP and JSON.
Now I've read how to upload a image file via the Django shell, here in a previous question. But all of the data entries into the databse will be done by remote computers in my case, all via json in the body of the HTTP requests.
Question: So is there a way to serialize an image file or txt file, so the various remote computers at our office can post them via json to the django server?
Note: These HTTP POST requests will be sent by scripts, in our case specifically every time a test finishes (python) it will send an http POST to our server url with the details about it's completion. So there's no human being to manually enter the shell and such to save an image into Django.
Yeah you could encode the images using base64 and just post them in the request but it's a bit of a hack. If you just save the base64 to the database then you will end up with a huge database which is bad.
There is a snippet here that uses base64 on the wire but saves as an ImageField:
http://www.snip2code.com/Snippet/93486/Django-rest-framework---Base64-image-fie
The only problem I can see with this is it makes debugging using Firebug (or similar) much more difficult as there is loads of extra data on the wire.
I guess this is the cleanest and shortest way to do this.
Let say you have a Model as follows:
class MyImageModel(models.Model):
image = models.ImageField(upload_to = 'geo_entity_pic')
data=model.CharField()
So the corresponding Serializer would be as follows:
from drf_extra_fields.fields import Base64ImageField
class MyImageModelSerializer(serializers.ModelSerializers):
image=Base64ImageField()
class meta:
model=MyImageModel
fields= ('data','image')
def create(self, validated_data):
image=validated_data.pop('image')
data=validated_data.pop('data')
return MyImageModel.objects.create(data=data,image=image)
The corresponding View can be as follows:
elif request.method == 'POST':
serializer = MyImageModelSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)
Notice in the Serializer, I have used implementation of Base64ImageField provided in the module django-extra-field.
To install this module run the following command:
pip install pip install django-extra-fields
Import the same and done!
Send (via post method) your image as an Base64 encoded string in JSON object along with any other data you have.

Categories

Resources