[MERGE] forward port branch 9.0 up to cbff8629fa

This commit is contained in:
Christophe Simonis
2017-01-04 18:41:22 +01:00
10 changed files with 98 additions and 50 deletions
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# flake8: noqa
import ir_module
import base_import_module
import ir_ui_view
@@ -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
@@ -148,12 +148,12 @@ widgets.SidebarFilter.include({
.filter([["id", "=",this.view.dataset.context.uid]])
.first()
.done(function(result){
$.map(self.ir_model_m2o.display_value, function(element,index) {
if (result.partner_id[0] != index){
self.ds_message = new data.DataSetSearch(self, 'calendar.contacts');
defs.push(self.ds_message.call("create", [{'partner_id': index}]));
}
});
var partner_id = self.ir_model_m2o.get_value();
// don't duplicate [Me] filter for current user
if (result.partner_id[0] != partner_id){
self.ds_message = new data.DataSetSearch(self, 'calendar.contacts');
defs.push(self.ds_message.call("create", [{'partner_id': partner_id}]));
}
}));
return $.when.apply(null, defs).then(function() {
return reload_favorite_list(self);
+1 -1
View File
@@ -75,7 +75,7 @@ class pad_common(models.AbstractModel):
if url:
try:
page = urllib2.urlopen('%s/export/html'%url).read()
mo = re.search('<body>(.*)</body>',page)
mo = re.search('<body>(.*)</body>', page, re.DOTALL)
if mo:
content = mo.group(1)
except:
+1 -1
View File
@@ -644,7 +644,7 @@ msgstr ""
#. module: sale
#: model:ir.model.fields,field_description:sale.field_sale_order_validity_date
msgid "Expiration Date"
msgstr "Дата окончания срока действия"
msgstr ""
#. module: sale
#: model:ir.ui.view,arch_db:sale.view_order_product_search
@@ -41,7 +41,7 @@
<span t-field="l.discount"/>
</td>
<td class="text-right">
<span t-esc="', '.join(map(lambda x: x.name, l.invoice_line_tax_ids))"/>
<span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
</td>
<td class="text-right">
<span t-field="l.price_subtotal"
@@ -93,10 +93,17 @@ class AccountAnalyticLine(models.Model):
@api.multi
def write(self, values):
so_lines = self.mapped('so_line')
self._update_values(values)
for line in self:
values = line._get_timesheet_cost(vals=values)
super(AccountAnalyticLine, line).write(values)
# Update delivered quantity on SO lines which are not linked to the analytic lines anymore
so_lines -= self.mapped('so_line')
if so_lines:
so_lines.with_context(force_so_lines=so_lines).sudo()._compute_analytic()
return True
@api.model
+1 -1
View File
@@ -1139,7 +1139,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)"""
+8 -4
View File
@@ -195,6 +195,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
# Set new modules and dependencies
modobj.write(cr, SUPERUSER_ID, [module_id], {'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):
@@ -341,6 +343,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 = openerp.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)""")
@@ -396,12 +403,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# STEP 6: verify custom views on every model
if update_module:
Views = registry['ir.ui.view']
custom_view_test = True
for model in registry.models.keys():
if not Views._validate_custom_views(cr, SUPERUSER_ID, 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.')
+43 -36
View File
@@ -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 openerp
from openerp.modules.module import get_resource_path
import openerp.release as release
import openerp.tools as tools
from openerp.tools.parse_version import parse_version
@@ -19,11 +21,14 @@ _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:
<moduledir>
@@ -31,47 +36,52 @@ class MigrationManager(object):
|-- 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 = openerp.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 +90,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 +111,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)