how to get_argument from nested dictionary and array in tornado - javascript

JavaScript client sending request like this:
$.ajax({
url: 'http://localhost:7973/test',
type: 'GET',
data: {'host': 'mike', 'guests': {'name': ['car', 'ball'], 'age': [6, 10, 7]}},
success: function(result){alert(result)},
error: function(error){alert(error)}
});
Python server handling request using tornado:
import tornado.ioloop
import tornado.web
class TestHandler(tornado.web.RequestHandler):
def get(self):
host = self.get_argument('host')
print(host)
guests = self.get_argument('guests')
print(guests)
def make_app():
return tornado.web.Application([
(r'/test', TestHandler)
])
if __name__ == "__main__":
app = make_app()
port = 7973
app.listen(port)
print('-' * 100)
print('server started, listening to ', port, '...\n')
tornado.ioloop.IOLoop.current().start()
The outputs on the server side is as below. Apparently, the 'host' argument is successfully got, but I have no clue how to get a argument whose value is a complex object itself (say an array or a dictionary). Please explain to me the mechanism of these casts and dumps between data structures and their string representation? I read the tornado document, but I'm not able to find the answer.
mike
WARNING:tornado.general:400 GET
/test?host=mike&guests%5Bname%5D%5B%5D=car&guests%5Bname%5D%5B%5D=ball&guests%5Bage%5D%5B%5D=6&guests%5Bage%5D%5B%5D=10&guests%5Bage%5D%5B%5D=7
(::1): Missing argument guests
WARNING:tornado.access:400 GET
/test?host=mike&guests%5Bname%5D%5B%5D=car&guests%5Bname%5D%5B%5D=ball&guests%5Bage%5D%5B%5D=6&guests%5Bage%5D%5B%5D=10&guests%5Bage%5D%5B%5D=7
(::1) 1.99ms

You can convert your json object to a json string.
change
data: {'host': 'mike', 'guests': {'name': ['car', 'ball'], 'age': [6, 10, 7]}},
to
data: JASON.stringify({'host': 'mike',
'guests': {'name': ['car', 'ball'],
'age': [6, 10, 7]}}),
and then on the server side you can do:
guests_string = self.get_argument('guests')
guests = json.loads(guests_string)
guests should be a dictionary that you can do whatever with in Python.

Related

JavaScript automatically sorts dict?

