Backbone.js Problems with fetch() in IE - django

I'm building a web app with Django on back-end and Backbone.js on front-end
I have problems with IE when I'm trying to fetch data from the server. When I run my HTML page in IE, the collection fetch always invoke the error func.
My code:
$(function(){
var Chapter = Backbone.Model.extend({});
var Chapters = Backbone.Collection.extend({
model: Chapter,
url: 'http://ip.olya.ivanovss.info/chapters'
});
var chapters = new Chapters();
var Router = new (Backbone.Router.extend({
routes: {
"": "choose_activity",
"/": "choose_activity"
},
choose_activity: function () {
chapters.fetch({
success: function () {
AppView.render();
},
error: function() {
alert('error');
}
});
}
}))();
var AppView = new (Backbone.View.extend({
el: '.popup',
templates: {
choose_activity: Handlebars.compile($('#tpl-activities').html())
},
render: function () {
this.$el.html(this.templates["choose_activity"]({ chapters: chapters.toJSON()}));
}
}))();
Backbone.history.start();
});
Django's View:
def chapters(request):
chapters = list(Chapter.objects.order_by('id'))
response = HttpResponse(json.dumps(chapters, default=encode_myway), mimetype='text/plain')
if request.META.get('HTTP_ORIGIN', None) in ('http://localhost', 'http://html.olya.ivanovss.info', 'http://10.0.2.2'):
response['Access-Control-Allow-Origin'] = request.META['HTTP_ORIGIN']
return response
Thank you in advance

