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)