So, the problem is that when ajax script receives dictionary from server it gains different order:
Server sent this:
{753: 'Wraith', 752: 'Phantom Extended', 751: 'Phantom', 750: 'Ghost Extended', 749: 'Ghost', 748: 'Dawn', 747: 'Cullinan', 746: 'Black Badge'}
But client gets this:
{746: "Black Badge", 747: "Cullinan", 748: "Dawn", 749: "Ghost", 750: "Ghost Extended", 751: "Phantom", 752: "Phantom Extended", 753: "Wraith"}
Js:
$.ajax({
method: 'GET',
url: request_url,
success: function(data){
console.log(data.response_models);
...
Also, the server is running on Django
Please help! I really appreciate it
If your object has properties that are "indexes" (numeric strings in range 0 .. 2^32-1), these properties are always enumerated is sorted numeric order. There's no way you can change that.
Have your server app return data in a more reasonable format, like an array of number-string pairs or an array of objects {id, value}. If this is not possible, convert your object to the said format on the client side, for example:
response = {753: 'Wraith', 752: 'Phantom Extended', 751: 'Phantom', 750: 'Ghost Extended', 749: 'Ghost', 748: 'Dawn', 747: 'Cullinan', 746: 'Black Badge'}
dataToDisplay = Object.entries(response).sort(
([key1, val1], [key2, val2]) =>
val1.localeCompare(val2))
console.log(dataToDisplay)

Unable to access JSON data from Javascript

I am passing the following from my Django back-end to my front-end in order to dynamically build a form :
{
"access_key": "93ec6137de00eacee6f8",
"profile_id": "7851E15D64",
"transaction_uuid": "c878c7e6db5657526",
}
Within the browser console, if I pass :
MyJSON = {
"access_key": "93ec6137de00eacee6f8",
"profile_id": "7851E15D64",
"transaction_uuid": "c878c7e6db5657526",
}
Then I can access each value properly, for example, MyJSON.access_key returns 93ec6137de00eacee6f8 perfectly within the console.
However, from my Javascript, I am unable to access any of those values as i get an "undefined".
var obj = JSON.parse(MyJSON)
console.log(obj) // returns the whole JSON String
console.log(typeof(obj)) // returns 'string'
alert(obj[0]) // returns "{", the very first character of the 'string'
alert(obj.access_key) // returns "undefined".
- How can access each Key and Value from MyJSON from my javascript? (not JQuery)
Note that I have reviewed many similar article but I must be missing the obvious ...
Hope you can assist !
Thanks in advance.
EDIT :
I have a list of Fields and a list of Values which i then merge into the below (pass the JSON Validator on https://jsonlint.com/):
{'access_key': '93ec6137d70aada23400eacee6f8', 'profile_id': '7851E53E-96BB-4D4-BD5-0FE61CC15D64', 'transaction_uuid': '00939a90-db7b-41cb-af45-669ec5cc69e8', 'signed_field_names': 'bill_to_forename,bill_to_surname,bill_to_email,bill_to_phone,bill_to_address_line1,bill_to_address_city,bill_to_address_postal_code,bill_to_address_country,transaction_type,reference_number,payment_method,amount,currency,locale,card_type,card_number,card_expiry_date', 'unsigned_field_names': 'card_type,card_number,card_expiry_date', 'signed_date_time': '2021-05-23T16:20:17Z', 'bill_to_forename': 'John', 'bill_to_surname': 'Doe', 'bill_to_email': 'null#cfgfg.com', 'bill_to_phone': '07922889582', 'bill_to_address_line1': '123 Random Street', 'bill_to_address_city': 'London', 'bill_to_address_postal_code': 'RG1T3X', 'bill_to_address_country': 'GB', 'transaction_type': 'sale', 'reference_number': 'o6yejf', 'payment_method': 'card', 'amount': '100', 'currency': 'USD', 'locale': 'en', 'card_type': '001', 'card_number': '4456530000001096', 'card_expiry_date': '12-2026', 'signature': 'Un5gNYB5qZaRazzCDWqrdZuNkTRARIcfAt9aF2a1wbY='}
Back-end Code
FieldList = ['access_key', 'profile_id', 'transaction_uuid', 'signed_field_names', 'unsigned_field_names', 'signed_date_time', 'bill_to_forename', 'bill_to_surname', 'bill_to_email', 'bill_to_phone', 'bill_to_address_line1', 'bill_to_address_city', 'bill_to_address_postal_code', 'bill_to_address_country', 'transaction_type', 'reference_number', 'payment_method', 'amount', 'currency', 'locale', 'card_type', 'card_number', 'card_expiry_date', 'signature']
ValueList = ['93ec6137d0aada23400eacee6f8', '7851E53E-96BB-4DF4-BD55-0FE61CC15D64', 'c4fe96b0-063f-4b94-a6a5-2137bb796bd9', 'bill_to_forename,bill_to_surname,bill_to_email,bill_to_phone,bill_to_address_line1,bill_to_address_city,bill_to_address_postal_code,bill_to_address_country,transaction_type,reference_number,payment_method,amount,currency,locale,card_type,card_number,card_expiry_date', 'card_type,card_number,card_expiry_date', '2021-05-23T16:27:24Z', 'John', 'Doe', 'null#cyrce.com', '07922889582', '123 Random Street', 'London', 'RG1T3X', 'GB', 'sale', 'xl42fn', 'card', '100', 'USD', 'en', '001', '4456530000001096', '12-2026', 'vvb73h0GUpzUrvoG9VDaMc3vQRV5GsL4QTATc7IrrPA=']
NewFormat = dict(zip(FieldList, ValueList))
MyJSON = json.dumps(NewFormat, indent=4)
return JsonResponse(MyJSON, safe=False)
Apologies for the large amount of data.
I am somehow forced to use "safe=False" in my Python back-end otherwise I end up with :
Traceback (most recent call last):
File "/home/pi/.local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/home/pi/.local/lib/python3.7/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/pi/Documents/Droplet/Droplet/Harness/sasop2.py", line 543, in signsasop
return JsonResponse(FinalJSONObject)
File "/home/pi/.local/lib/python3.7/site-packages/django/http/response.py", line 561, in __init__
'In order to allow non-dict objects to be serialized set the '
TypeError: In order to allow non-dict objects to be serialized set the safe parameter to False.
Since I am passing safe=False, is that why my front-end does not get the MyJSON as... JSON?
Would this be the root cause of the issue?
Front-End Sample :
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// Print received data from server
console.log('%c Processed Data \n',
'background: #000000; color: #FFFFFF; font-size: 30px'
,xhr.response);
// Dynamically create the ReconstructedForm
RawProcessedData = xhr.response
console.log(RawProcessedData)
// Convert to JSON
var obj = JSON.parse(RawProcessedData)
console.log(obj)
console.log(typeof(obj))
alert(obj[0])
alert(obj.access_key)
Thanks a lot for your quick input !
As per deceze's answer, I was essentially double parsing both in the back and front end for no reasons.
Removing the json.dumps from the backend allows the JSON object to be passed and managed in the front end without issue.
I have the same issue when i started developing on Django. If you need to pass dictionarys from django to javascripts, the best thing to do is just using django rest framework. It serialize ( in other words, it transform any data into a dictionary/json ) any given data from a model.
But if you want to make this without Django Rest, you should use fetch on javascript. This fetch ( also called as a "Promise") communicate with the backend ( in this case, Django ) and it GET or POST data from the frontend. I will give you an example.
Supose you have this on views.py:
from django.http.response import JsonResponse
def getJSON(request):
MyJSON = {
"access_key": "93ec6137de00eacee6f8",
"profile_id": "7851E15D64",
"transaction_uuid": "c878c7e6db5657526",
}
return JsonResponse(MyJSON)
And the you can link that to the urls.py like this:
urlpatterns = [
path('get-json', views.getJSON, name="get-json")
]
Then you can GET that JSON by doing this on your javascript:
function getDataJson(){
let url = 'get-json'
fetch(url,{
headers: {
"Content-type": "application/json",
},
})
.then(response => response.json())
.then(data=> console.log(data))
}
This will console log your data.

Sending True/False from python api call

i dont know if this is a stupid question but ive made a script that is making a api put call using requests in python. The data im trying to send contains a boolean value. So i have my script like this
import requests
data = {
'name': 'John',
'lastname': "Doe",
'email': "jd#gmail.com",
'is_staff' : True
}
url = 'http://api.url/user/'
response = requests.put(
url, data=data, verify=True, allow_redirects=False)
print(response)
But this gives me a 400 request error.
Then i thought that the problem was with the api call but when i remove the is_staff data.
data = {
'name': 'John',
'lastname': "Doe",
'email': "jd#gmail.com",
}
I get a 200 status code. My inutution is telling me that the api doesnt know what the boolean value from python is. But i could be wrong. Any ideas?
You need to JSON encode your string or rather 'serialize'.
Serialize obj to a JSON formatted str using this conversion table. If ensure_ascii is false, the result may contain non-ASCII characters and the return value may be a unicode instance.
Source
import requests
import json
data = {
'name': 'John',
'lastname': "Doe",
'email': "jd#gmail.com",
'is_staff' : True
}
url = 'http://api.url/user/'
headers = {'content-type': 'application/json'}
response = requests.put(url, data=json.dumps(data), headers=headers, verify=True, allow_redirects=False)
print(response)
The requests library takes the data dict and turns it into the request body, the request body is not a dict but a string containing the keys and values. In this case it turns it into the key1=value1&key2=value2 form.
You can view the request body the requests library is sending by:
response.request.body
It returns
'name=John&lastname=Doe&email=jd%40gmail.com&is_staff=True'
In this case the boolean you are sending is turned into "True" or "False". The problem could be that the webserver is expecting a "true" or "false" instead but it depends on the server.
Try:
data = {
'name': 'John',
'lastname': "Doe",
'email': "jd#gmail.com",
'is_staff' : "true"
}
Or the dynamic:
is_staff = True
data = {
'name': "John",
'lastname': "Doe",
'email': "jd#gmail.com",
'is_staff' : str(is_staff).lower()
}

JSON in client different from JSON returned by Rails server

I am making an AJAX call to this controller method:
def search_posts
keyword = params[:keyword]
results = #client.query("SELECT *
FROM posts
WHERE keyword = '#{keyword}'")
posts = []
results.each do |row|
posts << {
media_id: row["media_id"],
media_type: row["media_type"],
caption: row["caption"]
}
end
#client.close
render json: posts
end
and my JavaScript looks like this:
$.ajax({
url: '/search_posts',
type: "get",
data: {
keyword: keyword
},
success: function(res) {
console.log(res);
}
});
However, when I console.log the returned JSON, some of the media_ids are one less than the real value. For example, a media_id that should be 17924518156307537 is logged in the browser as 17924518156307536.
I have puts'd the posts hash just before the render json: line and the media_ids are correct at that point. What's very strange is that it only happens to some media_ids, not all.
Finally figured it out: JavaScript only supports up to 53-bit integers and some of the media_ids were larger than that. Solved this by converting the media_ids to strings in Ruby before sending them to the client.

django REST framework nested serializer and POST nested JSON with files

How do I send a POST request with nested data including files (images) to django REST nested serializers?
Given this JS object:
bookData: {
title: 'Anne of Green Gables',
coverImage: File(123456),
pages: 123,
author: {
name: 'Lucy Maud Montgomery',
born: 1874,
profilepicture_set: [
{file: File(234567), description: 'Young L. M. Montgomery'},
{file: File(234568), description: 'Old L. M. Montgomery}
],
quote_set: [
{text: "I'm so glad I live in a world where there are Octobers."},
{text: "True friends are always together in spirit."},
]
},
}
I want to send a POST request to my django REST API (I use VueJS on frontend, but this does not really matter).
# views.py
class CreateBookView(generics.CreateAPIView):
serializer_class = CreateBookSerializer
queryset = Book.objects.all()
# serializers.py
class CreateBookSerializer(serializers.ModelSerializer):
author = CreateAuthorSerializer()
class Meta:
model = Book
fields = ('title', 'pages', 'author')
#transaction.atomic
def create(self, validated_data):
author_data = validated_data.pop('author')
uploader = self.context['request'].user
book = Book.objects.create(uploader=uploader, **validated_data)
Author.objects.create(book=book, **author_data)
return book
# models.py
class AuthorManager(models.Manager):
def create(self, **author_data):
quotes_data = author_data.pop('quote_set')
photos_data = author_data.pop('profilepicture_set')
author = Author(**author_data)
author.save()
for quote_data in quotes_data:
Quote.objects.create(author=author, **quote_data)
for photo_data in photos_data :
ProfilePicture.objects.create(author=author, **photo_data)
return author
## Skipping the CreateAuthorSerializer, Quote and ProfilePicture Managers as they are analogous.
## Assume one Author can only write one Book (OneToOneField). No need to look the name up for existing Authors.
EDIT
I wanted to add some info on how I send the data in the frontend in VueJS.
sendData(){
var fd = new FormData()
var bookData = {
title: this.$store.getters.title, # 'Anne of Green Gables"
coverImage: this.$store.getters.coverImage, # File(somesize)
pages: this.$store.getters.pages, # 123
author: this.$store.getters.author, # nested object
...
}
fd = objectToFormData(bookData, fd) # external function, see below
this.$http.post('api/endpoint/create/', fd, headers: {
'Content-Type': 'multipart/form-data',
'X-CSRFToken': Cookies.get('csrftoken'),
}
}).then(...)
}
The sendData() is invoked on button click.
The objectToFormData is an external lib from npm (click) that transforms the nested object into flat form.
The problem with this solution (as well as #Saji Xavier's solution) is that the request.data contains an ugly QueryDict which is far from the original bookData object structure and is not accepted by the REST serializer (namely, the author field is missing - which is true, the object has been flattened).
<QueryDict: {
'title': ['Anne of Green Gables'],
'coverImage':[InMemoryUploadedFile: cover.jpg (image/jpeg)],
'pages': ['123'],
'name': ['Lucy Maud Montgomery'],
'born': ['1874'],
'profilepicture_set[][description]': ['Young L. M. Montgomery', 'Old L. M. Montgomery'],
'profilepicture_set[][file]': [
InMemoryUploadedFile: young.jpg (image/jpeg),
InMemoryUploadedFile: old.jpg (image/jpeg)
],
'quote_set[][text]': [
"I'm so glad I live in a world where there are Octobers.",
"True friends are always together in spirit."
]
}>
How do I deal with it now? It seems sooooo ugly. What I can think of is to overwrite the create() function.
class CreateBookView(generics.CreateAPIView):
(...)
def create(self, request, *args, **kwargs):
book_data = {}
book_data['title'] = request.data['title']
book_data['pages'] = request.data['pages']
book_data['cover_image'] = request.data['coverImage']
book_data['author'] = {}
book_data['author']['name'] = request.data['name']
book_data['author']['born'] = request.data['born']
(...)
serializer = self.get_serializer(data=book_data)
(...)
This is extremely ugly solution, ineffective for models with multitude of fields and several levels of nestedness (this book/author is just a toy example).
How should I handle it? Do i change the way of POSTing (to get a nice request.data format) or do I handle this ugly one?
According DRF documentation,
Most parsers, such as e.g. JSON don't support file uploads. Django's
regular FILE_UPLOAD_HANDLERS are used for handling uploaded files.
One option is to use Javascript FormData API to send multipart data (works with VueJS/AngularJS)
var fd = new FormData();
fd.append('title', 'Anne of Green Gables')
fd.append('author_name', 'Lucy Maud Montgomery')
fd.append('author_profilepicture_set', files[])
$http.post('url', fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
});

Categories

Resources