Skip to content

[fork-CI] [20.0][MIG] attachment_preview: Migration to 20.0 - #20

Draft
dnplkndll wants to merge 46 commits into
20.0from
20.0-mig-attachment_preview
Draft

dnplkndll wants to merge 46 commits into
20.0from
20.0-mig-attachment_preview

Conversation

@dnplkndll

@dnplkndll dnplkndll commented Oct 3, 2026 •

Copy link
Copy Markdown

Port of attachment_preview from 19.0 to 20.0. Follows the OCA migration guide.

No dependency pin: it depends only on core web and mail.

Carries a forward-port of #14 as its own [FIX] commit — that fix is still unmerged on 19.0, and 20.0 needs it for the same reason 19.0 did.

Non-mechanical adaptations worth flagging

The Python side is two API removals. The JavaScript is most of the work, because 20.0 removed both things this module was built on.

  • jQuery is gone from 20.0 entirely — no lib, no bundle entry, and @web/core/ensure_jquery deleted (zero core users). All nine call sites are vanilla DOM now. Where jQuery silently absorbed a null — an unrendered t-ref, a selector matching nothing — the replacement guards explicitly instead of throwing.
  • Owl 3 removed the static Component.env (zero core files use it). The widget reads this.env.bus; showPreview takes an env argument, since it is a free function and both callers are component methods.
  • Owl 3 raises on static props, and the throw propagates out of whatever mounted the component. With none to declare, core's own answer is to omit it.
  • Owl 3 resolves a bare template identifier against the render context, not the component, so the four t-on-click handlers are qualified with this. — unqualified they are silently undefined.
  • The Store hook was renamed and reshaped: _to_store_defaults → _store_attachment_fields(res: Store.FieldList, **kwargs), which appends to the list it is handed instead of returning one. The old name could not be reached even if it still existed, because Store.add only accepts a method name starting with _store_ and ending with _fields. Left unported, the extension never reaches the client and every preview button disappears — with nothing failing anywhere.
  • ir.attachment.datas is gone (raw now), and Binary fields are handed over as a BinaryValue holding RAW bytes where 19.0 gave base64. So magic.from_buffer needs real bytes, and the data-URL fallback must b64-encode rather than decode. bin_size no longer changes the value either.
  • The preview button attaches to 20.0's download button, now oi btn btn-link o_download_file_button with data-icon="download" — the old button.fa-download selector matched nothing. Keyed on the semantic class, scoped to the component's own DOM, and declining to attach when the match is ambiguous rather than decorating another field's button.
  • FontAwesome is gone, so the six template icons are oi + a data-icon ligature.
  • Owl 3 replaced the reactivity primitives outright. useState and useRef are not exported at all, and core calls neither: state is proxy({...}), and a ref is a signal typed t.ref(), declared as a class field, bound with t-ref="this.<name>" and read by calling it. Until this was fixed the widget threw useState is not a function from setup() and took the whole form view down behind an "Oops!" dialog, on any record carrying an attachment.
  • AttachmentList.previewableAttachments is gone. Core builds the equivalent inline in its own onClickAttachment, so the override takes it from props.attachmentGroups the same way. Left alone the list arrived undefined, showPreview fell through to its window.open branch, and the side panel silently never opened — with no error anywhere.
  • The click handler is handed its event rather than reading the implicit global window.event, which is only set while a native handler is on the stack. Owl 3 calls it through a wrapper, so the global was undefined and the click did nothing, again silently.

3/3 Python tests pass. Note what that does not cover: there are no JS tests and no tours, so the whole list above is verified by a browser walk on a migrated production copy rather than by CI — opening a record with a real PDF, asserting the preview button renders (which also proves extension reached the client), clicking it, and checking the panel opens with the iframe pointed at the viewer and zero client errors. Four of the items above were found only that way, three of them failing silently.

AI-assisted (Claude Code); every change reviewed, tested, and owned by the author.

hbrunn and others added 30 commits September 29, 2026 13:22
In order to get visibility on https://www.odoo.com/apps the OCA board has
decided to add the OCA as author of all the addons maintained as part of the
association.
[FIX] Lint

[FIX] lint and flake

[ADD] tests

[ADD] tests

[ADD] tests

[ADD] Tests

[ADD] Package python-magic

[ADD] Tests

[FIX] Lint
[REM] Old files

[ADD] Magic to travis file

[FIX] Nagivation refresh widget
Currently translated at 100.0% (2 of 2 strings)

Translation: knowledge-12.0/knowledge-12.0-attachment_preview
Translate-URL: https://translation.odoo-community.org/projects/knowledge-12-0/knowledge-12-0-attachment_preview/sl/
Currently translated at 100.0% (2 of 2 strings)

Translation: knowledge-12.0/knowledge-12.0-attachment_preview
Translate-URL: https://translation.odoo-community.org/projects/knowledge-12-0/knowledge-12-0-attachment_preview/tr/
vancouver29 and others added 15 commits September 29, 2026 13:22
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: knowledge-15.0/knowledge-15.0-attachment_preview
Translate-URL: https://translation.odoo-community.org/projects/knowledge-15-0/knowledge-15-0-attachment_preview/
Currently translated at 100.0% (4 of 4 strings)

Translation: knowledge-15.0/knowledge-15.0-attachment_preview
Translate-URL: https://translation.odoo-community.org/projects/knowledge-15-0/knowledge-15-0-attachment_preview/es/
Currently translated at 100.0% (4 of 4 strings)

