I want to create a character, with a name and an avatar. Here is my code :
CharacterApi.js, in which I make my network call
function addCharacter(name, avatar) {
const data = new FormData();
data.append(‘name’, name);
data.append(‘avatar’, avatar);
return authenticatedFetch({
path: '/teams/characters’,
method: 'post',
data
});
}
characters_controller.rb
def create
#character = #character.new(character_params)
if #character.save
render :show
else
render json: #character.errors, status: :unprocessable_entity
end
end
[. . .]
private
def character_params
params.fetch(:character, {}).permit(:name, :avatar)
end
Which, when I make my create request, gives me a 422: unprocessable entity error. However, debugs showed me that the right parameters are actually sent, and doing the following makes everything working fine again :
def create
#character = #characters.new
#character.name = params[:name]
#character.avatar = params[:avatar]
[. . .]
end
Even though this second method works, it is not very practical to me, since I’d like to add a few more params in the future.
If I send the params as simple JSON, I can’t get any file (always gives me a null value), but the first method works, this time :
With this I can’t send images or any files, but the initial rails syntax works again
function addCharacter(name, avatar) {
return authenticatedFetch({
path: '/teams/characters’,
method: 'post',
data: {name, avatar}
});
}
Is there a reason why the short syntax #character.new(character_params) isn’t working when I send FormData?
I use carrier wave to handle files in rails API. Thanks in advance for any help!
You are using Strong Parameters the wrong way.
It must be:
def character_params
params.require(:character).permit(:name, :avatar)
end
Related
I have an issue to send the contents of a .mat file to my frontend. My end goal is to allow clients to download the content of this .mat file at the click of a button so that they end up with the same file in their possession. I use Next.js + Django Rest Framework.
My first try was as follow:
class Download(APIView):
def get(self, request):
with open('file_path.mat', 'rb') as FID:
fileInstance = FID.read()
return Response(
fileInstance,
status=200,
content_type="application/octet-stream",
)
If I print out the fileInstance element I get some binary results:
z\xe1\xfe\xc6\xc6\xd2\x1e_\xda~\xda|\xbf\xb6\x10_\x84\xb5~\xfe\x98\x1e\xdc\x0f\x1a\xee\xe7Y\x9e\xb5\xf5\x83\x9cS\xb3\xb5\xd4\xb7~XK\xaa\xe3\x9c\xed\x07v\xf59Kbn(\x91\x0e\xdb\xbb\xe8\xf5\xc3\xaa\x94Q\x9euQ\x1fx\x08\xf7\x15\x17\xac\xf4\x82\x19\x8e\xc9...
But I can't send it back to my frontend because of a
"UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9c in position 137: invalid start byte"
This error is always the same regardless of which .mat file I try to send in my response.
Next I tried to use the scipy.io.loadmat() method. In this case, fileInstance gives me a much more readable dictionary object, but I still can't get it to transfer to the frontend because of the presence of NaN in my dict:
ValueError: Out of range float values are not JSON compliant
Finally, some suggested to use h5py to send back the data as such:
with h5py.File('file_path.mat', 'r') as fileInstance:
print(fileInstance)
But in that case the error I get is
Unable to open file (file signature not found)
I know my files are not corrupted because I can open them in Matlab with no problem.
With all this trouble I'm wondering if I'm using the right approach to this problem. I could technically send the dictionary obtained through 'scipy.io.loadmat()' as a str element instead of binary, but I'll have to figure out a way to convert this text back to binary inside a Javascript function. Would anybody have some ideas as to how I should proceed?
The problem was in my frontend after all. Still, here's the correct way to go about it:
class Download(APIView):
parser_classes = [FormParser, MultiPartParser]
def get(self, request):
try:
file_path = "xyz.mat"
response = FileResponse(file_path.open("rb"), content_type="application/octet-stream")
response["Content-Disposition"] = f"attachment; filename=file_name"
return response
except Exception as e:
return Response(status=500)
This should send to the frontend the right file in the right format. No need to worry about encoding and such.
Meanwhile, on the frontend you should receive the file as follows:
onClick={() => {
const url = '/url_to_your_api/';
axios({ method: 'get', url: url, responseType: 'blob' })
.then((response) => {
const { data } = response;
const fileName = 'file_name';
const blob = new Blob([data], { type: 'application/octet-stream' });
const href = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.download = fileName + '.mat';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(href);
})
.catch((response) => {
console.error(response);
});
}}
Long story short, the part I was missing was to specify to receive the data as blob inside the 'onClick()' function. By default, responseType from Axios is set to Json/String. For that reason, my file was modified at reception and would not be usable in matlab afterwards. If you face a similar problem in the future, try to use the 'shasum' BASH function to observe the hashed value of the file. It is with the help of that function that I could deduce that my API function would return the correct value and that therefore the problem was happenign on the frontend.
I am trying to send a JSON file and an image file together to a server, but am really struggling.
1) If I send just the quilt item, so skipping the formData and changing the $http part below to $http.post('quilts/create/', quilt), then set the server end point to expect (#RequestBody QuiltRequest quiltRequest) without the bits about transformRequest and headers, it processes the data therein quite happily but I don't have an image to add to the records.
2) If I don't add the quilt item to the formData, and tell the server to expect (#RequestParam("image") MultipartFile image), I can save the image file on my server and generate a url string for it, but have no other quilt information to make the corresponding database entry.
How can I send both the quilt and the image in one request, and have the server receive and process both?
Many thanks!
Client-side service:
this.create = function (quilt, image) {
quilt.size = JSON.parse(quilt.size);
quilt.maker = JSON.parse(quilt.maker);
const formData = new FormData();
formData.append('quiltRequest', quilt);
formData.append('image', image);
$http.post('quilts/create/', formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function (response) {
return window.location = '#!/quilts/created/' + response.data;
})
};
Server-side end point:
#PostMapping(path = "/create")
public BigInteger create(#RequestPart QuiltRequest quiltRequest, #RequestPart MultipartFile image) throws IOException {
// do stuff based on parameters received
}
Apart of it, i think you can try to encode the image to base64 string. Send it to server and at the server, You decode it
My required solution have given by a real-world hero, and is posted here in case anyone else with a similar problem stumbles upon this thread :) (But thank you to user3562932 for taking some time to read and make a suggestion).
On the client side, we have moved the five lines of data preparation into a separate method, such that the original create() now takes a bunch of parameters and jumps straight to $http.post(url, data which has been magically transformed into something appropriate to send {rules on how to send the data}).
$http.post('quilts/create/', formData(quilt, image), {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function (response) {
return window.location = '#!/quilts/created/' + response.data;
})
The magical transformation happens in new function formData(), which takes as its parameters the data we want to send and makes the necessary changes:
1) make a formData container for the data to be POSTed.
2) stringify information from the html form (e.g. text, numbers) into a JSON and append to formData.
2a) in this particular case, my quilt structure contains size and maker details which arrived from the backend as JSONs, and were selected in the webpage from drop-down lists of various sizes and makers, hence the parsing rows to get these items ready to be included in the formData.
3) convert files into BLOBs, and then likewise append.
4) return formData, with all required information neatly wrapped up and ready to go!
Note: in the services.js file, this formData() method actually appears above the create() method, but it feels more logical to talk about them this way around.
function formData(quilt, image) {
let formData = new FormData();
quilt.size = JSON.parse(quilt.size);
quilt.maker = JSON.parse(quilt.maker);
formData.append('quiltRequest', JSON.stringify(quilt));
formData.append('image', new Blob([image]));
return formData;
}
On the server side, we can now happily receive this through:
#PostMapping(path = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public BigInteger create(#RequestParam(value = "quiltRequest") String quiltRequest,
#RequestParam(value = "image") MultipartFile image) throws IOException {
QuiltRequest quilt = new ObjectMapper().readValue(quiltRequest, QuiltRequest.class);
QuiltResponse quiltResponse = quiltService.create(quilt, image);
return quiltResponse.getQuilt().getId();
}
In order to enable the end point to consume our exciting multimedia input, we have to add the following import at the top of the class:
import org.springframework.http.MediaType;
We use another import to enable the use of the MultipartFile class that we have designated for the incoming image file:
import org.springframework.web.multipart.MultipartFile;
The JSON object from the webpage has come through as a String, but that needs to be parsed into its underlying components to actually be of use. This is where the ObjectMapper comes into play. Call on its readValue() method, and pass in the string argument plus a template of what the information should look like when unwrapped (here, a QuiltRequest class with defined properties corresponding to the information we fed into the JSON back in the client-end server). Remember to include the necessary import to access the ObjectMapper:
import com.fasterxml.jackson.databind.ObjectMapper;
Hopefully this breakdown of the changes makes sense, with enough explanation to help other developers build end-to-end POST requests to suit their own projects.
I am using ajax to pass an array data to Django (1.10 under Python 3.5) view for further processing. However, I noticed that the passing is not success and results in None. I am wondering what place I have gone wrong and can anyone suggest solution to this. Thanks a lot. Details are as below:
Ajax query to pass array final_ele to view as the variable final_ele_view:
$.ajax({
url: "../exportNews/",
method: "POST",
data:
{
final_ele_view: 'final_ele',
csrfmiddlewaretoken: '{{ csrf_token }}'
}
});
Then in the urls.py:
urlpatterns = [
url(r'^admin/exportNews/', views.exportNews),
...
Then in views.exportNews:
from annoying.decorators import ajax_request
#ajax_request
#csrf_protect
def exportNews(request):
arr_tag = request.POST.getlist('final_ele_view[]')
return HttpResponse(arr_tag)
By the way, I just notice one thing. Even in the ajax I ask for the POST method, but in views.py which shows the method is actually GET (by using return HttpResponse(request.method) ). Is that the reason of the error, and why this happens?
Why the [] at the end of fiinal_ele_view[]? I bet the QueryDict returned from request.POST does not know how to handle square brackets because that's invalid Python syntax.
I have a resource, Answer, which has a composite key made of QuestionnaireId and QuestionId. The ngResource code is as follows:
function answerResource($resource) {
return $resource("/api/answers/:questionnaireId/:questionId",
{
questionnaireId: "#questionnaireId",
questionId: "#questionId"
}
);
}
I want to query this resource with the the questionnaire Id and get back all the answers. If I use:
answerResource.query(
{
questionnaireId: questionnaireId
}
);
Then the requested url is:
/api/answers/123
When I want it to be:
/api/answers?questionnaireId=123
Otherwise I have two routes that I need to handle for the query search - one with the Id in the querystring, the other with the Id as part of the url path. (I also have queries with search text where the questionnaire Id might not be present, that would use urls like /api/answers?q=sometext).
Surely any .query parameters should be passed as querystrings, not as part of the route. How do I get the desired behaviour?
The best option I can come up with, is to create a new search method on the resource, which doesn't have the parameters in the url:
// normal resource definition here...
,{
search: {
method: "GET",
url: "/api/answers",
isArray: true
}
}
Calling that with the composite key values will append them as querystring parameters and not as part of the url, e.g. /api/answers?questionnaireId=123
Faced a similar issue, but I'm messing with composite key (item, sequence).
1- html.erb: Borrowed option 2 from https://spin.atomicobject.com/2013/11/22/pass-rails-data-angularjs/
That way I could grab inside the .js script the item from the .erb form I already have (I'm doing some kind of "manage details -- crud" on request with angularjs)
2- in the controller .js file I grab the value from the custom data in the html element using document.getElementById and getAttribute, concatenating that to the request: '/items?personId='+div.getAttribute("data-personId")+'&format=json'
3- in the items_controller index action, by default rails g expects no argument and grabs all items, then I had to ask
if params[:personId].present?
#items = Item.where(personId: params[:personId])
else
grab-all-items
end
This works, but I'm not sure if it's the best approach because I'm a real newbie for angularjs.
Just messing with this issue, cannot figure yet how to remove a record.
$resource crete path instead of querystring because you define placeholders :
return $resource("/api/answers/:questionnaireId/:questionId"..
If you delete them from resource-level and then call $resource.query({param: value}) you can make querystring, also you can specify more actions for single $resource to perform different request overriding action url property.
I created a simple (and very fast) example might help you understand:
working example :https://jsfiddle.net/Nedev_so/b71feyc6/19/
EDIT :
after your comment i understand what you need, so
resource factory :
//only for example purpose
var answersBaseUrl = "https://example.com/api/answers";
var answerTemplateUri = commentsBaseUrl + '/:questionnaireId/:questionId'
var params = {questionnaireId: '#_questionnaireId',questionId: '#_questionId'};
var res = $resource(answerTemplateUri, params,{
one :{
method: "GET",
},
all: {
method: "GET",
url: answersBaseUrl,
isArray: true
}
});
return res;
});
and then in your controller :
//get answers by questionnaire
//GET /answers?questionnaireId=123
$scope.answersByQuestionnaire = answers.all({questionnaireId: 123});
//get answers by question
//GET /answers?questionId=123
$scope.answersByQuestion = answers.all({questionId:123});
//get single answer by questionnaire and question
//GET /answers/123/123
$scope.answer = answers.one({questionnaireId: 123,questionId:123 });
(check network logs and you can see expected behaviour)
I have a Cloud Endpoints method that looks like this:
//HTTP POST
#ApiMethod(name = "hylyts.insert")
public Hylyt insertHylyt(#Named("url") String url, Hylyt hylyt, User user)
throws OAuthRequestException{
log.info("Trying to save hylyt '"+hylyt+"' with id '"+hylyt.getId());
if (user== null) throw new OAuthRequestException("Your token is no good here.");
hylyt.setArticle(getArticleKey(url, user));
ofy().save().entity(hylyt);
return hylyt;
}
I call it from the Javascript Client Library using this:
gapi.client.hylytit.hylyts.insert({PARAMS}).execute(callback);
Now, if I structure {PARAMS} as suggested in the docs (second example),
{
'url': url,
'resource': {
'hylyt': {
'contentType': 'application/json',
'data': hylyt
}
}
}
I get a null object in the endpoint (not to mention that the whole point of this library is to make these calls simple, which this structure clearly violates).
When I structure {PARAMS} as these answers suggest,
{
'url': url,
'resource': hylyt
}
I get a null object in the endpoint again. The correct syntax is this:
{
'url': url,
'id': hylyt.id
'text': hylyt.text
}
Which just blows my mind. Am I doing this all wrong? Is this a bug? Is it only happening because gapi is also passing the auth token in the background?
Yes, I could use the request syntax instead, but, again, why even use the library if it's just as complex as making the XHRs in pure javascript? I wouldn't mind the complexity if Google explained in the docs why things are happening. But the docs, paraphrased, just say use these methods and the auth, CORS, and XHR magic will happen behind closed doors.
Is the API method correctly recognized as POST method?
The resource parameter which is sent as POST body won't work correctly in a GET request.
The way it looks you are actually sending a GET request with the Hylyt properties in the query string.
To make sure you can change the method annotation to this:
#ApiMethod(name = "hylyts.insert", httpMethod = HttpMethod.POST)
Yup, agreed it's a bug. caused me great pains as well.
So i guess the work around is to create a combined object to pass to your api all named and un named parameters. Rather than hardcode each.. a quick loop might be better.
var param = {};
param["url"] = url;
for (var prop in hylyt) {
param[prop] = hylyt[prop];
}
gapi.client.hylytit.hylyts.insert(param).execute(callback);
That mashing together of parameters / objects can become a slick function if you really want.. but it's a band aid for what I'd consider a defect.
I see in the related question (cloud endpoints resource attribute for transmitting named params & body not working), you actually logged a defect.. Good stuff. Though there still appears no movement on this one. fingers crossed for someday!
The bug has been resolved. The correct syntax is
gapi.client.hylytit.hylyts.insert({url: url}, hylyt).execute(callback);