Open a live session for the burger quiz, join as an attendee. Everytime
the host reveal the next question, the attendees get a traceback.
```js
TypeError: ... is not Iterable
```
A recent commit 7109f48 changed the controllers of survey, they used to
`return self._prepare_question_html(...)`, most of them now
`return {...}, self._prepare_question_html(...)`, with the notable
exception of the `survey_next_question` controller which continues on
returning only the `_prepare_question_html(...)`.
JS-side, the `self._prepare_question_html(...)` payload is retrieved via
`const [,result] = await nextScreenPromise;`, i.e. it excepts an array
of 2 elements and retrieve the second one.
As the other survey controllers have been modified inside 7109f48, the
`survey_next_question` should be updated too but was forgotten. Returns
an empty dict for the correct answers and the next page html.
Task-3374998
closesodoo/odoo#139204
Reference-to: 7109f48 ([IMP] survey: add scoring after each page)
Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
Start the a live session, I used Burger Quizz, answer a question. On the
manager side, the current vote count for each choice should be visible
on top of each bar in the chart. That vote count is missing.
During the migration of chartjs from v2 to v4, the datalabel plugin was
updated from v0.7 to v2.2 in the same time. Using the new version of the
datalabel plugin, the plugin is not automatically loaded anymore, one
must explicitely loads it.
We also removed the datalabel plugin from the bundle to load it
explicitelly instead. The JS framework finds it cleaner this way.
Task-3537480
Reference-to: 7e3c1ecdb8 ([REF] *: Update Chart.js to V4.3)
Part-of: odoo/odoo#139204
Co-authored-by: Pierre Pulinckx <pipu@odoo.com>
Go to the form view of mailing.mailing, write "hello " as the subject,
insert an emoji, you end up with "hello:)" instead of "hello :)". The
problem is that when the input loose the focus, it is automatically
trim.
Reconfigure the `char_emojis` widget so that is doesn't trim.
task-id-3493168
closesodoo/odoo#136530
X-original-commit: 2adda1eb2760ccf970761f424105e245afc3de3f
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Users sometime want to rename existing controllers URL, e.g. /shop as
french /magasin. The feature is possible within Odoo thanks to 308 type
redirections that can be configured via a hidden ?debug=1-only website
menu.
Upon generating the routing-map (the structure that links URLs to
controllers), when a URL is found inside of the `website.rewrite` model,
two links (called Rules in werkzeug jargon) are registered: the new,
translated, URL is linked to the controller and the original URL is
linked to a redirection to the new URL.
For the context of this PR, it is important to note that the redirection
only applies to the very URL saved inside the `website.rewrite` model:
if a controller has multiple routes, e.g. `/shop` and `/shop/shop`, only
`/shop` is redirected to `/magasin`, `/shop/shop` is left as-is.
Without 308 redirection:
/shop -> def shop()
/shop/shop -> def shop()
With 308 redirection:
website.rewrite(from_url='/shop', to_url='/magasin')
/shop -> /magasin
/shop/shop -> def shop()
/magasin -> def shop()
The redirection is set on the routing dictionnary of the endpoint, this
is the dictionnary that collect the informations set via the `@route`
decorator (auth=, method=, type=, ...).
Prior to [HTTPocalypse], that dictionnary was duplicated so that the
redirection was applied on the single route endpoint that matched
the `website.rewrite` record. With [HTTPocalypse] that duplication has
been wrongly removed: all original routes redirected to the new
translated one.
Bug introduced in [HTTPocalypse]:
website.rewrite(from_url='/shop', to_url='/magasin')
/shop -> /magasin
/shop/shop -> /magasin <-- wrong
/magasin -> def shop()
This PR fixes the problem, it makes sure that the redirection is saved
*only* on the route that matched the website.rewrite record, not the
other routes.
[HTTPocalypse]: https://github.com/odoo/odoo/pull/78857closesodoo/odoo#133960closesodoo/odoo#134206
X-original-commit: 144a22c22c95004171860cdaecd1f7d7975dc468
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
The `test_override_signatures` linter is about validating the parameters
of overriding functions. It verifies that when a method overrides another
from a parent model, the new method has a signature that is compatible
with the method it overrides: same arguments, same default values, same
annotations.
Because it also verified annotations, when a parent method was
annotated, all the child methods had to be annotated too. We actually
only care about the annotations when the two methods are annotated, we
don't want to enforce annotations on non-annotated methods.
Note 1: the shortcut to skip `(self, *args, **kwargs)` is broken, the
code has been removed.
Note 2: the code about `parent_class` is dead code that survived a
previous refactor.
Note 3: xdo likes it when I respect flake8-errmsg (EM101-103)
Part-of: odoo/odoo#133049
The limit was only enforced by the front-end, meaning that anybody could
forge a request with a huge file and get it processed by Odoo. According
to the documentation of werkzeug[^1], such limit should be enforced by
the server server instead of the wsgi application. It is the case for
Odoo Online but on-premise customers might not configure their servers.
The `web.max_file_upload_size` system paramter is now enforced upon
parsing the content of the request. It defaults at 128 MiB which is
enough for most documents and images. We do not want to host large
files (e.g. videos) in the Odoo filestore.
[^1]: https://werkzeug.palletsprojects.com/en/2.0.x/request_data/Fixes: #124646
Part-of: odoo/odoo#126914
The `-i`/`--init` and `-u`/`--update` cli options behavior is only
defined when using a single database with `-d`/`--database`/`db_name`.
Using those two cli options along with multiple databases is undefined
and can have disastrous consequences[^1].
The server now crashes in this situation.
Fixes: #107188Fixes: #128273
[^1]: https://github.com/odoo/odoo/issues/107188#issuecomment-1627996425closesodoo/odoo#128306
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Web lacked a default route for /robots.txt meaning that if you didn't
installed website (which comes with a full fledged robots.txt) you would
get a 404 page not found.
closesodoo/odoo#127402
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Steps to reproduce, on SaaS only, not reproductible in standard:
1. Start a new SaaS Trial on saas-16.3
2. Install helpdesk, setup an incoming mail server
3. Send an email, one that should create a new helpdesk ticket
4. Traceback, request has not 'is_frontend` attribute.
This commit doesn't solve the root issue, it only makes it possible to
use helpdesk again. Using getattr/hasattr to access request.is_frontend
SHOULD NOT be necessary since [HTTPocalypse] BUT there are some rogue
controllers that bypass the normal flow of execution and fail to meet
the expectations of the new http stack.
This commit only makes the code robust to a missing attribute in this
very case as this is a recurring problem (unusable helpdesk). The root
problem is unlikely to be located nor in http_routing, nor in helpdesk.
THIS SOLUTIONS OF USING `getattr`/`hasattr` TO ACCESS `is_frontend` MUST
NOT BE REPLICATED ELSEWHERE WITHOUT PRIOR CONSULTATION WITH PEOPLE IN
CHARGE.
[HTTPocalypse]: https://github.com/odoo/odoo#78857
See-also: https://github.com/odoo/odoo/pull/99667
See-also: https://github.com/odoo/internal/pull/1902
opw-3359740
opw-3365843
closesodoo/odoo#126386
X-original-commit: c808719619167a582dd28420ff2979b57102d2ab
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
The Gevent worker has specifc needs in term of maximum concurrent
connections to the database and those needs are not compatible with the
default limit that is primerly set for http workers.
This PR makes it possible to supply a configuration dedicated to the
gevent worker.
task-id-3193565
task-id-2146565
closesodoo/odoo#125190
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Send a request to any json-rpc route with a HTTP header
`Content-Type: application/json-rpc` but send non-json or
non-jsonrpc data in the body. The application crashes with
a 500 Internal Server Error instead of a 400 Bad Request one
closes odoo/odoo#124571
Closes: #122048
X-original-commit: a1b83b07996f3fe4744474ff4023a3a2ce17ac7f
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Run `odoo-bin help`, you'll notice that some descriptions are missing,
this commit fixes that.
Run `odoo-bin shell --help`, you'll notice that the `usage:` line says
`odoo-bin [options]` instead of `odoo-bin shell [options]`. Other
commands that depend on the server cli are broken too. Fix those too.
closesodoo/odoo#121085
X-original-commit: 9c6bac741ff437564e7dbe4d0aa8dbd307b71f8d
Signed-off-by: Vincent Schippefilt (vsc) <vsc@odoo.com>
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Install and then uninstall the utm module via the web client, you get a
traceback because the ir.http override of the utm module is still
present in the registry altought the module is not installed anymore.
The problem affects all modules that override the _post_dispatch method
of ir.http, it is not limited to UTM.
The problem is that, after the uninstallation, a new registry (without
the uninstalled modules) is created but the old registry was still used
by the HTTP stack.
closes odoo/odoo#122519
Closes: #121755
X-original-commit: 979844600a0bc5ea8b63cf4d58c7aba5133e82f3
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
This commit add a new `odoo.osv.expression.prettify_domain` function
that can be used to format a domain as a string with the correct
indentation.
Example:
['&', '|', ('name', 'like', 'Jack'), ('name', 'like', "O'Neill"),
('function', '=', 'Colonel')]
Becomes:
['&',
'|',
('name', 'like', 'Jack'),
('name', 'like', "O'Neill"),
('function', '=', 'Colonel')]
closesodoo/odoo#118067
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Rationale
---------
Given the following domain:
['|',
('company_id.name', 'like', 'BE'),
('company_id.city', 'like', 'Brussels')]
The ORM would generate the following SQL query:
SELECT "res_partner"."id"
FROM "res_partner"
WHERE "res_partner"."company_id" IN (
SELECT "res_company"."id"
FROM "res_company"
WHERE "res_company"."name" like 'BE'
) OR "res_partner"."company_id" IN (
SELECT "res_company"."id"
FROM "res_company"
WHERE "res_company"."email" like 'help@odoo.com'
);
Which is sub-optiomal as the WHERE clause of the two subqueries could be
regrouped inside of a single query like so:
SELECT "res_partner"."id"
FROM "res_partner"
WHERE "res_partner"."company_id" IN (
SELECT "res_company"."id"
FROM "res_company" WHERE (
"res_company"."name" like 'BE'
OR "res_company"."email" like 'help@odoo.com'
)
);
Postgres-wise, it is faster to execute the latter query than the former
one. This commit is about optimizing the ORM so that it generates
queries where the WHERE clause of compatible subqueries are grouped
together.
Technical solution
------------------
The solution explored by this work is to replace the regular relational
field accesses (over a dotted path) by a new `any` operator that look as
follow:
(relational_field, 'any', domain)
For instance, the above `('company_id.name', 'like', 'BE')` leaf becomes
`('company_id', 'any', [('name', 'like', 'BE')]` using `any`.
Having a domain as right-hand-side allow for the combination of the
domains of same compatible relations:
[('company_id', 'any', ['|',
('name', 'like', 'BE'),
('city', 'like', 'Brussels')])
Having combined domains allows for generating better SQL queries with
minimal changes to the expression parsing algorithm, which can only
translate a single leaf at a time to SQL. With the new `any` operator,
it is still a single leaf but the right-hand-side contains the merged
domain, thus the combined SQL WHERE clause is immediately generated.
task-id-3234671
Part-of: odoo/odoo#118067
Co-authored-by: Raphaël Collet <rco@odoo.com>
This commit hads a few tests that report the existing behavior (before
merging subqueries WHERE clauses).
task-id-3234671
Part-of: odoo/odoo#118067
Co-authored-by: Raphaël Collet <rco@odoo.com>
All major systems (debian stable, ubuntu lts, windows) support py3.8
and all dependencies used by Odoo come with wheels for that version.
Most developers at Odoo SA uses 3.8 already and runbot is using ubuntu
jammy (which comes with py3.10) to test the current 16.0/master.
closesodoo/odoo#119492
Signed-off-by: Christophe Monniez (moc) <moc@odoo.com>
Disable performance tests from both tef and tcf.
Too many PR are blocked due to broken assertQueryCount, either there are
too many queries, either there are too few.
It is too much work to run all tests three times only to update a comment
with the final count (module alone + community + enterprise).
It is too hard to keep track of the hundreds queries to determine those
that moved, those that are missing and those that are new between two
branches. We have to apply tons of string-replace and sorts just to help
some diff tools (e.g. meld) into showing what changed.
Basically, except a few people, nobody care to do the investigation work
and just increase the query count (without changing the comments).
We tried for one year, now it is time to let those test go.
closesodoo/odoo#118614
X-original-commit: 353074fa1f362560737bb2905270ef4ae35e177e
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
A visitor could visit a web page having a lang in its context that is
not installed in the databased he is connected to. The problem is that
visitors are not logged-in thus it is not possible to determine their
lang via their `res.users` preferences. The lang used instead is the
lang set in the `Accept-Language` header of the incoming request, that
header is set by various browsers in accordance to the user system
preferences or browser settings.
The browser lang (`Request.best`) is only parsed according to the
`babel` database, it is a lang syntactically speaking but not necessary
a lang that is installed in the database.
At the moment the browser lang is set in the context (inside of
`Request._get_dbname_and_session`), it is not possible to verify it is
installed in the database as no connection to any database as been
established yet. Instead the lang is validated inside of
`ir.http._pre_dispatch` which is the method responsible to prepare/fix
various stuff on the request/session/context.
In regard to 93b684d3c7, we prefer to fallback on English, hence the
modification in `get_lang`.
closesodoo/odoo#116683
Reference-to: 93b684d3c7 ([FIX] base: lang should fallback on english instead of arab)
X-original-commit: 743e97667e44cdaab6db334d807cf3d409cea621
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
As the public user, browse the website where you usually should see some
profile pictures (e.g. inside the forum). All the images are wrongly
replaced by the grey avatar placeholder.
When using `ir.binary._find_record` it was checking the access rights
and raising `AccessError` early even if the record was
`website_published`.
closesodoo/odoo#113526
X-original-commit: 0611fb437b699588317919e72ccfa1c41f1245bc
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
In an edge case the content of the field has already been
prefetched as sudo during the read of another field as sudo,
therefore it doesn't try to read the field as the normal user
closesodoo/odoo#88134
Signed-off-by: Julien Castiaux <juc@odoo.com>
Install a database with many langs, arab, french, english, ... Keep
english as the default lang. Start a shell and validate a sale-order
using the superuser. On the web client, the sale order has been
validated in arab instead of in english.
In 16.0 the `context_get` method was changed to ensure there was always
a lang set in the returned context. It used the following fallback
order: context > request. The solution was partial because in case there
was no request to extract a lang from, no lang was set on the context.
In a recent 16.0 fix (f2523c4a), the mechanism was changed to fix the
previous problem. The fallback order became: context > request > first
installed lang. This solution is sub-optimal because the first installed
lang isn't always the best pick. e.g. when you have a mostly english
company but that arab is installed for some website pages, arab is
selected instead of english (the langs are alphabetically sorted)
In this work, the fallback order is changed once again:
1. The lang set on the user's profile if activated
2. The best lang extracted from the user's browser if activated
3. (new) The lang of the user's current company if activated
4. (new) English if activated
5. The first lang (ordered by ISO code) if any
6. English
The 3rd should cover most of ill-cases. For the 4th step, we assume that
english is prioritaty to other installed langs when no lang standout.
closesodoo/odoo#113186
X-original-commit: 03134bf7cb1e3d63f3be435fbc734e6198ca029b
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Fine tuning of 8eaac97, the `errors` attribute was added in py38[^1]
[^1]: python/cpython@ca7b504a4dclosesodoo/odoo#112588
X-original-commit: c6c19ff6a3093fe64035d9f970ab35522f8080f5
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Remove custom open introduced by bpo-26789 as we do not need it.
closesodoo/odoo#112453
X-original-commit: 8eaac9744b93e7132827edb2a59c28bb43732ec1
Related: odoo/enterprise#36978
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
Co-authored-by: Martin Trigaux <mat@odoo.com>
The path matching logic got reimplemented in werkzeug 2.2[^1] and the
new router is no more compatible with regexp groups[^2]. Our custom
converter for slugged-records in urls (`'/partner/agrolait-5'` => `5`)
has been adapted to match the route using non-capturing groups. It still
extracts the slug/id pair using the groups-capturing regexp.
[^1]: https://github.com/pallets/werkzeug/pull/2433
[^2]: https://github.com/pallets/werkzeug/pull/2519
Part-of: odoo/odoo#112298
Based on RFC3462, a Content-Type text/rfc822-headers
exists and provide a mechanism to label and return
only the RFC 822 headers of a failed message (bounce)
These are only the headers and not the full message.
The Content-Type-Encoding should be either 7-bit(US-ASCII)
or Quoted-Printable (QP) as in the section 2 of the RFC.
Spawn the error:
After getting reported by a customer, I had to reproduce
by spamming wrong outlook addresses and add logging
in a sh database on the message_process of mail_thread.py
and logged the `message` variable.
After few retry, I got one of the part that was defined
as followed:
Content-Description: Undelivered Message Headers
Content-Type: text/rfc822-headers
Content-Transfer-Encoding: quoted-printable
The `get_payload()`function used was only assuming
that there is a full email on that part and that
it could only be encoded as an email, which was not
the case in this situation (quoted-printable:
76 characters per line, character `=` used
as the end of line character).
opw-3064589
task-3131561
closesodoo/odoo#110901
X-original-commit: f86f4b671696178f8fa42b81d8753d2591578431
Signed-off-by: Julien Castiaux <juc@odoo.com>
Co-authored-by: Baptiste Vergote <bve@odoo.com>
Well it is not a typo but a mini code improvement. The suggested diff
clarifies the intent of `_handle_control_frame` with regards to
`_get_messages`. Because it was `return self._handle...` we had to read
the method to determine if it was returning something in order to know
whether the message would be propagated to the rest of the websocket
routing by `_get_message`. Now it is clear that the control frames are
handled right away and that they are not propagated to the business.
closesodoo/odoo#110834
Signed-off-by: Julien Castiaux <juc@odoo.com>
Some cookies were left around even when the user logged out
The next user should log into the default company instead of the company
of the last user.
task-3077421
closesodoo/odoo#108998
Related: odoo/enterprise#35685
Signed-off-by: Julien Castiaux <juc@odoo.com>
Send an rich HTML email from Odoo, it lands in spam whereas it would
land in inbox in 13.0.
The problem is due to a missing "MIME-Version: 1.0" header on the email.
This header is correctly set on both the html and text alternatives of
the messages but it should be set on the enveloppe too.
The problem is present in the newer EmailMessage mail API of python that
is used since 14.0. Using the newer API, it doesn't set the header on
the enveloppe itself.
This reverts commit 8663f1e20727c315924bb20fc1de1accf3014c61.
opw-3098621
closesodoo/odoo#109212
X-original-commit: c480d2bf1de7b70892130e74bb7a8d4003a8a783
Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
Signed-off-by: Julien Castiaux <juc@odoo.com>
Start odoo-bin shell with `--geoip_country_db='/i/dont/exist'` and type
the following code:
from odoo.http import GeoIP
GeoIP('8.8.8.8').country_code
TypeError: The country method cannot be used with the GeoLite2-City
database
There are two "things" here: the raw country and city entries in the
databases and the rich python country and city objects returned when an
entry was found and parsed.
Then there are two "readers", a reader capable of reading and parsing
entries from the city database and **another** reader for the country.
The two readers are **only** compatible with their specific database:
the city reader cannot read or parse information from the country
database and vis-versa. That's the bug reported by the TypeError that
this commit fixes.
But, the two python instances created from parsing the entries of
respecting databases with respecting parsers **do** share a same API:
the city record class inherits from the country record class.
# Valid
city_db = geoip2.database.Reader(config['geoip_city_db'])
city_record = city_db.city('8.8.8.8')
city_record.country.name # works, a city record also
# holds country attributes
# Invalid
city_db = geoip2.database.Reader(config['geoip_city_db'])
country_record = city_db.country('8.8.8.8') # TypeError
closesodoo/odoo#109095
Signed-off-by: Julien Castiaux <juc@odoo.com>
The geoip resolver was moved from http_routing to code/http.py in #86015
and is always available since then. The `_geoip_resolver` global
variable is a leftover we forgot to remove.
closesodoo/odoo#91337
Related: odoo/documentation#2151
Related: odoo/enterprise#27399
Signed-off-by: Julien Castiaux <juc@odoo.com>
request.geoip is no more a dictionnary cached in the session. It is now
a full blown object with lazy and smart geolocalisation capabilities.
Among other things, the previous dictionnary API is now deprecated. The
changes are:
* `request.geoip['country_name']` -> `request.geoip.country_name`
* `request.geoip['country_code']` -> `request.geoip.country_code`
* `request.geoip['city']` -> `request.geoip.city.name`
* `request.geoip['latitude']` -> `request.geoip.location.latitude`
* `request.geoip['longitude']` -> `request.geoip.location.longitude`
* `request.geoip['region']` -> `(request.geoip.subdivisions[0].iso_code if request.geoip.subdivisions else None)`
* `request.geoip['time_zone']` -> `request.geoip.location.time_zone`
It is safe to access all the attributes. Doing `request.geoip.city.name`
when the geolocalization failed (missing db, invalid address, ...)
evaluates to None. It does not raise an AttributeError.
Task: 2848206
Part-of: odoo/odoo#91337
Maxmind offers multiple ip-geolocalization databases, historically we
have been using the City database which contains records on a
city-basis. Many years later it turns out we are primary using geoip to
know the country of the user. Geolocalization in the City database is
considered slow by our standard and we have been clever in order not to
geolocate each request by saving the info in the session.
On the other hand, the Country database that is offered by Maxmind is
much more lightweight and geoip using that country is considered a fast
operation by our standard.
In this work we make Odoo compatible with both the City and the Country
databases. Using multiple database at the same time, we can be smart and
only query each of the two on-demand. If a user ask for its country,
we'll use the fast Country db. If a user ask for its city/timezone we'll
use the slower City db.
By default it loads both database from the `/usr/share/GeoIP/` folder,
respectively the files `GeoLite2-City.mmdb` and `GeoLite2-Country.mmdb`,
you can provide alternative paths using the `--geoip-city-db` and
`--geoip-country-db` CLI options.
In the same mindset as #86015, geoip is still lazy. It is done on-demand
and the result is cached on the current request. The different with the
related PR is that as we know consider geoip to be fast, we no longer
cache the result in the session.
Task: 2848206
Part-of: odoo/odoo#91337
*: base_setup, hr_timesheet, mail, partner_autocomplete, web_tour
Start odoo without -d and with a --dbfilter that allows multiple
databases. Via JSON-RPC access the /web/session/authenticate route
providing a non-filtered database and valid credentials. Traceback,
`request.env` is None.
Since httpocalypse the initialization of the ORM (cursor, registry,
environment) is greedy. It means that the connection to the database is
established very early during the request routing or skip altogether in
case no dbname was known at that time. This contrast with prepocalypse
where the various ORM thingies were lazily setup the first time they
were accessed.
This changement has an important implication regarding authentication.
In prepocalypse, thanks to the lazy approache, a cursor/registry/env
would be setup on the database you just login upon using the
`request.env` for the first time. This was very nice in this regard but
had other problems.
Since httpocalypse such operation is no more possible. Devs must
initialize and use their own cursor/registry/env in case they
authenticate on another database than the one `request.cr` is (maybe)
connected to.
The `/web/session/authenticate` controller is an example of such case.
It crates its own cr/registry/environment after authentication. The
problem the controller uses `ir.http.session_info` and that not all
overrides were updated to use `self.env` (=the env created in the web
controller) instead of `request.env` (=the missing env of the request).
closesodoo/odoo#108063
X-original-commit: 7b9bd9d37731fae724dc5d91da656dab70aa9ad4
Related: odoo/enterprise#35012
Signed-off-by: Julien Castiaux <juc@odoo.com>
*: base, account, crm, hr, hr_attendance, test_access_rights
The various public methods of the ORM can be override in other models,
those overrides sometime don't implement the exact same signature as the
original method in the ORM. In this work we sanitize all the overrides
to ensure a better compatibility. The background objective is to make it
possible to call any public method using kwarg: `search(domain=[...])`.
* `search`, the first parameter was renamed from `args` to `domain` in
0e9adf7 but the overrides were not updated.
* `invalidate_models` and `invalidate_recordset`, a new `flush=True`
parameter was introduced in 9c3b9a4 but the overrides were not
updated.
* `update`, there is a clash between the `update` method responsible for
writing on a record and `update` in bus responsible to update the user
presence. The bus method has been renamed so it doesn't clash with the
ORM.
This sanitization comes with a new linter that verifies that all
overrides of BaseModel public methods share a compatible signature. The
linter has been disabled for `create`, `write` and `default_get` as too
many overrides don't respect the signature of BaseModel.
closesodoo/odoo#106999
Related: odoo/enterprise#34991
Signed-off-by: Julien Castiaux <juc@odoo.com>
Start a server without a -d and with a --dbfilter that allows for
multiple database. Make sure one of the database has the
`base_import_module` addon installed. Create an empty module using
scaffold and deploy it to the server, make sure to provide the `--db`
argument to the deploy command.
It zips the file and attempt to upload it but it fails for a 404 page
not found error.
The problem is that the controllers of base_import_module are only
accessible when the client is connected to a database. It must first
connect to a database (to have a db in his session) and then access the
controller.
The /web/login route is an example of a rather cheap route to get that
is both accessible without being connected to a database and that takes
a `?db=` argument to connect to one. Using that route, we can ensure
that we are connected to a database prior to uploading a module.
Closes#104589closesodoo/odoo#107906
X-original-commit: 8feb5f366255c8cb7927842acea3ec76bbe11df2
Signed-off-by: Julien Castiaux <juc@odoo.com>
The routing-map is a mapping that maps HTTP verbs and paths to python
controller methods (endpoints), e.g. it maps `GET /web/health/` to
`/web:Home.health`. The `_generate_routing_map` function is the function
responsible to generating the werkzeug routing-map in regard to the
controller inheritance mechanism. The mechanism makes it possible to
override an endpoint is various odoo modules to enrich it with new
features, e.g. `/web/login` is overriden in website to change the visual
of the page.
Implementation-wise, the informations from each `@route` decorator must
be merged with the other `@route` info for each endpoint override. When
a method is not overriden in a controller, there is not new info and
that controller should be skipped for that method.
The previous implementation attempted to skip such method using the
following idiom:
if not hasattr(controller, method_name):
continue
That idiom doesn't work as `hasattr` will perform a lookup on the full
controller's MRO instead of a lookup only on controller's own methods
and attributes. The controller's own methods and attributes are
actually found in `controller.__dict__`.
closesodoo/odoo#107064
X-original-commit: 42f52d26b6a81a68f1fe28e28971f0e7f4c97d11
Signed-off-by: Julien Castiaux <juc@odoo.com>
Define the following controller:
@route('/get-context', auth='public', type='json', website=True):
return request.env.context
Access that route via JSON-RPC providing a context:
this._rpc({
route: '/get-context',
params: {
context: {"a": 1}
}
})
Since httpocalypse, it replaces the context entirely instead of updating
it. i.e. before you would get along those lines:
{"a": 1, "lang": "en_US", "tz": "Europe/Brussels", "uid": 2}
but since you get instead (and that's wrong):
{"a": 1}
Note that after this commit, this merging-context behavior will be the
same whether or not website=True is defined (while it was only there for
website=True before).
Discovered via opw-2885948
X-original-commit: 7887cfc3f177d9e64f008aabb8bd710f54024dea
Part-of: odoo/odoo#107179
The wsgi application entrypoint moved during the [httpocalypse]. Some
clients don't use the odoo builtin wsgi server and have troubles
upgrading from 15.0 to 16.0 because the `odoo.service.wsgi_server`
module doesn't exist anymore.
[httpocalypse]: https://github.com/odoo/odoo/pull/78857closesodoo/odoo#106187
X-original-commit: 4e5d7d2e93fcd082bd1b3a9e76cedab85b46f456
Signed-off-by: Julien Castiaux <juc@odoo.com>
Create a 15.0 database with website, access the home page via your
browser. Stop the server and migrate the database to 16.0. Restart the
server with a `--dbfilter` that rejects the database you created and
refresh your browser. 500 Internal server error, attribute error:
the `request` object as no `session`.
An error could occurs after a migration to 16.0 due to the presence of
the `geoip` key in the session. `request.session.geoip` has been made a
deprecated alias to `request.geoip` between 15.0 and 16.0, see 04e9726.
Because the session was created before 16.0, the session dict does
contain a `geoip` key. Upon logging the session out, the session dict
is cleared. The default implementation of `clear()`[^1] inside of
`collections.abc.MutableMapping` can be summarized for our usecase to:
for key in self:
value = self[key]
del self[key]
There is an extra `__getitem__` call due to `value = self[key]`, in the
case of the `geoip` key, it would access the alias. It is not possible
to accessing that alias inside of the `_get_dbname_and_session` method
of request as the session has not been set on `self` (the request) yet.
Yet inside of that method, we do `session.logout()` which `clear()` the
session which (wrongly) access the alias because `geoip` exists in the
internal dict (`'geoip' in self.keys() # True`).
The solution has been to implement the `clear()` function ourself
instead of using the mixin of `MutableMapping`.
[^1]: https://github.com/python/cpython/blob/b43496c01a554cf41ae654a0379efae18609ad39/Lib/_collections_abc.py#L925-L931closesodoo/odoo#105763
X-original-commit: b66e1ffa8e348eedf2de735babbc398290a8bffb
Signed-off-by: Julien Castiaux <juc@odoo.com>
Start odoo on a specific database, e.g. 'db-example'. Drop it via JSON
or XML RPC. The database is successfully dropped but the RPC fails with
a traceback because it attempts to commit on a database that doesn't
exist anymore.
import requests
admin_passwd = ...
requests.post(
'http://127.0.0.1:8069/jsonrpc',
json={'params': {
'service': 'db',
'method': 'drop',
'args': [
admin_passwd, 'db-example'
]
}}
)
Closes odoo#104527
closesodoo/odoo#105710
X-original-commit: b6e195ccb3a6c37b0d980af159e546bdc67b1e42
Signed-off-by: Julien Castiaux <juc@odoo.com>
The new `borrow_request()` function has been introduced to properly
separate the HTTP layer from the RPC layer. We forgot to protect some
RPC endpoints, mainly inside of `odoo.addons.web.controllers.database`.
This commit moves `odoo.service.dispatch_rpc` to `odoo.http` as we only
permorm RPC from the controllers and that we don't want to import
`borrow_request` inside of `odoo.service` (circular import).
X-original-commit: d0ee8615d8820e02232989c50a6e6ffbc66a9266
Part-of: odoo/odoo#105710
Define a route that is website but not multilang, e.g.
@route('/example', website=True, multilang=False)
Login to the frontend, change the website lang to another (non-default)
lang (e.g. install french, keep english as default lang, log in the
french website) then access the '/example' controller by typing it
directly in your address bar.
You are being redirected to '/fr/example', you should not.
This commit restore the behavior pre-httpocalypse, that is the address
is kept as-is.
Note: in the comment, the 4th and 5th cases were inverted, we use this
commit as an opportunity to reorder the two.
closesodoo/odoo#105686
X-original-commit: be7a02917a66136a8d3b601d61a898b0419ff79d
Signed-off-by: Julien Castiaux <juc@odoo.com>
We introduced `odoo.http.Stream` and its `ir.binary` companion model
to refactor all the "stream data over http" bits into a single unified
API. The API takes care of creating a cache-aware, proxy-accelerated,
headers-rich HTTP response out of any path, attachment or binary-field.
closesodoo/odoo#99658
Signed-off-by: Julien Castiaux <juc@odoo.com>
The cron subsystem is the system responsible of running background task
at regular interval, it runs in multiple dedicated threads or workers
that are independent of the regular HTTP threads/workers.
There was a major overhaul of the system in v15 (4b28f1162a) to
introduce cron triggers, a way to run a task at a given moment in
addition to the regular configured interval. Although the cron system
was not extensively tested before that v15 refactor, no new test were
introduced with that refactor leaving the system mostly untested.
Since then we had to fix multiple subtle concurrency bugs such as
b940d1c25f and c06cee44fe. Due to the lack of an existing test suite,
no regression tests were added next to those fixes.
With this commit we introduce the missing cron test suite. The test
suite is separated in two different test cases:
- A standard pre-install TransactionCase to test everything that can be
tested with a single cursor. This case can be run the usual way with
`--test-tags :TestIrCron`.
- A non-standard post-install **database breaking** test case to run
concurrency tests that often require multiple SQL transactions. This
case requires the special `--test-tags database_breaking` to be
executed. You MUST backup your current database before running that
test or you'll loose data.
closesodoo/odoo#97087
Signed-off-by: Julien Castiaux <juc@odoo.com>
This fix allows generating the sitemap (via `website.search_pages`) via
RPC. The changes are necessary as since #99667 the `request` object is
no more available to RPC-executed functions.
See also: the `test_search` test case of `website.tests.test_page`.
closesodoo/odoo#104567
X-original-commit: 8f4e214417cfb1aed7e595b8d6ab71f33a347b29
Signed-off-by: Julien Castiaux <juc@odoo.com>
Install website then render a qweb template via xmlrpc, attribute error:
`request` has no `is_frontend` attribute.
Inside the qweb's `_prepare_environment` override of the http_routing
module (a dependency of website) is the following code snippet:
if (not irQweb.env.context.get('minimal_qcontext') and
request and request.is_frontend):
return irQweb._prepare_frontend_environment(values)
The conditionnal is about injecting extra informations in the qweb's
context in case we are serving a frontend request. By default, there is
no `is_frontend` attribute on the request object. That attribute is set
by the ir.http's `_match` override of the http_routing module (a website
dependency): it is set True when we don't match any endpoint or that we
match an endpoint that is `website=True`, it is set False otherwise.
The ir.http's `_match` method is called whenever we are serving a http
request whoose session is bound to a specific database. i.e. when there
is a valid database saved in the request's session. When there is not
database in the request's session (or that it is invalid) the matched
endpoint is directly called without going throught ir.http. Most
endpoints are only accessible via ir.http.
The two `/xmlrpc` and `/jsonrpc` endpoints are examples of endpoint that
do not require an established database connection to work. They perform
the request authentication and database connection themselves. It is
possible to call those two endpoints with no database saved in the
session, thus it is possible to call those two endpoints without going
throught ir.http. This is expected.
The two endpoints's duty is to execute public model methods and return
the xml/json serialized result. To do so, a registry is loaded on the
database with all the installed modules, including http_routing.
We fall in a situation where (1) there is a request, (2) we are using a
registry where http_routing is loaded, (3) there is no `is_frontend`
attribute on `request` as we didn't serve the endpoint via ir.http. This
situation is illegale.
To solve the problem, instead of working on the `if request.is_frontend`
bit of the above conditional, we decided to work on the `if request`
bit. ISO-model wise, RPC is an extra 8th layer built on top of HTTP.
HTTP is merely a transparent transport between a RPC client and a RPC
server, any other request-response capable procotol could fit. The
method executed via RPC must be independant from the usage of HTTP as
mean of transportation thus it should not be capable of using the
current request.
The proposed change is to temporary un-expose the current request from
the local-stack during the execution of the RPC method. This fixes the
problem as the code now run like it was executed from the shell or from
a cron. The other benefit is that the pattern used inside the condition:
`if request and request.is_frontend` doesn't need to change.
X-original-commit: f28863bfdaac3f644fcb274c856e0770783ed75c
Part-of: odoo/odoo#104567
Install website_links, create a link e.g. to http://example.com, install
the russian language and translate the default website. Access the short
link you created before-hand, 404 website page not found.
Accessing a website starting with /r is ambiguous, is /r the
link-tracker controller or is /r a russian lang alias (nearest lang
algorithm)? The controller should be prioritary to the lang alias.
This restore the behavior as it was before the httpocalyse.
closesodoo/odoo#99555
X-original-commit: e8a1b4c0cdffb38e48ca980205a61c75c564dee8
Signed-off-by: Jérémy Kersten <jke@odoo.com>
Signed-off-by: Julien Castiaux <juc@odoo.com>
Commit c06cee44fe corrected a nasty concurrency error in crons but the
serialization error was still logged.
closesodoo/odoo#98741
X-original-commit: 81c70a594a785fc0e32c9ff47c28c92b0f837923
Signed-off-by: Julien Castiaux <juc@odoo.com>
Prior to saas-15.4, access /web/content/res.users/4/image_128 without
being logged in, there was an internal server error because you cannot
access the public user (id=4) image.
The saas-15.4 branch is unaffected by the bug but it is a good idea to
forward-port the test that was next to the fix merged in prior versions.
Closes#94258closesodoo/odoo#98201
X-original-commit: 4deb7e373e046844d997ddc0a11999cc12b765d8
Signed-off-by: Julien Castiaux <juc@odoo.com>
Co-authored-by: ypn <ypnwebdev@gmail.com>
An HTTP Partial Request is a regular GET request with a Range header
that indicate what chunk of the data the browser wishes to download. It
is useful to peek in a video stream or to resume an interrupted
download. The server can respond with a 206 - Partial Content response,
set the appropriate Content-Length and Content-Range headers and only
send a chunk of the data in the body.
Werkzeug's `send_file`, the library function we use to stream files over
http, is fully compliant with partial requests and responses. It
understands the Range request header and create the corresponding
response.
NGINX does also comply with the Range header even via X-Accel-Redirect.
closesodoo/odoo#97422
Signed-off-by: Julien Castiaux <juc@odoo.com>
`_post_dispatch` is a late addition to the httpocalypse. It can be used
to alter the response object e.g. to inject headers or cookies. The
function is automatically called for regular, fallback and error
responses. On the other hand, `_dispatch` is only called when an
endpoint is matched and might not return a response in case of error.
task-2839031
closesodoo/odoo#96651
Related: odoo/upgrade#3710
Signed-off-by: Julien Castiaux <juc@odoo.com>
Create an empty image attachment and load it via an `<img>` html tag in
a document. Upon rendering the image is replaced by the default browser
placeholder instead of the pretty Odoo one.
closesodoo/odoo#95702
Task: 2886028
X-original-commit: 981d56f131f85d33e2415edaef36cfa501d86f0e
Signed-off-by: Julien Castiaux <juc@odoo.com>
Install multiple langages, each time translating the website. In a
private browsing session access /contactus, show the page source, the
multiple alternate URL (`<link rel="alternative">` in the `<head>`) are
all pointing the canonical URL instead of the alternative.
closesodoo/odoo#95683
X-original-commit: 3d4b4d3dcff864613a9e7038137e21674425ed08
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
Signed-off-by: Julien Castiaux <juc@odoo.com>
Each cookie binds to a domain name, multiple cookies can be set for the
same name if they are for different domain name. In this case, two
`session_id` cookies were set: (1) the first set right on the opener at
`opener.cookies[...] = ...`, (2) the second set upon inside of
`http.Request._save_session` because the session was rotated upon login.
The problem is that the former cookie (the one set on the opener, the
one *not* rotated) was used instead of the second cookie (the one
holding the registered user) in the subsequent queries. There is a long
comment explaining the same problem inside of
`odoo.tests.common.HttpCase.authenticate`, we used the same solution as
they did inside of `authenticate`: we diched the previous opener.
closesodoo/odoo#94773
Signed-off-by: Christophe Monniez (moc) <moc@odoo.com>
Install website and website_hr_recruitment, open /web with ?debug=1, go
to website > configuration > redirect, create a temporary (302)
redirection from `/jobs/detail/experienced-developer-4` to `/404`. Open
the `/jobs/detail/experienced-developer-4` as admin and unpublish the
page. Open the same URL via private browsing (so that you are not
connected), you get the default 403 - Forbidden page, you were not
redirected to the 404 - Not Found page.
Custom 301 (permanent) and 302 (temporary) redirections are fallback
redirections when the requested page does not exist or is not accessible
to the current user. The HTTPocalypse broke the later case, it was not
checking for existing redirection upon access error.
The use case is the one supported with [1] where people want/need to
display something better than a 403 when they unpublish a record like a
job position for instance (most of the requested cases on opw).
Indeed:
- People have link to that record/job everywhere on the internet
- The job position / record is no more relevant, and people need to
unpublish it
- People don't want to delete it (or can't sometimes due to record
relations)
- People don't want visitors to land on a 403, mainly because it is a
non customizable advanced/technical page (it displays a technical
message including the record name etc)
- Their need is to either land a their customizable friendly 404 or
sometimes on another record to promote it.
[1]: https://github.com/odoo/odoo/commit/3b9cd536607b1631dd375ab2e5cc94eb814a6e9bclosesodoo/odoo#93981
X-original-commit: eb7eecec976570ae3301c17a04adc9c110d5b14a
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
Signed-off-by: Julien Castiaux <juc@odoo.com>
Co-authored-by: Romain Derie <rde@odoo.com>
Create an empty database and start 2 http workers, go on the web app
menu and install website (don't install website via -i). Once website is
installed, you are redirected on `/website/configurator` but the route
does not exist and it fails with a 500 internal server error.
The problem is due to an invalid registry manipulation introduced in the
saas-15.3's httpocalypse. When new modules are installed the registry
must be reloaded in all workers. The function that determine if the
registry must be reloaded and reloads it is `check_signaling`.
When the current registry is up-to-date, it is returned as-is by
`check_signaling`. When it is outdated, `check_signaling` creates and
returns a new fresh registry; it does not nor discard nor change
in-place the previous (outdated) registry, it is up to the callee to
discard the previous registry itself.
closesodoo/odoo#93579
X-original-commit: 23bdcc3fd31e6908ee6737cb74787aefa5d057a5
Signed-off-by: Raphael Collet <rco@odoo.com>
Signed-off-by: Julien Castiaux <juc@odoo.com>
Fine tuning of da8def8e41closesodoo/odoo#93407
X-original-commit: 7efa8743b1bbe9efc4b81a10331f096e8de1e52d
Signed-off-by: Julien Castiaux <juc@odoo.com>
Access `/web/image/82303?height=16`, traceback because the placeholder
image cannot be resized to `"16"`.
closesodoo/odoo#92891
Signed-off-by: Julien Castiaux <juc@odoo.com>
Rationnals
----------
Web servers can serve some resources (e.g. static files) right away
without any interaction with the web application. The network model of
most web servers makes them capable of handling thousands of
simultaneous requests when it comes to intensive IO operations such as
streaming data from a file. The network model of Odoo is different: it
is capable of a lot of processing power but can only serve a handful of
requests at a time, i.e. Odoo (with some help from postgres) is
optimized for CPU operations, not IO.
Some users don't configure their web server, they use a basic
configuration that relay all requests to Odoo. The result is that many
Odoo HTTP Workers can be busy streaming static files instead of
processing other requests. This can lead to a worker starvation, i.e.
all workers are busy streaming files and cannot process new requests.
X-Sendfile
----------
In this work, we add the support for the [X-Sendfile] header family,
they are multiples http headers that can be used by the web application
to communicate with the web server in order to delegate the delivery of
files stored on the file system. Odoo still receives the request but it
does no more stream the file content from within its HTTP worker,
instead it skips the response body altogether and sets the `X-Sendfile`
special header with the path of the file on the filesystem. The web
server intercepts that special header, open the file and stream it.
Using those headers, we can use the best of both the web application and
the web server. The web application is still responsible to locate the
resource and verify the access rights, the web server is still
responsible of streaming the content.
Using X-Sendfile is opt-in via the `--x-sendfile` CLI flag. We set both
`X-Sendfile` (apache) and `X-Accel-Redirect` (nginx). If you are using
apache, make sure `mod_xsendfile` is enabled. If you are using NGINX
you have to add the following location block:
location /web/filestore { # custom path, hardcoded within Odoo
# Prevent access from the outside world, i.e. makes this
# route only accessible via X-Accel. MANDATORY!!!
internal;
# Give access to the filestore using this server's
# permissions. Odoo is in charge of verifying the access
# rights.
alias /path/to/odoo/data-dir/filestore;
}
The Odoo [deployment documentation] has been updated accordingly.
[X-Sendfile]: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/
[deployment documentation]: https://www.odoo.com/documentation/master/administration/install/deploy.html#serving-static-files-and-attachments
Changes to the API
------------------
To benefit most from X-Sendfile, all APIs related to streaming content
over HTTP has to be adapted. They are: (1) `request._serve_static`,
(2) `ir.http._serve_fallback`, (3) `/web/content` and (4) `/web/image`.
Each used it own way to deliver content: (1) `_serve_static` was using
`send_file` (flask's send_file that as been vendored with odoo 10
years ago and not maintenained since then), (2) _serve_fallback was
handcrafting a `werkzeug.wrappers.Response`, (3) /web/content-image were
using the "binary server" `ir.http.binary_content` API.
I has been decided to remove all 3 APIs and to merge the code inside of
the new `http.Stream` object and the `ir.binary` helper model.
A Stream wraps what is going to be sent to the browser, it can be a path
to a file on the locale filesystem, a blob of raw data or an URL to an
external resource. The Stream also holds various metadata that are
mainly used for caching. The preferred way to create a Stream is via one
of its three factories so that all the metadata are set. The factories
are: `from_path`, `from_attachment` and `from_binary_field`. A stream
instance exposes a single method `get_response()` used to create the
corresponding HTTP response object out of the stream.
Inside of `ir.http` were a few methods that were not related to the http
routing and formed what was called the "binary server". All those
methods have been removed and the feature have been refactored inside of
the new `ir.binary` model. The removed methods are:
- `_xmlid_to_obj`
- `_get_record_and_check`
- `_binary_ir_attachment_redirect_content`
- `_binary_record_content`
- `_binary_set_headers`
- `binary_content`
- `_response_by_status`
- `_get_content_common`
- `_content_image`
- `_content_image_get_response`
- `_placeholder_image_get_response`
The new `ir.binary` abstract model exposes the following utilities:
**`_find_record`**
Find an attachment or a record with a binary-field out of an xmlid or
out of a pair record-model/record-id. Check the access rights and the
access token.
**`_get_stream_from`**
Create a Stream from an attachment or a record with a binary-field.
**`_get_image_stream_from`**
Same as `_get_stream_from` but adapted for images. It sets a sensible
ETag on the stream and has image resizing support.
**`_placeholder`**
Get the image placeholder blob.
Testing
-------
It is possible to test the web server configuration using the
`test_http` module. Install the module then run the unittest using the
`webserver` test-tag. By default it attempts to connect to a web-server
running on `http://localhost:80`, you can change this URL by setting the
`WEB_SERVER_URL` environment variable.
odoo-bin -i test_http --stop-after-init
WEB_SERVER_URL='http://localhost:80' odoo-bin --test-tags webserver --stop-after-init
closesodoo/odoo#88134
Task: 2801675
Related: odoo/documentation#2083
Related: odoo/enterprise#26191
Signed-off-by: Julien Castiaux <juc@odoo.com>
We introduce a new decorator/context-manager to catch some exceptions
and re-raise them as another error. This utility main's purpose is to
hide a route that a user has no access to behind a fake HTTP 404 Page
not Found error.
The utility is at `odoo.tools.misc.replace_exceptions(*exceptions, by)`.
Its usage is as follow:
@route('/some/route', auth='public')
@replace_exceptions(AccessError, AccessDenied, by=NotFound())
def some_route(self):
if not request.session.uid:
raise AccessError("Must be connected to see this route")
...
Or as a context-manager if you don't want to except an entire function:
@route('/some/route', auth='public')
def some_route(self):
with replace_exceptions(AccessError, AccessDenied, by=NotFound()):
if not request.session.uid:
raise AccessError("Must be connected to see this route")
...
Task: 2800772
Close: #90433
Part-of: odoo/odoo#88134
When the homepage was set to a different page than /, the resulting page
was blank.
The problem is related to a change of the rerouting algorithm, before
the httpocalypse it was re-dispatching the request itself, now it is up
to the called to do so. Here it is what `serve_path` does.
closesodoo/odoo#91034
X-original-commit: b00fefec2a26eb36be8a13cb845ee4f12f87d4eb
Signed-off-by: Julien Castiaux <juc@odoo.com>
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
A reroute in an internal redirection, the URL on the current request is
changed. The actual changes occurs on the "WSGI environment", a
dictionnary that is populated with the request information by the WSGI
server (werkzeug in our case). Among all the properties there are
PATH_INFO, RAW_URI and REQUEST_URI.
* PATH_INFO is the standard value, it contains a RFC-3986 path.
`request.httprequest.path` is based on that value.
* RAW_URI and REQUEST_PATH are two custom values set by werkzeug to
mimic other popular wsgi servers (gunicorn, uwsgi, mod_wsgi), they
also only contain a RFC-3986 path.
When rerouting only PATH_INFO and RAW_URI are updated. REQUEST_URI is
left as-is in order to keep the original path somewhere. When building
the cannonical path, one must use the original path instead of the
rerouted one.
X-original-commit: 779486c4dff23b946b4b14e3dca164ecca4f85a8
Part-of: odoo/odoo#91034
When started with the CLI option --dev=werkzeug, errors in controllers
are caught by a friendly web debugger. This debugger should not be
started in case of error in a JSON-RPC controller.
closesodoo/odoo#90420
Task: 2837457
X-original-commit: 711a29e76f55e87d73dad89ddbef889dd5c7af10
Signed-off-by: Julien Castiaux <juc@odoo.com>
With this work we relax the http controller so that it accepts all
requests, including `Content-Type: application/json`. We also enrich
the framework with two new helper methods dedicated to serialaze json
requests and responses:
- `request.get_json_body()`, loads the json content from the request's
body and returns the corresponding python object (usually a dict).
- `request.make_json_response(data)`, dumps `data` to json and makes an
http response out of it.
closesodoo/odoo#86300
Task: 2779837
Signed-off-by: Julien Castiaux <juc@odoo.com>
Commit "[IMP] core: don't save visitor default session" moved the geoip
from the session to the request with a deprecation warning. This commit
adapts the remaining modules to use `request.geoip` instead of
`request.session.geoip`.
Geoip is always set on the request but it can be an empty dictionnary in
case the geolocalization failed.
closesodoo/odoo#86015
Task: 2789035
Related: odoo/enterprise#25192
Signed-off-by: Julien Castiaux <juc@odoo.com>
Every request comes with a session, a dictionary that is persisted on
the filesystem and that saves various information such as the user
cart on the ecommerce.
When a user simply visits the website, a default session is created and
saved on disk, this bloats the filestore with many sessions. Creating
the session on-the-fly is cheaper than loading it from the filesystem.
With this work the default session is not saved on disk anymore unless
explicitly asked via `session.touch()`.
An exception to the statement "creating the session on-the-fly is
cheaper" is geoip, the ip geolocalization is not cheap. In this work,
geoip have been moved from http_routing/request.session.geoip to a
lazy property core/request.geoip. When requested the info is persisted
on the session. Like other keys from the default session, geoip will not
be persisted unless there is non-default stuff in the session.
Because the CSRF-TOKEN is based on the session-id, it is important the
session-id stays the same across multiples requests even when the
session is not persisted on disk. Even when a session is not persisted
on disk, the session-id cookie is still set so that the next session
created on-the-fly uses the same session-id.
Technical note regarding the session, it has been decided to drop the
session-snapshot protocol and to reintroduce a "modified" flag. It has
been decided not to use werkzeug's session (which natively comes with a
"modified" flag) and to keep our own session object. We decided to
extend MutableMapping instead of dict; using MutableMapping we only
have to override __setitem__ and __detitem__; using dict we would had to
override update()/pop()/... too.
Task: 2789035
Part-of: odoo/odoo#86015
Steps to reproduce:
1) start from a clean database (http_routing should not be installed).
2) go to the app menu and install project (don't install via `-i`!!).
3) traceback: `request` has not attribute `is_frontend` while rendering
a template.
The `is_frontend` attribute on request is set by the http_routing
module. At the moment the "install now" button is clicked, http_routing
was not installed so the `is_frontend` attribute was not set.
FF to the end of the installation: the registry is reloaded to include
the modules that have been installed, http_routing among them.
We are in a tricky situation: (1) there is a request, (2) http_routing
is installed and (3) the `is_frontend` attribute is missing from the
request.
This situation is illegal, when http_routing is installed, the
`is_frontend` attribut should always be set. In this work we reset the
missing attributes using sensitive default values via post-init hooks.
closesodoo/odoo#87684
Signed-off-by: Julien Castiaux <juc@odoo.com>
The odoo.addons.web.controllers.main python module have been splitted
over multiple files on the basis 1 controller = 1 file. In this work we
adapt all modules to use the new imports.
A non-exhaustive list of where stuff have been moved:
* main.Home --> home.Home
* main.Session --> session.Session
* main.WebClient --> webclient.WebClient
* main.clean_action --> action.clean_action
* main.ensure_db --> home.ensure_db
The complete list is accessible in odoo.addons.web.controllers.main.
closesodoo/odoo#87571
Related: odoo/enterprise#25746
Signed-off-by: Raphael Collet <rco@odoo.com>
The odoo.addons.web.controllers.main module was a very long bloated file
where many different controllers were concatened. It proved difficult to
work on that file on a regular basis,mainly because ctrl-p "web main.py"
was not pointing the right file.
In this work the file has been split on the basic 1 controller = 1 file.
The original way of importing stuff (through main.py) is still possible
thanks to deprecated aliases.
Part-of: odoo/odoo#87571
Install auth_signup, go to /web/login, 500 Internal Server Error.
auth_signup extends the /web/login template and in this extension calls
`keep_query()` which has been wrongly moved from base to http_routing in
commit 880954ebfc. Here, we restored `keep_query()` in the base module
but moved in ir_qweb.
closesodoo/odoo#87491
Related: odoo/enterprise#25754
Signed-off-by: Julien Castiaux <juc@odoo.com>
With the iot-box it is possible to download modules from an odoo server
into the iot-box. Those odoo modules are `exec`-like and exposed on the
box. With those modules come some controllers. Because they were
`exec`-like, they lack a python module name which makes
`_generate_routing_rules` to reject them.
With this fix, we authorise to declare controllers outside of odoo
addons. Those controllers cannot be extended, nor can they override
other controllers.
closesodoo/odoo#86275
Signed-off-by: Julien Castiaux <juc@odoo.com>
Install website, create a custom web page, we'll call it page_1. Ensure
you are not in debug mode (go to /web/health?debug=0 to disable it).
Open the web page enabling the debug-mode /page_1?debug=1, the page
opens but the debug mode is disabled.
Because web pages are served using another routing mechanism than
controlers we have to ensure we load the debug query-string in those
mechanisms too.
closesodoo/odoo#85340
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
Install the website_sale and open '/shop/whatever-9999' in your browser,
the response is a 500 Internal Error instead of a 404 Page not Found.
Because `request.endpoint` has been murdered by the httpocalypse, we
must re-match to get the endpoint later used to re-build the URL. When
an URL contains a record-id but that record does not exists, matching
the URL will raise a `odoo.exceptions.MissingError`.
Part-of: odoo/odoo#85340
This commit is the 15th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
Enhance the new test_http module with more tests.
Pr: odoo#78857
Task: 2571224
Related: odoo/enterprise#21849
Related: odoo/design-themes#524
Signed-off-by: Julien Castiaux <juc@odoo.com>
This commit is the 14th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
* `request.uid = x` => `request.update_env(user=x)`.
* `request.context = x` => `request.update_env(context=x)`.
* `request.context = dict(request.context, x=y)`
=> `request.update_context(x=y)`.
* `request.cr = None` => `request.cr.close()`.
* `http.mono_db()` => `request.db`.
* `http.dispatch_rpc()` => `service.dispatch_rpc()`.
* `@service.model.check` => `service.model.retrying()`.
* `request.endpoint`
=> `env['ir.http']._match(request.httprequest.path)[0].endpoint`.
* `request.routing_iteration `=> `removed`.
* `request.jsonrequest` => `request.dispatcher.jsonrequest`.
Note that `request.params` is now set much later in the process. If you
are in a situation where you values from the query string or the
http body you can use `request.get_http_params()`.
Note that using the new `request.future_response`, it is possible to
add headers and cookies on the response object before the response
object is initialized. Please note that headers/cookies saved on
the future response will NOT be injected in case of error.
PR: odoo#78857
Task: 2571224
This commit is the 13th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
Here be dragons.
First and foremost, `http_routing` is a technical module that aim to
provide the minimum viable compatibility code between portal and
website. Its primary job is to take care of the lang inserted in the
path of URLs, e.g. `en` in `/en/my_blog`. Both when routing a request
with a lang in the URL and when rendering templates with multilang
support.
Next to `http_routing` is website, the module used by customers to
create pretty web page accessible online. Website uses a different
routing logic than the backend, webpages are **not** registered in the
routing map of werkzeug but instead delivered by website dedicated
code. It means that **all** request targeting a website page thrown at
the werkzeug router **fail** with a HTTP 404 error. The reality is that
website abuses the fallback mechanism (`_serve_fallback`) to deliver
its pages.
Using `_serve_fallback` as the standard way to deliver pages is broken
by design. It is the least crappy way to deliver content as long as
website page don't have a dedicated path prefix. Using a path prefix
(e.g. `p` in `/p/fr/mon_blog`) it would have been possible to route the
request to a dedicated endpoint using the same router as the backend and
with no change to the HTTP dispatching code. Sadly, the business does
not want such prefix so we have to stick with a broken design.
It is broken because prior to serving a page, website needs to setup A
LOT of stuff on the system. It needs to ensure a proper user is set on
the environment, it needs to setup the GeoIP database, it also needs
to determine the lang the user requested the page and it also needs to
save multiple attributes on the request objet itself (`is_frontend`,
`is_frontend_multilang`, `routing_iteration`, `website`, `lang`,
`rerouting` and `website_routing`). Since `base/ir.http@_match()` will
fail, everything must be set either prior to calling this method or in
`website/ir.http@_serve_fallback`.
---
The original implementation was overriding the `_dispatch` method which
was responsible to call the four `_match()`, `_authenticate()`,
`_postprocess_args` and finally `WebRequest._dispatch()`. The override
was very special, here is an attempt to explain it:
1) try to match an endpoint using the backend router, 404-errors are
ignored.
2) include the geoip stuff.
3) authenticate using the `auth` @route argument if an endpoint matched
in (1), otherwise authenticate with the public user.
4) if not endpoint matched or if a frontend endpoint matched in (1):
a) call `_add_dispatch_parameters` which sets many arguments on the
`request` object, including the lang found in the request cookies
b) try to extract a lang from the URL: abort with a redirection when
the lang is missing or wrong, remove the lang from the request
path when it is set (updating both `request.lang` and the cookie).
5) return the result of `super()._dispatch()`
Note that `_serve_fallback()` is called during `super()._dispatch()`
when the path still does not route to an endpoint. Thanks to the
`_dispatch` overrides in http_routing and website, it is garanteed that
the system is setup prior to calling `_serve_fallback`.
---
Because it is now `http.py@Request._serve_ir_http()` that is responsible
of calling the four`_match()`, `_authenticate()`, `_pre_dispatch` and
finally `_(http|json)_dispatch()` it is no more possible to override it
to take over the dispatching to perform the http_routing/website magic.
The prior implementation can not work with the new design thus is has
been refactored too.
To render a website page, there must be a user configured on the
environment (not None) and the various special attributes must be set on
the request object. The special `lang` attribute is popped from the
request path but backend endpoints must be delivered in priority.
Using the new design, it has been decided to override the `_match`
method to implement the lang-in-path logic, to override both
`_pre_dispatch` and `_serve_fallback` to call `_add_dispatch_parameters`
which have been renamed `_frontend_pre_dispatch` and to also grant the
public user in the `_serve_fallback` override.
The `_handle_error` override in website has similar needs, the function
is called upon error (4xx/5xx) in order to render a pretty
website-looking page. Because such error can occurs as early as in
`_match()` (page not found and no fallback), when nothing has been setup
yet, website is yet again responsible for setuping everything: request,
orm, frontend.
Many other small improvement are not described here. Hopefully the added
comments in the source code are enought.
PR: odoo#78857
Task: 2571224
This commit is the 12th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
The web module is twofold, on one side there are many controllers: /,
/web, /web/login, /web/database/selector, /web/dataset/call_kw, etc, on
the other side there is `session_info`: the method responsible to create
the web client's environ.
This module is kinda an exception as it is (with base) a server wide
module. In the case of the HTTP framework, it means that the controllers
of web are always accessible, i.e. going to / or /web/login will never
return a 404 Not Found even if the user is not connected to a database.
This is both a blessing and a curse. It is a blessing because the
controllers are always accessible it means that a new users can freely
access those routes. It is a curse because *any* user can access them,
even user who don't have a session yet thus who are not connected to a
database yet. From a developer standpoint, we have to put extra care to
correct serve users with and without a database. An example is the
/web/login route, the login/password pair is stored in a database,
without database it is impossible to validate a user login but users can
still access this route without db.
To solve this problem, there is the `ensure_db` function. This function
attempts to find a database using various sources (?db= query-string,
session db, mono db) and to save it on the user session. In case no db
is found, the user is redirected to the database selector. In a way,
this function grants a database to the user in a seamingly experience.
In a way, this function brings a welcome differentiation between
`auth='none'` with a database and `auth='none'` without a database. Such
differentiation only matters for the server wide modules as "regular"
module controllers are only accessible via the ir.http routing map, i.e.
it is not possible to declare a nodb controller outside of server wide
modules.
An important changement is the `session.authenticate` method, before it
was possible to call the method when the cursor was not yet initialized,
authenticate would open a cursor against the given database, setup a
registry and an environment and ultimately save everything on the
current request. Because the cursor is now greedily created, it is no
more possible to update the request environment when authenticating on
another database.
PR: odoo#78857
Task: 2571224
This commit is the 11th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals. See also [REF] core: HTTPocalypse (9) ORM initialization.
Where `request._serve_db` is reponsible for initializing the ORM,
`request._serve_ir_http` is responsible to do the match -> authenticate
-> pre dispatch -> dispatch -> post dispatch sequence. This sequence is
very much similar to `_serve_nodb` with the notable addition of the
authenticate step.
Where `_serve_nodb` was working in a db-free environment, calling the
methods of (Http|Json)Dispatcher right away, `_serve_ir_http` delegates
to the `ir.http` abstract model which in turn use the dispatchers.
The ir.http model is crucial for several modules, web, portal and
website on top. They override the various methods to add advenced
capabilities to the http framework.
An important changement to the ir.http model in this work is regarding
the `_dispatch` method. Before this work it was that method that was
responsible of doing the sequence match -> pre dispatch -> ... . It was
decided to move the responsibility to http.py in order to prevent
modules from taking over the entire framework. One such take-over was
done in the `http_routing` addon and resulted in arguably very poor and
fragile code.
All modules must now carefully override the correct method. You need to
do smart stuff on the URL prior to matching an endpoint? You should
override `_match`. You need to save a special option from the query-
strings to the session before calling the controller? Override
`_pre_dispatch`.
PR: odoo#78857
Task: 2571224
This commit is the 10th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
One of the objectives of this comprehensive refactor was to simplify the
error reporting, to show developers shorter and more informative
traceback upon errors.
We argue that the current error reporting is too much bloated:
1. There is a "dual" traceback, one traceback for the frames more recent
than `/base:ir.http_dispatch` (`@route` wrappers + controller) and a
second traceback for the former frames (http framework + web server).
2. We trace too far back, we argue that it is useless to trace back to
the thread/worker creation, that we should only trace back until the
WSGI entry point.
3. There is too much noise, many frames are either implementation
details or useless information. e.g. middlewares, `_dispatch`
overrides, wrappers, "checks".
To solve the above problem we did the following:
1. All errors bubble up to `request.__call__` where it is logged, any
wrapper that catch an error must log it or re-raise it. By logging
exceptions in `__call__`, we ensure the traceback at that function.
2. When one raises an HTTP error, e.g. 404/NotFound, the object is at a
same time an exception and a response. We don't log it and use the
response right way.
3. When one raises an exception, e.g. ValueError, the exception is sent
to `handle_error`, its job is to return the HTTP error corresponding
to the given exception, e.g. 500/InternalError in case of ValueError.
The given exception's `__traceback__` and `__clause__` are not
modified, we log the real exception but use the http error as
response.
Minimalistic example showing a traceback before and after this work:
https://gist.github.com/Julien00859/d6a48d523cac42d0df2e3136f5ba3e54
PR: odoo#78857
Task: 2571224
This commit is the 9th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals. See also [REF] core: HTTPocalypse (11) ir.http base model.
This is a two-part commit with "(11) ir.http base model". In this commit
we focus on the initialization of the various ORM objects, namely: the
registry, the cursor, the environment, the user and the context. In the
other n°11 commit we focus on the ir.http model and its relation to the
current http.py module.
One of the objectives of this comprehensive refactor was to ease the
cognitive complexity of the http framework, in other words to make it
simplier. One of the problem identified quite early during the
preparation of this work is the way the various ORM objects are
initialized, modified and cleaned during the request lifetime.
Before this work, all the ORM internals were lazily initialized via
properties. It is the first time one uses `request.cr` that a cursor is
opened to `request.db` and stored on `request._cr`. It is the first time
one uses `request.env` that an environment is create with the current
`request.user` and `request.context`. Upon user or context modification,
the current environment is discarded, the next usage of `request.env`
will create yet another environment on the fly using the modified user
and/or context.
Using this model, no ressource is initialized if not necessary. It is
possible for nodb-compatible endpoint to be served via the db-compatible
router and ir.http without ever opening a cursor to the database.
But this model is harder to reason about and ultimately to maintain.
In this work we propose to drop the lazy approach for a greedy one. In
this work the first steps of `_serve_db`, the db-compatible counter-part
of `_serve_nodb`, are dedicated to setup a registry, open a cursor to
the database and create an environment using the session's user and
context. In this work, when one wants to change the environ's user or
context, he must call `request.update_env` or `request.update_context`,
both method will recreate the environment *now* with the given values.
The downside of this approach is that resources are always allocated
even when it is not necessary. We argue that, in general, the
controllers that do not use the ORM are rare thus it is rare we allocate
unecessary resources. We also argue that the cognitive benefits are more
than welcome and that the new APIs will help at writing more robust
applications.
PR: odoo#78857
Task: 2571224
This commit is the 8th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
PR: odoo#78857
Task: 2571224
See also: [REF] core: HTTPocalypse (6) serve db-free routes
This commit is the 7th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
PR: odoo#78857
Task: 2571224
See also: [REF] core: HTTPocalypse (6) serve db-free routes
This commit is the 6th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
There are some routes that must work even when the user is not connected
to a database yet. Two obvious examples are the index (/) and the
database selector (/web/database/selector). Accessing those routes
should be possible even when the user is not connected to a database yet,
the `nodb_routing_map` and the `_serve_nodb` method fulfill this need.
Along with the introduction of the `_serve_nodb` method, we also
introduce the match -> pre dispatch -> dispatch -> post dispatch
sequence. `match` matches an endpoint using the http path,
`pre_dispatch` prepares the system, `dispatch` is the actual endpoint
call, `post_dispatch` cleanups the system and add some http headers to
the http response.
At the beginning, when an endpoint is matched, we retrieve the `routing`
dictionnary that is attached to the endpoint method. This dictionnary
contains the information set by the `@route` decorator, among other
values is the routing type `http` or `json`. This routing type is used
to specialize the request via one of the dispatchers: HttpDispatcher for
http and JsonDispatcher for json. Via the dispatchers, it is possible to
add custom behavior in the form of (pre_|post_)dispatch override. One
example of such custom behavior is the way the request body will be
loaded: urllib.parse.parse_qs (http) vs json.loads (json).
Before this work, it was not possible for multiple dispatchers to be
compatible with a same request mimetype. Because `JsonRequest` was
implementing the support for `application/json` (and like) mimetypes,
all requests having a body's mimetype `application/json` were considered
JSON-RPC2 (the protocol implemented by `JsonRequest`) even if the
request was actually bare json data. With the introduction of the
`is_compatible_with` classmethod, multiple dispatchers can be compatible
with a same mimetype and it is up to the `@route(type=...)` param to
define which dispatcher should be used with this route.
PR: odoo#78857
Task: 2571224
This commit is the 5th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
A session is a file that contains user data that must be preserved
across several requests. It is a small (=at most a few kb) file that is
stored on the filesystem in the session store. Each filename is a random
sha-1 string, the same sha-1 is saved in the request's cookies. The
browser has no access to the session, it only possesses the random sha-1
cookie which the server use to retrieve the session upon each request.
It is in the session that the database that the user is connected to is
stored. The first time the user accesses the server the database is
determined using the HTTP Host header of the request plus the list of
available databases and is saved into the user's session. Later requests
merely ensure the database stored in the session is still enabled.
The session "feeling" is the same as before the httpocalypse. The
session is still a DotDict-like structure with methods such as
`authenticate` and `logout`. The difference is the implementation, in
master we were using the werkzeug session, with this work we decided to
get rid of those off-the-shelf sessions to implement our own object.
The major difference between the two implementations is the way we
determine when a session is "dirty", when the session should be written
back on the filesystem. In werkzeug it is a `modified` flag attribute
that is set as soon as `__setitem__` is called, in our implemention we
keep an immutable copy of the original session, at the end the of
request lifecycle we compare the live session to the copy to determine
if it was changed. The former was setting the `modified` flag even when
one was setting the same value as existing in the session. The later
approach is not affected by this limitation.
PR: odoo#78857
Task: 2571224
This commit is the 4th commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
Complete refactor of the "registry" of controllers. The `ControllerType`
metaclass have been replaced by an abstract class with a py3.7
`__init_subclass__`. The `Endpoint` class is gone too, replaced by a
clever usage of `functools.partial`. This refactor is "pure", it doesn't
add any new feature, it only merely adds a few warnings.
The four `@route`, `Controller`, `_generate_routing_rule`, `routing_map`
work as follow:
1. A reference to each immediate child class of `Controller` (not grand-
children) is registered in a global list indexed by module (thus a
dictionnary) everytime the server starts. Remember that every first-
child (not grand-children) of `Controller` is the primary controller,
the one that can later be extended by other controllers (the grand-
children) in other modules. Remember that it is possible to get each
class's children via the `__subclasses__` dunder method.
2. Every controller method that is decorated with `@route` is granted an
attribute: `original_routing`, a dictionnary containing the `@route`
arguments. When a controller method has the `original_routing`
attribute, this method is called an `endpoint`.
3. `_generate_routing_rules` receives the list of installed module
names, this list is topologicaly sorted according to the modules
dependencies. That is `base` comes before `web` in this list. The
objectif of this function is to pair each route to an endpoint whoose
class's MRO respect the above-mentioned order. Whe achieve this by
carefully crafting classes at runtime, classes inheritating from the
correct "source code" controllers in accordance to the topology. This
method is also responsible of merging each method's `original_routing`
into one `routing` dictionnary, the very `rule.endpoint.routing` dict
that is used through the rest of the http framework.
4. Each route-endpoint pair is saved into a `routing_map`, an object
that bind each route to its endpoint. This object exposes a `match`
method used to find back the endpoint given its route (=http path).
In addition to this refactor, we added some new helpers in our tools,
among them `submap` that implement a kind of `dict() - set() -> dict()`
operator. Filtering a dict on a set of keys is a common operation but
the python standard library lacks a dedicated operator.
PR: odoo#78857
Task: 2571224
This commit is the 3rd commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
Static files were historically served thanks to a werkzeug middleware,
the usage of that middleware prevented easy modification of the response
headers. Using `http.send_file` and `request._serve_static` we achieve
the same result as the middleware with less code and less frames in the
callstack.
The historic `http.send_file` have been splitted in two. One to send
a file via its path on the filesystem (prefered function) and one to
send a file already open. It is preferred to use `http.send_filepath`.
During development we tried to remove the fallback from the
`_serve_static` function into the more capables (and resource hungry)
db-compatible router. The purpose was to quickly respond to all requests
requesting static files at the cost of reduced customization. We have
been prooved wrong, e.g. the website themes all depends on the fallback
mechanism to deliver static files.
PR: odoo#78857
Task: 2571224
This commit is the 2nd commit of a comprehensive refactor of our HTTP
framework. See odoo/odoo#78857 for complete historic, discussions and
rationnals.
This commit poses the global structure of the new HTTP framework. Major
classes are present (yet empty) with a minimal docstring explaining
their general purpose.
PR: odoo#78857
Task: 2571224