Repository navigation
Conversation
Map project.collaborator.limited_access to access_mode, copy private-project followers into team members, rebuild stored duration_tracking from mail tracking, and refresh the project_todo onboarding template. project_sms and project_mail_plugin have no data to move. Co-authored-by: André de Kock <adekock11@users.noreply.github.com>
mail.thread.cc is gone in 20.0, and project.task / project.update do not redeclare email_cc. Append the escaped addresses to description and drop the column, matching the crm.lead migration. Co-authored-by: André de Kock <adekock11@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migration scripts for the project family from Odoo 19.0 to 20.0, on top of the generated analysis in OCA/OpenUpgrade#6068 (head
hbrunn/OpenUpgrade@2d9eb6bab7e7e579953e38d7ad3b96aad6fcc44d).This branch does not cherry-pick the OCA#6068 commits. It copies that PR's
upgrade_analysis.txt/noupdate_changes.xmlfor the modules below, and it copiesdocsource/modules190-200.rstso the status cells can be filled. If OCA#6068 lands first, rebase this branch: the coverage table should shrink to the four status cells this PR changes (project,project_todo,project_sms,project_mail_plugin).project20.0.1.4project_todo20.0.1.0project_sms20.0.1.1ir.accessrename only, handled by baseproject_mail_plugin20.0.1.0Runtime dependency: base must already have merged
ir.model.accessandir.ruleintoir.access(the base scripts in OCA#6068, Odoo commite7cc76b290). The project post-migration loads a noupdateir.accessrecord and assumes that merge has run.openupgrade_frameworkfrom the same PR is what actually executes these scripts.project
Analysis source:
openupgrade_scripts/scripts/project/20.0.1.4/upgrade_analysis.txt(unchanged from OCA#6068). Annotations are inupgrade_analysis_work.txt.project.collaborator.limited_accessdeleted,access_moderequiredOdoo commits a59b237f77 and 04e9a2509d (task 6479946 / 6479918, PR odoo/odoo#284245).
v19 stored a boolean. The share wizard exposed it as:
limited_accessfalse, label "Edit": the portal user could write every task, includingstage_idandpriority.limited_accesstrue, label "Edit with limited access": the portal user could write tasks they followed. Visibility was row-level and tied to followers.project.collaborator. The portal project rule wasmessage_partner_ids child_ofthe user's commercial partner.v20 replaces that with a required selection
view/edit/advanced_edit. Read access no longer depends on following a task.editmay change ordinary fields and may not change stage or priority.advanced_editmay change stage and priority. The new default isview.Without a script, every existing collaborator would become
view(the column default) and every portal user who could open a project only because they followed it would lose the project. The v20 portal project rule requires a collaborator row.Pre-migration, before the ORM adds the required column:
limited_accessfalse, and NULL (the old default was false) →advanced_editlimited_accesstrue →editportalorinvited_usersproject and are not already collaborators → new rowaccess_mode = viewviewrow for every existing portal user of that follower's commercial entity, so the oldchild_ofcommercial-partner rule still holdseditis a behaviour change for old limited users: they can now see every task, and they can no longer move stage or priority. That is the split Odoo describes in04e9a2509d. Keeping them onadvanced_editwould have preserved stage writes and also given them every task, which is a larger grant than v19. The full "Edit" users go toadvanced_editso they keep stage and priority.project.project.allowed_internal_user_ids(Team Members)Odoo commit bae4e441d9.
v19
project_public_members_rulelet any internal user read a project when they followed it. v20 readsallowed_internal_user_idsand ignores followers.employeesandportalprojects stay visible to every internal user.followersandinvited_usersprojects become invisible to everyone except the team members and the project admin group.The new field is a stored compute that only adds
user_id(the project manager) and then filters by company. Letting that compute run on an empty relation would drop every other internal follower.Pre-migration creates
project_allowed_internal_users_rel(columnsproject_project_id,res_users_id, the names Odoo assigns when the m2m declaration omits them) and fills it:shareis false) followers offollowers/invited_usersprojects, when the project has no company or the user belongs to that companyMany2many.update_dbqueues the stored compute only when the relation table does not exist yet. Creating the table first stops that compute from replacing the copied followers with the manager alone. Foreign keys are still added by the ORM'spost_init.duration_trackingis now stored, andproject.project.date_last_stage_updateis newOdoo commit d5e8a68f4b (
mail.tracking.duration.mixin, also project).v19 computed
duration_trackingon the fly frommail.tracking.valueas{stage_id: seconds}, including the still-open visit. v20 stores JSON and the compute only continues from what is already stored:The burndown and time-by-stage SQL add the open visit from
dat read time, in minutes. An empty column plus the new compute would record "entered the current stage during the upgrade" and drop history.Pre-migration adds the
jsonbcolumn (so_init_column_datadoes not recompute it: the field is not required and the column is already non-null) and fills it frommail.tracking.valueforstage_idonproject.taskandproject.project:mail_tracking_value.idfloor(epoch / 60)between tracking dates, starting fromcreate_datesis the currentstage_idor 0dis the last tracking date, elsecreate_datedate_last_stage_updateonproject.projectuses that same timestamp. The field default isnow(), which would mark every project as just moved and would skew rotting. Tasks already have this column from v19; it is left alone.email_ccremoved fromproject.taskandproject.updateConfirmed on
odoo/odoo20.0 (d7014bd26978) against 19.0.odoo/odoo@3c26c0553754 (
[IMP] mail,*: remove MailThreadCC, part of odoo/odoo#238694) deletesmail.thread.cc. The mixin was the only definition of the storedemail_ccchar. 20.0addons/mailhas nomail.thread.ccmodel.Who had the column in 19:
project.taskinheritsmail.thread.ccand redeclaresemail_cc(project/models/project_task.py). The gateway writes unmatched CC addresses there (message_new). 20.0project.taskinheritsmail.threadviamail.tracking.duration.mixinand does not declareemail_cc.project.updateinheritsmail.thread.ccin 19 andmail.threadin 20. It never redeclared the field, so the column exists only because of the mixin. 20.0project.updatehasdescriptionand noemail_cc.project.projectnever inherited the mixin, so it has noemail_cccolumn.ir.model.fields._reflect_fieldsupserts fields that still exist. It does not unlink a base field that left the registry, and it does notDROP COLUMN._drop_columnruns only fromir.model.fields.unlink. After the upgrade the form no longer shows the field, and the addresses sit inproject_task.email_cc/project_update.email_ccwith nothing reading them.mail.message.incoming_email_ccstill holds the CC of each message, which is not the accumulated list the document field kept for the composer.Same preservation as PR #2 for
crm.lead(that PR's pre-migration), because both models lost the mixin and both keep an Htmldescription:Pre-migration, while the column exists:
&,<and>(soAnn <a@x.com> & Bobcannot break the description).<p>Email cc preserved from previous version: …</p>. A non-empty description gets a<br/>before that paragraph. An empty description gets only the paragraph.strposskips a row whose description already contains that sentence, so a crash between theUPDATEand theDROPdoes not append twice.DROP COLUMN. A later run sees no column and returns._drop_columnalso checkscolumn_existsbefore dropping, so a later field unlink does not fail on the missing column.There is no other v20 field for these addresses. New mail subscribes partners it can match and does not store the unmatched list on the task.
Fields with nothing to do
website_message_idsremoved on milestone, project, task and project update. It is a non-stored one2many from portal'smail.threadinherit (overridden onproject.task). v20 portal drops the field. Messages stay onmail.message. No column. The portal migration owns the field removal.project.project.stage.colorandproject.task.type.colordeleted. Cosmetic kanban index, no replacement field. The integers are dropped.project.project.stage.rotting_threshold_daysnew, default 0 (rotting off). No v19 source.project.role.user_ids/res.users.project_role_ids: one new m2m. v19 roles had no members.project.task.allow_recurring_tasksandallow_task_dependenciesare no longer stored related fields. They are non-stored computes: the project flag, oris_templatewhen the task has no project. The ORM drops the obsolete columns. Template tasks without a project will now read True instead of the old related False.project.update._orderisdate desc, id desc. Ordering only.datealready exists.res.users.followed_project_idsis a non-stored search overmail.followers.project.task.stage.report(odoo/odoo@eb9e98c656). The ORM creates it.constraint_project_task_private_task_has_no_parentwas relaxed to allow template subtasks. Rows that passed the old CHECK pass the new one.XML
ir.model.accessandir.rulelines in the analysis are the base rename toir.access(odoo/odoo@e7cc76b290). Standard XML is reloaded fromir.access.csv. The domains that now mentionallowed_internal_user_idsandaccess_modebecome true because of the pre-migration above.Post-migration loads
noupdate_changes.xml(generated by the analysis, unchanged):mail_template_data_project_taskandrating_project_request_email_templategainemail_layout_xmlid = mail.mail_notification_light.body_htmlis not in the file, so a customized body stays.project_message_user_assigned:t-escbecomest-out. The sentence is the same. A customized arch of this noupdate qweb is overwritten.t-escstill parses in 20.0, so leaving a translated arch alone would not crash.project_task_rule_portal_project_sharing: domain requiresaccess_mode in ('edit', 'advanced_edit'), operationcu, group portal. Loaded after base has converted the oldir.ruletoir.access.New views, menus, server actions, digest tips and
portal.entryrows are module data. Deleted views were not noupdate.project_todo
No model or field changes. The
ir.access/ir.rulelines are the same base rename.task_edition_rule_internalkeeps its xmlid, domain and crud operation, so the noupdate flag does not freeze a stale rule.Post-migration reloads noupdate qweb
todo_user_onboarding. Upstream wrapped the shortcut block in<t name="shortcut_section">and setwidth: 50%on the two screenshots. The wording is unchanged. A database that edited this onboarding HTML gets the upstream arch back.project_sms
Nothing to do. The only analysis lines are
ir.model.access/ir.rulebecomingir.access, with no noupdate domain diff. Base preserves manual rows.project_mail_plugin
Nothing to do.
project_task_action_form_editwas not noupdate. The module no longer shipsproject_client.pyorproject_task_views.xml. The module update deletes the xmlid. No stored business data.Left for other PRs
These have real 19→20 changes and should not ride along with project:
account.analytic.line.sequenceis new.portal_timesheet_ids/show_portal_timesheetsare new. The analysis reportsbusiness_domainlosing thepurchase_orderkey. The noupdate portal timesheet rule changes operation from full CRUD to read and drops themessage_partner_idsclause. Project's collaborator move does not make the old domain invalid: analytic lines still have followers. It does leave portal timesheet rights wider than v20 until that module is migrated.ir.accessrename only. Belongs with timesheets.sale_timesheetdropsso_line,timesheet_invoice_idandtimesheet_invoice_type, and movesorder_idtosale.sale_projectadds storedbillable_typeandcategory_report, andproject.milestone.quantity_percentagestops being a compute.project_sale_expenseaddsaccount.analytic.line.category. That is a sale migration.ir.accessrenames, or a new non-unique index (project_timesheet_holidaysindex on timesheet lines). No stored data to move. Their docsource rows are left blank in the copied table so the owning PR can mark them.How it was tested
PostgreSQL 16 fixture, not a full Odoo database. The SQL in
pre-migration.pywas executed throughopenupgradelib.logged_query/add_columnsagainst hand-built tables (project_task,project_project,project_collaborator,mail_tracking_value,mail_message,mail_followers,res_partner,res_users,res_company_users_rel,ir_model_fields).Assertions that passed:
{"1": 2880, "s": 2, "d": "2026-01-03 ..."}(2880 minutes = 2 days). A tracking row on a different field is ignored.1 → 1440,2 → 2880,s = 1,d= the second tracking date. The open visit is not in the totals.s = 0andd = create_dateonly."7",s = 8, anddate_last_stage_update= 2026-03-01 (notnow()).limited_accessfalse →advanced_edit, true →edit, NULL →advanced_edit. Running the access migration again does not overwrite a filled mode.viewfor that partner and for the portal user of the same commercial entity. An existing limited collaborator on that project stayseditand is not duplicated.followersproject gains team members for the same-company internal follower and the manager, and not for an internal follower of another company. Anemployeesproject gains nobody. A project with no company gains the follower even when that user sits on another company. The manager of aninvited_usersproject is inserted. The secondary index on the relation is created.email_ccAnn <a@x.com> & Bobon a task with a description becomes…<br/><p>Email cc preserved from previous version: Ann <a@x.com> & Bob</p>, and the column is dropped. Whitespace and NULL are left alone. A NULL description becomes only the paragraph. The same append runs onproject_update. A second call is a no-op. Putting the column back after the append and running again does not add a second paragraph, then drops the column.ruff checkandruff format --check(ruff 0.16.8, the pre-commit pin) pass on the Python files. Both noupdate XML files parse.Not done: installing Odoo 19 and 20 and upgrading a demo database. The scripts were not executed by Odoo's migration loader, and
noupdate_changes.xmlwas not loaded throughopenupgrade.load_data. The column-vs-recompute reasoning is fromodoo/orm/fields.pyupdate_db/_init_column_dataandMany2many.update_dbonodoo/odoo20.0d7014bd26978, not from watching a module update.Gaps and risks
advanced_editby hand.email_ccis text inside the description. It is not restored as a composer CC list, and new messages do not keep writing that list.mail.message.incoming_email_ccis unchanged.project.project.stageandproject.task.typeare dropped.duration_trackingminutes are floored. A visit under 60 seconds stores 0 and is omitted. Tracking rows whosecreate_dateis beforecreate_datecontribute 0.viewrows are created for share followers and for portal users of the same commercial partner. A portal user of a child company with a differentcommercial_partner_idis not included. The v19child_ofrule was the same boundary.update_db_foreign_keys. If a custom module already createdproject_allowed_internal_users_relwith different column names, the INSERT fails.project_task_rule_portal_project_sharingis noupdate. Loading it overwrites a locally edited domain.