1

I've searched for answers to this issue for nearly two weeks. I have a simple system built in web2py. Note: I'm not exactly a python veteran. I am attempting to use the web2py rest api to post data to the database. If I run the curl command, the database table is updated and the rest returns the id of the newly added row. This is the desired outcome. However, if I attempt to use an ajax request to perform the same action, the request runs successful but the rest returns an empty object and the database is not updated. I've added a CORS wrapper class which allows me to get past the cross-origin issue; but I'm not sure if this is at the same time preventing the database from updating etc. I'm stomped. Also note that I formatted the data (in the ajax call) as a json object as well, but still nothing. Please find all the code below.

MOST IMPORTANT: I am receiving the following message - Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Any/All help is greatly appreciated guys. Thanks :)

#Web2py model
db.define_table(‘myboard',
#Field('userid','reference auth_user'),
    Field('userid',db.auth_user,default=auth.user_id),
Field('title',requires=IS_LENGTH(100,1),label=“Board Title"),
Field(‘idea_a',requires=IS_LENGTH(75,1),label=“Idea A"),
Field(‘idea_b',requires=IS_LENGTH(75,1),label=“Idea B"),
Field('description','text',requires=IS_LENGTH(250,1),label=“Board Description"),
Field('contributors','integer',default='0'),
    Field('status','integer',writable=False,readable=False,default='1'), #1=draft, 2=public
Field('created_on','datetime',writable=False,default=request.now))


#Web2py controllers
def CORS(f):
"""
Enables CORS for any action
"""
def wrapper(*args, **kwargs): #*args, **kwargs
    if request.env.http_origin:
        response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
        response.headers['Access-Control-Allow-Credentials'] = "true";
        response.headers['Access-Control-Allow-Headers'] = "Authorization,Content-Type,data";
        return dict()
    return f(*args, **kwargs)
return wrapper


auth.settings.allow_basic_login = True
@CORS
@request.restful()
def api():
from gluon.serializers import json
response.view = 'generic.'+request.extension
def GET(*args,**vars):
    patterns = 'auto'
    parser = db.parse_as_rest(patterns,args,vars)
    if parser.status == 200:
        return dict(content=parser.response)
    else:
        raise HTTP(parser.status,parser.error)
def POST(table_name,**vars):
    return json(db[table_name].validate_and_insert(**vars))
    return dict()
def PUT(table_name,record_id,**vars):
    return db(db[table_name]._id==record_id).update(**vars)
def DELETE(table_name,record_id):
    return db(db[table_name]._id==record_id).delete()
return dict(GET=GET, PUT=PUT, POST=POST,  DELETE=DELETE)


//CURL COMMAND - This Works!

curl -i --user somename@gmail.com:thepassword -H Accept:application/json -X POST http://127.0.0.1:8000/cc/default/api/myboard.json -H Content-Type: application/json -d 'userid=2&title=THE_TITLE&description=THE_DESCRIP&idea_a=THE 1st idea&idea_b=THE 2nd idea’


//AJAX CALL - Doesn't Work :(

var userid = 2;
var title = "THE_TITLE_HERE";
var description = "THE_DESCRIPTION_HERE"
var idea_a = "THE 1st idea";
var idea_b = "THE 2nd idea";


var userID = ’somename@gmail.com';
var password = ’thepassword';

var theData = "userid=2&title="+title+"&description="+description+”&idea_a=“+ideaA+”&idea_b=“+ideaB;

$.ajax({
    type: 'POST',
    headers: {"Authorization": "Basic " + btoa(userID + ":" + password)},
    url: "http://127.0.0.1:8000/cc/default/api/myboard.json",
    contentType:  "application/json; charset=utf-8",
    data: theData,
    success: function (data,textStatus,jqXHR) {
        alert(textStatus);
        console.log(data);
    },
    error: function(){
        alert("Cannot get data");
    }
});

The database table does not update but the request runs successfully. It returns an empty object each time.. {}

2 Answers2

0

Your wrapper function calls return dict(), which executes before the call to f(), so the decorated api function never gets called. Just remove that line.

Also, note that depending on the nature of the Ajax request, the browser might first make a preflight request. So, your decorator code will need to detect and respond to such requests with the appropriate headers.

Community
  • 1
  • 1
Anthony
  • 25,201
  • 3
  • 23
  • 54
  • Hey Anthony, thanks a bunch. I commented out the return dict() line of code and now I'm getting the following error... XMLHttpRequest cannot load http://127.0.0.1:8000/cc/default/api/myboard.json. Response for preflight is invalid (redirect) – Ethan-Anthony Jun 14 '16 at 12:42
  • Two other points: 1. After commenting out the following decorators @auth.requires_login() AND @request.restful() as well as changing the response.view to 'generic.'+request.extension ajax request ran successfully (code 200) but the data was not inserted into the database: 2 and the return message was html script from the api page. Very strange – Ethan-Anthony Jun 14 '16 at 16:56
  • I updated the answer. Also, note that you cannot remove the `@request.restful` decorator, as it is required in order for web2py to convert the call to `api()` into a call to the embedded `POST()` function. – Anthony Jun 14 '16 at 18:08
  • Thanks Anthony. That makes total sense. So after uncommenting the @request.restful decorator I received the following 'preflight' message: "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:63342' is therefore not allowed access. The response had HTTP status code 405." It is like the CORS decorator doesn't even exist now. – Ethan-Anthony Jun 14 '16 at 18:28
  • I'm also getting a 405 METHOD NOT ALLOWED message. The method that the network is showing is OPTIONS – Ethan-Anthony Jun 14 '16 at 19:35
  • What web server are you using? Also, please show your updated CORS decorator code (i.e., including the preflight check and response). – Anthony Jun 15 '16 at 00:55
  • Hi Anthony, thanks the help. I solved the issue. Your guidance was super useful. – Ethan-Anthony Jun 16 '16 at 12:47
0

Ok I solved this issue by doing three things. 1. I removed the CORS decorator code from the controller file - default.py 2. I inserted the below code at the top of the default.py controller file

if request.env.http_origin:
     response.headers['Access-Control-Allow-Origin'] = request.env.http_origin;
     response.headers['Access-Control-Allow-Methods'] = "POST,GET,OPTIONS";
     response.headers['Access-Control-Allow-Credentials'] = "true";
     response.headers['Access-Control-Allow-Headers'] = "Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Accept-Encoding";
  1. I modified the api class to include the OPTIONS verb by adding the following at the bottom of the class, after the DELETE verb. The modified api class is as follows.. Note: The change is in the last 4 lines of code.

     def api():
     from gluon.serializers import json
     response.view = 'generic.'+request.extension
     def GET(*args,**vars):
          patterns = 'auto'
          parser = db.parse_as_rest(patterns,args,vars)
          if parser.status == 200:
             return dict(content=parser.response)
          else:
             raise HTTP(parser.status,parser.error)
     def POST(table_name,**vars):
         #return db[table_name].validate_and_insert(**vars)
         #data = gluon.contrib.simplejson.loads(request.body.read())
         return json(db[table_name].validate_and_insert(**vars))
         return dict()
     def PUT(table_name,record_id,**vars):
         return db(db[table_name]._id==record_id).update(**vars)
     def DELETE(table_name,record_id):
         return db(db[table_name]._id==record_id).delete()
     def OPTIONS(*args,**vars):
         print "OPTION called"
         return True
     return dict(GET=GET,POST=POST,PUT=PUT,DELETE=DELETE,OPTIONS=OPTIONS)
    

And that's it. The issue was with web2py in that I needed to include the OPTIONS verb in the api call AND including the code for the headers directly at the top of the controller file instead of putting it in a wrapper seemed to work for me. Now everything connects and the database is updated.

reference url: https://github.com/OpenTreeOfLife/phylesystem-api/issues/26