Create an user application

Hello,

I’m trying to develop an user application which is going to have access to some routes in the server, like chronos and timesheet do.

This is my first time developing something on Tryton werkzeug/wsgi/web - you call it - related, so bear with me please.

Setup
  • Blank database initialized with a main module simulating a future client module.
    This module installs a second module called autoventa which is going to be my user application module.

  • Basic configuration of the admin user on Tryton, the party for the company and the company itself.

  • autoventa installs res module. and adds the following code to append my application to the application selection, alongside some routes which I intended to use in a future to check the workflow.

user.py

from trytond.pool import PoolMeta


class UserApplication(metaclass=PoolMeta):
    __name__ = 'res.user.application'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.application.selection.append(('autoventa', 'Autoventa'))

routes.py

from werkzeug.wrappers import Response

from trytond.wsgi import app
from trytond.protocols.wrappers import user_application

autoventa_application = user_application('autoventa')
url_start = '/<database_name>/autoventa'


@app.route(f'{url_start}/healthcheck', methods=['GET'])
@autoventa_application
def health_check(request):
    return Response(None, 200)


@app.route(f'{url_start}/hello_world', methods=['GET'])
@autoventa_application
def hello_world(request):
    return {'text': 'Hello World'}
Configuration / Environment variables
DB_NAME=ingsys
TRYTOND_DATABASE__URI=postgresql://
TRYTOND_WEB__LISTEN=0.0.0.0:8121
TRYTOND_WEB__ROOT=/home/hodei/ingsys/tryton/6.0/sao/
HTML
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />

		<style>
			#register-form h1 {
				margin: 0;
				padding: 0;
				text-align: center;
			}

			#register-form hr {
				margin: 20px;
			}

			#form-inputs {
				display: grid;
				grid-template-columns: 25%;
				justify-content: center;
				row-gap: 10px;
			}
		</style>
	</head>
	<body>
		<form id="register-form">
			<h1>Settings</h1>
			<hr />
			<div id="form-inputs">
				<label for="inputURL">URL</label>
				<input type="text" name="inputURL" placeholder="URL" />
				<label for="inputDatabase">Database</label>
				<input
					type="text"
					name="inputDatabase"
					placeholder="Database"
				/>
				<label for="inputApplication">Application</label>
				<input
					type="text"
					name="inputApplication"
					value="autoventa"
					disabled
				/>
				<label for="inputUser">User</label>
				<input type="text" name="inputUser" placeholder="User" />

				<button type="submit">Send</button>
			</div>
		</form>
	</body>
</html>

SAO runs on 0.0.0.0:8121 and the HTML runs on a live-server by the vscode extension on 127.0.0.1:5500.

I’m writing the following data on the form:

On form submit I’m trying to access the route /<database_name>/user/application/ as User Application — trytond latest documentation indicates, but I’m getting the following errors:

Client Side

XHR OPTIONS http://0.0.0.0:8121/ingsys/user/application 
CORS Missing Allow Origin

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://0.0.0.0:8121/ingsys/user/application. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://0.0.0.0:8121/ingsys/user/application. (Reason: CORS request did not succeed).

Server side

17370 140689018676800 [2021-08-29 12:53:47,122] INFO werkzeug 127.0.0.1 - - [29/Aug/2021 12:53:47] "OPTIONS /ingsys/user/application HTTP/1.1" 500 -
17370 140689018676800 [2021-08-29 12:53:47,122] ERROR werkzeug Error on request:
Traceback (most recent call last):
  File "/home/hodei/ingsys/clients/ingsys/.venv/lib/python3.9/site-packages/werkzeug/serving.py", line 323, in run_wsgi
    execute(self.server.app)
  File "/home/hodei/ingsys/clients/ingsys/.venv/lib/python3.9/site-packages/werkzeug/serving.py", line 312, in execute
    application_iter = app(environ, start_response)
  File "/home/hodei/ingsys/clients/ingsys/.venv/lib/python3.9/site-packages/trytond/wsgi.py", line 204, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/hodei/ingsys/clients/ingsys/.venv/lib/python3.9/site-packages/trytond/wsgi.py", line 210, in __call__
    return self.app(environ, start_response)
  File "/home/hodei/ingsys/clients/ingsys/.venv/lib/python3.9/site-packages/trytond/wsgi.py", line 176, in wsgi_app
    abort(HTTPStatus.FORBIDDEN)
  File "/home/hodei/ingsys/clients/ingsys/.venv/lib/python3.9/site-packages/werkzeug/exceptions.py", line 822, in abort
    return _aborter(status, *args, **kwargs)
  File "/home/hodei/ingsys/clients/ingsys/.venv/lib/python3.9/site-packages/werkzeug/exceptions.py", line 807, in __call__
    raise self.mapping[code](*args, **kwargs)
werkzeug.exceptions.Forbidden: 403 Forbidden: You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.

I’ve tried my own code then copied chronos: settings.js (tryton.org) but I’m getting the same error on the client side.

JavaScript

Method #1

<script>
	const $ = (selector) => document.getElementById(selector);

	const form = $('register-form');

	const handleFormSubmit = async function (event) {
		event.preventDefault();

		const url = form.elements.inputURL.value;
		const database = form.elements.inputDatabase.value;
		const application = form.elements.inputApplication.value;
		const user = form.elements.inputUser.value;

		const fetchUrl = `${url}/${database}/user/application`;

		const data = { user: user, application: application };
		const headers = { 'Content-Type': 'application/json' };

		const fetchSettings = {
			method: 'POST',
			body: data,
			headers: headers,
		};

		const request = await fetch(fetchUrl, fetchSettings);
	};

	form.addEventListener('submit', handleFormSubmit);
