odoo-addon-dms 17.0.1.2.0__py3-none-any.whl → 18.0.1.0.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.
- odoo/addons/dms/README.rst +69 -62
- odoo/addons/dms/__manifest__.py +1 -5
- odoo/addons/dms/data/onboarding_data.xml +0 -1
- odoo/addons/dms/i18n/de.po +0 -203
- odoo/addons/dms/i18n/dms.pot +51 -55
- odoo/addons/dms/i18n/es.po +0 -214
- odoo/addons/dms/i18n/fr.po +0 -45
- odoo/addons/dms/i18n/he_IL.po +0 -139
- odoo/addons/dms/i18n/it.po +0 -219
- odoo/addons/dms/i18n/nl.po +0 -6
- odoo/addons/dms/i18n/pt.po +0 -215
- odoo/addons/dms/i18n/pt_BR.po +0 -201
- odoo/addons/dms/models/access_groups.py +9 -8
- odoo/addons/dms/models/directory.py +21 -23
- odoo/addons/dms/models/dms_category.py +2 -2
- odoo/addons/dms/models/dms_file.py +15 -16
- odoo/addons/dms/models/dms_security_mixin.py +46 -20
- odoo/addons/dms/models/ir_binary.py +2 -2
- odoo/addons/dms/models/mixins_thumbnail.py +1 -1
- odoo/addons/dms/models/storage.py +3 -3
- odoo/addons/dms/readme/CONTRIBUTORS.md +2 -0
- odoo/addons/dms/readme/ROADMAP.md +2 -0
- odoo/addons/dms/static/description/index.html +13 -6
- odoo/addons/dms/static/src/js/fields/path_json/path_owl.esm.js +5 -5
- odoo/addons/dms/static/src/js/fields/preview_binary/preview_record.esm.js +2 -4
- odoo/addons/dms/static/src/js/views/dms_file_upload.esm.js +2 -5
- odoo/addons/dms/static/src/js/views/file_kanban_controller.esm.js +0 -2
- odoo/addons/dms/static/src/js/views/file_kanban_record.esm.js +0 -2
- odoo/addons/dms/static/src/js/views/file_kanban_renderer.esm.js +0 -2
- odoo/addons/dms/static/src/js/views/file_kanban_view.esm.js +0 -2
- odoo/addons/dms/static/src/js/views/file_list_controller.esm.js +0 -2
- odoo/addons/dms/static/src/js/views/file_list_renderer.esm.js +0 -2
- odoo/addons/dms/static/src/js/views/file_list_view.esm.js +0 -2
- odoo/addons/dms/static/src/js/views/search_panel.esm.js +1 -5
- odoo/addons/dms/static/src/models/attachment.esm.js +0 -2
- odoo/addons/dms/static/src/models/attachment_image.esm.js +0 -2
- odoo/addons/dms/static/src/models/attachment_viewer_viewable.esm.js +0 -2
- odoo/addons/dms/static/tests/tours/dms_portal_tour.esm.js +3 -7
- odoo/addons/dms/template/portal.xml +8 -6
- odoo/addons/dms/tests/common.py +9 -10
- odoo/addons/dms/tests/test_benchmark.py +19 -1
- odoo/addons/dms/tests/test_directory.py +8 -5
- odoo/addons/dms/tests/test_file_database.py +1 -1
- odoo/addons/dms/tests/test_portal.py +16 -17
- odoo/addons/dms/views/dms_access_groups_views.xml +13 -13
- odoo/addons/dms/views/dms_category.xml +5 -5
- odoo/addons/dms/views/dms_directory.xml +101 -110
- odoo/addons/dms/views/dms_file.xml +66 -84
- odoo/addons/dms/views/dms_tag.xml +14 -33
- odoo/addons/dms/views/menu.xml +22 -13
- odoo/addons/dms/views/storage.xml +189 -193
- odoo/addons/dms/wizards/wizard_dms_file_move_views.xml +1 -1
- {odoo_addon_dms-17.0.1.2.0.dist-info → odoo_addon_dms-18.0.1.0.0.2.dist-info}/METADATA +72 -65
- {odoo_addon_dms-17.0.1.2.0.dist-info → odoo_addon_dms-18.0.1.0.0.2.dist-info}/RECORD +56 -59
- odoo/addons/dms/static/src/scss/directory_kanban.scss +0 -53
- odoo/addons/dms/static/src/scss/dms_common.scss +0 -69
- odoo/addons/dms/static/src/scss/file_kanban.scss +0 -70
- {odoo_addon_dms-17.0.1.2.0.dist-info → odoo_addon_dms-18.0.1.0.0.2.dist-info}/WHEEL +0 -0
- {odoo_addon_dms-17.0.1.2.0.dist-info → odoo_addon_dms-18.0.1.0.0.2.dist-info}/top_level.txt +0 -0
odoo/addons/dms/i18n/pt_BR.po
CHANGED
@@ -2299,204 +2299,3 @@ msgstr "exe,msi"
|
|
2299
2299
|
#: model_terms:ir.ui.view,arch_db:dms.view_dms_directory_form
|
2300
2300
|
msgid "mail.catchall.domain"
|
2301
2301
|
msgstr ""
|
2302
|
-
|
2303
|
-
#~ msgid ""
|
2304
|
-
#~ "<i class=\"fa fa-archive\"/>\n"
|
2305
|
-
#~ " Archive"
|
2306
|
-
#~ msgstr ""
|
2307
|
-
#~ "<i class=\"fa fa-archive\"/>\n"
|
2308
|
-
#~ " Arquivar"
|
2309
|
-
|
2310
|
-
#~ msgid ""
|
2311
|
-
#~ "<i class=\"fa fa-archive\"/>\n"
|
2312
|
-
#~ " Unarchive"
|
2313
|
-
#~ msgstr ""
|
2314
|
-
#~ "<i class=\"fa fa-archive\"/>\n"
|
2315
|
-
#~ " Desarquivar"
|
2316
|
-
|
2317
|
-
#~ msgid ""
|
2318
|
-
#~ "<i class=\"fa fa-download\"/>\n"
|
2319
|
-
#~ " Download"
|
2320
|
-
#~ msgstr ""
|
2321
|
-
#~ "<i class=\"fa fa-download\"/>\n"
|
2322
|
-
#~ " Baixar"
|
2323
|
-
|
2324
|
-
#~ msgid ""
|
2325
|
-
#~ "<i class=\"fa fa-external-link\"/>\n"
|
2326
|
-
#~ " Open"
|
2327
|
-
#~ msgstr ""
|
2328
|
-
#~ "<i class=\"fa fa-external-link\"/>\n"
|
2329
|
-
#~ " Abrir"
|
2330
|
-
|
2331
|
-
#~ msgid ""
|
2332
|
-
#~ "<i class=\"fa fa-external-link\"/>\n"
|
2333
|
-
#~ " Open"
|
2334
|
-
#~ msgstr ""
|
2335
|
-
#~ "<i class=\"fa fa-external-link\"/>\n"
|
2336
|
-
#~ " Abrir"
|
2337
|
-
|
2338
|
-
#~ msgid ""
|
2339
|
-
#~ "<i class=\"fa fa-file-o\"/>\n"
|
2340
|
-
#~ " Files"
|
2341
|
-
#~ msgstr ""
|
2342
|
-
#~ "<i class=\"fa fa-external-link\"/>\n"
|
2343
|
-
#~ " Abrir"
|
2344
|
-
|
2345
|
-
#~ msgid ""
|
2346
|
-
#~ "<i class=\"fa fa-folder-o\"/>\n"
|
2347
|
-
#~ " Directories"
|
2348
|
-
#~ msgstr ""
|
2349
|
-
#~ "<i class=\"fa fa-folder-o\"/>\n"
|
2350
|
-
#~ " Diretórios"
|
2351
|
-
|
2352
|
-
#~ msgid ""
|
2353
|
-
#~ "<i class=\"fa fa-lock\"/>\n"
|
2354
|
-
#~ " Lock"
|
2355
|
-
#~ msgstr ""
|
2356
|
-
#~ "<i class=\"fa fa-lock\"/>\n"
|
2357
|
-
#~ " Bloquear"
|
2358
|
-
|
2359
|
-
#~ msgid ""
|
2360
|
-
#~ "<i class=\"fa fa-pencil-square-o\"/>\n"
|
2361
|
-
#~ " Edit"
|
2362
|
-
#~ msgstr ""
|
2363
|
-
#~ "<i class=\"fa fa-pencil-square-o\"/>\n"
|
2364
|
-
#~ " Editar"
|
2365
|
-
|
2366
|
-
#~ msgid ""
|
2367
|
-
#~ "<i class=\"fa fa-pencil-square-o\"/>\n"
|
2368
|
-
#~ " Edit"
|
2369
|
-
#~ msgstr ""
|
2370
|
-
#~ "<i class=\"fa fa-pencil-square-o\"/>\n"
|
2371
|
-
#~ " Editar"
|
2372
|
-
|
2373
|
-
#~ msgid ""
|
2374
|
-
#~ "<i class=\"fa fa-trash-o\"/>\n"
|
2375
|
-
#~ " Delete"
|
2376
|
-
#~ msgstr ""
|
2377
|
-
#~ "<i class=\"fa fa-trash-o\"/>\n"
|
2378
|
-
#~ " Excluir"
|
2379
|
-
|
2380
|
-
#~ msgid ""
|
2381
|
-
#~ "<i class=\"fa fa-trash-o\"/>\n"
|
2382
|
-
#~ " Delete"
|
2383
|
-
#~ msgstr ""
|
2384
|
-
#~ "<i class=\"fa fa-trash-o\"/>\n"
|
2385
|
-
#~ " Excluir"
|
2386
|
-
|
2387
|
-
#~ msgid ""
|
2388
|
-
#~ "<i class=\"fa fa-unlock-alt\"/>\n"
|
2389
|
-
#~ " Unlock"
|
2390
|
-
#~ msgstr ""
|
2391
|
-
#~ "<i class=\"fa fa-unlock-alt\"/>\n"
|
2392
|
-
#~ " Destravar"
|
2393
|
-
|
2394
|
-
#~ msgid ""
|
2395
|
-
#~ "<span class=\"o_form_label\">\n"
|
2396
|
-
#~ " Storages\n"
|
2397
|
-
#~ " </span>"
|
2398
|
-
#~ msgstr ""
|
2399
|
-
#~ "<span class=\"o_form_label\">\n"
|
2400
|
-
#~ " Armazenamentos\n"
|
2401
|
-
#~ " </span>"
|
2402
|
-
|
2403
|
-
#~ msgid ""
|
2404
|
-
#~ "<span class=\"o_form_label\">File\n"
|
2405
|
-
#~ " Extensions\n"
|
2406
|
-
#~ " </span>"
|
2407
|
-
#~ msgstr ""
|
2408
|
-
#~ "<span class=\"o_form_label\">File\n"
|
2409
|
-
#~ " Extensões\n"
|
2410
|
-
#~ " </span>"
|
2411
|
-
|
2412
|
-
#~ msgid "<span class=\"o_form_label\">File Size</span>"
|
2413
|
-
#~ msgstr "<span class=\"o_form_label\">Tamanho do arquivo</span>"
|
2414
|
-
|
2415
|
-
#, python-format
|
2416
|
-
#~ msgid "A file with the same name already exists"
|
2417
|
-
#~ msgstr "Já existe um arquivo com o mesmo nome"
|
2418
|
-
|
2419
|
-
#, python-format
|
2420
|
-
#~ msgid "A file with the same name already exists."
|
2421
|
-
#~ msgstr "Já existe um arquivo com o mesmo nome."
|
2422
|
-
|
2423
|
-
#~ msgid "Alias domain"
|
2424
|
-
#~ msgstr "Domínio alternativo"
|
2425
|
-
|
2426
|
-
#~ msgid ""
|
2427
|
-
#~ "Define the maximum upload size of a\n"
|
2428
|
-
#~ " file in MB"
|
2429
|
-
#~ msgstr ""
|
2430
|
-
#~ "Defina o tamanho máximo de carregamento de um\n"
|
2431
|
-
#~ " arquivo em MB"
|
2432
|
-
|
2433
|
-
#~ msgid "Dropdown menu"
|
2434
|
-
#~ msgstr "Menu Suspenso"
|
2435
|
-
|
2436
|
-
#~ msgid ""
|
2437
|
-
#~ "Indicate if directories and files access work only with related model "
|
2438
|
-
#~ "access (for example, if some directories are related with any sale, only "
|
2439
|
-
#~ "users with read access to these sale can acess)"
|
2440
|
-
#~ msgstr ""
|
2441
|
-
#~ "Indica se o acesso a diretórios e arquivos funciona apenas com acesso de "
|
2442
|
-
#~ "modelo relacionado (por exemplo, se alguns diretórios estiverem "
|
2443
|
-
#~ "relacionados a alguma venda, apenas usuários com acesso de leitura a "
|
2444
|
-
#~ "essas vendas poderão acessar)"
|
2445
|
-
|
2446
|
-
#, python-format
|
2447
|
-
#~ msgid "It is not possible to change parent to other storage."
|
2448
|
-
#~ msgstr "Não é possível alterar o pai para outro armazenamento."
|
2449
|
-
|
2450
|
-
#~ msgid "Last Modified on"
|
2451
|
-
#~ msgstr "Última Modificação em"
|
2452
|
-
|
2453
|
-
#, python-format
|
2454
|
-
#~ msgid "Loading"
|
2455
|
-
#~ msgstr "Carregando"
|
2456
|
-
|
2457
|
-
#~ msgid "Main Attachment"
|
2458
|
-
#~ msgstr "Anexo Principal"
|
2459
|
-
|
2460
|
-
#~ msgid "Only admin user"
|
2461
|
-
#~ msgstr "Somente usuário administrador"
|
2462
|
-
|
2463
|
-
#~ msgid "Operations"
|
2464
|
-
#~ msgstr "Operações"
|
2465
|
-
|
2466
|
-
#~ msgid "Owner"
|
2467
|
-
#~ msgstr "Proprietário"
|
2468
|
-
|
2469
|
-
#~ msgid "The configuration is done!"
|
2470
|
-
#~ msgstr "A configuração está feita!"
|
2471
|
-
|
2472
|
-
#~ msgid ""
|
2473
|
-
#~ "The owner of records created upon receiving emails on this alias. If this "
|
2474
|
-
#~ "field is not set the system will attempt to find the right owner based on "
|
2475
|
-
#~ "the sender (From) address, or will use the Administrator account if no "
|
2476
|
-
#~ "system user is found for that address."
|
2477
|
-
#~ msgstr ""
|
2478
|
-
#~ "O proprietário dos registros criados ao receber e-mails neste alias. Se "
|
2479
|
-
#~ "este campo não estiver definido, o sistema tentará encontrar o "
|
2480
|
-
#~ "proprietário correto com base no endereço do remetente (De) ou usará a "
|
2481
|
-
#~ "conta de Administrador se nenhum usuário do sistema for encontrado para "
|
2482
|
-
#~ "esse endereço."
|
2483
|
-
|
2484
|
-
#~ msgid ""
|
2485
|
-
#~ "The save type is used to determine how a file is saved by the\n"
|
2486
|
-
#~ " system. If you change this setting, you can migrate existing "
|
2487
|
-
#~ "files\n"
|
2488
|
-
#~ " manually by triggering the action."
|
2489
|
-
#~ msgstr ""
|
2490
|
-
#~ "O tipo de salvamento é usado para determinar como um arquivo é salvo "
|
2491
|
-
#~ "pelo\n"
|
2492
|
-
#~ " sistema. Se você alterar essa configuração, poderá migrar os "
|
2493
|
-
#~ "arquivos existentes\n"
|
2494
|
-
#~ " manualmente acionando a ação."
|
2495
|
-
|
2496
|
-
#, python-format
|
2497
|
-
#~ msgid "Viewer"
|
2498
|
-
#~ msgstr "Visualizador"
|
2499
|
-
|
2500
|
-
#, python-format
|
2501
|
-
#~ msgid "Error has not been raised"
|
2502
|
-
#~ msgstr "O erro não foi levantado"
|
@@ -14,7 +14,7 @@ class DmsAccessGroups(models.Model):
|
|
14
14
|
_parent_name = "parent_group_id"
|
15
15
|
|
16
16
|
name = fields.Char(string="Group Name", required=True, translate=True)
|
17
|
-
parent_path = fields.Char(index="btree"
|
17
|
+
parent_path = fields.Char(index="btree")
|
18
18
|
|
19
19
|
# Permissions written directly on this group
|
20
20
|
perm_create = fields.Boolean(string="Create Access")
|
@@ -122,9 +122,9 @@ class DmsAccessGroups(models.Model):
|
|
122
122
|
for one in self:
|
123
123
|
one.update(
|
124
124
|
{
|
125
|
-
"perm_inclusive_
|
126
|
-
one["perm_
|
127
|
-
or one.parent_group_id["perm_inclusive_
|
125
|
+
f"perm_inclusive_{perm}": (
|
126
|
+
one[f"perm_{perm}"]
|
127
|
+
or one.parent_group_id[f"perm_inclusive_{perm}"]
|
128
128
|
)
|
129
129
|
for perm in ("create", "unlink", "write")
|
130
130
|
}
|
@@ -155,10 +155,11 @@ class DmsAccessGroups(models.Model):
|
|
155
155
|
)
|
156
156
|
record.update({"users": users, "count_users": len(users)})
|
157
157
|
|
158
|
-
def
|
159
|
-
|
160
|
-
|
161
|
-
|
158
|
+
def copy_data(self, default=None):
|
159
|
+
vals_list = super().copy_data(default)
|
160
|
+
for group, vals in zip(self, vals_list, strict=False):
|
161
|
+
vals["name"] = _("%s (copy)") % group.name
|
162
|
+
return vals_list
|
162
163
|
|
163
164
|
@api.constrains("parent_path")
|
164
165
|
def _check_parent_recursiveness(self):
|
@@ -17,8 +17,6 @@ from odoo.exceptions import UserError, ValidationError
|
|
17
17
|
from odoo.osv.expression import AND, OR
|
18
18
|
from odoo.tools import consteq, human_size
|
19
19
|
|
20
|
-
from odoo.addons.http_routing.models.ir_http import slugify
|
21
|
-
|
22
20
|
from ..tools.file import check_name, unique_name
|
23
21
|
|
24
22
|
_logger = logging.getLogger(__name__)
|
@@ -46,7 +44,7 @@ class DmsDirectory(models.Model):
|
|
46
44
|
_parent_name = "parent_id"
|
47
45
|
_directory_field = _parent_name
|
48
46
|
|
49
|
-
parent_path = fields.Char(index="btree"
|
47
|
+
parent_path = fields.Char(index="btree")
|
50
48
|
is_root_directory = fields.Boolean(
|
51
49
|
default=False,
|
52
50
|
help="""Indicates if the directory is a root directory.
|
@@ -216,7 +214,7 @@ class DmsDirectory(models.Model):
|
|
216
214
|
"""Special rules for directories."""
|
217
215
|
self_filter = [
|
218
216
|
("storage_id_inherit_access_from_parent_record", "=", False),
|
219
|
-
("id", "
|
217
|
+
("id", "in", self._get_access_groups_query(operation)),
|
220
218
|
]
|
221
219
|
# Upstream only filters by parent directory
|
222
220
|
result = super()._get_domain_by_access_groups(operation)
|
@@ -237,7 +235,7 @@ class DmsDirectory(models.Model):
|
|
237
235
|
def _compute_access_url(self):
|
238
236
|
res = super()._compute_access_url()
|
239
237
|
for item in self:
|
240
|
-
item.access_url = "/my/dms/directory
|
238
|
+
item.access_url = f"/my/dms/directory/{item.id}"
|
241
239
|
return res
|
242
240
|
|
243
241
|
def check_access_token(self, access_token=False):
|
@@ -278,7 +276,7 @@ class DmsDirectory(models.Model):
|
|
278
276
|
and consteq(current_directory.access_token, access_token)
|
279
277
|
)
|
280
278
|
or not access_token
|
281
|
-
and current_directory.
|
279
|
+
and current_directory.check_access("read")
|
282
280
|
):
|
283
281
|
return directories
|
284
282
|
current_directory = current_directory.parent_id
|
@@ -389,9 +387,8 @@ class DmsDirectory(models.Model):
|
|
389
387
|
def _compute_complete_name(self):
|
390
388
|
for category in self:
|
391
389
|
if category.parent_id:
|
392
|
-
category.complete_name =
|
393
|
-
category.parent_id.complete_name
|
394
|
-
category.name,
|
390
|
+
category.complete_name = (
|
391
|
+
f"{category.parent_id.complete_name} / {category.name}"
|
395
392
|
)
|
396
393
|
else:
|
397
394
|
category.complete_name = category.name
|
@@ -530,7 +527,7 @@ class DmsDirectory(models.Model):
|
|
530
527
|
# Constrains
|
531
528
|
@api.constrains("parent_id")
|
532
529
|
def _check_directory_recursion(self):
|
533
|
-
if
|
530
|
+
if self._has_cycle():
|
534
531
|
raise ValidationError(_("Error! You cannot create recursive directories."))
|
535
532
|
return True
|
536
533
|
|
@@ -594,18 +591,18 @@ class DmsDirectory(models.Model):
|
|
594
591
|
not_starred_records.write({"user_star_ids": [(4, self.env.uid)]})
|
595
592
|
starred_records.write({"user_star_ids": [(3, self.env.uid)]})
|
596
593
|
|
597
|
-
def
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
return
|
594
|
+
def copy_data(self, default=None):
|
595
|
+
vals_list = super().copy_data(default)
|
596
|
+
for directory, vals in zip(self, vals_list, strict=False):
|
597
|
+
if vals.get("parent_id"):
|
598
|
+
parent_directory = self.browse(vals.get("parent_id"))
|
599
|
+
names = parent_directory.sudo().child_directory_ids.mapped("name")
|
600
|
+
elif directory.is_root_directory:
|
601
|
+
names = self.sudo().storage_id.root_directory_ids.mapped("name")
|
602
|
+
else:
|
603
|
+
names = self.sudo().parent_id.child_directory_ids.mapped("name")
|
604
|
+
vals["name"] = unique_name(directory.name, names)
|
605
|
+
return vals_list
|
609
606
|
|
610
607
|
def _alias_get_creation_values(self):
|
611
608
|
values = super()._alias_get_creation_values()
|
@@ -628,7 +625,8 @@ class DmsDirectory(models.Model):
|
|
628
625
|
parent_directory._process_message(msg_dict)
|
629
626
|
return parent_directory
|
630
627
|
names = parent_directory.child_directory_ids.mapped("name")
|
631
|
-
|
628
|
+
slug = self.env["ir.http"]._slug
|
629
|
+
subject = slug(msg_dict.get("subject", _("Alias-Mail-Extraction")))
|
632
630
|
defaults = dict(
|
633
631
|
{"name": unique_name(subject, names, escape_suffix=True)}, **custom_values
|
634
632
|
)
|
@@ -40,7 +40,7 @@ class DMSCategory(models.Model):
|
|
40
40
|
comodel_name="dms.category",
|
41
41
|
inverse_name="parent_id",
|
42
42
|
)
|
43
|
-
parent_path = fields.Char(index="btree"
|
43
|
+
parent_path = fields.Char(index="btree")
|
44
44
|
tag_ids = fields.One2many(
|
45
45
|
string="Tags", comodel_name="dms.tag", inverse_name="category_id"
|
46
46
|
)
|
@@ -99,6 +99,6 @@ class DMSCategory(models.Model):
|
|
99
99
|
|
100
100
|
@api.constrains("parent_id")
|
101
101
|
def _check_category_recursion(self):
|
102
|
-
if
|
102
|
+
if self._has_cycle():
|
103
103
|
raise ValidationError(_("Error! You cannot create recursive categories."))
|
104
104
|
return True
|
@@ -150,14 +150,14 @@ class DMSFile(models.Model):
|
|
150
150
|
):
|
151
151
|
one.image_1920 = one.content
|
152
152
|
|
153
|
-
def
|
154
|
-
self.mapped("directory_id").
|
155
|
-
return super().
|
153
|
+
def check_access(self, operation):
|
154
|
+
self.mapped("directory_id").check_access(operation)
|
155
|
+
return super().check_access(operation)
|
156
156
|
|
157
157
|
def _compute_access_url(self):
|
158
158
|
res = super()._compute_access_url()
|
159
159
|
for item in self:
|
160
|
-
item.access_url = "/my/dms/file
|
160
|
+
item.access_url = f"/my/dms/file/{item.id}/download"
|
161
161
|
return res
|
162
162
|
|
163
163
|
def check_access_token(self, access_token=False):
|
@@ -240,7 +240,7 @@ class DMSFile(models.Model):
|
|
240
240
|
return [extension.strip() for extension in extensions.split(",")]
|
241
241
|
|
242
242
|
def _get_icon_placeholder_name(self):
|
243
|
-
return self.extension and "file_
|
243
|
+
return self.extension and f"file_{self.extension}.svg" or ""
|
244
244
|
|
245
245
|
# Actions
|
246
246
|
def action_migrate(self, should_logging=True):
|
@@ -581,17 +581,16 @@ class DMSFile(models.Model):
|
|
581
581
|
del res_vals["content"]
|
582
582
|
return res_vals
|
583
583
|
|
584
|
-
def
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
return super().copy(default)
|
584
|
+
def copy_data(self, default=None):
|
585
|
+
vals_list = super().copy_data(default)
|
586
|
+
for dms_file, vals in zip(self, vals_list, strict=False):
|
587
|
+
if vals.get("directory_id"):
|
588
|
+
directory = self.env["dms.directory"].browse(vals.get("directory_id"))
|
589
|
+
names = directory.sudo().file_ids.mapped("name")
|
590
|
+
else:
|
591
|
+
names = dms_file.sudo().directory_id.file_ids.mapped("name")
|
592
|
+
vals["name"] = file.unique_name(dms_file.name, names, dms_file.extension)
|
593
|
+
return vals_list
|
595
594
|
|
596
595
|
@api.model_create_multi
|
597
596
|
def create(self, vals_list):
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Copyright 2020 Creu Blanca
|
2
|
-
# Copyright 2021 Tecnativa - Víctor Martínez
|
2
|
+
# Copyright 2021-2025 Tecnativa - Víctor Martínez
|
3
3
|
# Copyright 2024 Subteno - Timothée Vannier (https://www.subteno.com).
|
4
4
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
5
5
|
|
@@ -7,12 +7,14 @@
|
|
7
7
|
from logging import getLogger
|
8
8
|
|
9
9
|
from odoo import api, fields, models
|
10
|
+
from odoo.exceptions import AccessError
|
10
11
|
from odoo.osv.expression import (
|
11
12
|
FALSE_DOMAIN,
|
12
13
|
NEGATIVE_TERM_OPERATORS,
|
13
14
|
OR,
|
14
15
|
TRUE_DOMAIN,
|
15
16
|
)
|
17
|
+
from odoo.tools import SQL
|
16
18
|
|
17
19
|
_logger = getLogger(__name__)
|
18
20
|
|
@@ -86,10 +88,10 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
86
88
|
)
|
87
89
|
return
|
88
90
|
|
89
|
-
creatable = self.
|
90
|
-
readable = self.
|
91
|
-
unlinkable = self.
|
92
|
-
writeable = self.
|
91
|
+
creatable = self._filtered_access("create")
|
92
|
+
readable = self._filtered_access("read")
|
93
|
+
unlinkable = self._filtered_access("unlink")
|
94
|
+
writeable = self._filtered_access("write")
|
93
95
|
for one in self:
|
94
96
|
one.update(
|
95
97
|
{
|
@@ -135,7 +137,9 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
135
137
|
)
|
136
138
|
continue
|
137
139
|
# Check model access only once per batch
|
138
|
-
|
140
|
+
try:
|
141
|
+
model.check_access(operation)
|
142
|
+
except AccessError:
|
139
143
|
continue
|
140
144
|
domains.append([("res_model", "=", model._name), ("res_id", "=", False)])
|
141
145
|
# Check record access in batch too
|
@@ -143,7 +147,7 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
143
147
|
# Apply exists to skip records that do not exist. (e.g. a res.partner
|
144
148
|
# deleted by database).
|
145
149
|
model_records = model.browse(res_ids).exists()
|
146
|
-
related_ok = model_records.
|
150
|
+
related_ok = model_records._filtered_access(operation)
|
147
151
|
if not related_ok:
|
148
152
|
continue
|
149
153
|
domains.append(
|
@@ -161,7 +165,7 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
161
165
|
"unlink": "AND dag.perm_inclusive_unlink",
|
162
166
|
"write": "AND dag.perm_inclusive_write",
|
163
167
|
}[operation]
|
164
|
-
select = f"""
|
168
|
+
select = f"""(
|
165
169
|
SELECT
|
166
170
|
dir_group_rel.aid
|
167
171
|
FROM
|
@@ -172,22 +176,25 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
172
176
|
ON users.gid = dag.id
|
173
177
|
WHERE
|
174
178
|
users.uid = %s {operation_check}
|
175
|
-
"""
|
176
|
-
|
179
|
+
)"""
|
180
|
+
sql = SQL(
|
181
|
+
select,
|
182
|
+
self.env.uid,
|
183
|
+
)
|
184
|
+
return sql
|
177
185
|
|
178
186
|
@api.model
|
179
187
|
def _get_domain_by_access_groups(self, operation):
|
180
188
|
"""Get domain for records accessible applying DMS access groups."""
|
181
189
|
result = [
|
182
190
|
(
|
183
|
-
"
|
184
|
-
% self._directory_field,
|
191
|
+
f"{self._directory_field}.storage_id_inherit_access_from_parent_record",
|
185
192
|
"=",
|
186
193
|
False,
|
187
194
|
),
|
188
195
|
(
|
189
196
|
self._directory_field,
|
190
|
-
"
|
197
|
+
"in",
|
191
198
|
self._get_access_groups_query(operation),
|
192
199
|
),
|
193
200
|
]
|
@@ -215,7 +222,6 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
215
222
|
_self._get_domain_by_inheritance(operation),
|
216
223
|
]
|
217
224
|
)
|
218
|
-
|
219
225
|
if not positive:
|
220
226
|
result.insert(0, "!")
|
221
227
|
return result
|
@@ -236,16 +242,37 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
236
242
|
def _search_permission_write(self, operator, value):
|
237
243
|
return self._get_permission_domain(operator, value, "write")
|
238
244
|
|
239
|
-
def
|
245
|
+
def filtered_domain(self, domain):
|
246
|
+
"""This method is needed to inhibit the behavior when called from the
|
247
|
+
_check_access() method with sudo() https://github.com/odoo/odoo/blob/fc737a147b9aefbd6ae5d111835ce3f4f7b4240a/odoo/models.py#L4465.
|
248
|
+
It would cause the error that multiple records are not accessed to be
|
249
|
+
displayed.
|
250
|
+
The _filtered_access() method is also overwritten to prevent this sudo()
|
251
|
+
specific behavior and to be able to access only the appropriate records.
|
252
|
+
"""
|
253
|
+
if self.env.su:
|
254
|
+
return self
|
255
|
+
return super().filtered_domain(domain)
|
256
|
+
|
257
|
+
def _filtered_access_no_recursion(self, operation: str):
|
258
|
+
"""This method is just the same as _filtered_access
|
259
|
+
but it can not be called withoud super due to
|
260
|
+
recursion error.
|
261
|
+
"""
|
262
|
+
if self and not self.env.su and (result := self._check_access(operation)):
|
263
|
+
return self - result[0]
|
264
|
+
return self
|
265
|
+
|
266
|
+
def _filtered_access(self, operation):
|
240
267
|
# Only kept to not break inheritance; see next comment
|
241
|
-
result = super().
|
268
|
+
result = super()._filtered_access(operation)
|
242
269
|
# HACK Always fall back to applying rules by SQL.
|
243
|
-
# Upstream `
|
270
|
+
# Upstream `_filtered_access()` doesn't use computed fields
|
244
271
|
# search methods. Thus, it will take the `[('permission_{operation}',
|
245
272
|
# '=', user.id)]` rule literally. Obviously that will always fail
|
246
273
|
# because `self[f"permission_{operation}"]` will always be a `bool`,
|
247
274
|
# while `user.id` will always be an `int`.
|
248
|
-
result |= self.
|
275
|
+
result |= self._filtered_access_no_recursion(operation)
|
249
276
|
return result
|
250
277
|
|
251
278
|
@api.model_create_multi
|
@@ -258,6 +285,5 @@ class DmsSecurityMixin(models.AbstractModel):
|
|
258
285
|
res.flush_recordset()
|
259
286
|
# Go back to the original sudo state and check we really had creation permission
|
260
287
|
res = res.sudo(self.env.su)
|
261
|
-
res.
|
262
|
-
res.check_access_rule("create")
|
288
|
+
res.check_access("create")
|
263
289
|
return res
|
@@ -8,7 +8,7 @@ from odoo import models
|
|
8
8
|
class IrBinary(models.AbstractModel):
|
9
9
|
_inherit = "ir.binary"
|
10
10
|
|
11
|
-
def _find_record_check_access(self, record, access_token):
|
11
|
+
def _find_record_check_access(self, record, access_token, field):
|
12
12
|
if record._name in ("dms.file", "dms.directory"):
|
13
13
|
if record.sudo().check_access_token(access_token):
|
14
14
|
# sudo because the user might not usually have access to the record but
|
@@ -16,4 +16,4 @@ class IrBinary(models.AbstractModel):
|
|
16
16
|
# Used to display the icon in the portal.
|
17
17
|
return record.sudo()
|
18
18
|
|
19
|
-
return super()._find_record_check_access(record, access_token)
|
19
|
+
return super()._find_record_check_access(record, access_token, field)
|
@@ -35,7 +35,7 @@ class Thumbnail(models.AbstractModel):
|
|
35
35
|
"""Obtain URL to record icon."""
|
36
36
|
local_path = self._get_icon_disk_path()
|
37
37
|
icon_name = os.path.basename(local_path)
|
38
|
-
return "/dms/static/icons
|
38
|
+
return f"/dms/static/icons/{icon_name}"
|
39
39
|
|
40
40
|
@api.depends("image_128")
|
41
41
|
def _compute_icon_url(self):
|
@@ -18,9 +18,9 @@ class Storage(models.Model):
|
|
18
18
|
name = fields.Char(required=True)
|
19
19
|
save_type = fields.Selection(
|
20
20
|
selection=[
|
21
|
-
("database",
|
22
|
-
("file",
|
23
|
-
("attachment",
|
21
|
+
("database", "Database"),
|
22
|
+
("file", "Filestore"),
|
23
|
+
("attachment", "Attachment"),
|
24
24
|
],
|
25
25
|
default="database",
|
26
26
|
required=True,
|
@@ -17,3 +17,5 @@
|
|
17
17
|
means. It would be nice to be able to remove that rule at some point.
|
18
18
|
- Searchpanel in files: Highlight items (shading) without records when
|
19
19
|
filtering something (by name for example).
|
20
|
+
- Accessing the clipboard (for example copy share link of file/directory)
|
21
|
+
is limited to secure connections. It also happens in any part of Odoo.
|