+
diff --git a/addons/base_import_module/models/__init__.py b/addons/base_import_module/models/__init__.py
index 07d35e729c2..18a246fa151 100644
--- a/addons/base_import_module/models/__init__.py
+++ b/addons/base_import_module/models/__init__.py
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
+# flake8: noqa
import base_import_module
import ir_module
+import ir_ui_view
diff --git a/addons/base_import_module/models/ir_ui_view.py b/addons/base_import_module/models/ir_ui_view.py
new file mode 100644
index 00000000000..af0b13bdfd1
--- /dev/null
+++ b/addons/base_import_module/models/ir_ui_view.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from operator import itemgetter
+from openerp import api, models
+
+class IrUiView(models.Model):
+ _inherit = 'ir.ui.view'
+
+ @api.model
+ def _validate_custom_views(self, model):
+ # views from imported modules should be considered as custom views
+ result = super(IrUiView, self)._validate_custom_views(model)
+
+ self._cr.execute("""
+ SELECT max(v.id)
+ FROM ir_ui_view v
+ LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
+ LEFT JOIN ir_module_module m ON (m.name = md.module)
+ WHERE m.imported = true
+ AND v.model = %s
+ AND v.active = true
+ GROUP BY coalesce(v.inherit_id, v.id)
+ """, [model])
+
+ ids = map(itemgetter(0), self._cr.fetchall())
+ views = self.with_context(load_all_views=True).browse(ids)
+ return views._check_xml() and result
diff --git a/addons/hr_holidays/models/hr.py b/addons/hr_holidays/models/hr.py
index fe52be16430..eb69a0437b1 100644
--- a/addons/hr_holidays/models/hr.py
+++ b/addons/hr_holidays/models/hr.py
@@ -207,7 +207,7 @@ class Employee(models.Model):
today_date = datetime.datetime.utcnow().date()
today_start = fields.Datetime.to_string(today_date) # get the midnight of the current utc day
today_end = fields.Datetime.to_string(today_date + relativedelta(hours=23, minutes=59, seconds=59))
- holidays = self.env['hr.holidays'].search([
+ holidays = self.env['hr.holidays'].sudo().search([
('employee_id', '!=', False),
('state', 'not in', ['cancel', 'refuse']),
('date_from', '<=', today_end),
diff --git a/addons/pad/models/pad.py b/addons/pad/models/pad.py
index 5df19a7e3fb..dfa1dbf859b 100644
--- a/addons/pad/models/pad.py
+++ b/addons/pad/models/pad.py
@@ -80,7 +80,7 @@ class PadCommon(models.AbstractModel):
if url:
try:
page = urllib2.urlopen('%s/export/html' % url).read()
- mo = re.search('(.*)', page)
+ mo = re.search('(.*)', page, re.DOTALL)
if mo:
content = mo.group(1)
except:
diff --git a/addons/sale/report/invoice_report_templates.xml b/addons/sale/report/invoice_report_templates.xml
index 3f615241e15..ef26e950a8f 100644
--- a/addons/sale/report/invoice_report_templates.xml
+++ b/addons/sale/report/invoice_report_templates.xml
@@ -43,7 +43,7 @@
-
+
https://github.com/PinZhang
diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py
index c7615248c29..fcd24de2f79 100644
--- a/odoo/addons/base/ir/ir_qweb/qweb.py
+++ b/odoo/addons/base/ir/ir_qweb/qweb.py
@@ -649,7 +649,7 @@ class QWeb(object):
if hasattr(self, interpret_handler):
_logger.warning(
"Directive '%s' must be AST-compiled. Dynamic interpreter %s will ignored",
- name, interpret_handler
+ mname, interpret_handler
)
return compile_handler(el, options)
diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py
index 039cbf1bd7c..5c74c83b9a9 100644
--- a/odoo/addons/base/ir/ir_ui_view.py
+++ b/odoo/addons/base/ir/ir_ui_view.py
@@ -1111,7 +1111,7 @@ actual arch.
query = """SELECT max(v.id)
FROM ir_ui_view v
LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id)
- WHERE md.module IS NULL
+ WHERE md.module NOT IN (SELECT name FROM ir_module_module)
AND v.model = %s
AND v.active = true
GROUP BY coalesce(v.inherit_id, v.id)"""
diff --git a/odoo/addons/base/res/res_currency_data.xml b/odoo/addons/base/res/res_currency_data.xml
index c89552c5783..22478fbcfa2 100644
--- a/odoo/addons/base/res/res_currency_data.xml
+++ b/odoo/addons/base/res/res_currency_data.xml
@@ -71,7 +71,7 @@
HUFFt
- 0.01
+ 1.00
diff --git a/odoo/addons/base/res/res_partner.py b/odoo/addons/base/res/res_partner.py
index 8616e7c47bb..c8bcaa042ad 100644
--- a/odoo/addons/base/res/res_partner.py
+++ b/odoo/addons/base/res/res_partner.py
@@ -351,10 +351,12 @@ class Partner(models.Model):
result['value'] = {key: convert(self.parent_id[key]) for key in address_fields}
return result
- @api.onchange('state_id')
- def onchange_state(self):
- if self.state_id:
- self.country_id = self.state_id.country_id
+ @api.onchange('country_id')
+ def _onchange_country_id(self):
+ if self.country_id:
+ return {'domain': {'state_id': [('country_id', '=', self.country_id.id)]}}
+ else:
+ return {'domain': {'state_id': []}}
@api.onchange('email')
def onchange_email(self):
diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py
index 60e2760c7c9..932268d717c 100644
--- a/odoo/addons/base/tests/test_expression.py
+++ b/odoo/addons/base/tests/test_expression.py
@@ -241,6 +241,9 @@ class TestExpression(TransactionCase):
partners = Partner.search([('company_id.partner_id', 'in', [])])
self.assertFalse(partners)
+ # check multi-level expressions with magic columns
+ partners = Partner.search([('create_uid.active', '=', True)])
+
# check that multi-level expressions with negative op work
all_partners = Partner.search([('company_id', '!=', False)])
res_partners = Partner.search([('company_id.partner_id', 'not in', [])])
diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py
index f379f55d3c0..7841b23fd0e 100644
--- a/odoo/modules/loading.py
+++ b/odoo/modules/loading.py
@@ -191,6 +191,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
# Set new modules and dependencies
module.write({'state': 'installed', 'latest_version': ver})
+ package.load_state = package.state
+ package.load_version = package.installed_version
package.state = 'installed'
for kind in ('init', 'demo', 'update'):
if hasattr(package, kind):
@@ -338,6 +340,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
registry.setup_models(cr)
+ # STEP 3.5: execute migration end-scripts
+ migrations = odoo.modules.migration.MigrationManager(cr, graph)
+ for package in graph:
+ migrations.migrate_module(package, 'end')
+
# STEP 4: Finish and cleanup installations
if processed_modules:
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
@@ -394,12 +401,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# STEP 6: verify custom views on every model
if update_module:
View = env['ir.ui.view']
- custom_view_test = True
for model in registry:
if not View._validate_custom_views(model):
- custom_view_test = False
- _logger.error('invalid custom view(s) for model %s', model)
- report.record_result(custom_view_test)
+ _logger.warning('invalid custom view(s) for model %s', model)
if report.failures:
_logger.error('At least one test failed when loading the modules.')
diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py
index 67f1d4d5c89..552c9d8dae7 100644
--- a/odoo/modules/migration.py
+++ b/odoo/modules/migration.py
@@ -3,12 +3,14 @@
""" Modules migration handling. """
+from collections import defaultdict
+import glob
import imp
import logging
import os
from os.path import join as opj
-import odoo
+from odoo.modules.module import get_resource_path
import odoo.release as release
import odoo.tools as tools
from odoo.tools.parse_version import parse_version
@@ -19,59 +21,64 @@ _logger = logging.getLogger(__name__)
class MigrationManager(object):
"""
This class manage the migration of modules
- Migrations files must be python files containing a "migrate(cr, installed_version)" function.
- Theses files must respect a directory tree structure: A 'migrations' folder which containt a
- folder by version. Version can be 'module' version or 'server.module' version (in this case,
- the files will only be processed by this version of the server). Python file names must start
- by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
+ Migrations files must be python files containing a `migrate(cr, installed_version)`
+ function. Theses files must respect a directory tree structure: A 'migrations' folder
+ which containt a folder by version. Version can be 'module' version or 'server.module'
+ version (in this case, the files will only be processed by this version of the server).
+ Python file names must start by `pre` or `post` and will be executed, respectively,
+ before and after the module initialisation. `end` scripts are run after all modules have
+ been updated.
Example:
-
`-- migrations
|-- 1.0
| |-- pre-update_table_x.py
| |-- pre-update_table_y.py
- | |-- post-clean-data.py
- | `-- README.txt # not processed
- |-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
+ | |-- post-create_plop_records.py
+ | |-- end-cleanup.py
+ | `-- README.txt # not processed
+ |-- 9.0.1.1 # processed only on a 9.0 server
| |-- pre-delete_table_z.py
| `-- post-clean-data.py
- `-- foo.py # not processed
-
- This similar structure is generated by the maintenance module with the migrations files get by
- the maintenance contract
-
+ `-- foo.py # not processed
"""
+
def __init__(self, cr, graph):
self.cr = cr
self.graph = graph
- self.migrations = {}
+ self.migrations = defaultdict(dict)
self._get_files()
def _get_files(self):
-
- """
- import addons.base.maintenance.utils as maintenance_utils
- maintenance_utils.update_migrations_files(self.cr)
- #"""
+ def get_scripts(path):
+ if not path:
+ return {}
+ return {
+ version: glob.glob1(opj(path, version), '*.py')
+ for version in os.listdir(path)
+ if os.path.isdir(opj(path, version))
+ }
for pkg in self.graph:
- self.migrations[pkg.name] = {}
- if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
+ if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade' or
+ getattr(pkg, 'load_state', None) == 'to upgrade'):
continue
- get_module_filetree = odoo.modules.module.get_module_filetree
- self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
- self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
+ self.migrations[pkg.name] = {
+ 'module': get_scripts(get_resource_path(pkg.name, 'migrations')),
+ 'maintenance': get_scripts(get_resource_path('base', 'maintenance', 'migrations', pkg.name)),
+ }
def migrate_module(self, pkg, stage):
- assert stage in ('pre', 'post')
+ assert stage in ('pre', 'post', 'end')
stageformat = {
'pre': '[>%s]',
'post': '[%s>]',
+ 'end': '[$%s]',
}
+ state = pkg.state if stage in ('pre', 'post') else getattr(pkg, 'load_state', None)
- if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade') or pkg.state == 'to install':
+ if not (hasattr(pkg, 'update') or state == 'to upgrade') or state == 'to install':
return
def convert_version(version):
@@ -80,18 +87,17 @@ class MigrationManager(object):
return "%s.%s" % (release.major_version, version)
def _get_migration_versions(pkg):
- def __get_dir(tree):
- return [d for d in tree if tree[d] is not None]
-
versions = list(set(
- __get_dir(self.migrations[pkg.name]['module']) +
- __get_dir(self.migrations[pkg.name]['maintenance'])
+ ver
+ for lv in self.migrations[pkg.name].values()
+ for ver, lf in lv.items()
+ if lf
))
versions.sort(key=lambda k: parse_version(convert_version(k)))
return versions
def _get_migration_files(pkg, version, stage):
- """ return a list of tuple (module, file)
+ """ return a list of migration script files
"""
m = self.migrations[pkg.name]
lst = []
@@ -102,17 +108,15 @@ class MigrationManager(object):
}
for x in mapping.keys():
- if version in m[x]:
+ if version in m.get(x):
for f in m[x][version]:
- if m[x][version][f] is not None:
- continue
if not f.startswith(stage + '-'):
continue
lst.append(opj(mapping[x], version, f))
lst.sort()
return lst
- parsed_installed_version = parse_version(pkg.installed_version or '')
+ parsed_installed_version = parse_version(getattr(pkg, 'load_version', pkg.installed_version) or '')
current_version = parse_version(convert_version(pkg.data['version']))
versions = _get_migration_versions(pkg)
diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py
index a5b753f322c..b698ccfcb3a 100644
--- a/odoo/osv/expression.py
+++ b/odoo/osv/expression.py
@@ -836,9 +836,6 @@ class expression(object):
new_leaf = create_substitution_leaf(leaf, dom_leaf, model)
push(new_leaf)
- elif path[0] in MAGIC_COLUMNS:
- push_result(leaf)
-
# ----------------------------------------
# PATH SPOTTED
# -> many2one or one2many with _auto_join:
@@ -1134,8 +1131,8 @@ class expression(object):
# final sanity checks - should never fail
assert operator in (TERM_OPERATORS + ('inselect', 'not inselect')), \
"Invalid operator %r in domain term %r" % (operator, leaf)
- assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in model._fields \
- or left in MAGIC_COLUMNS, "Invalid field %r in domain term %r" % (left, leaf)
+ assert leaf in (TRUE_LEAF, FALSE_LEAF) or left in model._fields, \
+ "Invalid field %r in domain term %r" % (left, leaf)
assert not isinstance(right, BaseModel), \
"Invalid value %r in domain term %r" % (right, leaf)