[MERGE] forward port branch 9.0 up to cbff8629fa
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# flake8: noqa
|
||||||
import ir_module
|
import ir_module
|
||||||
import base_import_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]])
|
.filter([["id", "=",this.view.dataset.context.uid]])
|
||||||
.first()
|
.first()
|
||||||
.done(function(result){
|
.done(function(result){
|
||||||
$.map(self.ir_model_m2o.display_value, function(element,index) {
|
var partner_id = self.ir_model_m2o.get_value();
|
||||||
if (result.partner_id[0] != index){
|
// don't duplicate [Me] filter for current user
|
||||||
self.ds_message = new data.DataSetSearch(self, 'calendar.contacts');
|
if (result.partner_id[0] != partner_id){
|
||||||
defs.push(self.ds_message.call("create", [{'partner_id': index}]));
|
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 $.when.apply(null, defs).then(function() {
|
||||||
return reload_favorite_list(self);
|
return reload_favorite_list(self);
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@ class pad_common(models.AbstractModel):
|
|||||||
if url:
|
if url:
|
||||||
try:
|
try:
|
||||||
page = urllib2.urlopen('%s/export/html'%url).read()
|
page = urllib2.urlopen('%s/export/html'%url).read()
|
||||||
mo = re.search('<body>(.*)</body>',page)
|
mo = re.search('<body>(.*)</body>', page, re.DOTALL)
|
||||||
if mo:
|
if mo:
|
||||||
content = mo.group(1)
|
content = mo.group(1)
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ msgstr ""
|
|||||||
#. module: sale
|
#. module: sale
|
||||||
#: model:ir.model.fields,field_description:sale.field_sale_order_validity_date
|
#: model:ir.model.fields,field_description:sale.field_sale_order_validity_date
|
||||||
msgid "Expiration Date"
|
msgid "Expiration Date"
|
||||||
msgstr "Дата окончания срока действия"
|
msgstr ""
|
||||||
|
|
||||||
#. module: sale
|
#. module: sale
|
||||||
#: model:ir.ui.view,arch_db:sale.view_order_product_search
|
#: model:ir.ui.view,arch_db:sale.view_order_product_search
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<span t-field="l.discount"/>
|
<span t-field="l.discount"/>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<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>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span t-field="l.price_subtotal"
|
<span t-field="l.price_subtotal"
|
||||||
|
|||||||
@@ -93,10 +93,17 @@ class AccountAnalyticLine(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, values):
|
def write(self, values):
|
||||||
|
so_lines = self.mapped('so_line')
|
||||||
self._update_values(values)
|
self._update_values(values)
|
||||||
for line in self:
|
for line in self:
|
||||||
values = line._get_timesheet_cost(vals=values)
|
values = line._get_timesheet_cost(vals=values)
|
||||||
super(AccountAnalyticLine, line).write(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
|
return True
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
|
|||||||
@@ -1139,7 +1139,7 @@ actual arch.
|
|||||||
query = """SELECT max(v.id)
|
query = """SELECT max(v.id)
|
||||||
FROM ir_ui_view v
|
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_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.model = %s
|
||||||
AND v.active = true
|
AND v.active = true
|
||||||
GROUP BY coalesce(v.inherit_id, v.id)"""
|
GROUP BY coalesce(v.inherit_id, v.id)"""
|
||||||
|
|||||||
@@ -195,6 +195,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
|||||||
# Set new modules and dependencies
|
# Set new modules and dependencies
|
||||||
modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
|
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'
|
package.state = 'installed'
|
||||||
for kind in ('init', 'demo', 'update'):
|
for kind in ('init', 'demo', 'update'):
|
||||||
if hasattr(package, kind):
|
if hasattr(package, kind):
|
||||||
@@ -341,6 +343,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
|||||||
|
|
||||||
registry.setup_models(cr)
|
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
|
# STEP 4: Finish and cleanup installations
|
||||||
if processed_modules:
|
if processed_modules:
|
||||||
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
|
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
|
# STEP 6: verify custom views on every model
|
||||||
if update_module:
|
if update_module:
|
||||||
Views = registry['ir.ui.view']
|
Views = registry['ir.ui.view']
|
||||||
custom_view_test = True
|
|
||||||
for model in registry.models.keys():
|
for model in registry.models.keys():
|
||||||
if not Views._validate_custom_views(cr, SUPERUSER_ID, model):
|
if not Views._validate_custom_views(cr, SUPERUSER_ID, model):
|
||||||
custom_view_test = False
|
_logger.warning('Invalid custom view(s) for model %s', model)
|
||||||
_logger.error('invalid custom view(s) for model %s', model)
|
|
||||||
report.record_result(custom_view_test)
|
|
||||||
|
|
||||||
if report.failures:
|
if report.failures:
|
||||||
_logger.error('At least one test failed when loading the modules.')
|
_logger.error('At least one test failed when loading the modules.')
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
|
|
||||||
""" Modules migration handling. """
|
""" Modules migration handling. """
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
import glob
|
||||||
import imp
|
import imp
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from os.path import join as opj
|
from os.path import join as opj
|
||||||
|
|
||||||
import openerp
|
from openerp.modules.module import get_resource_path
|
||||||
import openerp.release as release
|
import openerp.release as release
|
||||||
import openerp.tools as tools
|
import openerp.tools as tools
|
||||||
from openerp.tools.parse_version import parse_version
|
from openerp.tools.parse_version import parse_version
|
||||||
@@ -19,11 +21,14 @@ _logger = logging.getLogger(__name__)
|
|||||||
class MigrationManager(object):
|
class MigrationManager(object):
|
||||||
"""
|
"""
|
||||||
This class manage the migration of modules
|
This class manage the migration of modules
|
||||||
Migrations files must be python files containing a "migrate(cr, installed_version)" function.
|
Migrations files must be python files containing a `migrate(cr, installed_version)`
|
||||||
Theses files must respect a directory tree structure: A 'migrations' folder which containt a
|
function. Theses files must respect a directory tree structure: A 'migrations' folder
|
||||||
folder by version. Version can be 'module' version or 'server.module' version (in this case,
|
which containt a folder by version. Version can be 'module' version or 'server.module'
|
||||||
the files will only be processed by this version of the server). Python file names must start
|
version (in this case, the files will only be processed by this version of the server).
|
||||||
by 'pre' or 'post' and will be executed, respectively, before and after the module initialisation
|
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:
|
Example:
|
||||||
|
|
||||||
<moduledir>
|
<moduledir>
|
||||||
@@ -31,47 +36,52 @@ class MigrationManager(object):
|
|||||||
|-- 1.0
|
|-- 1.0
|
||||||
| |-- pre-update_table_x.py
|
| |-- pre-update_table_x.py
|
||||||
| |-- pre-update_table_y.py
|
| |-- pre-update_table_y.py
|
||||||
| |-- post-clean-data.py
|
| |-- post-create_plop_records.py
|
||||||
| `-- README.txt # not processed
|
| |-- end-cleanup.py
|
||||||
|-- 5.0.1.1 # files in this folder will be executed only on a 5.0 server
|
| `-- README.txt # not processed
|
||||||
|
|-- 9.0.1.1 # processed only on a 9.0 server
|
||||||
| |-- pre-delete_table_z.py
|
| |-- pre-delete_table_z.py
|
||||||
| `-- post-clean-data.py
|
| `-- post-clean-data.py
|
||||||
`-- foo.py # not processed
|
`-- foo.py # not processed
|
||||||
|
|
||||||
This similar structure is generated by the maintenance module with the migrations files get by
|
|
||||||
the maintenance contract
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cr, graph):
|
def __init__(self, cr, graph):
|
||||||
self.cr = cr
|
self.cr = cr
|
||||||
self.graph = graph
|
self.graph = graph
|
||||||
self.migrations = {}
|
self.migrations = defaultdict(dict)
|
||||||
self._get_files()
|
self._get_files()
|
||||||
|
|
||||||
def _get_files(self):
|
def _get_files(self):
|
||||||
|
def get_scripts(path):
|
||||||
"""
|
if not path:
|
||||||
import addons.base.maintenance.utils as maintenance_utils
|
return {}
|
||||||
maintenance_utils.update_migrations_files(self.cr)
|
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:
|
for pkg in self.graph:
|
||||||
self.migrations[pkg.name] = {}
|
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade' or
|
||||||
if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
|
getattr(pkg, 'load_state', None) == 'to upgrade'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
get_module_filetree = openerp.modules.module.get_module_filetree
|
self.migrations[pkg.name] = {
|
||||||
self.migrations[pkg.name]['module'] = get_module_filetree(pkg.name, 'migrations') or {}
|
'module': get_scripts(get_resource_path(pkg.name, 'migrations')),
|
||||||
self.migrations[pkg.name]['maintenance'] = get_module_filetree('base', 'maintenance/migrations/' + pkg.name) or {}
|
'maintenance': get_scripts(get_resource_path('base', 'maintenance', 'migrations', pkg.name)),
|
||||||
|
}
|
||||||
|
|
||||||
def migrate_module(self, pkg, stage):
|
def migrate_module(self, pkg, stage):
|
||||||
assert stage in ('pre', 'post')
|
assert stage in ('pre', 'post', 'end')
|
||||||
stageformat = {
|
stageformat = {
|
||||||
'pre': '[>%s]',
|
'pre': '[>%s]',
|
||||||
'post': '[%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
|
return
|
||||||
|
|
||||||
def convert_version(version):
|
def convert_version(version):
|
||||||
@@ -80,18 +90,17 @@ class MigrationManager(object):
|
|||||||
return "%s.%s" % (release.major_version, version)
|
return "%s.%s" % (release.major_version, version)
|
||||||
|
|
||||||
def _get_migration_versions(pkg):
|
def _get_migration_versions(pkg):
|
||||||
def __get_dir(tree):
|
|
||||||
return [d for d in tree if tree[d] is not None]
|
|
||||||
|
|
||||||
versions = list(set(
|
versions = list(set(
|
||||||
__get_dir(self.migrations[pkg.name]['module']) +
|
ver
|
||||||
__get_dir(self.migrations[pkg.name]['maintenance'])
|
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)))
|
versions.sort(key=lambda k: parse_version(convert_version(k)))
|
||||||
return versions
|
return versions
|
||||||
|
|
||||||
def _get_migration_files(pkg, version, stage):
|
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]
|
m = self.migrations[pkg.name]
|
||||||
lst = []
|
lst = []
|
||||||
@@ -102,17 +111,15 @@ class MigrationManager(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for x in mapping.keys():
|
for x in mapping.keys():
|
||||||
if version in m[x]:
|
if version in m.get(x):
|
||||||
for f in m[x][version]:
|
for f in m[x][version]:
|
||||||
if m[x][version][f] is not None:
|
|
||||||
continue
|
|
||||||
if not f.startswith(stage + '-'):
|
if not f.startswith(stage + '-'):
|
||||||
continue
|
continue
|
||||||
lst.append(opj(mapping[x], version, f))
|
lst.append(opj(mapping[x], version, f))
|
||||||
lst.sort()
|
lst.sort()
|
||||||
return lst
|
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']))
|
current_version = parse_version(convert_version(pkg.data['version']))
|
||||||
|
|
||||||
versions = _get_migration_versions(pkg)
|
versions = _get_migration_versions(pkg)
|
||||||
|
|||||||
Reference in New Issue
Block a user