[19.0][MIG] dms: Migration to 19.0 - #475
Conversation
|
Thanks for the contribution. Please preserve commit history following technical method explained in https://github.com/OCA/maintainer-tools/wiki/Migration-to-version-19.0. If the jump is between several versions, you have to modify the source branch in the main command to accommodate it to this circumstance. |
43752f4 to
660b3c9
Compare
|
Thanks for the review @pedrobaeza — sorry I missed the technical method on the first round. I've now:
CI is re-running now. |
4affd39 to
724ff8b
Compare
|
Friendly ping @pedrobaeza @victoralmau @eLBati @etobella |
etobella
left a comment
There was a problem hiding this comment.
Using AI for coding is fine (I personally think it is a nice tool to improve your results), but you need to understand the code you're submitting.
In this PR specifically:
- Some functionalities were removed
- A lot of unnecessary diff was introduced
These are basic mistakes that would be caught by simply reviewing the changes your AI assistant supplied before opening a PR.
That is also a license risk: AI tools are trained on code with various licenses (GPL, MIT, Apache…). If you can't read the output, you can't detect contamination, missing attribution, or patent-encumbered patterns. Evenmore, you cannot claim ownership and copyright (completely necessary in an OpenSource community). "The AI wrote it" is not a legal defense.
Review what you ship, always.
24e9535 to
7128196
Compare
Follow-up to OCA#475 phase 1 (the minimal-viable 19.0 [MIG]). This PR layers the OWL/UX modernisations that were held back to keep the base MIG reviewer-friendly. Opens on the ledoent fork against the phase 1 branch so reviewers can preview the delta on top of OCA#475 before it lands. - **Kanban buttons**: drop the classic `dms.KanbanButtons` template (mobile-Scan + desktop-Upload + hidden file input) and rewire `buttonTemplate` to the modern `dms.FileKanbanView.Buttons` (single Upload button, `t-ref="uploadFileInput"` hook pattern, modern `onFileInputChange` handler). Mobile-Scan UX is intentionally dropped — the file drop-zone (already wired via `createFileDropZoneExtension()`) covers the mobile-upload flow. - **Breadcrumb modernisation**: replace `path_owl.xml`'s inline-style `<a oe_form_uri>` + `<span style="display: inline">` pattern with Odoo 19's `<ol class="o_breadcrumb breadcrumb"><li class="breadcrumb-item">` Bootstrap idiom. Final segment uses `class="breadcrumb-item active"` with `aria-current="page"` per Bootstrap a11y guidance. - **Restore `filter_domain` on `dms_category.xml`** search view — `filter_domain="['|', ('name', 'ilike', self), ('parent_id', 'child_of', raw_value)]"` was removed in OCA#475 and re-introduces the 18.0 UX of filtering by parent-category subtree when typing a parent's name. The bare 19.0 default `ilike` on name lost that behaviour. (Tag search left as default; 18.0's `filter_domain` was equivalent to 19.0's default for that field.) Deliberately held for follow-up rounds (not in this PR): - **JS dead-code audit** of `attachment_image.esm.js` / `attachment_viewer_viewable.esm.js`: grep across odoo/odoo:19.0 + OCA org confirmed only our own files reference the patches, and core 19.0 `LinkPreview` has no native `imageUrl` getter — verification inconclusive on whether the patches are still load-bearing. Keeping both files until a runtime regression test or a maintainer's explicit "core handles it now" signal. - **Coverage closure**: add `coverage.py` integration + tests for the new 19.0 code paths (`_search()` override on `dms_security_mixin`; `_search_starred` operator normalisation in `directory`). Separable. - **Code-style sweep** (whitespace + indentation): not bundled here to keep the diff focused on user-visible UX changes. Signed-off-by: Daniel Kendall <dkendall@ledoweb.com>
|
Thanks for the partial response, but several inline comments were left unanswered... More importantly, your own Phase 2 PR restores things that were removed in this PR (kanban buttons, breadcrumb, filter_domain). That is the clearest possible confirmation that functionality was silently dropped and went unnoticed — exactly the problem I feared in the review. Please address all inline comments before requesting another review, and make sure this PR is complete and self-consistent before opening follow-ups to fix what it broke. Vibecoding without knowing what you are doing is a problem for the code quality, for licensing, and for the reviewers who have to clean up after it. So, can you justify all the actions done? |
|
@etobella certainly, thanks for the review! Apologies for the AI-review failure on the first round — I do try to keep it reined in. Your feedback is the best teacher; sometimes between all the notes and skill updates I wonder, but I believe next year the careful conventions will pay back in speed without losing quality. this PR feedback is delays as I fix my internal runboat. for the reasoning on the button change I wanted to get the phase 2 IMP up. will try and get that later today. Force-pushed
Final scope: 31 files / +190-122 (down from 36 / +430-431, ~64% churn reduction). All 7 CI checks green. A follow-up PR is opened as a draft preview on the ledoent fork: ledoent#2 — base = this PR's branch, so the diff visible there is only the phase-2 delta. Contents:
Deferred to a later round in that PR: JS dead-code audit (verification was inconclusive; core 19.0 Will promote ledoent#2 to an OCA upstream draft PR once this one lands |
Follow-up to OCA#475 phase 1 (the minimal-viable 19.0 [MIG]). Opens on the ledoent fork against the phase 1 branch so reviewers can preview the delta on top of OCA#475 before it lands. - **Breadcrumb modernisation**: replace `path_owl.xml`'s inline-style `<a oe_form_uri>` + `<span style="display: inline">` pattern with Odoo 19's `<ol class="o_breadcrumb breadcrumb"><li class="breadcrumb-item">` Bootstrap idiom. Final segment uses `class="breadcrumb-item active"` with `aria-current="page"` per Bootstrap a11y guidance. - **Restore `filter_domain` on `dms_category.xml`** search view — `filter_domain="['|', ('name', 'ilike', self), ('parent_id', 'child_of', raw_value)]"` was removed in OCA#475 and re-introduces the 18.0 UX of filtering by parent category subtree when typing a parent's name. The bare 19.0 default `ilike` on name lost that behaviour. (Tag search left as default — 18.0's filter was equivalent to 19.0's default for that field.) Originally this PR also rewired the kanban `buttonTemplate` from `dms.KanbanButtons` to the orphan `dms.FileKanbanView.Buttons` template, but that template uses invalid OWL inheritance syntax (`<div role="toolbar" position="inside">` instead of `<xpath ...>`) AND references controller hooks (`uploadFileInputRef`, `onFileInputChange`) that don't exist on the controller. Reverted; phase 1's classic `dms.KanbanButtons` template stays in place (it's now self-contained without `t-inherit` since 19.0's `web.KanbanView.Buttons` is empty). Deferred to a later round: JS dead-code audit (inconclusive — core 19.0 `LinkPreview` has no native `imageUrl` getter, so our patches may still be load-bearing), coverage closure, and a proper kanban UX modernisation once `dms.FileKanbanView.Buttons` is fixed or replaced. Signed-off-by: Daniel Kendall <dkendall@ledoweb.com>
|
Even this response seems AI-generated. Please, stop doing this, it is only making things worse @dnplkndll We don't have yet an AI Policy, but I hope we will have one soon. meanwhile, you can see one example of what should be and shouldn't be allowed to be done with AI by checking other community policies... |
|
@etobella looking over the policy that matched up with the pycore team podcast from maintainers I just mentioned to @JordiBForgeFlow . I get it. the social part of this think is important and maintainers are not here to fix slop. what I actually do is cli 6 sessions and always try my best to review and update. clarify fix everything I do. I understand I own it and I do understand the value of being the human. but I make mistakes. miss things, like I have in the past in many contributions I made before llms. but I can assure you I am present trying to be an enguaged policy following participant in the community. I continue to have much more grandiuos goals than my time and budget and am included to learn and grow with all the feedback I can get. I will comment on your inlines and to be honest that last one I was like wth, but I did not then have time / remember to open that file to verify. what is hard for me at this point is going through. then working on something else and a change happend in the wrong window I miss. I get it this is your time and will try to do better at making sure nothing hits the upstream repo intill I am ready to own it. |
agreed. it was just an attempt to adapt to the most concise modern access methods. unneeded in this mig. |
|
Please, cherry-pick #503 to commit history before migration commit. |
| ) | ||
| # search | ||
| if search and search_in == "name": | ||
| domain = Domain.AND([domain, Domain.OR([[], [("name", "ilike", search)]])]) |
There was a problem hiding this comment.
| domain = Domain.AND([domain, Domain.OR([[], [("name", "ilike", search)]])]) | |
| domain += Domain.OR([[], [("name", "ilike", search)]]) |
There was a problem hiding this comment.
@victoralmau I applied this but was looking at core, eq https://github.com/odoo/odoo/blob/57ffa1abb6e1ed481b8bccc5db5534ea0ad2c6b5/addons/hr_timesheet/controllers/portal.py#L113 and you can see the explaination on the last comit. I will drop it if you do not agree but debugging the non matching term did seem to prove it out.
| def _process_attachments_for_post(self, attachments, attachment_ids, message_data): | ||
| """Indicate to DMS that we're attaching a message to a record.""" | ||
| _self = self.with_context(attaching_to_record=True) | ||
| return super(MailThread, _self)._process_attachments_for_post( | ||
| attachments, attachment_ids, message_data | ||
| ) |
There was a problem hiding this comment.
| def _process_attachments_for_post(self, attachments, attachment_ids, message_data): | |
| """Indicate to DMS that we're attaching a message to a record.""" | |
| _self = self.with_context(attaching_to_record=True) | |
| return super(MailThread, _self)._process_attachments_for_post( | |
| attachments, attachment_ids, message_data | |
| ) | |
| def _process_attachments_for_post(self, attachments, attachment_ids, message_values): | |
| """Indicate to DMS that we're attaching a message to a record.""" | |
| self = self.with_context(attaching_to_record=True) | |
| return super()._process_attachments_for_post( | |
| attachments, attachment_ids, message_values | |
| ) |
| """ | ||
| Open the form to create a new storage from the onboarding panel. | ||
| """ | ||
| return self.env.ref("dms.action_dms_storage_new").read()[0] |
There was a problem hiding this comment.
| return self.env.ref("dms.action_dms_storage_new").read()[0] | |
| return self.env["ir.actions.actions"]._for_xml_id( | |
| "dms.action_dms_storage_new" | |
| ) |
| Open the form to create a new directory from the onboarding panel. | ||
| """ | ||
| storage = self.env["dms.storage"].search([], order="create_date desc", limit=1) | ||
| action = self.env.ref("dms.action_dms_directory_new").read()[0] |
There was a problem hiding this comment.
| action = self.env.ref("dms.action_dms_directory_new").read()[0] | |
| action = self.env["ir.actions.actions"]._for_xml_id( | |
| "dms.action_dms_directory_new" | |
| ) |
| directory = self.env["dms.directory"].search( | ||
| [], order="create_date desc", limit=1 | ||
| ) | ||
| action = self.env.ref("dms.action_dms_file_new").read()[0] |
There was a problem hiding this comment.
| action = self.env.ref("dms.action_dms_file_new").read()[0] | |
| action self.env["ir.actions.actions"]._for_xml_id( | |
| "dms.action_dms_file_new" | |
| ) |
There was a problem hiding this comment.
probably you mean
action = self.env["ir.actions.actions"]._for_xml_id(
"dms.action_dms_file_new"
)
I see you miss "="
| def _find_record_check_access(self, record, access_token, field): | ||
| if record._name in ("dms.file", "dms.directory"): | ||
| if record.sudo().check_access_token(access_token): | ||
| # sudo because the user might not usually have access to the record but | ||
| # now the token is valid. | ||
| # Used to display the icon in the portal. | ||
| return record.sudo() | ||
|
|
||
| return super()._find_record_check_access(record, access_token, field) |
There was a problem hiding this comment.
This method no longer exists in v19; you'll likely need to override the _can_return_content() method in the dms.file and dms.directory models to achieve similar behavior.
There was a problem hiding this comment.
you are correct, replaced with_can_return_content()
98f4088 to
4fb9072
Compare
|
Please, cherry-pick #502 to commit history before migration commit. |
Previous implementation was failing to convert strings to base64, which is the case when we receive plain HTML encoded as base64; as shown in mail02.eml. This in turn lead to inconsistencies when saving/reading these contents afterwards, as the rest of the code always assumes dms.file::content contains base64 data. This was falling through cracks because although the resulting data is invalid base64, the b64decode impl in python 3.12/3.13 is lax enough it would ignore failures in this case because our test string is "base64-ish" enough. However, when running Odoo on Debian with the patch https://sources.debian.org/patches/python3.13/3.13.5-2+deb13u3/CVE-2026-3446.patch/ applied, this test on mail02.eml produced errors in dms.file::_inverse_content when trying to b64decode: > <class 'binascii.Error'> Invalid base64-encoded string: number of data characters (429) cannot be 1 more than a multiple of 4
4fb9072 to
7df167b
Compare
|
I checked the cherry-pick of #502 it's perfect (code was identical). |
|
Please, cherry-pick #505 to commit history. Remember: The last commits should always be the |
|
/ocabot migration dms @victoralmau @etobella is the rest of the migration OK for unblocking this? |
No, I'm reviewing a few things (right now). |
| ) | ||
| if not positive: | ||
| result = ~Domain(result) | ||
| return result |
There was a problem hiding this comment.
| return result | |
| def _get_permission_domain(self, operator, value, operation): | |
| _self = self.sudo(False) if self.env.su else self | |
| if _self.env.su: | |
| return Domain.TRUE if operator == "in" else Domain.FALSE | |
| return Domain.OR( | |
| [ | |
| _self._get_domain_by_access_groups(operation), | |
| _self._get_domain_by_inheritance(operation), | |
| ] | |
| ) |
This suggestion has required an in-depth analysis; this change is necessary because https://github.com/odoo/odoo/blob/c504f7fba1fc5671df6210b9ff1642aee52f83b3/odoo/orm/domains.py#L922 applies a change (automatic optimization) in this case to the permission_* fields (compute with search defined), which alter the defined value (user.id) (as well as the operator).
This change will also require changing the ir.rule records from [('permission_**', '=', user.id)] to [('permission_**', '=', True)]
Related to #495 (comment)
There was a problem hiding this comment.
accepted, but see test_permission_search_polarity() for the negation test
7df167b to
fa4fddb
Compare
dms.file.check_access_token compared the directory walker against self.directory_id instead of against the directory owning the token, so both the loop test and the "fix last level" test were self-comparisons that are always true. The branch collapsed to `return True` whenever any directory carried the supplied token, granting read access to every dms.file in the database regardless of directory or group inheritance. Compare against item.id, matching the correct implementation in dms.directory.check_access_token, and sudo the walk so ancestors the caller cannot read are still traversable. _get_files in the portal controller relied on the always-true behaviour: it called check_access_token on an empty dms.file recordset, where False == False happened to authorise the shared listing. Validate the token against the directory being browsed instead, mirroring the pattern already used by _get_directories. Adds regression tests covering unrelated trees, root-level files, descendant directories and a file's own token. Assisted-by: Claude Opus 5 [FIX] dms: bound the share-token ancestor walk The file-side walk previously returned on its first iteration because of the self-comparison bug, so it never actually traversed. Now that it does, a parent cycle would spin forever in a request reachable without authentication. _check_directory_recursion rejects cycles created through the ORM, so this only bites on corrupted data or direct SQL, but an unbounded loop on an anonymous endpoint is not worth leaving open. Track visited ids in both dms.file and dms.directory rather than imposing an arbitrary depth limit, so legitimate deep trees are unaffected. Assisted-by: Claude Opus 5 Co-authored-by: sajjad <sajjad@millow.co>
Assisted-by: Claude Fable 5
Assisted-by: Claude Fable 5
OR([[], ('name', 'ilike', search) leaf]) contains the empty domain, which
normalizes to TRUE — the absorbing element of OR — so the search term was
discarded: 18.0 expression.combine returns its zero, 19.0 Domain.OR
optimizes to [(1, '=', 1)]. Verified live in both forms; a search for a
non-existent term returned every directory.
The empty seed came from the core portal multi-field accumulator idiom
(38d8463, 2020), where core appends per-field domains to a plain list and
ORs once at the end; inlined to the current shape by the 17.0 migration.
Assisted-by: Claude Fable 5
fa4fddb to
510c714
Compare
just saw this one and now done |
Port of
dmsfrom 18.0 to 19.0 (migration guide).Non-mechanical adaptations worth flagging
osv.expression→fields.Domainthroughout the security mixin, andread_group→_read_group(keywordaggregates=, tuple unpacking insteadof dicts).
_search_permission_*methods and the computed-permission
ir.rules are kept as-is. An earlierrevision of this PR carried an extra
[IMP]that moved this to_search/_check_accessoverrides; that commit has been dropped —@victoralmau was right that it was not necessary for the migration, and the
same access tests pass without it. It now lives on a fork branch for
separate consideration after this lands.
with_demo=False,so fixtures are built in
setUpClasswithnew_test_user()andcreate()rather than skipping when a demo xmlid is absent — a skip would have meant
those tests silently never ran in CI.
AI-assisted (Claude Code); every change reviewed, tested, and owned by the author.