Skip to content

Commit

Permalink
[IMP] Allow addons to fully support multiple Odoo versions
Browse files Browse the repository at this point in the history
In order to make possible to implement a different workflow on downstream addon maintenance teams, as explained in OCA/maintainer-tools#334, Odoo needs to support version-specific code.

This can be currently done in Python by checking `odoo.release.version_info` and in JS by using the `/web/webclient/version_info` controller. However, when dealing with data files, we were out of luck.

With this patch:

- A new cached helper is found in JS sessions, that lazy-caches version info once per session, which will make version-specific assertions a pleasure in JS.
- A new attribute `versions` is supported in `<odoo>`, `<openerp>` and `<data>` elements within XML data files, that allows the programmer to specify a version spec that makes that data section be omitted when Odoo version doesn't match it.
- A new `overrides` key is supported in the `__manifest__.py` file, which has the form of a dict, where keys are version specs, and values are dicts that will directly override the addon base manifest.

So, for instance, to declare an addon to be available only until version 13.0 of Odoo, put this in the manifest:

```python
{
  ...
  "installable": False,
  "overrides": {
    ":13": {
      "installable": True,
    }
  }
}
```
  • Loading branch information
yajo committed Jun 21, 2018
1 parent ec30c1b commit c8c73c0
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 3 deletions.
14 changes: 14 additions & 0 deletions addons/web/static/src/js/core/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,20 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
options.session = this;
return ajax.get_file(options);
},
/**
* Get current server version info, lazy-cached
*
* @returns {$.Deferred} deferred with version info dict
*/
version_info: function (cache) {
if (
this._version_def === undefined ||
this._version_def.isRejected()
) {
this._version_def = this.rpc("/web/webclient/version_info");
}
return this._version_def;
},
/**
* (re)loads the content of a session: db name, username, user id, session
* context and status of the support contract
Expand Down
1 change: 1 addition & 0 deletions odoo/import_xml.rng
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@
</rng:choice>
<rng:optional><rng:attribute name="noupdate" /></rng:optional>
<rng:optional><rng:attribute name="context" /></rng:optional>
<rng:optional><rng:attribute name="versions" /></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:text/>
Expand Down
17 changes: 14 additions & 3 deletions odoo/modules/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import ast
import functools
import imp
import importlib
import inspect
Expand All @@ -16,14 +15,13 @@
import types
import unittest
import threading
from operator import itemgetter
from os.path import join as opj

import odoo
import odoo.tools as tools
import odoo.release as release
from odoo import SUPERUSER_ID, api
from odoo.tools import pycompat
from odoo.tools.parse_version import version_match

MANIFEST_NAMES = ('__manifest__.py', '__openerp__.py')
README = ['README.rst', 'README.md', 'README.txt']
Expand Down Expand Up @@ -334,6 +332,19 @@ def load_information_from_description_file(module, mod_path=None):
finally:
f.close()

# Version-specific overrides
try:
overrides = info.pop("overrides")
except KeyError:
pass
else:
if len(overrides) > 1 and sys.version_info < (3, 6):
_logger.warning("Python interpreter without PEP 468 support; "
"overrides are randomly sorted!")
for wanted_versions, subdict in overrides.items():
if version_match(wanted_versions):
info.update(subdict)

if not info.get('description'):
readme_path = [opj(mod_path, x) for x in README
if os.path.isfile(opj(mod_path, x))]
Expand Down
6 changes: 6 additions & 0 deletions odoo/tools/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .misc import file_open, unquote, ustr, SKIPPED_ELEMENT_TYPES
from .translate import _
from odoo import SUPERUSER_ID
from odoo.tools.parse_version import version_match

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -241,6 +242,9 @@ def get_uid(self, data_node, node):
return self.id_get(node_uid)
return self.uid

def _version_matches(self, data_node):
return version_match(data_node.get("versions", ":"))

def _test_xml_id(self, xml_id):
id = xml_id
if '.' in xml_id:
Expand Down Expand Up @@ -737,6 +741,8 @@ def parse(self, de, mode=None):
raise Exception("Root xml tag must be <openerp>, <odoo> or <data>.")
for rec in de:
if rec.tag in roots:
if not self._version_matches(rec):
_logger.debug("Skipping XML section because version does not match")
self.parse(rec, mode)
elif rec.tag in self._tags:
try:
Expand Down
36 changes: 36 additions & 0 deletions odoo/tools/parse_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import print_function
import re

from odoo.release import version
from odoo.tools import pycompat

component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
Expand Down Expand Up @@ -65,6 +66,41 @@ def parse_version(s):
parts.append(part)
return tuple(parts)


def version_match(valid_ranges, current=version):
"""Check if a version range matches the specified Odoo version.
:param str valid_ranges:
One or many version ranges. A range are 2 versions, separated by ``:``.
The 1st one is the minimum version, and the 2nd one is the maximum.
If any of the versions is empty, it will always match.
Multiple ranges can be separated by ``,``.
Examples that would return ``True``::
version_match(":")
version_match("12.0.0:,:15", "12.0")
version_match("11.0:", "12.0")
version_match(":13.0", "12.0")
Check ``parse_version`` to know how those versions are parsed.
:param str current:
Target version to check against. It defaults to current Odoo version.
:return bool:
Indicates if Odoo version matches the range or not.
"""
current_parsed = parse_version(current)
for valid_range in valid_ranges.split(","):
min_, max_ = valid_range.split(":")
min_ = parse_version(min_) if min_ else current_parsed
max_ = parse_version(max_) if max_ else current_parsed
if not min_ <= current_parsed <= max_:
return False
return True


if __name__ == '__main__':
def chk(lst, verbose=False):
pvs = []
Expand Down

0 comments on commit c8c73c0

Please sign in to comment.