diff --git a/addons/project/migrations/8.0.1.1/noupdate_changes.xml b/addons/project/migrations/8.0.1.1/noupdate_changes.xml
new file mode 100644
index 000000000000..968fd3431125
--- /dev/null
+++ b/addons/project/migrations/8.0.1.1/noupdate_changes.xml
@@ -0,0 +1,32 @@
+
+
+
+ 14
+
+ 30
+
+ ['|',
+ ('project_id.privacy_visibility', 'in', ['public', 'employees']),
+ '&',
+ ('project_id.privacy_visibility', '=', 'followers'),
+ ('message_follower_ids', 'in', [user.partner_id.id]),
+ ]
+ Project/Task: employees: public or employee or (followers and following)
+
+ 10
+
+
+
+ PP001
+
+ 13
+
+ 11
+
+ 12
+
+ 20
+
+
+
+
diff --git a/addons/project/migrations/8.0.1.1/openupgrade_analysis_work.txt b/addons/project/migrations/8.0.1.1/openupgrade_analysis_work.txt
new file mode 100644
index 000000000000..aea2f97636d1
--- /dev/null
+++ b/addons/project/migrations/8.0.1.1/openupgrade_analysis_work.txt
@@ -0,0 +1,76 @@
+---Fields in module 'project'---
+# NTD cosmetic change
+project / project.project / doc_count (int) : type is now 'integer' ('int')
+
+# Apply set_message_last_post
+project / project.project / message_last_post (datetime) : NEW
+project / project.task / message_last_post (datetime) : NEW
+
+# NTD Reverse relation of existing many2one on task model
+project / project.project / task_ids (one2many) : NEW relation: project.task
+# NTD Tracking a write on the task stage_id. No meaningful value to apply during migration.
+project / project.task / date_last_stage_update (datetime): NEW
+
+# Adapt to new semantics and range
+project / project.task / priority (selection) : selection_keys is now '['0', '1', '2']' ('['0', '1', '2', '3', '4']')
+
+# NTD Existing table column now referred in the model
+project / project.task / write_date (datetime) : NEW
+
+# NTD State is no longer available on task, therefore neither in the history.
+2project / project.task.history / state (selection) : DEL selection_keys: ['cancelled', 'done', 'draft', 'open', 'pending']
+project / project.task.history.cumulative / state (selection) : DEL selection_keys: ['cancelled', 'done', 'draft', 'open', 'pending']
+project / project.task.type / state (selection) : DEL required: required, selection_keys: ['cancelled', 'done', 'draft', 'open', 'pending'], req_default: open
+
+# NTD this report is a view, not table
+project / report.project.task.user / date_last_stage_update (date) : NEW
+project / report.project.task.user / day (char) : DEL
+project / report.project.task.user / month (selection) : DEL selection_keys: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
+project / report.project.task.user / priority (selection) : selection_keys is now '['0', '1', '2']' ('['0', '1', '2', '3', '4']')
+project / report.project.task.user / stage_id (many2one) : NEW relation: project.task.type
+project / report.project.task.user / year (char) : DEL
+
+# Additionally, take into account how project aliases are now configured
+# in the project's create() method. Adapt existing aliases.
+
+---XML records in module 'project'---
+# Regular updated data
+DEL ir.actions.act_window: project.action_project_task_reevaluate
+DEL ir.actions.act_window: project.my_open_tasks_action
+DEL ir.actions.act_window: project.open_board_project
+NEW ir.model.access: project.access_mail_alias
+NEW ir.model.access: project.access_project_task_type_manager
+NEW ir.model.access: project.access_resource_calendar_leaves_user
+DEL ir.ui.menu: project.menu_project_dashboard
+NEW ir.ui.view: project.assets_backend
+DEL ir.ui.view: project.board_project_form
+DEL ir.ui.view: project.view_project_task_reevaluate
+DEL ir.ui.view: project.view_task_history_tree
+DEL ir.ui.view: project.view_task_project_user_tree
+DEL ir.ui.view: project.view_task_tree
+
+# Rename functionally similar mail subtype ids
+DEL mail.message.subtype: project.mt_project_task_started
+NEW mail.message.subtype: project.mt_project_task_assigned
+DEL mail.message.subtype: project.mt_task_closed
+NEW mail.message.subtype: project.mt_task_assigned
+DEL mail.message.subtype: project.mt_task_started
+NEW mail.message.subtype: project.mt_task_ready
+
+# No more mail subtype for a project's task end of life
+DEL mail.message.subtype: project.mt_project_task_closed
+
+# Data for abandoned process module
+DEL process.node: project.process_node_donetask0
+DEL process.node: project.process_node_drafttask0
+DEL process.node: project.process_node_opentask0
+DEL process.node: project.process_node_taskbydelegate0
+DEL process.process: project.process_process_tasksprocess0
+DEL process.transition: project.process_transition_delegate0
+DEL process.transition: project.process_transition_draftopentask0
+DEL process.transition: project.process_transition_opendonetask0
+DEL process.transition.action: project.process_transition_action_draftcanceltask0
+DEL process.transition.action: project.process_transition_action_draftopentask0
+DEL process.transition.action: project.process_transition_action_opencanceltask0
+DEL process.transition.action: project.process_transition_action_opendrafttask0
+DEL process.transition.action: project.process_transition_action_openpendingtask0
diff --git a/addons/project/migrations/8.0.1.1/post-migration.py b/addons/project/migrations/8.0.1.1/post-migration.py
new file mode 100644
index 000000000000..127c3f06b711
--- /dev/null
+++ b/addons/project/migrations/8.0.1.1/post-migration.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Odoo, a suite of business apps
+# This module Copyright (C) 2014 Therp BV ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.modules.registry import RegistryManager
+from openerp import SUPERUSER_ID as uid
+from openerp.openupgrade import openupgrade, openupgrade_80
+
+
+@openupgrade.migrate()
+def migrate(cr, version):
+ registry = RegistryManager.get(cr.dbname)
+
+ openupgrade.map_values(
+ cr,
+ openupgrade.get_legacy_name('priority'),
+ 'priority',
+ [('4', '0'), ('3', '0'), ('2', '1'), ('1', '2'), ('0', '2')],
+ table='project_task', write='sql')
+
+ openupgrade_80.update_aliases(
+ cr, registry, 'project.project',
+ set_parent_thread_id=True,
+ defaults_id_key='project_id')
+
+ openupgrade_80.set_message_last_post(
+ cr, uid, registry, ['project.project', 'project.task']
+ )
+
+ openupgrade.load_data(
+ cr, 'project', 'migrations/8.0.1.1/noupdate_changes.xml')
diff --git a/addons/project/migrations/8.0.1.1/pre-migration.py b/addons/project/migrations/8.0.1.1/pre-migration.py
new file mode 100644
index 000000000000..4658e9e24b71
--- /dev/null
+++ b/addons/project/migrations/8.0.1.1/pre-migration.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Odoo, a suite of business apps
+# This module Copyright (C) 2014 Therp BV ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.openupgrade import openupgrade
+
+
+column_renames = {
+ 'project_task': [('priority', None)]}
+
+xmlid_renames = [
+ ('project.mt_project_task_started', 'project.mt_project_task_assigned'),
+ ('project.mt_task_started', 'project.mt_task_assigned'),
+ ('project.mt_task_closed', 'project.mt_task_ready'),
+ ]
+
+
+@openupgrade.migrate()
+def migrate(cr, version):
+ openupgrade.rename_columns(cr, column_renames)
+ openupgrade.rename_xmlids(cr, xmlid_renames)
diff --git a/openerp/openupgrade/doc/source/modules70-80.rst b/openerp/openupgrade/doc/source/modules70-80.rst
index 44bb112d971f..0fa734bce757 100644
--- a/openerp/openupgrade/doc/source/modules70-80.rst
+++ b/openerp/openupgrade/doc/source/modules70-80.rst
@@ -370,7 +370,7 @@ Status :
+-----------------------------------+-----------------------------------+
|product_visible_discount | Nothing to do |
+-----------------------------------+-----------------------------------+
-|project | |
+|project | Done |
+-----------------------------------+-----------------------------------+
|project_issue | Done |
+-----------------------------------+-----------------------------------+
diff --git a/openerp/openupgrade/openupgrade.py b/openerp/openupgrade/openupgrade.py
index 55dbed1622a5..5c2a3fbd7d7a 100644
--- a/openerp/openupgrade/openupgrade.py
+++ b/openerp/openupgrade/openupgrade.py
@@ -58,6 +58,7 @@
'check_values_selection_field',
'move_field_m2o',
'convert_field_to_html',
+ 'map_values',
]
@@ -67,6 +68,8 @@ def check_values_selection_field(cr, table_name, field_name, allowed_values):
has only the values 'allowed_values'.
If not return False and log an error.
If yes, return True.
+
+ .. versionadded:: 8.0
"""
res = True
cr.execute("SELECT %s, count(*) FROM %s GROUP BY %s;" %
@@ -197,6 +200,8 @@ def rename_xmlids(cr, xmlids_spec):
One usage example is when an ID changes module. In OpenERP 6 for example,
a number of res_groups IDs moved to module base from other modules (
although they were still being defined in their respective module).
+
+ :param xmlids_spec: a list of tuples (old module.xmlid, new module.xmlid).
"""
for (old, new) in xmlids_spec:
if not old.split('.') or not new.split('.'):
@@ -401,12 +406,17 @@ def write_value(ids, field, value):
def logged_query(cr, query, args=None):
"""
- Logs query and affected rows at level DEBUG
+ Logs query and affected rows at level DEBUG.
+
+ :param query: a query string suitable to pass to cursor.execute()
+ :param args: a list, tuple or dictionary passed as substitution values \
+to cursor.execute().
"""
if args is None:
- args = []
+ args = ()
+ args = tuple(args) if type(args) == list else args
cr.execute(query, args)
- logger.debug('Running %s', query % tuple(args))
+ logger.debug('Running %s', query % args)
logger.debug('%s rows affected', cr.rowcount)
return cr.rowcount
@@ -520,6 +530,65 @@ def float_to_integer(cr, table, field):
})
+def map_values(
+ cr, source_column, target_column, mapping,
+ model=None, table=None, write='sql'):
+ """
+ Map old values to new values within the same model or table. Old values
+ presumably come from a legacy column.
+
+ :param cr: The database cursor
+ :param source_column: the database column that contains old values to be \
+ mapped
+ :param target_column: the database column, or model field (if 'write' is \
+ 'orm') that the new values are written to
+ :para mapping: list of tuples [(old value, new value)]
+ :param model: used for writing if 'write' is 'orm', or to retrieve the \
+ table if 'table' is not given.
+ :param table: the database table used to query the old values, and write \
+ the new values (if 'write' is 'sql')
+ :param write: Either 'orm' or 'sql'. Note that old ids are always \
+ identified by an sql read.
+
+ .. versionadded:: 8.0
+ """
+
+ if write not in ('sql', 'orm'):
+ logger.exception(
+ "map_values is called with unknown value for write param: %s",
+ write)
+ if not table:
+ if not model:
+ logger.exception("map_values is called with no table and no model")
+ table = model._table
+ if source_column == target_column:
+ logger.exception(
+ "map_values is called with the same value for source and old"
+ " columns : %s",
+ source_column)
+ for old, new in mapping:
+ values = {
+ 'table': table,
+ 'source': source_column,
+ 'target': target_column,
+ 'old': old,
+ 'new': new,
+ }
+ if write == 'sql':
+ query = """UPDATE %(table)s
+ SET %(target)s = %%(new)s
+ WHERE %(source)s = %%(old)s""" % values
+ else:
+ query = """SELECT id FROM %(table)s
+ WHERE %(source)s = %%(old)s""" % values
+ logged_query(cr, query, values)
+ if write == 'orm':
+ model.write(
+ cr, SUPERUSER_ID,
+ [row[0] for row in cr.fetchall()],
+ {target_column: new})
+
+
def message(cr, module, table, column,
message, *args, **kwargs):
"""
diff --git a/openerp/openupgrade/openupgrade_80.py b/openerp/openupgrade/openupgrade_80.py
index bca5ef190bfa..c3cb96c8cd44 100644
--- a/openerp/openupgrade/openupgrade_80.py
+++ b/openerp/openupgrade/openupgrade_80.py
@@ -25,6 +25,9 @@
# docs in the latest release.
+from openerp import SUPERUSER_ID
+
+
def get_last_post_for_model(cr, uid, ids, model_pool):
"""
Given a set of ids and a model pool, return a dict of each object ids with
@@ -72,3 +75,37 @@ def set_message_last_post(cr, uid, pool, models):
last_posts = get_last_post_for_model(cr, uid, obj_ids, model_pool)
for i in obj_ids:
model_pool.write(cr, uid, [i], {'message_last_post': last_posts[i]})
+
+
+def update_aliases(
+ cr, registry, model_name, set_parent_thread_id,
+ alias_defaults=None, defaults_id_key=False):
+ """
+ Update a model's aliases according to how they are configured
+ in the model's create() method.
+
+ :param model_name: The name of the model whose aliases are to be updated. \
+ The model_id is also set as the aliases' alias_parent_model_id.
+ :param set_parent_thread_id': When set, set the ids of the resources as \
+ their alias' alias_parent_thread_id
+ :param alias_defaults: Static dictionary, recorded as a string on each \
+ alias
+ :param defaults_id_key: When defined, add this key to each alias' defaults \
+ dictionary with the resource id as its value.
+ """
+ model_id = registry['ir.model'].search(
+ cr, SUPERUSER_ID, [('model', '=', model_name)])[0]
+ vals = {'alias_parent_model_id': model_id}
+ if defaults_id_key and alias_defaults is None:
+ alias_defaults = {}
+ res_ids = registry[model_name].search(
+ cr, SUPERUSER_ID, [], context={'active_test': False})
+ for res in registry[model_name].browse(
+ cr, SUPERUSER_ID, res_ids):
+ if set_parent_thread_id:
+ vals['alias_parent_thread_id'] = res.id
+ if defaults_id_key:
+ alias_defaults[defaults_id_key] = res.id
+ if alias_defaults is not None:
+ vals['alias_defaults'] = str(alias_defaults)
+ res.alias_id.write(vals)