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.
Related
I have to create a small web app in Flask which contains an API and also an interface and I'm facing the following problem:
This would be how i handle a GET request:
#app.route('/member/<id>', methods=['GET'])
def member_get(id):
member = cursor.execute(f"select * from members where id={id}").fetchone()
if member is not None:
return to_json(member), 200
else:
return 'Not found', 404
And I would like to create some small forms with which I could do GET,POST,PUT,DELETE operations.
This would be how I get the data from the form:
#app.route('/dashboard', methods=['POST'])
def dashboard_post():
id = request.form['get_id']
return redirect(url_for("member_get",id=id))
My question is how can I get the data from the API method without actually redirecting to that page?
More precise, can I call somehow redirect(url_for("member_get",id=id)) and get the response data directly? (if I print the return of the redirect method it only shows the request status)
I assume one solution would be using the requests module, but is there a way to do it directly in Flask?
First of all, an API should always return a response in a format that is consistent and predictable. The code for member_get returns JSON in case of success but plain text in case of failure. This is not okay. You should return JSON always, with the appropriate HTTP status code. Here you are using 404 to express Not found, this is good.
You can use the jsonify function in Flask for that. And maybe normalize the response, so that is always has the same shape, whether the member ID is found or not.
Otherwise, parsing the response from your API will be harder because it is not consistent depending on the scenario.
Second point, if I understand it right: if you want to invoke the route '/member/' from within your API, you could simply do:
return member_get(id)
You call the function that is attached to the route, not the route itself. Of course you could actually fetch the page with the requests module but this is unnecessary, since the function is available internally.
Your question is not clear, but as I understand, (1) you think you have to get your form data from one view and send it to another view to do operations. (2) you are not familiar with flask request and flask-wtf. and maybe (3) looking for a way to do this without refreshing or redirecting the page.
You don't need to separate your GET and POST methods. instead you can integrate both in one view.
#app.route('/member/<id>', methods=['GET', 'POST])
To handling data, you can use flask request.
from flask import request
and access to data in your view like this:
id = request.form.get("idField")
but you can also use Flask-WTF to simply make and handle forms.
with Flask-WTF your view would be like this:
from app.forms import SearchForm
#app.route('/your-endpoint', methods=['GET', 'POST'])
def yourView():
form = your_form()
if form.validate_on_submit():
id=form.idField.data
return render_template('test.html', form=form)
the condition form.validate_on_submit() checks if you are submitting a from or you just opened it. if you submit a form and it's data are valid based on validators defined in your form, the code runs. else just renders the template and returns the page.
To learn how to make forms with Flask-WTF I recommend reading this article:
If you don't want to refresh the page or redirect it after submitting the form, you can use AJAx on your page.
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.
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.
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.
I have a web application running on a Flask backend with a JS client handling the front-end work. I'm running into problems trying to save a key-value pair to Flask's session object (flask.session) through a simple Flask API.
The session object I'm trying to modify is called account_id and the two API routes basically look like this:
GET
#access_service.route('/current_account.json', methods=['GET'])
#login_required
def show_current_account():
return jsonify(account_id=session.get('account_id'))
POST
#access_service.route('/current_account.json', methods=['POST'])
#login_required
def update_current_account():
if request.json:
session['account_id'] = request.json['account_id']
return jsonify(account_id=session.get('account_id'))
return jsonify()
In the JS frontend a call to the POST route is made as follows:
$.ajax({
url: '/current_account.json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({ 'account_id': 10 })
});
Which does appear to work correctly, the ajax call returns with 200 OK and a correct return value. Logging from the Flask application also reveals that the session now contains the key account_id with value 10. However, looking up /current_account.json immediately after the POST request is made simply returns an account_id with value null.
What's stranger still is that using a simple in-browser REST client and making an identical POST request to current_account.json causes the session to work and persist as expected through full-page refresh, etc. Since that is the case, it leads me to believe that the problem has to do with the request itself rather than with Flask's session object, although I can't seem to figure out what exactly is causing it.
In my previous project, we experienced the same issue and it turns out that $.ajax does not carry cookies. We used manual session store to remedy the problem.