IE7 doesn't support CORS.
There are 2 ways around this. The EASY way is Proxy over your API. My Python is rusty (I'm a Node/PHP dev), but I'm sure that there are a million and one resources on how do do this. The good thing about this is you don't have to touch the API. But it means your local server has to CURL and return every single request from your API server.
And second (and much less server intensive way) is JSONP! The idea of JSONP is that it appends a <script> to the document with the URL you specify. jQuery appends a ?callback=jQueryNNN where NNN is a random number. So effectively when the <script> loads, it calls jQueryNNN('The Response Text') and jQuery knows to parse the response from there. The bad thing about this is you have to wrap all of your responses on the API side (which is super easy if you're just starting, not so easy if you already have an infrastructure built out).
The annoying things about JSONP is that by it's nature you can't do a POST/PUT/DELETE. BUT you can emulate it if you have access to the API:
Backbone.emulateHTTP = true;
model.save(); // POST to "/collection/id", with "_method=PUT" + header.
To integrate JSONP with Backbone is pretty simple (little secret Backbone.sync uses jQuery's $.ajax() and the options parameters forwards over to jQuery ;)).
For each one of your models/collections which access a cross origin you can add a su
var jsonpSync = function (method, model, options) {
options.timeout = 10000; // for 404 responses
options.dataType = "jsonp";
return Backbone.sync(method, model, options);
};
In each collection and model what does cross-origin:
var MyCollection = Backbone.Collection.extend({
sync : jsonpSync
});
Or just overwrite the whole Backbone sync
Backbone.__sync = Backbone.sync;
var jsonpSync = function (method, model, options) {
options.timeout = 10000; // for 404 responses
options.dataType = "jsonp";
return Backbone.__sync(method, model, options);
};
Backbone.sync = jsonpSync;
On the server side you can do: this to return a JSONP response (copy pasted here):
def randomTest(request):
callback = request.GET.get('callback', '')
req = {}
req ['title'] = 'This is a constant result.'
response = json.dumps(req)
response = callback + '(' + response + ');'
return HttpResponse(response, mimetype="application/json")

Related

Postman test script - how to call an api twice to simulate 409 error

I am trying to run a few automated testing using the Postman tool. For regular scenarios, I understand how to write pre-test and test scripts. What I do not know (and trying to understand) is, how to write scripts for checking 409 error (let us call it duplicate resource check).
I want to run a create resource api like below, then run it again and ensure that the 2nd invocation really returns 409 error.
POST /myservice/books
Is there a way to run the same api twice and check the return value for 2nd invocation. If yes, how do I do that. One crude way of achieving this could be to create a dependency between two tests, where the first one creates a resource, and the second one uses the same payload once again to create the same resource. I am looking for a single test to do an end-to-end testing.
Postman doesn't really provide a standard way, but is still flexible. I realized that we have to write javascript code in the pre-request tab, to do our own http request (using sendRequest method) and store the resulting data into env vars for use by the main api call.
Here is a sample:
var phone = pm.variables.replaceIn("{{$randomPhoneNumber}}");
console.log("phone:", phone)
var baseURL = pm.variables.replaceIn("{{ROG_SERVER}}:{{ROG_PORT}}{{ROG_BASE_URL}}")
var usersURL = pm.variables.replaceIn("{{ROG_SERVICE}}/users")
var otpURL = `${baseURL}/${phone}/_otp_x`
// Payload for partner creation
const payload = {
"name": pm.variables.replaceIn("{{username}}"),
"phone":phone,
"password": pm.variables.replaceIn("{{$randomPassword}}"),
}
console.log("user payload:", payload)
function getOTP (a, callback) {
// Get an OTP
pm.sendRequest(otpURL, function(err, response) {
if (err) throw err
var jsonDaata = response.json()
pm.expect(jsonDaata).to.haveOwnProperty('otp')
pm.environment.set("otp", jsonDaata.otp)
pm.environment.set("phone", phone);
pm.environment.set("username", "{{$randomUserName}}")
if (callback) callback(jsonDaata.otp)
})
}
// Get an OTP
getOTP("a", otp => {
console.log("OTP received:", otp)
payload.partnerRef = pm.variables.replaceIn("{{$randomPassword}}")
payload.otp = otp
//create a partner user with the otp.
let reqOpts = {
url: usersURL,
method: 'POST',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify(payload)
}
pm.sendRequest(reqOpts, (err, response) => {
console.log("response?", response)
pm.expect(response).to.have.property('code', 201)
})
// Get a new OTP for the main request to be executed.
getOTP()
})
I did it in my test block. Create your normal request as you would send it, then in your tests, validate the original works, and then you can send the second command and validate the response.
You can also use the pre and post scripting to do something similar, or have one test after the other in the file (they run sequentially) to do the same testing.
For instance, I sent an API call here to create records. As I need the Key_ to delete them, I can make a call to GET /foo at my API
pm.test("Response should be 200", function () {
pm.response.to.be.ok;
pm.response.to.have.status(200);
});
pm.test("Parse Key_ values and send DELETE from original request response", function () {
var jsonData = JSON.parse(responseBody);
jsonData.forEach(function (TimeEntryRecord) {
console.log(TimeEntryRecord.Key_);
const DeleteURL = pm.variables.get('APIHost') + '/bar/' + TimeEntryRecord.Key_;
pm.sendRequest({
url: DeleteURL,
method: 'DELETE',
header: { 'Content-Type': 'application/json' },
body: { TimeEntryRecord }
}, function (err, res) {
console.log("Sent Delete: " + DeleteURL );
});
});
});

Google Cloud Function with Basic Auth not working properly

React Client Code - Using request promises to send username and password in Header
var password = values.password;
var email = values.email;
request
.head(
"https://us-central1-simplineet-754e8.cloudfunctions.net/CreateUserAuth"
)
.set('Content-Type', 'application/x-www-form-urlencoded')
.auth(email, password, false)
.query(dataobj)
.then(res => {
console.log(res);
if (res.statusCode === 200) {
console.log("statusText",res.body);
} else {
console.log("statusText",res.statusText);
}
})
.catch(err => {});
Backend - Google Cloud Function to Handle Basic Auth Requests from Client
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors({origin: true}));
exports.CreateUserAuth = functions.https.onRequest((request, response) => {
var corsFn = cors();
corsFn(request, response, function () {
// Request Header
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
response.setHeader('Access-Control-Allow-Credentials', true);
response.setHeader('Access-Control-Allow-Origin', '*');
var auth = require('basic-auth') // basic-auth NPM package to extract username and password from header
var user = auth(request)
var email = user.name; // Getting username from Auth
var password = user.pass; // Getting password from Auth
var username = request.query.username;
response.send('Hello from Firebase!'); // Not getting this response in Client
});
});
Response Getting in Client :
Response {req: Request, xhr: XMLHttpRequest, text: null, statusText: "", statusCode: 200, …}
As per MDN docs, HEAD responses should not have a body:
The HTTP HEAD method requests the headers that are returned if the specified resource would be requested with an HTTP GET method. Such a request can be done before deciding to download a large resource to save bandwidth, for example.
A response to a HEAD method should not have a body. If so, it must be ignored. Even so, entity headers describing the content of the body, like Content-Length may be included in the response. They don't relate to the body of the HEAD response, which should be empty, but to the body of similar request using the GET method would have returned as a response.
My guess is that GCP is handling it as a GET and stripping out the body before returning a response.
However, keep in mind that Google Cloud Functions HTTP trigger docs don't explicitly say that HEAD is a supported method:
You can invoke Cloud Functions with an HTTP request using the POST, PUT, GET, DELETE, and OPTIONS HTTP methods.
It looks like you are making a HEAD request instead of a POST request. Change to request.post() and it should work

Unexpected token P in JSON at position 0 when trying to return Excel spreadhsheet

I have an emberJS application where I can make a POST AJAX call to a Django backend. A function in Django creates an xlsx file for a bunch of queried items based on IDs coming in the POST request. It goes through the Django view function without any issues, but when the HTTP response is returned to ember, I get the error
SyntaxError: Unexpected token P in JSON at position 0
at parse (<anonymous>)
at ajaxConvert (jquery.js:8787)
at done (jquery.js:9255)
at XMLHttpRequest.<anonymous> (jquery.js:9548)
at XMLHttpRequest.nrWrapper (base-content:20)
I'm setting the response content type to application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, so I'm unsure as to why its trying to read the response as JSON.
Python Code
file_path = '/User/path_to_spreadsheet/content.xlsx'
fsock = open(file_path, "rb")
response = HttpResponse(fsock, content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename="content.xlsx"'
return response
EmberJS Code
export default Controller.extend({
actions: {
storeProductId(products) {
let product_ids = []
products.forEach(function(product){
product_ids.push(product.id)
});
let adapter = this.store.adapterFor('product-export');
adapter.export_products(product_ids).then(function(response){
console.log(response)
}).catch(function(response) {
console.log('ERROR')
console.log(response)
})
}
}
});
Product-Export Adapter Code
export default ApplicationAdapter.extend(FormDataAdapterMixin, {
export_products(products) {
let url = this.buildURL('unified-product');
url = `${url}export/`;
return this.ajax(url, 'POST', { data: {'products': products} });
}
});
By default, Ember Data makes some assumptions around how things should be handled (including that you’ll be receiving JSON data back). Is there a reason you are using Ember Data instead of using a direct Ajax call to your backend? Seems like that would greatly simplify things here ...

Incorrect base URL for Backbone DELETE requests - uses relative instead of absolute URLs

TL;DR version:
Building a Phonegap app using Backbone, and have a model called Client and a collection called Clients. Using a Tastypie API to communicate with a separate server. When I run fetch(), the URL uses the correct absolute URL (something like http://127.0.0.1:8000/api/v1/client/1/, but when I run Client.destroy(), it uses a relative URL of file:///api/v1/client/1/. How can I make it use the absolute URL for deleting the object?
Long version:
I'm building a mobile app with Backbone.js that consumes a Django/Tastypie API, and I've run into some seemingly odd behaviour that I can't figure out.
I define a base URL for the server at the top of the file:
// Set the base URL for querying the API
baseUrl = 'http://127.0.0.1:8000/api/v1/';
I have the following model and collection:
// Client model
Client = Backbone.Model.extend({
urlRoot: baseUrl + 'client',
// Default values
defaults: {
id: '',
name: '',
mobile: '',
email: '',
notes: '',
operator: '',
date_client_joined: '',
address: '',
postcode: ''
}
});
// Client collection
Clients = Backbone.Collection.extend({
// Will hold Client objects
model: Client,
// Set URL
url: baseUrl + 'client/'
});
And the individual clients are rendered in a list using the following view:
// Client list item view
ClientListItemView = Backbone.View.extend({
tagName: 'li',
events: {
'click .delete': 'deleteclient'
},
render: function () {
// Render the client list item template
var template = _.template($('#client-list-item-template').html());
this.$el.html(template(this.model.toJSON()));
// Return the object
return this;
},
deleteclient: function () {
this.model.destroy();
return false;
}
});
Now, the app actually uses jQuery Mobile and each client has a Delete button next to it with a class of delete, so the deleteclient function is executed each time one of these buttons is clicked. I'm also using backbone-tastypie to iron out the inconsistencies between Backbone and Tastypie.
The deleteclient function is running, but it sends the HTTP DELETE request to a relative URL of file:///api/v1/client/1/ (as this is a Phonegap app, I'm just viewing the files locally). From the documentation setting urlRoot manually seems like the way to go, but doing so didn't seem to solve the issue. Running the fetch() function to populate the collection works absolutely fine, though - it uses the correct absolute URL.
So, my question is how can I override the default behaviour and ensure my HTTP DELETE request is sent to the correct URL?
By looking at your code it should work ok. The Model in backbone already has a url() function defined which should do this:
url: function() {
var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
},
Can you use the debugger to see if it enters inside this code and what is the result of it? Mainly check the values from the _.result() calls...
Anyway, you can override the url property in your models rather than passing it in every call to destroy():
Client = Backbone.Model.extend({
url: function () { return baseUrl + 'client/' + this.id + '/'; }
// other code...
});
I found a solution, though I'm not entirely happy with it:
deleteclient: function () {
if (confirm('Are you sure you wish to delete this client?')) {
// Destroy the model
this.model.destroy({
url: baseUrl + 'client/' + this.model.get('id') + '/'
});
// Remove the view
this.remove();
}
}
Basically, if I explicitly pass through the URL to destroy(), that does the trick. It's a little annoying that I can't find a more DRY way to do this, so I'm open to any other method of doing the same thing.

Tastypie ajax POST obj_create - How to return json

Im sending a POST that creates a new User, and that works.
My question is how do I get back for example the pk of the created user to the ajax response?
$.ajax({
url: 'http://localhost:8080/api/v1/create/user/',
type: 'POST',
contentType: 'application/json',
data: '{"uuid": "12345"}',
dataType: 'json',
processData: false,
success: function (r) {
console.log(r)
},
});
def obj_create(self, bundle, request=None, **kwargs):
try:
user = User.objects.create_user(bundle.data['uuid'],'1')
user.save()
except:
pass
return bundle
you can set always_return_data=True within your UserResource's Meta and on POST and PUT request it will return the created object back.
From the docs
always_return_data
Specifies all HTTP methods (except DELETE) should return a serialized form of the data. Default is False.
If False, HttpNoContent (204) is returned on POST/PUT with an empty body & a Location header of where to request the full resource.
If True, HttpAccepted (202) is returned on POST/PUT with a body containing all the data in a serialized form.
Each resource has dehydrate method. You can use it to add any data to response. Here are the docs - http://django-tastypie.readthedocs.org/en/latest/cookbook.html#adding-custom-values
You could either use the Location header (set by Tastypie by default) or you could try to make Tastypie send the newly created entity back. I believe the first one is simpler. You may also take a look at related SO question: Is it ok by REST to return content after POST?
First you need to slightly modify jQuery XHR objects,
// Required for reading Location header of POST responses.
var _super = $.ajaxSettings.xhr;
$.ajaxSetup({
xhr: function () {
var xhr = _super();
var getAllResponseHeaders = xhr.getAllResponseHeaders;
xhr.getAllResponseHeaders = function () {
var allHeaders = getAllResponseHeaders.call(xhr);
if (allHeaders) {
return allHeaders;
}
allHeaders = "";
$(["Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma", "Location"]).each(function (i, header_name) {
if (xhr.getResponseHeader(header_name)) {
allHeaders += header_name + ": " + xhr.getResponseHeader(header_name) + "\n";
}
});
return allHeaders;
};
return xhr;
}
});
This is required because (after jQuery $.ajax docs):
At present, due to a bug in Firefox where .getAllResponseHeaders() returns the empty string although .getResponseHeader('Content-Type') returns a non-empty string, automatically decoding JSON CORS responses in Firefox with jQuery is not supported.
A workaround to this is possible by overriding jQuery.ajaxSettings.xhr as follows:
Then you can read the header in the successCallback, like so:
successCallback: errorAwareCall(function (data, t, textStatus, XMLHttpRequest) {
var loc = XMLHttpRequest.getAllResponseHeaders();
var pk = parseInt(loc.match(/\/(\d+)(\/)*/)[1]);
// Do something with the PK
})