Translation: knowledge-15.0/knowledge-15.0-attachment_preview
Translate-URL: https://translation.odoo-community.org/projects/knowledge-15-0/knowledge-15-0-attachment_preview/it/
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: knowledge-15.0/knowledge-15.0-attachment_preview
Translate-URL: https://translation.odoo-community.org/projects/knowledge-15-0/knowledge-15-0-attachment_preview/
Currently translated at 100.0% (6 of 6 strings)

Translation: knowledge-18.0/knowledge-18.0-attachment_preview
Translate-URL: https://translation.odoo-community.org/projects/knowledge-18-0/knowledge-18-0-attachment_preview/it/
Odoo 19 removed o-mail-AttachmentCard-aside, so the inheritance cannot resolve
and the whole Chatter fails to render on any record with an attachment. Extend
the o-mail-AttachmentButtons hover group instead, where the per-attachment
actions now live, and match its button styling.

Keep both buttons: _onPreviewAttachment reads data-target and treats anything
but "new" as split-screen.

Also apply the manifest author spacing requested in review.

Assisted-by: Claude Opus 5
Signed-off-by: Don Kendall <dkendall@ledoweb.com>
Assisted-by: Claude Opus 5
@dnplkndll
dnplkndll force-pushed the 20.0-mig-attachment_preview branch 2 times, most recently from ef98f41 to 6a54c95 Compare October 3, 2026 01:03
The Python side is two API removals; the JavaScript is most of the work,
because 20.0 removed the two things this module was built on.

* ir.attachment.datas is gone. get_attachment_extension reads `raw`, and the
  tests create attachments with it.
* Binary fields are handed over as a BinaryValue holding the RAW bytes, where
  19.0 gave base64. Two consequences in get_binary_extension: magic.from_buffer
  needs real bytes, and the data-URL fallback has to b64-encode rather than
  decode, or it feeds raw bytes to a utf-8 decode. bin_size no longer changes
  the value either, so the "load fully" second pass gets content either way.
  The field read is still inside a try, and an unreadable or empty value now
  skips the sniffing entirely rather than sniffing b"": this method takes
  the field NAME from its caller, 19.0 raised inside that try and left the
  result falsy, and libmagic does not agree with itself about an empty
  buffer -- empty file locally, text/plain on the CI image.
* The Store hook was renamed AND reshaped: _to_store_defaults ->
  _store_attachment_fields(res: Store.FieldList, **kwargs), which appends to the
  list it is given instead of returning one. The old name could not be reached
  even if it still existed, because Store.add only accepts a method name
  starting with _store_ and ending with _fields (discuss.py _get_fields_method).
  Left unported the extension simply never reaches the client and every preview
  button disappears, with nothing failing anywhere.
* jQuery is gone from 20.0 entirely -- no lib, no bundle entry, and
  @web/core/ensure_jquery deleted -- so all nine call sites are vanilla DOM now.
  Where jQuery silently absorbed a null (an unrendered t-ref, a selector
  matching nothing) the replacement is guarded explicitly rather than throwing.
* Owl 3 removed the static Component.env. The widget uses this.env.bus, and
  showPreview takes an `env` argument since it is a free function; both of its
  callers are component methods.
* Owl 3 raises on `static props`, and the throw propagates out of whatever
  mounted the component. With none to declare, core's answer is to omit it.
* Owl 3 resolves a bare identifier in a template against the render context
  rather than the component, so the four t-on-click handlers are qualified with
  this. -- unqualified they are silently undefined.
* The preview button is inserted next to 20.0's download button, which is now
  `oi btn btn-link o_download_file_button` with data-icon="download". The old
  `button.fa-download` selector matched nothing. Keyed on the semantic class,
  scoped to the component's own DOM where Owl still offers it, and declining to
  attach at all when the match is ambiguous.
* FontAwesome is gone, so the six icons are `oi` with a data-icon ligature.

* Owl 3 replaced the reactivity primitives outright. useState and useRef are
  not exported at all and core calls neither: state is proxy({...})
  (search_panel.js:54) and a ref is a signal typed t.ref(), declared as a class
  field, bound with t-ref="this.<name>" and read by CALLING it
  (emoji_picker.js:96-98). Until this was fixed the widget threw "useState is
  not a function" from setup(), which took the whole form view down behind an
  "Oops!" dialog -- on any record carrying an attachment.
* AttachmentList.previewableAttachments is gone. Core builds the equivalent
  inline in its own onClickAttachment (attachment_list.js:166), so the override
  takes it from props.attachmentGroups the same way. Left as it was, the list
  arrived undefined, showPreview fell through to its window.open branch, and the
  side panel silently never opened -- no error anywhere.
* _onPreviewAttachment is handed its event instead of reading the implicit
  global window.event, which is only set while a native handler is on the stack.
  Owl 3 invokes the handler through a wrapper, so the global was undefined and
  the click did nothing, again silently.

* 20.0's BaseCommon runs each test as a non-superuser test user holding
  base.group_user, where 19.0 left cls.env as the superuser. The one block that
  reaches for a non-attachment model with a Binary field -- ir.module.module's
  icon_image -- therefore needs sudo, since that model is readable only by
  base.group_system. Caught by CI, not locally.

Signed-off-by: Don Kendall <dkendall@ledoweb.com>
Assisted-by: Claude Opus 5
@dnplkndll
dnplkndll force-pushed the 20.0-mig-attachment_preview branch from 6a54c95 to ee3c919 Compare October 3, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.