I'm working on a website where the frontend is done in React and the backend in Python with FastAPI. I made a form which takes a some data and sends it to the backend with axios. It looks like this
{
name='Jonathan',
aliases=["Johnny"],
birthdate='2-15-1980',
gender='male',
height=178
weight=90
nationalities=["American", "French"],
occupations=["Programmer", "Comedian"],
status='single',
images=[
{'attachment': FileList,
'location': 'Berlin',
'date': '10-14-2019'
}
]
}
However, when I submit it, FastAPI seems to remove the images from the form.
name='Jonathan',
aliases=["Johnny"],
birthdate='2-15-1980',
gender='male',
height=178
weight=90
nationalities=["American", "French"],
occupations=["Programmer", "Comedian"],
status='single',
images=[
{'attachment': {'0': {}}, 'location': 'Berlin', 'date': '10-14-2019'}
]
This is what the route currently looks like
#router.post("/register/user")
def register_user(user_data: UserCreate):
print(user_data)
I'm not entirely sure what's going on. I'm guessing it has something to do with how the data is send and its encryption. I'm at a dead end here. Thanks in advance.
Edit: This is what the UserCreate Schema looks like
class CharacterCreate(BaseModel):
name: str
aliases: list
birthdate: Optional[str]
gender: str
height: Optional[float]
weight: Optional[float]
nationalities: Optional[set[str]]
occupations: Optional[set[str]]
status: str
images: Optional[list]
As per the documentation, you can't have both JSON and Files / form-data in the same request, as the body is encoded using multipart/form-data (when files are included) or application/x-www-form-urlencoded (if only Form data included) instead of application/json. Have a look at this answer.
Thus, one way to solve this is to use a single parameter to hold the various "metadata" and have a second one for the files/images. Using Dependency Injection you can check the received data against the Pydantic model using parse_raw method, before proceeding to the endpoint. If a ValidationError is thrown, then an HTTPException (HTTP_422_UNPROCESSABLE_ENTITY) should be raised, including the errors. Example below. Alternatively, have a look at Method 4 in this answer.
app.py
from fastapi import FastAPI, Form, File, UploadFile, status
import pydantic
from pydantic import BaseModel
from typing import Optional, List
from fastapi.exceptions import HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi import Depends
app = FastAPI()
class User(BaseModel):
name: str
aliases: List[str]
height: Optional[float]
weight: Optional[float]
def checker(data: str = Form(...)):
try:
user = User.parse_raw(data)
except pydantic.ValidationError as e:
raise HTTPException(detail=jsonable_encoder(e.errors()), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
return user
#app.post("/submit")
def submit(user: User = Depends(checker), files: List[UploadFile] = File(...)):
return {"User": user, "Uploaded Files": [file.filename for file in files]}
test.py
import requests
url = 'http://127.0.0.1:8000/submit'
files = [('files', open('test_files/a.txt', 'rb')), ('files', open('test_files/b.txt', 'rb'))]
data = {'data' : '{"name": "foo", "aliases": ["alias_1", "alias_2"], "height": 1.80, "weight": 80.5}'}
resp = requests.post(url=url, data=data, files=files)
print(resp.json())
Related
I am new to ReactJS,I am using a django as backend, React as frontend i need to send a dict/json to reactjs which is locally created by choices not from data base. is there a way to send Data from django to reactjs without restframe work?
you can easily convert dic to json using json.dumps() method.
import json
# Data to be written
dictionary ={
"id": "04",
"name": "sunil",
"department": "HR"
}
# Serializing json
json_object = json.dumps(dictionary, indent = 4)
You can use JsonResponse instead of ordinary HttpResponse:
from django.http import JsonResponse
def hello_dictionary(request):
data = {'name': 'Alison', 'grade': 9}
return JsonResponse(data)
def hello_list(request):
data = [{'name': 'Alison', 'grade':9}, {'name': 'Alice', 'grade': 8}, {'name': 'Tom', 'grade': 10}]
return JsonResponse(data, safe=False)
If you pass something that is not a dictionary remember to add safe=false
Reference:
https://docs.djangoproject.com/en/4.1/ref/request-response/#jsonresponse-objects
I have been trying to figure out how to do 2fa with webauthn and I have the registration part working. The details are really poorly documented, especially all of the encoding payloads in javascript. I am able to register a device to a user, but I am not able to authenticate with that device. For reference, I'm using these resources:
https://github.com/cedarcode/webauthn-ruby
https://www.passwordless.dev/js/mfa.register.js
And specifically, for authentication, I'm trying to mimic this js functionality:
https://www.passwordless.dev/js/mfa.register.js
In my user model, I have a webauthn_id, and several u2f devices, each of which has a public_key and a webauthn_id.
In my Rails app, I do:
options = WebAuthn::Credential.options_for_get(allow: :webauthn_id)
session[:webauthn_options] = options
In my javascript, I try to mimic the js file above and I do (this is embedded ruby):
options = <%= raw #options.as_json.to_json %>
options.challenge = WebAuthnHelpers.coerceToArrayBuffer(options.challenge);
options.allowCredentials = options.allowCredentials.map((c) => {
c.id = WebAuthnHelpers.coerceToArrayBuffer(c.id);
return c;
});
navigator.credentials.get({ "publicKey": options }).then(function (credentialInfoAssertion)
{
// send assertion response back to the server
// to proceed with the control of the credential
alert('here');
}).catch(function (err)
{
debugger
console.error(err); /* THIS IS WHERE THE ERROR IS THROWN */
});
The problem is, I cannot get past navigator.credentials.get, I get this error in the javascript console:
TypeError: CredentialsContainer.get: Element of 'allowCredentials' member of PublicKeyCredentialRequestOptions can't be converted to a dictionary
options at the time navigator.credentials.get is called looks like this:
I've tried every which way to convert my db-stored user and device variables into javascript properly encoded and parsed variables but cannot seem to get it to work. Anything obvious about what I'm doing wrong?
Thanks for any help,
Kevin
UPDATE -
Adding options json generated by the server:
"{\"challenge\":\"SSDYi4I7kRWt5wc5KjuAvgJ3dsQhjy7IPOJ0hvR5tMg\",\"timeout\":120000,\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"OUckfxGNLGGASUfGiX-1_8FzehlXh3fKvJ98tm59mVukJkKb_CGk1avnorL4sQQASVO9aGqmgn01jf629Jt0Z0SmBpDKd9sL1T5Z9loDrkLTTCIzrIRqhwPC6yrkfBFi\"},{\"type\":\"public-key\",\"id\":\"Fj5T-WPmEMTz139mY-Vo0DTfsNmjwy_mUx6jn5rUEPx-LsY51mxNYidprJ39_cHeAOieg-W12X47iJm42K0Tsixj4_Fl6KjdgYoxQtEYsNF-LPhwtoKwYsy1hZgVojp3\"}]}"
This is an example of the serialised JSON data returned by our implementation:
{
"challenge": "MQ1S8MBSU0M2kiJqJD8wnQ",
"timeout": 60000,
"rpId": "identity.acme.com",
"allowCredentials": [
{
"type": "public-key",
"id": "k5Ti8dLdko1GANsBT-_NZ5L_-8j_8TnoNOYe8mUcs4o",
"transports": [
"internal"
]
},
{
"type": "public-key",
"id": "LAqkKEO99XPCQ7fsUa3stz7K76A_mE5dQwX4S3QS6jdbI9ttSn9Hu37BA31JUGXqgyhTtskL5obe6uZxitbIfA",
"transports": [
"usb"
]
},
{
"type": "public-key",
"id": "nbN3S08Wv2GElRsW9AmK70J1INEpwIywQcOl6rp_DWLm4mcQiH96TmAXSrZRHciZBENVB9rJdE94HPHbeVjtZg",
"transports": [
"usb"
]
}
],
"userVerification": "discouraged",
"extensions": {
"txAuthSimple": "Sign in to your ACME account",
"exts": true,
"uvi": true,
"loc": true,
"uvm": true
}
}
This is parsed to an object and the code used to coerce those base64url encoded values is:
credentialRequestOptions.challenge = WebAuthnHelpers.coerceToArrayBuffer(credentialRequestOptions.challenge);
credentialRequestOptions.allowCredentials = credentialRequestOptions.allowCredentials.map((c) => {
c.id = WebAuthnHelpers.coerceToArrayBuffer(c.id);
return c;
});
Hope that helps. The JSON data is retreived via a fetch() call and the byte[] fields are encoded as base64url on the serverside.
I'm uploading a base64 string but the GraphQL gets hung. If I slice the string to less than 50,000 characters it works. After 50,000 characters, graphQL never makes it to the resolve function, yet does not give an error. On the smaller strings, it works just fine.
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
const imageArray = reader.result;
this.context.fetch('/graphql', {
body: JSON.stringify({
query: `mutation s3Upload($img: String!) {
s3Upload(file: $img) {
logo,
}
}`,
variables: {
img: imageArray,
},
}),
}).then(response => response.json())
.then(({ data }) => {
console.log(data);
});
}
const s3Upload = {
type: S3Type,
args: {
file: { type: new NonNull(StringType) },
},
resolve: (root, args, { user }) => upload(root, args, user),
};
const S3Type = new ObjectType({
name: 'S3',
fields: {
logo: { type: StringType },
},
});
The correct approach here is to perform an actual S3 upload via a complex type using AWS AppSync - what you illustrate here looks more like you are attempting to save a base64 encoded image as a string to a field in what I can only assume to be a DynamoDB table entry. For this to work, though, you need to modify your mutation such that the file field is not a String!, but an S3ObjectInput.
There's a few moving parts under the hood you need to make sure you have in place before this "just works" (TM). First of all, you need to make sure you have an appropriate input and type for an S3 object defined in your GraphQL schema
enum Visibility {
public
private
}
input S3ObjectInput {
bucket: String!
region: String!
localUri: String
visibility: Visibility
key: String
mimeType: String
}
type S3Object {
bucket: String!
region: String!
key: String!
}
The S3ObjectInput type, of course, is for use when uploading a new file - either by way of creating or updating a model within which said S3 object metadata is embedded. It can be handled in the request resolver of a mutation via the following:
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id),
},
#set( $attribs = $util.dynamodb.toMapValues($ctx.args.input) )
#set( $file = $ctx.args.input.file )
#set( $attribs.file = $util.dynamodb.toS3Object($file.key, $file.bucket, $file.region, $file.version) )
"attributeValues": $util.toJson($attribs)
}
This is making the assumption that the S3 file object is a child field of a model attached to a DynamoDB datasource. Note that the call to $utils.dynamodb.toS3Object() sets up the complex S3 object file, which is a field of the model with a type of S3ObjectInput. Setting up the request resolver in this way handles the upload of a file to S3 (when all the credentials are set up correctly - we'll touch on that in a moment), but it doesn't address how to get the S3Object back. This is where a field level resolver attached to a local datasource becomes necessary. In essence, you need to create a local datasource in AppSync and connect it to the model's file field in the schema with the following request and response resolvers:
## Request Resolver ##
{
"version": "2017-02-28",
"payload": {}
}
## Response Resolver ##
$util.toJson($util.dynamodb.fromS3ObjectJson($context.source.file))
This resolver simply tells AppSync that we want to take the JSON string that is stored in DynamoDB for the file field of the model and parse it into an S3Object - this way, when you do a query of the model, instead of returning the string stored in the file field, you get an object containing the bucket, region, and key properties that you can use to build a URL to access the S3 Object (either directly via S3 or using a CDN - that's really dependent on your configuration).
Do make sure you have credentials set up for complex objects, however (told you I'd get back to this). I'll use a React example to illustrate this - when defining your AppSync parameters (endpoint, auth, etc.), there is an additional property called complexObjectCredentials that needs to be defined to tell the client what AWS credentials to use to handle S3 uploads, e.g.:
const client = new AWSAppSyncClient({
url: AppSync.graphqlEndpoint,
region: AppSync.region,
auth: {
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials()
},
complexObjectsCredentials: () => Auth.currentCredentials(),
});
Assuming all of these things are in place, S3 uploads and downloads via AppSync should work.
AWS AppSync (https://aws.amazon.com/appsync/) provides this with functionality known as "Complex Objects" where you can have a types for the S3 Object and the input:
type S3Object {
bucket: String!
key: String!
region: String!
}
input S3ObjectInput {
bucket: String!
key: String!
region: String!
localUri: String
mimeType: String
}
You could then do something like this to define this object as part of another type:
type UserProfile {
id: ID!
name: String
file: S3Object
}
And then specify a mutation to add it:
type Mutation {
addUser(id: ID! name: String file: S3ObjectInput): UserProfile!
}
Your client operations would need to specify the appropriate bucket, key (with file extension), region, etc.
More here: https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-react.html#complex-objects
As I'm going deeper and deeper into Ember.js application building process I hit another wall.
Before I was using mirage with great success - I just copy output from API that I wanted build around to mirage fixtures and it was working great.
Now I have problem with making it work with real API.
I first disabled mirage in config/environment.js
ember g adapter filter
import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
findAll: function(store, type, label) {
var url = `${this.host}/${this.namespace}/${type.modelName}`;
console.log(`${url}`);
return this.ajax(url, 'GET');
},
});
The application adapter looks like this
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
host: 'http://127.0.0.1:1234',
namespace: 'api',
headers: Ember.computed(function(){
return {"secret": "1234"};
})
});
And that way when I enter /filter
app/routes/filter/index
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('filter');
}
});
I can see that url is build ok as http://127.0.0.1:1234/api/filter and there is no 404 but I get error
Error while processing route: filter.index The adapter operation was aborted EmberError#http://127.0.0.1:4200/assets/vendor.js:29616:15
and as I don't fully grasp the know-how of Ember Inspector im trying to figure this out somehow
My filter model that worked before (with mirage) looks like this:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr(),
url: DS.attr()
});
The api returns list [{"id":1, "name": "namex", "url": "http://"},{"id":2, "name": "namey", "url": "http://"}]
I'm sure If its the way api return data as its not "filters" as I had problem with plurals before.
Edit: THIS RESOLVE THe problem
import DS from 'ember-data';
export default DS.RESTSerializer.extend(
{
normalizeFindAllResponse(store, type, payload)
{
var data = [];
payload.forEach(
function(item, index, enumerable)
{
var ob = {};
Ember.set(ob, 'id', item.id);
Ember.set(ob, 'type', 'filter');
Ember.set(ob, 'attributes', item);
data[index]=ob;
console.log(data[index]);
}
);
return {
data: data
};
}
});
This is almost the fix but not quite.
I can access some of model attributes like name, but there is a array object with 3 arrays inside, 2 of them are array the one in middle is Getter (what ever is that) and I cannot access it as its not array anymore. So im not sure if it binding to object correctly this way. Also I wasn't able to do anything with "data" because no matter what RESTAdapter here and there I put it would ignore it and ask for object with data/meta/errors attribute... dunno if its a bug or not.
As you are using JSONAPIAdapter your api response should follow the JSON format specification.
{
"data": [{
"type": "filter",
"id": "1",
"attributes": {
"name": "namex",
"url": "http://"
}
}, {
"type": "filter",
"id": "2",
"attributes": {
"name": "namey",
"url": "http://"
}
}]
}
or if you are not following JSONAPI in that case either you can change application adapter to extend RESTAdapter.
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://127.0.0.1:1234',
namespace: 'api',
headers: Ember.computed(function(){
return {"secret": "1234"};
})
});
RESTAdapter response would be like,
{"filters":[{"id":1, "name": "namex", "url": "http://"},{"id":2, "name": "namey", "url": "http://"}]}
either you need to send it like above format or manipulate it to produce the required format.(override normalizeFindAllResponse)
I have this python code, which fetches json and parses it:
from django.http import HttpResponse
import json, requests
def find(request):
context = {}
platformUrl = 'https://www.igdb.com/api/v1/platforms'
platformReq = requests.get(platformUrl, headers={'Authorization': 'Token token="1234"'})
platformData = json.loads(platformReq.text)
platformList = platformData['platforms']
print platformList
It outputs this with the print statement:
[{u'slug': u'saturn', u'id': 32, u'name': u'Sega Saturn'}, {u'slug': u'mac', u'id': 14, u'name': u'Mac'}, {u'slug': u'vc', u'id': 47, u'name': u'Virtual Console (Nintendo)'}
I would like to pass that data to javascript and have the ID and name from the json put in this javascript. (selectize.js) This would probably require some type of for loop, could be javascript, or even djangos?:
options: [
{id: 1, title: 'Spectrometer'},
{id: 2, title: 'Star Chart'},
{id: 3, title: 'Electrical Tape'}
]
Thanks
EDIT:
Following #satoru's link, I looked into it, and updated my code to this:
from django.http import HttpResponse
import json, requests
def find(request):
context = {}
platformUrl = 'https://www.igdb.com/api/v1/platforms'
platformReq = requests.get(platformUrl, headers={'Authorization': 'Token token="1234"'})
platformList = json.dumps(platformData)
print platformList
return render_to_response('find.html',{'platformList':platformList})
It now outputs this:
{"platforms": [{"slug": "saturn", "id": 32, "name": "Sega Saturn"}, {"slug": "mac", "id": 14, "name": "Mac"}, {"slug": "vc", "id": 47, "name": "Virtual Console (Nintendo)"}
How would I then pass that into javascript?
If the API already return a JSON-encoded response, you can just use platformReq.text. After passing it to render_to_response, you have access to a JSON-encoded string in your template.
Now you can design your Javascript module to be configurable with an object, for example:
<script src="my_module.js"></script>
<script>
MyModule.initialize({{ json_encoded }})
</script>
Check out this template library to convert your platform list to a JSON object from within the template
Example(Inside your template):
<script src="your_standalone_js.js">
<script>
var js_variable = {{platformList | jsonify }};
function_in_standalone_js(js_variable);
</script>
your_standalone_js.js:
var function_in_standalone_js = function(js_variable){
var options = js_variable['platforms'];
//What ever you need to do with options
};