odoo-addon-fs-attachment-s3 19.0.1.1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,516 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
6
+ <title>README.rst</title>
7
+ <style type="text/css">
8
+
9
+ /*
10
+ :Author: David Goodger (goodger@python.org)
11
+ :Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
12
+ :Copyright: This stylesheet has been placed in the public domain.
13
+
14
+ Default cascading style sheet for the HTML output of Docutils.
15
+ Despite the name, some widely supported CSS2 features are used.
16
+
17
+ See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
18
+ customize this style sheet.
19
+ */
20
+
21
+ /* used to remove borders from tables and images */
22
+ .borderless, table.borderless td, table.borderless th {
23
+ border: 0 }
24
+
25
+ table.borderless td, table.borderless th {
26
+ /* Override padding for "table.docutils td" with "! important".
27
+ The right padding separates the table cells. */
28
+ padding: 0 0.5em 0 0 ! important }
29
+
30
+ .first {
31
+ /* Override more specific margin styles with "! important". */
32
+ margin-top: 0 ! important }
33
+
34
+ .last, .with-subtitle {
35
+ margin-bottom: 0 ! important }
36
+
37
+ .hidden {
38
+ display: none }
39
+
40
+ .subscript {
41
+ vertical-align: sub;
42
+ font-size: smaller }
43
+
44
+ .superscript {
45
+ vertical-align: super;
46
+ font-size: smaller }
47
+
48
+ a.toc-backref {
49
+ text-decoration: none ;
50
+ color: black }
51
+
52
+ blockquote.epigraph {
53
+ margin: 2em 5em ; }
54
+
55
+ dl.docutils dd {
56
+ margin-bottom: 0.5em }
57
+
58
+ object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
59
+ overflow: hidden;
60
+ }
61
+
62
+ /* Uncomment (and remove this text!) to get bold-faced definition list terms
63
+ dl.docutils dt {
64
+ font-weight: bold }
65
+ */
66
+
67
+ div.abstract {
68
+ margin: 2em 5em }
69
+
70
+ div.abstract p.topic-title {
71
+ font-weight: bold ;
72
+ text-align: center }
73
+
74
+ div.admonition, div.attention, div.caution, div.danger, div.error,
75
+ div.hint, div.important, div.note, div.tip, div.warning {
76
+ margin: 2em ;
77
+ border: medium outset ;
78
+ padding: 1em }
79
+
80
+ div.admonition p.admonition-title, div.hint p.admonition-title,
81
+ div.important p.admonition-title, div.note p.admonition-title,
82
+ div.tip p.admonition-title {
83
+ font-weight: bold ;
84
+ font-family: sans-serif }
85
+
86
+ div.attention p.admonition-title, div.caution p.admonition-title,
87
+ div.danger p.admonition-title, div.error p.admonition-title,
88
+ div.warning p.admonition-title, .code .error {
89
+ color: red ;
90
+ font-weight: bold ;
91
+ font-family: sans-serif }
92
+
93
+ /* Uncomment (and remove this text!) to get reduced vertical space in
94
+ compound paragraphs.
95
+ div.compound .compound-first, div.compound .compound-middle {
96
+ margin-bottom: 0.5em }
97
+
98
+ div.compound .compound-last, div.compound .compound-middle {
99
+ margin-top: 0.5em }
100
+ */
101
+
102
+ div.dedication {
103
+ margin: 2em 5em ;
104
+ text-align: center ;
105
+ font-style: italic }
106
+
107
+ div.dedication p.topic-title {
108
+ font-weight: bold ;
109
+ font-style: normal }
110
+
111
+ div.figure {
112
+ margin-left: 2em ;
113
+ margin-right: 2em }
114
+
115
+ div.footer, div.header {
116
+ clear: both;
117
+ font-size: smaller }
118
+
119
+ div.line-block {
120
+ display: block ;
121
+ margin-top: 1em ;
122
+ margin-bottom: 1em }
123
+
124
+ div.line-block div.line-block {
125
+ margin-top: 0 ;
126
+ margin-bottom: 0 ;
127
+ margin-left: 1.5em }
128
+
129
+ div.sidebar {
130
+ margin: 0 0 0.5em 1em ;
131
+ border: medium outset ;
132
+ padding: 1em ;
133
+ background-color: #ffffee ;
134
+ width: 40% ;
135
+ float: right ;
136
+ clear: right }
137
+
138
+ div.sidebar p.rubric {
139
+ font-family: sans-serif ;
140
+ font-size: medium }
141
+
142
+ div.system-messages {
143
+ margin: 5em }
144
+
145
+ div.system-messages h1 {
146
+ color: red }
147
+
148
+ div.system-message {
149
+ border: medium outset ;
150
+ padding: 1em }
151
+
152
+ div.system-message p.system-message-title {
153
+ color: red ;
154
+ font-weight: bold }
155
+
156
+ div.topic {
157
+ margin: 2em }
158
+
159
+ h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
160
+ h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
161
+ margin-top: 0.4em }
162
+
163
+ h1.title {
164
+ text-align: center }
165
+
166
+ h2.subtitle {
167
+ text-align: center }
168
+
169
+ hr.docutils {
170
+ width: 75% }
171
+
172
+ img.align-left, .figure.align-left, object.align-left, table.align-left {
173
+ clear: left ;
174
+ float: left ;
175
+ margin-right: 1em }
176
+
177
+ img.align-right, .figure.align-right, object.align-right, table.align-right {
178
+ clear: right ;
179
+ float: right ;
180
+ margin-left: 1em }
181
+
182
+ img.align-center, .figure.align-center, object.align-center {
183
+ display: block;
184
+ margin-left: auto;
185
+ margin-right: auto;
186
+ }
187
+
188
+ table.align-center {
189
+ margin-left: auto;
190
+ margin-right: auto;
191
+ }
192
+
193
+ .align-left {
194
+ text-align: left }
195
+
196
+ .align-center {
197
+ clear: both ;
198
+ text-align: center }
199
+
200
+ .align-right {
201
+ text-align: right }
202
+
203
+ /* reset inner alignment in figures */
204
+ div.align-right {
205
+ text-align: inherit }
206
+
207
+ /* div.align-center * { */
208
+ /* text-align: left } */
209
+
210
+ .align-top {
211
+ vertical-align: top }
212
+
213
+ .align-middle {
214
+ vertical-align: middle }
215
+
216
+ .align-bottom {
217
+ vertical-align: bottom }
218
+
219
+ ol.simple, ul.simple {
220
+ margin-bottom: 1em }
221
+
222
+ ol.arabic {
223
+ list-style: decimal }
224
+
225
+ ol.loweralpha {
226
+ list-style: lower-alpha }
227
+
228
+ ol.upperalpha {
229
+ list-style: upper-alpha }
230
+
231
+ ol.lowerroman {
232
+ list-style: lower-roman }
233
+
234
+ ol.upperroman {
235
+ list-style: upper-roman }
236
+
237
+ p.attribution {
238
+ text-align: right ;
239
+ margin-left: 50% }
240
+
241
+ p.caption {
242
+ font-style: italic }
243
+
244
+ p.credits {
245
+ font-style: italic ;
246
+ font-size: smaller }
247
+
248
+ p.label {
249
+ white-space: nowrap }
250
+
251
+ p.rubric {
252
+ font-weight: bold ;
253
+ font-size: larger ;
254
+ color: maroon ;
255
+ text-align: center }
256
+
257
+ p.sidebar-title {
258
+ font-family: sans-serif ;
259
+ font-weight: bold ;
260
+ font-size: larger }
261
+
262
+ p.sidebar-subtitle {
263
+ font-family: sans-serif ;
264
+ font-weight: bold }
265
+
266
+ p.topic-title {
267
+ font-weight: bold }
268
+
269
+ pre.address {
270
+ margin-bottom: 0 ;
271
+ margin-top: 0 ;
272
+ font: inherit }
273
+
274
+ pre.literal-block, pre.doctest-block, pre.math, pre.code {
275
+ margin-left: 2em ;
276
+ margin-right: 2em }
277
+
278
+ pre.code .ln { color: gray; } /* line numbers */
279
+ pre.code, code { background-color: #eeeeee }
280
+ pre.code .comment, code .comment { color: #5C6576 }
281
+ pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
282
+ pre.code .literal.string, code .literal.string { color: #0C5404 }
283
+ pre.code .name.builtin, code .name.builtin { color: #352B84 }
284
+ pre.code .deleted, code .deleted { background-color: #DEB0A1}
285
+ pre.code .inserted, code .inserted { background-color: #A3D289}
286
+
287
+ span.classifier {
288
+ font-family: sans-serif ;
289
+ font-style: oblique }
290
+
291
+ span.classifier-delimiter {
292
+ font-family: sans-serif ;
293
+ font-weight: bold }
294
+
295
+ span.interpreted {
296
+ font-family: sans-serif }
297
+
298
+ span.option {
299
+ white-space: nowrap }
300
+
301
+ span.pre {
302
+ white-space: pre }
303
+
304
+ span.problematic, pre.problematic {
305
+ color: red }
306
+
307
+ span.section-subtitle {
308
+ /* font-size relative to parent (h1..h6 element) */
309
+ font-size: 80% }
310
+
311
+ table.citation {
312
+ border-left: solid 1px gray;
313
+ margin-left: 1px }
314
+
315
+ table.docinfo {
316
+ margin: 2em 4em }
317
+
318
+ table.docutils {
319
+ margin-top: 0.5em ;
320
+ margin-bottom: 0.5em }
321
+
322
+ table.footnote {
323
+ border-left: solid 1px black;
324
+ margin-left: 1px }
325
+
326
+ table.docutils td, table.docutils th,
327
+ table.docinfo td, table.docinfo th {
328
+ padding-left: 0.5em ;
329
+ padding-right: 0.5em ;
330
+ vertical-align: top }
331
+
332
+ table.docutils th.field-name, table.docinfo th.docinfo-name {
333
+ font-weight: bold ;
334
+ text-align: left ;
335
+ white-space: nowrap ;
336
+ padding-left: 0 }
337
+
338
+ /* "booktabs" style (no vertical lines) */
339
+ table.docutils.booktabs {
340
+ border: 0px;
341
+ border-top: 2px solid;
342
+ border-bottom: 2px solid;
343
+ border-collapse: collapse;
344
+ }
345
+ table.docutils.booktabs * {
346
+ border: 0px;
347
+ }
348
+ table.docutils.booktabs th {
349
+ border-bottom: thin solid;
350
+ text-align: left;
351
+ }
352
+
353
+ h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
354
+ h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
355
+ font-size: 100% }
356
+
357
+ ul.auto-toc {
358
+ list-style-type: none }
359
+
360
+ </style>
361
+ </head>
362
+ <body>
363
+ <div class="document">
364
+
365
+
366
+ <a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
367
+ <img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
368
+ </a>
369
+ <div class="section" id="fs-attachment-s3">
370
+ <h1>Fs Attachment S3</h1>
371
+ <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
372
+ !! This file is generated by oca-gen-addon-readme !!
373
+ !! changes will be overwritten. !!
374
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
375
+ !! source digest: sha256:fa6cd8ca3c6ea2e5d68db8c84d27f352c43b8082fc09986d14a15a7adbbc1050
376
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
377
+ <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/storage/tree/19.0/fs_attachment_s3"><img alt="OCA/storage" src="https://img.shields.io/badge/github-OCA%2Fstorage-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/storage-19-0/storage-19-0-fs_attachment_s3"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/storage&amp;target_branch=19.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
378
+ <p>This module extends the functionality of
379
+ <a class="reference external" href="https://github.com/OCA/storage/tree/16.0/fs_attachment">fs_attachment</a>
380
+ to better support Amazon S3 storage. It includes features such as:</p>
381
+ <ul class="simple">
382
+ <li>Special handling of X-Accel-Redirect headers for S3 storages.</li>
383
+ <li>Options for using signed URLs in X-Accel-Redirect. (This is required
384
+ to be able to serve files from a private S3 bucket using
385
+ X-Accel-Redirect without exposing the files publicly.)</li>
386
+ <li>Enforcing the mimetype of files stored in S3.</li>
387
+ </ul>
388
+ <p><strong>Table of contents</strong></p>
389
+ <div class="contents local topic" id="contents">
390
+ <ul class="simple">
391
+ <li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
392
+ <li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
393
+ <li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
394
+ <li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
395
+ <li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
396
+ <li><a class="reference internal" href="#other-credits" id="toc-entry-6">Other credits</a></li>
397
+ <li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
398
+ </ul>
399
+ </li>
400
+ </ul>
401
+ </div>
402
+ <div class="section" id="configuration">
403
+ <h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
404
+ <p>On the Odoo instance, go to <em>Settings</em> &gt; <em>Technical</em> &gt; <em>Storage</em> &gt; <em>File
405
+ Storage</em>.</p>
406
+ <p>When you create a new storage for s3 or modify an existing one, when you
407
+ activate the option “Use X-Sendfile To Serve Internal Url”, 2 additional
408
+ fields will appear:</p>
409
+ <ul class="simple">
410
+ <li><strong>S3 Uses Signed URL For X-Accel-Redirect</strong>: If checked, the
411
+ X-Accel-Redirect path will be a signed URL, which is useful for S3
412
+ storages that require signed URLs for access.</li>
413
+ <li><strong>S3 Signed URL Expiration</strong>: The expiration time for the signed URL
414
+ in seconds. This field is only relevant if the previous option is
415
+ checked. By default, it is set to 30 seconds but it could be less
416
+ since the url generated into the X-Accel-Redirect process is directly
417
+ used by the web server to serve the file.</li>
418
+ </ul>
419
+ <p>The value of these fields can also be set in the server environment
420
+ variables using the keys:</p>
421
+ <ul class="simple">
422
+ <li><em>s3_uses_signed_url_for_x_sendfile</em></li>
423
+ <li><em>s3_signed_url_expiration</em></li>
424
+ </ul>
425
+ <p>When the option “Use X-Sendfile To Serve Internal Url” is enabled, the
426
+ system will generate an X-Accel-Redirect header in the response to a
427
+ request to get a file. In the case of S3 storages, it will follow the
428
+ format:</p>
429
+ <pre class="code text literal-block">
430
+ X-Accel-Redirect: /fs_x_sendfile/{scheme}/{host}/{path with query if any}
431
+ </pre>
432
+ <p>Where:</p>
433
+ <ul class="simple">
434
+ <li><tt class="docutils literal">{scheme}</tt>: The URL scheme (http or https).</li>
435
+ <li><tt class="docutils literal">{host}</tt>: The host of the S3 storage.</li>
436
+ <li><tt class="docutils literal">{path with query if any}</tt>: The path to the file in the S3 storage,
437
+ including any query parameters. (Query parameters are set when the
438
+ <tt class="docutils literal">s3_uses_signed_url_for_x_sendfile</tt> option is enabled.)</li>
439
+ </ul>
440
+ <p>In order to serve files using X-Accel-Redirect, you must ensure that
441
+ your web server is configured to handle these headers correctly. This
442
+ typically involves setting up a location block in your web server
443
+ configuration that matches the X-Accel-Redirect path and proxies the
444
+ request to the S3 storage.</p>
445
+ <p>For example, if you are using Nginx, you would add a location block like
446
+ this:</p>
447
+ <pre class="code nginx literal-block">
448
+ <span class="k">location</span><span class="w"> </span><span class="p">~</span><span class="w"> </span><span class="sr">^/fs_x_sendfile/(.*?)/(.*?)/(.*)</span><span class="w"> </span><span class="p">{</span><span class="w">
449
+ </span><span class="kn">internal</span><span class="p">;</span><span class="w">
450
+ </span><span class="kn">set</span><span class="w"> </span><span class="nv">$url_scheme</span><span class="w"> </span><span class="nv">$1</span><span class="p">;</span><span class="w">
451
+ </span><span class="kn">set</span><span class="w"> </span><span class="nv">$url_host</span><span class="w"> </span><span class="nv">$2</span><span class="p">;</span><span class="w">
452
+ </span><span class="kn">set</span><span class="w"> </span><span class="nv">$url_path</span><span class="w"> </span><span class="nv">$3</span><span class="p">;</span><span class="w">
453
+ </span><span class="kn">set</span><span class="w"> </span><span class="nv">$url</span><span class="w"> </span><span class="nv">$url_scheme://$url_host/$url_path</span><span class="p">;</span><span class="w">
454
+
455
+ </span><span class="kn">proxy_pass</span><span class="w"> </span><span class="nv">$url$is_args$args</span><span class="p">;</span><span class="w">
456
+ </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">Host</span><span class="w"> </span><span class="nv">$url_host</span><span class="p">;</span><span class="w">
457
+ </span><span class="kn">proxy_ssl_server_name</span><span class="w"> </span><span class="no">on</span><span class="p">;</span><span class="w">
458
+
459
+ </span><span class="p">}</span>
460
+ </pre>
461
+ <p>Unlike the standard implementation of X-Accel-Redirect on non S3
462
+ storages, the S3 implementation does not require a base URL to be set in
463
+ the storage configuration. The X-Accel-Redirect path is constructed
464
+ directly from the S3 storage’s URL defined for the connection, the
465
+ directory name as bucket name, and the file path.</p>
466
+ </div>
467
+ <div class="section" id="bug-tracker">
468
+ <h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
469
+ <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/storage/issues">GitHub Issues</a>.
470
+ In case of trouble, please check there if your issue has already been reported.
471
+ If you spotted it first, help us to smash it by providing a detailed and welcomed
472
+ <a class="reference external" href="https://github.com/OCA/storage/issues/new?body=module:%20fs_attachment_s3%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
473
+ <p>Do not contact contributors directly about support or help with technical issues.</p>
474
+ </div>
475
+ <div class="section" id="credits">
476
+ <h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
477
+ <div class="section" id="authors">
478
+ <h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
479
+ <ul class="simple">
480
+ <li>ACSONE SA/NV</li>
481
+ </ul>
482
+ </div>
483
+ <div class="section" id="contributors">
484
+ <h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
485
+ <ul class="simple">
486
+ <li>Laurent Mignon <a class="reference external" href="mailto:laurent.mignon&#64;acsone.eu">laurent.mignon&#64;acsone.eu</a> (<a class="reference external" href="https://www.acsone.eu">https://www.acsone.eu</a>)</li>
487
+ <li>Stéphane Bidoul <a class="reference external" href="mailto:stephane.bidoul&#64;acsone.eu">stephane.bidoul&#64;acsone.eu</a> (<a class="reference external" href="https://www.acsone.eu">https://www.acsone.eu</a>)</li>
488
+ </ul>
489
+ </div>
490
+ <div class="section" id="other-credits">
491
+ <h3><a class="toc-backref" href="#toc-entry-6">Other credits</a></h3>
492
+ <p>The development of this module has been financially supported by:</p>
493
+ <ul class="simple">
494
+ <li>ACSONE SA/NV (<a class="reference external" href="https://www.acsone.eu">https://www.acsone.eu</a>)</li>
495
+ <li>Alcyon Belux</li>
496
+ </ul>
497
+ </div>
498
+ <div class="section" id="maintainers">
499
+ <h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
500
+ <p>This module is maintained by the OCA.</p>
501
+ <a class="reference external image-reference" href="https://odoo-community.org">
502
+ <img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
503
+ </a>
504
+ <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
505
+ mission is to support the collaborative development of Odoo features and
506
+ promote its widespread use.</p>
507
+ <p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
508
+ <p><a class="reference external image-reference" href="https://github.com/lmignon"><img alt="lmignon" src="https://github.com/lmignon.png?size=40px" /></a></p>
509
+ <p>This module is part of the <a class="reference external" href="https://github.com/OCA/storage/tree/19.0/fs_attachment_s3">OCA/storage</a> project on GitHub.</p>
510
+ <p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
511
+ </div>
512
+ </div>
513
+ </div>
514
+ </div>
515
+ </body>
516
+ </html>
@@ -0,0 +1 @@
1
+ from . import test_fs_attachment_s3
@@ -0,0 +1,63 @@
1
+ # Copyright 2025 ACSONE SA/NV (http://acsone.eu).
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from odoo.tests.common import TransactionCase
5
+
6
+ from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
7
+
8
+
9
+ class TestFSAttachmentS3Common(TransactionCase):
10
+ @classmethod
11
+ def setUpClass(cls):
12
+ super().setUpClass()
13
+ cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT))
14
+ cls.s3_backend_config = {
15
+ "name": "S3 Storage",
16
+ "protocol": "s3",
17
+ "code": "s3tst",
18
+ "directory_path": "test-bucket",
19
+ "json_options": {
20
+ "key": "aws-key",
21
+ "secret": "aws-secret",
22
+ "client_kwargs": {
23
+ "endpoint_url": "http://minio.minio/",
24
+ "region_name": "aws-region",
25
+ },
26
+ },
27
+ "base_url": False, # S3 does not use base_url for x-sendfile
28
+ }
29
+ cls.s3_backend = cls.env["fs.storage"].create(cls.s3_backend_config)
30
+ cls.ir_attachment_model = cls.env["ir.attachment"]
31
+
32
+ cls.fake_attachment_s3 = cls.env["ir.attachment"].create(
33
+ {
34
+ "name": "fake_s3_file.txt",
35
+ "fs_storage_id": cls.s3_backend.id,
36
+ }
37
+ )
38
+ cls.fake_attachment_s3.flush_recordset()
39
+ # update the attachment into database since we don't have a real S3 bucket
40
+ # and we can't use moto to mock S3 since s3fs rely on aiobotocore
41
+ cls.env.cr.execute(
42
+ """
43
+ UPDATE
44
+ ir_attachment
45
+ SET
46
+ store_fname = 's3tst://dir/sub/fake_s3_file.txt',
47
+ fs_filename = 'fake_s3_file.txt',
48
+ fs_storage_code = 's3tst',
49
+ checksum = 234,
50
+ file_size = 1234,
51
+ fs_storage_id = %s
52
+ WHERE
53
+ id = %s
54
+ """,
55
+ (cls.s3_backend.id, cls.fake_attachment_s3.id),
56
+ )
57
+ cls.fake_attachment_s3.invalidate_recordset()
58
+
59
+ def setUp(self):
60
+ super().setUp()
61
+ # enforce backend_config fields since it seems that they are reset on
62
+ # savepoint rollback when managed by server_environment -> TO Be investigated
63
+ self.s3_backend.write(self.s3_backend_config)
@@ -0,0 +1,34 @@
1
+ # Copyright 2025 ACSONE SA/NV (http://acsone.eu).
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+
5
+ from .common import TestFSAttachmentS3Common
6
+
7
+
8
+ class TestFSAttachementS3(TestFSAttachmentS3Common):
9
+ def test_get_x_sendfile_path_s3_signed(self):
10
+ """Test the X-Accel-Redirect path generation for S3 storage."""
11
+ self.s3_backend.write(
12
+ {
13
+ "s3_uses_signed_url_for_x_sendfile": True,
14
+ "s3_signed_url_expiration": 60,
15
+ }
16
+ )
17
+
18
+ url = self.fake_attachment_s3._get_x_sendfile_path()
19
+ self.assertTrue(
20
+ url.startswith(
21
+ "/fs_x_sendfile/http/minio.minio/test-bucket/dir/sub/fake_s3_file.txt?"
22
+ ),
23
+ "The end of the path should contain the path to the file "
24
+ f"name and query parameters. ({url})",
25
+ )
26
+
27
+ def test_get_x_sendfile_path(self):
28
+ """Test the X-Accel-Redirect path generation."""
29
+ url = self.fake_attachment_s3._get_x_sendfile_path()
30
+ self.assertEqual(
31
+ url,
32
+ "/fs_x_sendfile/http/minio.minio/test-bucket/dir/sub/fake_s3_file.txt",
33
+ f"The X-Accel-Redirect path should match the expected format. ({url})",
34
+ )
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <!-- Copyright 2025 ACSONE SA/NV
3
+ License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
4
+ <odoo>
5
+ <record model="ir.ui.view" id="fs_storage_form_view">
6
+ <field name="model">fs.storage</field>
7
+ <field name="inherit_id" ref="fs_attachment.fs_storage_form_view" />
8
+ <field name="arch" type="xml">
9
+ <field name="use_x_sendfile_to_serve_internal_url" position="after">
10
+ <field
11
+ name="s3_uses_signed_url_for_x_sendfile"
12
+ invisible="not use_x_sendfile_to_serve_internal_url or protocol != 's3'"
13
+ />
14
+ <field
15
+ name="s3_signed_url_expiration"
16
+ invisible="not use_x_sendfile_to_serve_internal_url or protocol != 's3'"
17
+ />
18
+ </field>
19
+ </field>
20
+ </record>
21
+ </odoo>