</script>

Method #2

<script>
	const $ = (selector) => document.getElementById(selector);

	const form = $('register-form');

	const handleFormSubmit = async function (event) {
		event.preventDefault();

		const url = form.elements.inputURL.value;
		const database = form.elements.inputDatabase.value;
		const application = form.elements.inputApplication.value;
		const user = form.elements.inputUser.value;

		const fetchUrl = `${url}/${database}/user/application`;

		const data = { user: user, application: application };

		jQuery
			.ajax({
				url: fetchUrl,
				contentType: 'application/json',
				data: JSON.stringify(data),
				dataType: 'json',
				type: 'POST',
			})
			.done(function (data) {
				console.log(data);
			})
			.fail(function (request, status, error) {
				console.log(request);
				console.log(status);
				console.log(error);
			});
	};

	form.addEventListener('submit', handleFormSubmit);
</script>

I’ve read some posts and responses in some places but I didn’t get it to work, like I said it’s my first time. Any pointers will be appreciated.

Thank you

You must serve your application from the same origin (host and port) to avoid cross-origin protection.
Or you must setup cross-origin headers.

The application will be contained inside a docker next to other dockers which will contain other clients trytond servers and / or applications in the same machine, so I guess best solution would be setting up cross-origin headers.

I found how to do it with nginx on the machine containing the dockers, but maybe you could teach me how to avoid this on a localhost / development server since I’m not using nginx?

You can use the cors variable in your trytond.conf. See Configuration file for Tryton — trytond latest documentation. You will get something like:

[web]
.....
cors =
    http://localhost:8121
    http://127.0.0.1:5000
    http://192.168.0.200:4200

Trying this solution the server is getting a POST request but is returning a 308.

127778 139907302368832 [2021-08-31 11:00:17,845] INFO werkzeug 127.0.0.1 - - [31/Aug/2021 11:00:17] "OPTIONS /ingsys/user/application HTTP/1.1" 204 -
127778 139907302368832 [2021-08-31 11:00:17,848] INFO werkzeug 127.0.0.1 - - [31/Aug/2021 11:00:17] "POST /ingsys/user/application HTTP/1.1" 308 -

The client side keeps showing Cross-Origin Request Blocked error - both CORS header ‘Access-Control-Allow-Origin’ missing and CORS request did not succeed.

I’ve modified my headers in the fetch settings adding the Access-Control-Allow-Origin following all 3 options from Access-Control-Allow-Origin - HTTP | MDN (mozilla.org) but still the same result.

On cors configuration you must add the Origin value that your custom application use to sent request to the trytond server.

I’ve set the cors like @edbo mentioned earlier. My current trytond.conf is as follows:

[database]
uri=postgresql://

[web]
listen=0.0.0.0:8121
root=/home/hodei/ingsys/tryton/6.0/sao/
cors=
    http://localhost:3000

I’m running my web app on http://localhost:3000.

You must check that it is actually what the browser set as Origin in the header. Because the matching must be exact.

Be sure that it’s localhost and not 127.0.0.1. It also can be your ip-addres of your system, even you run on localhost.

When I don’t get it working, I put a print statement in wsgi.py around line 172 (trytond 6.0) where the cors check is done. Running Tryton in the foreground, I get the all the data I need.

Response Headers
Content-Length
	295
Content-Type
	text/html; charset=utf-8
Date
	Tue, 31 Aug 2021 12:30:48 GMT
Location
	http://0.0.0.0:8121/ingsys/user/application/
Server
	Werkzeug/1.0.1 Python/3.9.6
Request Headers
POST /ingsys/user/application HTTP/1.0
Host: 0.0.0.0:8121
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,es;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost:3000/
Content-Type: application/json
Origin: http://localhost:3000
Content-Length: 42
DNT: 1
Connection: keep-alive
Server
132975 140483670455872 [2021-08-31 12:31:06,470] INFO werkzeug 127.0.0.1 - - [31/Aug/2021 12:31:06] "OPTIONS /ingsys/user/application HTTP/1.1" 204 -
trytond.conf get web cors: 
http://localhost:3000
http://0.0.0.0:3000
localhost:3000
0.0.0.0:3000
origin: http://localhost:3000
origin_host: localhost:3000
host: 0.0.0.0:8121
cors: ('http://localhost:3000', 'http://0.0.0.0:3000', 'localhost:3000', '0.0.0.0:3000')
origin not in cors: False
132975 140483670455872 [2021-08-31 12:31:06,472] INFO werkzeug 127.0.0.1 - - [31/Aug/2021 12:31:06] "POST /ingsys/user/application HTTP/1.1" 308 -

I don’t know if it has something to do with it but the request object on the server is returning a <JSONRequest (invalid WSGI environ)> which then on line 114 trytond/wsgi.py returns the 308

werkzeug.routing.RequestRedirect: 308 Permanent Redirect: None

So it is working, it is just that the path is missing an ending /.

:sob: :sob:
Thank you so much @ced. Indeed a / was missing in the url.

(Solution)

const fetchUrl = `${url}/${database}/user/application/`;

Thanks to you too @edbo ! Appreciated :slight_smile: