So I need to make a Rest service which will call data from an specific model.
In order to do this, I must create a controller.
I'm newbie with Odoo, also with Json, and I can't find anything that could work for this.
Far I have is all I need but in a http request.
class RestService(http.Controller):
#http.route('/orders/<model("sale.order"):order>', auth='none', website=True)
def consulta_orden(self , order):
return request.render('consulta_pedidos.order', {'order': order})
return {'order_id': order_id}
The idea is that when I hace the data loaded, I can render this data in a template. Already did this with an Http request, but I need it with Json.
Any tip about how to implement it better.
I solved my problem in a very simple way.
The code:
#http.route(['/orden_detalle', '/orden_detalle/<int:order_id>'], type='json', auth='user')
def orden_detalle(self, order_id=None):
if order_id:
domain = [('id', '=', order_id)]
else:
domain = []
sales_rec = request.env['sale.order'].search(domain)
sales = []
for rec in sales_rec:
vals = {
'id': rec.id,
'name': rec.name,
'partner_id': rec.partner_id.name,
'user_id': rec.user_id.name,
}
sales.append(vals)
data = {'status': 200, 'response': sales, 'message': 'Sale(s) returned'}
return data
My source:
https://www.youtube.com/watch?v=wGvuRbCyytk
Another of my questions: How to get specific record in json controller in Odoo 12
If you want to return Json (for a REST-API) you just need to return it in a Response object.
Make sure you have Response imported from odoo.http.
Then you can return the json data like this:
return Response(json.dumps({'order_id': order_id}), status=200, content_type="application/json")
I usually wrap this logic in a separate function (outside of the controller) that logs the request and response on the Odoo object that is updated, and then returns the Response object.
That way it can also be easily reused in Error handling (where you can then return a custom error response in Json format)
EDIT: also make sure you're not including Content-Type: application/json in the request, in that case Odoo will think it's a JSON-RPC request.
Related
I want to pass a variable called manual to Flask a route, which will then do something based on the value in the POST form data. But the forms data is interpreted as string in flask even though I send it in a form as a dictionary.
here's the code
#app.route("/result", methods= [ 'POST', 'GET'])
def result():
manual = request.form.get("manual")
if manual is None:
return "manual is required"
here's how I am sending the data
r = requests.get('http://127.0.0.1:5000/result'
,data={manual':False})
I understand that I can do something like;
if manual == 'True'
but I don't want to be comparing strings, I want to do it in the standard way whichever it is.
Thanks
First of all, do a POST request, not a GET:
r = requests.post('http://127.0.0.1:5000/result', json={'manual': False})
Then (untested):
#app.route("/result", methods=['POST'])
def result():
json_data = flask.request.json
manual = json_data.get("manual")
if manual is None:
return "manual is required"
Have a look at the doc for details: More complicated POST requests.
Note that there are differences between using the data parameter and the json parameter. An important thing to note is the presence of the Content-Type header:
Using the json parameter in the request will change the Content-Type
in the header to application/json.
The application/json in the request header and json string in the request body when I initiate an http request , the Odoo server receives the request, but the json returned to the client is not what I want to return.
Here are two additional key,jsonrpc,id,result.The dictionary corresponding to the key result is what I really want to return to the client.
And if I change the type variable in the http.route to http instead of json, I will can't receive json format data from the client.
What shoul I do?Thanks everyone!
My Odoo version is 10,python version is 2.7.12
Here is my code
controllers.py
from odoo.http import Controller,route
class API(Controller):
#route('/v1/access_something',type='json',auth='none',csrf=False,methods=['GET'])
def access_something(self,**kwargs):
return {"a":1,"b":2}
Test interface with requests
import requests
re = requests.get('http://192.168.1.55:8069/v1/access_something',json={"c":1},headers={'Content-Type':'application/json'})
print(re.json())
The data in re.json()
{
"jsonrpc": "2.0",
"id": null,
"result": {
"a": 1,
"b": 2
}
}
But the following result is what I want.
{
"a": 1,
"b": 2
}
I've found a way to solve this problem.
This problem arises because there is a method _json_responsein the source code JsonRequestthat we can overwrite dynamically.
In order not to interfere with the use of the original framework by others, we can pass our own specific parameters in our own decorator#http.routeby using kwargs. We construct the json dictionary we need to return to the client by determining whether the decorator has our own parameters.
Here is my codecontrollers.py
from odoo.http import Controller,route,JsonRequest
def _json_response(self, result=None, error=None):
lover = self.endpoint.routing.get('lover')
if lover == 'chun':
response = {}
if error is not None:
response['error'] = error
if result is not None:
response = result
else:
response = {
'jsonrpc': '2.0',
'id': self.jsonrequest.get('id')
}
if error is not None:
response['error'] = error
if result is not None:
response['result'] = result
if self.jsonp:
# If we use jsonp, that's mean we are called from another host
# Some browser (IE and Safari) do no allow third party cookies
# We need then to manage http sessions manually.
response['session_id'] = self.session.sid
mime = 'application/javascript'
body = "%s(%s);" % (self.jsonp, json.dumps(response),)
else:
mime = 'application/json'
body = json.dumps(response)
return Response(
body, headers=[('Content-Type', mime),
('Content-Length', len(body))])
setattr(JsonRequest,'_json_response',_json_response) #overwrite the method
class API(Controller):
#route('/v1/access_something',type='json',auth='none',csrf=False,methods=['GET'],lover='chun')
def access_something(self,**kwargs):
return {"a":1,"b":2}
The specific parameter lover='chun' is basis of our judgment.In method _json_response,we can get this parameter through self.endpoint.routing.get('lover')
I'm using Python Flask RestPlus framework, pretty new to Python. Trying to send back a response object along with some description and status code but failing with the following error:
TypeError: Object of type Response is not JSON serializable
This is what i am doing
from flask import jsonify, Response
from flask_restplus import Resource
class MyUsage(Resource):
def get(self):
# Do something
return "My Description" + jsonify(myObject), 200
I even tried sending the object like:
result = {'Desc': 'My Description',
'Result': jsonify(myObject)}
return result, 200
and
return jsonify('Desc': 'My Description',
'Result': myObject), 200
Everything failed with the same error.
jsonify will not serialize an object. It is used to convert a dictionary to a valid JSON response (there may be some exceptions to this).
There are a few ways to handle this. My personal favorite is with the marshmallow library because you can use it to deserialize request data into an object while also validating the data and for serializing your objects into a dictionary. This way your objects are never instantiated in an invalid state.
Another way that may be easier but less scalable, is writing a to_data method for your object.
class Object():
def __init__(self, a, b):
self.a = a
self.b = b
def to_data(self):
return {
'a': self.a,
'b', self.b
}
You can use this method to serialize your object.
myObject = Object(1,2)
data = myObject.to_data()
return jsonify(data), 200
just as #Kevin said,
jsonify will not serialize an object. It is used to convert a
dictionary to a valid JSON response (there may be some exceptions to
this).
I will provide another solution.
I notice that flask_restplus was used in your code, actually flask_restplus will do jsonfy automatically. The following source code is from flask_rest
def make_response(self, data, *args, **kwargs):
'''
Looks up the representation transformer for the requested media
type, invoking the transformer to create a response object. This
defaults to default_mediatype if no transformer is found for the
requested mediatype. If default_mediatype is None, a 406 Not
Acceptable response will be sent as per RFC 2616 section 14.1
:param data: Python object containing response data to be transformed
'''
default_mediatype = kwargs.pop('fallback_mediatype', None) or self.default_mediatype
mediatype = request.accept_mimetypes.best_match(
self.representations,
default=default_mediatype,
)
if mediatype is None:
raise NotAcceptable()
if mediatype in self.representations:
resp = self.representations[mediatype](data, *args, **kwargs)
resp.headers['Content-Type'] = mediatype
return resp
elif mediatype == 'text/plain':
resp = original_flask_make_response(str(data), *args, **kwargs)
resp.headers['Content-Type'] = 'text/plain'
return resp
else:
raise InternalServerError()
and the mediatype of self.representations is application/json, which means when media type of request is application/json, response will use representations['application/json'] function, to build up Response.
And that will call
def output_json(data, code, headers=None):
'''Makes a Flask response with a JSON encoded body'''
settings = current_app.config.get('RESTPLUS_JSON', {})
# If we're in debug mode, and the indent is not set, we set it to a
# reasonable value here. Note that this won't override any existing value
# that was set.
if current_app.debug:
settings.setdefault('indent', 4)
# always end the json dumps with a new line
# see https://github.com/mitsuhiko/flask/pull/1262
dumped = dumps(data, **settings) + "\n"
resp = make_response(dumped, code)
resp.headers.extend(headers or {})
return resp
With that said, you could setup RESTPLUS_JSON in your flask app.config
For exacmple, datetime is not a serializable object, therefore you could provide a convertor for that type as following code show.
First, you need to define a converter:
def datetime_json_converter(o):
if isinstance(o, datetime.datetime):
return o.__str__()
Then, you just need to set RESTPLUS_JSON in your flask app.config
app.config['RESTPLUS_JSON'] = {'default': datetime_json_converter}
One of the best solution is to use marshalling.
First you need to write a model, explaining what attributes from the object you want to use and what will be type of each attribute.
from flask_restplus import Resource, fields
model = api.model('Model', {
'name': fields.String,
'address': fields.String,
'date_updated': fields.DateTime(dt_format='rfc822'),
})
then you need to apply that on resource methods you want.
#api.route('/<id>')
class Todo(Resource):
#api.marshal_with(model) <-- Here you refer the model you created above
def get(self, id):
# find or prepare your object and just return that object
return object
I'm using DRF to make a GET request to another server and return this reponse to the client.
What I want to know is how do I get only selected fields from that other server's response to return to my client.
What I have is a response like this:
{
"plans": [
{
"setup_fee": 500,
"amount": 990,
"code": "plano01",
}
{
"setup_fee:...
"code": "plano02",
}...
An array with many objects. And I want to give to the client something like this:
{
"plans": [
{
"code": "plano01",
}
{"code": "plano02"...
Only the code field.
Whats the best way to do this with Django/DRF?
There's not much point in using DRF here. You're getting a response in JSON format, you just need to parse it to a dict, extract the elements you want, and return it back as JSON again; DRF would be overkill. The view can simply be:
def get_plan_codes(request):
data = requests.get('external_api...').json()
codes = [{'code': plan['code']} for plan in data['plans']]
data['plans'] = codes
return JsonResponse(data)
I don't think I would use DRF for this personally, although you could.
I think this could be done in a fairly straight foward way using just requests and django itself.
Something like:
from django.http import JsonResponse, HttpResponse
import json
import requests
def plans(request):
response = requests.get("my url for the request")
if response.status_code == 200:
json_content = json.loads(response.content)
# json_content is a python dictionary, so you can just postprocess the way you want it here
return JsonResponse(json_content)
else:
return #You will want to return an HttpResponse with the appropriate status code, see the docs below on that
# https://docs.djangoproject.com/en/1.11/ref/request-response/#django.http.HttpResponse.status_code
I'm trying to write some unit tests for some Django json_view views and I'm having trouble passing the json_string to the view. I posted a related question yesterday about passing a json string to a Django view from the JS, the issue was that in my JS I was just passing the json string where I needed to be passing the string as the attribute of an object, because I was failing to do this the string was being taken as the key for the resulting query dict. I'm having a similar problem again except that this time it is form a Django unit test to the Django View. Here is a simplified version of my code which produces the same result.
class MyTestCase(TestCase):
def setUp(self):
self.u = User.objects.create_user('test','test','test')
self.u.is_active = True
self.u.save()
self.client.login(username='test',password='test')
def test_create_object_from_form(self):
"""Test the creation of the Instance from the form data."""
import json
json_string json.dumps({'resource':{'type':'book','author':'John Doe'}})
print(json_string)
response = self.client.post(reverse('ajax_view'),
{'form':json_string},'json')
self.assetNotContains(response,'error')
and the view looks like this
#json_view
def ajax_view(request):
"""Process the incoming form data."""
if request.method == 'POST':
print(request.POST)
form_data = json.loads(request.POST['form'])
resource_data = form_data['resource']
form = MyUserForm(resource_data)
if form.is_valid():
...
Here is what the two print statements produce when the test is run. The json_string is
{"resource": {"type": "book", "author": "John Doe"}}
and the query dict looks like
<QueryDict: {u'{\'form\': \'{"resource": {"type": "book", "author": "John Doe"}}\'}': [u'']}>
I'm total newbie with JS and ajax, so don't worry about hurting my pride, the answer is probably so close it could jump up and bite me.
Final edit
I originally stated that header HTTP_X_REQUESTED_WITH='XMLHttpRequest' was necessary in the post call but this is currently false while in tests. This header is necessary for the csrf middleware but csrf is disabled in tests. However, I still believe it is a good practice to put in test even if middleware disables csrf since most javascript library already pass this header by default when doing ajax. Also, if another piece of code that is not disabled ever use the is_ajax method, you won't need to debug your unittest for hours to figure out that the header was missing.
The problem is with the content-type because when django gets a value in there that is different than text/html, it doesn't use the default post data handling which is to format your data like in a query: type=book&author=JohnDoe for example.
Then the fixed code is:
response = self.client.post(reverse('ajax_view'),
{'form':json_string},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
Here's how I'm using it myself:
post_data = {
"jsonrpc" : "2.0", "method": method, "params" : params, "id" : id }
return client.post('/api/json/',
json.dumps(post_data), "text/json",
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
to do some json-rpc. Notice that since I pass a different content-type than the default value, my data is passed as is in the post request.
Thank you to #Eric_Fortin for turning me on to the header, it does not however resolve my issue with the malformed query dictionary using 'client.post'. Once I made the change from POST to GET with the XMLHttpRequest header my query dictionary straitened itself out. Here is the current solution:
response = self.client.get(reverse('ajax_view'),
{'form':json_string},'json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
this is only a partial answer since this request is going to change data on the server and should be POST not a GET.
Edit:
Here is the final code in my test that works for passing a JSON string via POST to my view:
response = self.client.post(reverse('ajax_view'),
{'form':json.dumps(json_dict)})
Now printing from the view shows that the query dictionary is well formed.
<QueryDict: {u'form': [u'{"resource": {"status": "reviewed", "name": "Resource Test", "description": "Unit Test"}}']}>
I found the answer while tinkering with one of my co-workers, removing the content_type 'json' fixed the malformed query dictionary. The view that is being tested does not make use of or call the 'HttpRequest.is_ajax()', sending the header XMLHttpRequest' has no impact on my issue, though including the header would constitute good-form since this post is an ajax request.