Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,44 @@
# DMS Field
Odoo addon: dms_field
## Installation
```bash
pip install odoo-bringout-oca-dms-dms_field
```
## Dependencies
This addon depends on:
- dms
## Manifest Information
- **Name**: DMS Field
- **Version**: 16.0.1.1.5
- **Category**: N/A
- **License**: LGPL-3
- **Installable**: False
## Source
Based on [OCA/dms](https://github.com/OCA/dms) branch 16.0, addon `dms_field`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,117 @@
=========
DMS Field
=========
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4c2029fa91a7142bb6adb4fa9c78281736dbf3f2b2bce45b90ae068dba046f41
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fdms-lightgray.png?logo=github
:target: https://github.com/OCA/dms/tree/16.0/dms_field
:alt: OCA/dms
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/dms-16-0/dms-16-0-dms_field
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/dms&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This addon creates a new kind of view and allows to define a folder
related to a record.
**Table of contents**
.. contents::
:local:
Configuration
=============
To use the embedded view in any module, the module must inherit from the mixin
dms.field.mixin (You have an example with res.partner in this module).
Once this is done, in the form view of the model we will have to add the following:
.. code-block:: xml
<field name="dms_directory_ids" mode="dms_list" />
In addition, it will be necessary to create an Embedded DMS template for this model.
#. *Go to Documents > Configuration > Embedded DMS templates* and create a new record.
#. Set a storage, a model (res.partner for example) and the access groups you want.
#. You can also use expressions in "Directory format name", for example: {{object.name}}
#. Click on the "Documents" tab icon and a folder hierarchy will be created.
#. You can set here the hierarchy of directories, subdirectories and files you need, this hierarchy will be used as a base when creating a new record (res.partner for example).
Usage
=====
#. Go to the form view of an existing partner and click on the "DMS" tab icon, a hierarchy of
folders and files linked to that record will be created.
#. Create a new partner. A hierarchy of folders and files linked to that record will be created.
Known issues / Roadmap
======================
- Add drag & drop compatibility to the dms_tree mode
- Multiple selection support (e.g. cut several files and paste to another folder).
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/dms/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/dms/issues/new?body=module:%20dms_field%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Creu Blanca
Contributors
~~~~~~~~~~~~
* Enric Tobella <etobella@creublanca.es>
* Jaime Arroyo <jaime.arroyo@creublanca.es>
* `Tecnativa <https://www.tecnativa.com>`_:
* Víctor Martínez
* Carlos Roca
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/dms <https://github.com/OCA/dms/tree/16.0/dms_field>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,33 @@
# Copyright 2020 Creu Blanca
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
"name": "DMS Field",
"summary": """
Create DMS View and allow to use them inside a record""",
"version": "16.0.1.1.5",
"license": "LGPL-3",
"author": "Creu Blanca,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/dms",
"depends": ["dms"],
"data": [
"views/dms_access_group_views.xml",
"views/dms_directory.xml",
"views/dms_field_template_views.xml",
"views/dms_storage.xml",
"security/ir.model.access.csv",
"security/security.xml",
],
"assets": {
"web.assets_backend": [
"dms_field/static/src/**/*",
("remove", "dms_field/static/src/views/fields/x2many/x2many_field.xml"),
(
"after",
"/web/static/src/views/fields/x2many/x2many_field.xml",
"dms_field/static/src/views/fields/x2many/x2many_field.xml",
),
],
},
"demo": ["demo/partner_dms.xml"],
}

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">res.partner.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<notebook position="inside">
<page
name="dms"
string="DMS"
attrs="{'invisible': [('id', '=', False)]}"
>
<field name="dms_directory_ids" mode="dms_list" />
</page>
</notebook>
</field>
</record>
<record id="access_group_demo" model="dms.access.group">
<field name="name">Admin (dms_field module)</field>
<field name="perm_create">True</field>
<field name="perm_write">True</field>
<field name="perm_unlink">True</field>
<field
name="explicit_user_ids"
eval="[(6, 0, [ref('base.user_admin'), ref('base.user_demo')])]"
/>
</record>
<record id="field_template_partner" model="dms.field.template">
<field name="name">Partner</field>
<field name="storage_id" ref="dms.storage_demo" />
<field name="model_id" ref="base.model_res_partner" />
<field name="group_ids" eval="[(6, 0, [ref('dms_field.access_group_demo')])]" />
</record>
</odoo>

View file

@ -0,0 +1,395 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * dms_field
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Radni prozor"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Actions"
msgstr "Akcije"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add Directory: "
msgstr "Dodaj direktorij: "
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add File: "
msgstr "Dodaj datoteku: "
#. module: dms_field
#: model:dms.access.group,name:dms_field.access_group_demo
msgid "Admin (dms_field module)"
msgstr "Administrator (dms_field modul)"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "An error occurred during the upload"
msgstr "Došlo je do pogreške tijekom učitavanja"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "Autogenerated group from %(model)s (%(name)s) #%(id)s"
msgstr "Automatski generisana grupa iz %(model)s (%(name)s) #%(id)s"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__company_id
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__company_id
msgid "Company"
msgstr "Preduzeće"
#. module: dms_field
#: model:ir.model,name:dms_field.model_res_partner
msgid "Contact"
msgstr "Kontakt"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create File"
msgstr "Kreiraj datoteku"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create directory"
msgstr "Kreiraj direktorij"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_uid
msgid "Created by"
msgstr "Kreirao"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_date
msgid "Created on"
msgstr "Kreirano"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Cut"
msgstr "Izreži"
#. module: dms_field
#: model_terms:ir.ui.view,arch_db:dms_field.view_partner_form
msgid "DMS"
msgstr "DMS"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_mixin__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_partner__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_users__dms_directory_ids
msgid "DMS Directories"
msgstr "DMS direktoriji"
#. module: dms_field
#: model:ir.model.fields.selection,name:dms_field.selection__ir_actions_act_window_view__view_mode__dms_list
#: model:ir.model.fields.selection,name:dms_field.selection__ir_ui_view__type__dms_list
msgid "DMS Tree"
msgstr "DMS stablo"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__dms_field_ref
msgid "DMS field reference"
msgstr "Referenca DMS polja"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Delete"
msgstr "Obriši"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "Directories of this storage must be related to a record"
msgstr "Direktoriji ovog skladišta moraju biti povezani sa zapisom"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_directory
msgid "Directory"
msgstr "Imenik"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__directory_format_name
msgid "Directory format name"
msgstr "Format naziva direktorija"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__display_name
msgid "Display Name"
msgstr "Prikazani naziv"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_template
msgid "Dms Field Template"
msgstr "DMS šablon polja"
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.dms_storage_act_window
#: model:ir.ui.menu,name:dms_field.dms_storage_menu
#: model_terms:ir.ui.view,arch_db:dms_field.view_dms_field_template_form
msgid "Documents"
msgstr "Dokumenti"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Download"
msgstr "Preuzimanje"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Elements:"
msgstr "Elementi:"
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.action_dms_field_template
#: model:ir.ui.menu,name:dms_field.menu_dms_field_template
msgid "Embedded DMS templates"
msgstr "Ugrađeni DMS šabloni"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_storage__field_template_ids
msgid "File templated ids"
msgstr "ID-jevi datoteka šablona"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Files:"
msgstr "Datoteke:"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__group_ids
msgid "Groups"
msgstr "Grupe"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__id
msgid "ID"
msgstr "ID"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template____last_update
msgid "Last Modified on"
msgstr "Zadnje mijenjano"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_uid
msgid "Last Updated by"
msgstr "Zadnji ažurirao"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_date
msgid "Last Updated on"
msgstr "Zadnje ažurirano"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_mixin
msgid "Mixin to use DMS Field"
msgstr "Mješavina za korištenje DMS polja"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model_id
msgid "Model"
msgstr "Model"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model
msgid "Model name"
msgstr "Model"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/ir_ui_view.py:0
#, python-format
msgid "Model not found: %(model)s"
msgstr "Model nije pronađen: %(model)s"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__name
msgid "Name"
msgstr "Naziv:"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Open"
msgstr "Otvori"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Open: "
msgstr "Otvori: "
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_directory__parent_id
msgid "Parent Directory"
msgstr "Roditeljski direktorij"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__parent_directory_id
msgid "Parent directory"
msgstr "Roditeljski direktorij"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Paste"
msgstr "Zalijepi"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Preview"
msgstr "Pregled"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_access_group
msgid "Record Access Groups"
msgstr "Grupe pristupa zapisa"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Rename"
msgstr "Preimenuj"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Size:"
msgstr "Veličina:"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "Some directories are inconsistent with the storage models"
msgstr "Neki direktoriji su nekonzistentni sa modelima skladišta"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_storage
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__storage_id
msgid "Storage"
msgstr "Skladište"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Subdirectories:"
msgstr "Poddirektoriji:"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "There are directories not associated to a record"
msgstr "Postoje direktoriji koji nisu povezani sa zapisom"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a linked directory created."
msgstr "Već je kreiran povezani direktorij."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a template created for this model."
msgstr "Već je kreiran šablon za ovaj model."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_access_group.py:0
#, python-format
msgid "There is already an access group created for this record."
msgstr "Već je kreirana grupa pristupa za ovaj zapis."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is no template linked to this model"
msgstr "Nema šablona povezanog sa ovim modelom"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "This record is already related in this storage"
msgstr "Ovaj zapis je već povezan u ovom skladištu"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__user_field_id
msgid "User field"
msgstr "Korisničko polje"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_ui_view
msgid "View"
msgstr "Pregled"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:dms_field.field_ir_ui_view__type
msgid "View Type"
msgstr "Vrsta pregleda"
#. module: dms_field
#: model:ir.model.fields,help:dms_field.field_dms_field_template__directory_format_name
msgid ""
"You can set expressions to be used for the directory name,\n"
" e.g.: {{object.name}}"
msgstr ""

View file

@ -0,0 +1,395 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * dms_field
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Actions"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add Directory: "
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add File: "
msgstr ""
#. module: dms_field
#: model:dms.access.group,name:dms_field.access_group_demo
msgid "Admin (dms_field module)"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "An error occurred during the upload"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "Autogenerated group from %(model)s (%(name)s) #%(id)s"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__company_id
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__company_id
msgid "Company"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_res_partner
msgid "Contact"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create File"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create directory"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_uid
msgid "Created by"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_date
msgid "Created on"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Cut"
msgstr ""
#. module: dms_field
#: model_terms:ir.ui.view,arch_db:dms_field.view_partner_form
msgid "DMS"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_mixin__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_partner__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_users__dms_directory_ids
msgid "DMS Directories"
msgstr ""
#. module: dms_field
#: model:ir.model.fields.selection,name:dms_field.selection__ir_actions_act_window_view__view_mode__dms_list
#: model:ir.model.fields.selection,name:dms_field.selection__ir_ui_view__type__dms_list
msgid "DMS Tree"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__dms_field_ref
msgid "DMS field reference"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Delete"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "Directories of this storage must be related to a record"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_directory
msgid "Directory"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__directory_format_name
msgid "Directory format name"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__display_name
msgid "Display Name"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_template
msgid "Dms Field Template"
msgstr ""
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.dms_storage_act_window
#: model:ir.ui.menu,name:dms_field.dms_storage_menu
#: model_terms:ir.ui.view,arch_db:dms_field.view_dms_field_template_form
msgid "Documents"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Download"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Elements:"
msgstr ""
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.action_dms_field_template
#: model:ir.ui.menu,name:dms_field.menu_dms_field_template
msgid "Embedded DMS templates"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_storage__field_template_ids
msgid "File templated ids"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Files:"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__group_ids
msgid "Groups"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__id
msgid "ID"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template____last_update
msgid "Last Modified on"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_uid
msgid "Last Updated by"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_date
msgid "Last Updated on"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_mixin
msgid "Mixin to use DMS Field"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model_id
msgid "Model"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model
msgid "Model name"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/ir_ui_view.py:0
#, python-format
msgid "Model not found: %(model)s"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__name
msgid "Name"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Open"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Open: "
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_directory__parent_id
msgid "Parent Directory"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__parent_directory_id
msgid "Parent directory"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Paste"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Preview"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_access_group
msgid "Record Access Groups"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Rename"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Size:"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "Some directories are inconsistent with the storage models"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_storage
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__storage_id
msgid "Storage"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Subdirectories:"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "There are directories not associated to a record"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a linked directory created."
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a template created for this model."
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_access_group.py:0
#, python-format
msgid "There is already an access group created for this record."
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is no template linked to this model"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "This record is already related in this storage"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__user_field_id
msgid "User field"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_ui_view
msgid "View"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:dms_field.field_ir_ui_view__type
msgid "View Type"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,help:dms_field.field_dms_field_template__directory_format_name
msgid ""
"You can set expressions to be used for the directory name,\n"
" e.g.: {{object.name}}"
msgstr ""

View file

@ -0,0 +1,519 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * dms_field
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-16 11:00+0000\n"
"PO-Revision-Date: 2024-04-16 13:01+0200\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Poedit 3.0.1\n"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Vista de la ventana de acción"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Actions"
msgstr "Acciones"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add Directory: "
msgstr "Añadir directorio: "
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add File: "
msgstr "Agregar archivo: "
#. module: dms_field
#: model:dms.access.group,name:dms_field.access_group_demo
msgid "Admin (dms_field module)"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "An error occurred during the upload"
msgstr "Se ha producido un error durante la carga"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "Autogenerated group from %(model)s (%(name)s) #%(id)s"
msgstr "Grupo autogenerado desde %(model)s (%(name)s) #%(id)s"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__company_id
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__company_id
msgid "Company"
msgstr "Compañía"
#. module: dms_field
#: model:ir.model,name:dms_field.model_res_partner
msgid "Contact"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create File"
msgstr "Crear archivo"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create directory"
msgstr "Crear directorio"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_date
msgid "Created on"
msgstr "Creado el"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Cut"
msgstr "Cortar"
#. module: dms_field
#: model_terms:ir.ui.view,arch_db:dms_field.view_partner_form
msgid "DMS"
msgstr "DMS"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_mixin__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_partner__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_users__dms_directory_ids
msgid "DMS Directories"
msgstr "Directorios DMS"
#. module: dms_field
#: model:ir.model.fields.selection,name:dms_field.selection__ir_actions_act_window_view__view_mode__dms_list
#: model:ir.model.fields.selection,name:dms_field.selection__ir_ui_view__type__dms_list
msgid "DMS Tree"
msgstr "Árbol DMS"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__dms_field_ref
msgid "DMS field reference"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Delete"
msgstr "Borrar"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "Directories of this storage must be related to a record"
msgstr ""
"Los directorios de este almacenamiento deben estar relacionados con un "
"registro"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_directory
msgid "Directory"
msgstr "Directorio"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__directory_format_name
msgid "Directory format name"
msgstr "Formato de nombre del directorio"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__display_name
msgid "Display Name"
msgstr "Nombre a Mostrar"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_template
msgid "Dms Field Template"
msgstr "Plantilla de Campo Dms"
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.dms_storage_act_window
#: model:ir.ui.menu,name:dms_field.dms_storage_menu
#: model_terms:ir.ui.view,arch_db:dms_field.view_dms_field_template_form
msgid "Documents"
msgstr "Documentos"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Download"
msgstr "Descargar"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Elements:"
msgstr "Elementos:"
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.action_dms_field_template
#: model:ir.ui.menu,name:dms_field.menu_dms_field_template
msgid "Embedded DMS templates"
msgstr "Plantillas DMS embebidas"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_storage__field_template_ids
msgid "File templated ids"
msgstr "Fichero de identificadores plantillas"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Files:"
msgstr "Archivos:"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__group_ids
msgid "Groups"
msgstr "Grupos"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__id
msgid "ID"
msgstr "ID (identificación)"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template____last_update
msgid "Last Modified on"
msgstr "Última Modificación el"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_uid
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_date
msgid "Last Updated on"
msgstr "Última Actualización el"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_mixin
msgid "Mixin to use DMS Field"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model_id
msgid "Model"
msgstr "Modelo"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model
msgid "Model name"
msgstr "Nombre de modelo"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/ir_ui_view.py:0
#, python-format
msgid "Model not found: %(model)s"
msgstr "Modelo no encontrado: %(model)s"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__name
msgid "Name"
msgstr "Nombre"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Open"
msgstr "Abrir"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Open: "
msgstr "Abrir "
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_directory__parent_id
msgid "Parent Directory"
msgstr "Directorio parental"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__parent_directory_id
msgid "Parent directory"
msgstr "Carpeta padre"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Paste"
msgstr "Pegar"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Preview"
msgstr "Previsualizar"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_access_group
msgid "Record Access Groups"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Rename"
msgstr "Renombrar"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Size:"
msgstr "Tamaño:"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "Some directories are inconsistent with the storage models"
msgstr ""
"Algunos directorios tienen incompatibilidades con los modelos de "
"almacenamiento"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_storage
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__storage_id
msgid "Storage"
msgstr "Almacenamiento"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Subdirectories:"
msgstr "Subdirectorios:"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "There are directories not associated to a record"
msgstr "Hay directorios no asociados a un registro"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a linked directory created."
msgstr "Ya hay un directorio creado relacionado."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a template created for this model."
msgstr "Ya hay una plantilla creada para este modelo."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_access_group.py:0
#, python-format
msgid "There is already an access group created for this record."
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is no template linked to this model"
msgstr "No hay plantilla vinculada a este modelo"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "This record is already related in this storage"
msgstr "Este registro ya está relacionado en este almacenamiento"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__user_field_id
msgid "User field"
msgstr "Campo de usuario"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_ui_view
msgid "View"
msgstr "Vista"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:dms_field.field_ir_ui_view__type
msgid "View Type"
msgstr "Tipo de vista"
#. module: dms_field
#: model:ir.model.fields,help:dms_field.field_dms_field_template__directory_format_name
msgid ""
"You can set expressions to be used for the directory name,\n"
" e.g.: {{object.name}}"
msgstr ""
"Puede utilizar expresiones para definir el nombre del directorio,\n"
"\tejemplo: {{object.name}}"
#, python-format
#~ msgid "A file with the same name already exists"
#~ msgstr "Ya existe un archivo con el mismo nombre"
#~ msgid "Base"
#~ msgstr "Base/Fuente"
#, python-format
#~ msgid "Action Buttons"
#~ msgstr "Botones de acción"
#, python-format
#~ msgid "Close the selected node"
#~ msgstr "Cerrar el nodo seleccionado"
#, python-format
#~ msgid "Context Menu"
#~ msgstr "Menú contextual"
#, python-format
#~ msgid "Create"
#~ msgstr "Crear"
#, python-format
#~ msgid "Drag and Drop"
#~ msgstr "Arrastrar y soltar"
#, python-format
#~ msgid "END"
#~ msgstr "FIN"
#, python-format
#~ msgid "Edit"
#~ msgstr "Editar"
#, python-format
#~ msgid "Edit the selected node"
#~ msgstr "Editar el nodo seleccionado"
#, python-format
#~ msgid "F2"
#~ msgstr "F2"
#, python-format
#~ msgid "HOME"
#~ msgstr "CASA"
#, python-format
#~ msgid "Jump to the bottom"
#~ msgstr "Saltar a la parte inferior"
#, python-format
#~ msgid "Jump to the top"
#~ msgstr "Saltar a la parte superior"
#, python-format
#~ msgid "Keyboard Shortcuts"
#~ msgstr "Métodos abreviados de teclado"
#, python-format
#~ msgid "Move down one node"
#~ msgstr "Desplazarse un nodo hacia abajo"
#, python-format
#~ msgid "Move up one node"
#~ msgstr "Desplazarse un nodo hacia arriba"
#, python-format
#~ msgid "Open all nodes"
#~ msgstr "Abrir todos los nodos"
#, python-format
#~ msgid "Open the selected node"
#~ msgstr "Abrir el nodo seleccionado"
#, python-format
#~ msgid "Refresh"
#~ msgstr "Actualizar"
#, python-format
#~ msgid "Show Help"
#~ msgstr "Mostrar ayuda"
#, python-format
#~ msgid ""
#~ "The action button at the top of the view can be used to open,\n"
#~ " create, edit or delete a node."
#~ msgstr ""
#~ "El botón de acción en la parte superior de la vista se puede utilizar "
#~ "para abrir,\n"
#~ " crear, editar o eliminar un nodo."
#, python-format
#~ msgid ""
#~ "You can change the structure by moving nodes. It is also possible\n"
#~ " to create new nodes by dragging files and even "
#~ "entire folder\n"
#~ " structures on the view."
#~ msgstr ""
#~ "Puede cambiar la estructura moviendo nodos. También es posible\n"
#~ " crear nuevos nodos arrastrando archivos e incluso "
#~ "estructuras de carpetas\n"
#~ " completas en la vista."
#, python-format
#~ msgid ""
#~ "You can open a context menu by right-clicking on any node. This\n"
#~ " contains further options for interacting with the "
#~ "node."
#~ msgstr ""
#~ "Puede abrir un menú contextual haciendo clic con el botón derecho en "
#~ "cualquier nodo. Este\n"
#~ " contiene más opciones para interactuar con el "
#~ "nodo."

View file

@ -0,0 +1,395 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * dms_field
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Actions"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add Directory: "
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add File: "
msgstr ""
#. module: dms_field
#: model:dms.access.group,name:dms_field.access_group_demo
msgid "Admin (dms_field module)"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "An error occurred during the upload"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "Autogenerated group from %(model)s (%(name)s) #%(id)s"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__company_id
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__company_id
msgid "Company"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_res_partner
msgid "Contact"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create File"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create directory"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_uid
msgid "Created by"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_date
msgid "Created on"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Cut"
msgstr ""
#. module: dms_field
#: model_terms:ir.ui.view,arch_db:dms_field.view_partner_form
msgid "DMS"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_mixin__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_partner__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_users__dms_directory_ids
msgid "DMS Directories"
msgstr ""
#. module: dms_field
#: model:ir.model.fields.selection,name:dms_field.selection__ir_actions_act_window_view__view_mode__dms_list
#: model:ir.model.fields.selection,name:dms_field.selection__ir_ui_view__type__dms_list
msgid "DMS Tree"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__dms_field_ref
msgid "DMS field reference"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Delete"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "Directories of this storage must be related to a record"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_directory
msgid "Directory"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__directory_format_name
msgid "Directory format name"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__display_name
msgid "Display Name"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_template
msgid "Dms Field Template"
msgstr ""
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.dms_storage_act_window
#: model:ir.ui.menu,name:dms_field.dms_storage_menu
#: model_terms:ir.ui.view,arch_db:dms_field.view_dms_field_template_form
msgid "Documents"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Download"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Elements:"
msgstr ""
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.action_dms_field_template
#: model:ir.ui.menu,name:dms_field.menu_dms_field_template
msgid "Embedded DMS templates"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_storage__field_template_ids
msgid "File templated ids"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Files:"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__group_ids
msgid "Groups"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__id
msgid "ID"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template____last_update
msgid "Last Modified on"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_uid
msgid "Last Updated by"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_date
msgid "Last Updated on"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_mixin
msgid "Mixin to use DMS Field"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model_id
msgid "Model"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model
msgid "Model name"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/ir_ui_view.py:0
#, python-format
msgid "Model not found: %(model)s"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__name
msgid "Name"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Open"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Open: "
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_directory__parent_id
msgid "Parent Directory"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__parent_directory_id
msgid "Parent directory"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Paste"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Preview"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_access_group
msgid "Record Access Groups"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Rename"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Size:"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "Some directories are inconsistent with the storage models"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_storage
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__storage_id
msgid "Storage"
msgstr ""
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Subdirectories:"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "There are directories not associated to a record"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a linked directory created."
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a template created for this model."
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_access_group.py:0
#, python-format
msgid "There is already an access group created for this record."
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is no template linked to this model"
msgstr ""
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "This record is already related in this storage"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__user_field_id
msgid "User field"
msgstr ""
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_ui_view
msgid "View"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:dms_field.field_ir_ui_view__type
msgid "View Type"
msgstr ""
#. module: dms_field
#: model:ir.model.fields,help:dms_field.field_dms_field_template__directory_format_name
msgid ""
"You can set expressions to be used for the directory name,\n"
" e.g.: {{object.name}}"
msgstr ""

View file

@ -0,0 +1,586 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * dms_field
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-26 20:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Vista maschera azione"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Actions"
msgstr "Azioni"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add Directory: "
msgstr "Aggiungi cartella: "
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Add File: "
msgstr "Aggiungi file: "
#. module: dms_field
#: model:dms.access.group,name:dms_field.access_group_demo
msgid "Admin (dms_field module)"
msgstr "Amministratore (modulo dms_field)"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "An error occurred during the upload"
msgstr "Si è verificato un errore durante il caricamento"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "Autogenerated group from %(model)s (%(name)s) #%(id)s"
msgstr "Gruppo autogenerato da %(model)s (%(name)s) n°%(id)s"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__company_id
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__company_id
msgid "Company"
msgstr "Azienda"
#. module: dms_field
#: model:ir.model,name:dms_field.model_res_partner
msgid "Contact"
msgstr "Contatto"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create File"
msgstr "Crea file"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Create directory"
msgstr "Crea cartella"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__create_date
msgid "Created on"
msgstr "Creato il"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Cut"
msgstr "Taglia"
#. module: dms_field
#: model_terms:ir.ui.view,arch_db:dms_field.view_partner_form
msgid "DMS"
msgstr "DMS"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_mixin__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_partner__dms_directory_ids
#: model:ir.model.fields,field_description:dms_field.field_res_users__dms_directory_ids
msgid "DMS Directories"
msgstr "Cartelle DMS"
#. module: dms_field
#: model:ir.model.fields.selection,name:dms_field.selection__ir_actions_act_window_view__view_mode__dms_list
#: model:ir.model.fields.selection,name:dms_field.selection__ir_ui_view__type__dms_list
msgid "DMS Tree"
msgstr "Albero DMS"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_access_group__dms_field_ref
msgid "DMS field reference"
msgstr "Riferimento campo DMS"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Delete"
msgstr "Elimina"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "Directories of this storage must be related to a record"
msgstr "Le cartelle di questo storage devono essere collegate ad un record"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_directory
msgid "Directory"
msgstr "Cartella"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__directory_format_name
msgid "Directory format name"
msgstr "Nome formato cartella"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_template
msgid "Dms Field Template"
msgstr "Modello campo DMS"
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.dms_storage_act_window
#: model:ir.ui.menu,name:dms_field.dms_storage_menu
#: model_terms:ir.ui.view,arch_db:dms_field.view_dms_field_template_form
msgid "Documents"
msgstr "Documenti"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Download"
msgstr "Scarica"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Elements:"
msgstr "Elementi:"
#. module: dms_field
#: model:ir.actions.act_window,name:dms_field.action_dms_field_template
#: model:ir.ui.menu,name:dms_field.menu_dms_field_template
msgid "Embedded DMS templates"
msgstr "Modelli DMS incorporati"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_storage__field_template_ids
msgid "File templated ids"
msgstr "ID file modelli"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Files:"
msgstr "File:"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__group_ids
msgid "Groups"
msgstr "Gruppi"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__id
msgid "ID"
msgstr "ID"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_field_mixin
msgid "Mixin to use DMS Field"
msgstr "Mixin per utilizzo campo DMS"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model_id
msgid "Model"
msgstr "Modello"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__model
msgid "Model name"
msgstr "Nome modello"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/ir_ui_view.py:0
#, python-format
msgid "Model not found: %(model)s"
msgstr "Modello non trovato: %(model)s"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__name
msgid "Name"
msgstr "Nome"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Open"
msgstr "Apri"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Open: "
msgstr "Apri: "
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_directory__parent_id
msgid "Parent Directory"
msgstr "Cartella padre"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__parent_directory_id
msgid "Parent directory"
msgstr "Cartella padre"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Paste"
msgstr "Incolla"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Preview"
msgstr "Anteprima"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_access_group
msgid "Record Access Groups"
msgstr "Gruppi di accesso al record"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.esm.js:0
#, python-format
msgid "Rename"
msgstr "Rinomina"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Size:"
msgstr "Dimensione:"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "Some directories are inconsistent with the storage models"
msgstr "Alcune cartelle non sono coerenti con i modelli dello storage"
#. module: dms_field
#: model:ir.model,name:dms_field.model_dms_storage
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__storage_id
msgid "Storage"
msgstr "Deposito"
#. module: dms_field
#. odoo-javascript
#: code:addons/dms_field/static/src/views/dms_list/dms_list_renderer.xml:0
#, python-format
msgid "Subdirectories:"
msgstr "Sottocartelle:"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_storage.py:0
#, python-format
msgid "There are directories not associated to a record"
msgstr "Sono presenti cartelle non associate ad un record"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a linked directory created."
msgstr "Esiste già una cartella collegata."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is already a template created for this model."
msgstr "Esiste già uno schema creato per questo modello."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_access_group.py:0
#, python-format
msgid "There is already an access group created for this record."
msgstr "Esiste già un gruppo di accesso creato per questo record."
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_field_template.py:0
#, python-format
msgid "There is no template linked to this model"
msgstr "Non c'è uno schema collegato a questo modello"
#. module: dms_field
#. odoo-python
#: code:addons/dms_field/models/dms_directory.py:0
#, python-format
msgid "This record is already related in this storage"
msgstr "Questo record è già correlato a questo storage"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_dms_field_template__user_field_id
msgid "User field"
msgstr "Campo utente"
#. module: dms_field
#: model:ir.model,name:dms_field.model_ir_ui_view
msgid "View"
msgstr "Vista"
#. module: dms_field
#: model:ir.model.fields,field_description:dms_field.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:dms_field.field_ir_ui_view__type
msgid "View Type"
msgstr "Tipo vista"
#. module: dms_field
#: model:ir.model.fields,help:dms_field.field_dms_field_template__directory_format_name
msgid ""
"You can set expressions to be used for the directory name,\n"
" e.g.: {{object.name}}"
msgstr ""
"Si possono impostare espressioni da utilizzare per il nome della cartella,\n"
" es.: {{object.name}}"
#, python-format
#~ msgid "Action Buttons"
#~ msgstr "Pulsanti azione"
#~ msgid "Base"
#~ msgstr "Base"
#, python-format
#~ msgid "Close the selected node"
#~ msgstr "Chiudi il nodo selezionato"
#, python-format
#~ msgid "Context Menu"
#~ msgstr "Menù contestuale"
#, python-format
#~ msgid "Create"
#~ msgstr "Crea"
#, python-format
#~ msgid "Directory %s must be root in order to be related to a record"
#~ msgstr "La cartella %s deve essere radice per essere collegata ad un record"
#, python-format
#~ msgid "Drag and Drop"
#~ msgstr "Tascina e rilascia"
#, python-format
#~ msgid "END"
#~ msgstr "FINE"
#, python-format
#~ msgid "Edit"
#~ msgstr "Modifica"
#, python-format
#~ msgid "Edit the selected node"
#~ msgstr "Modifica il nodo selezionato"
#, python-format
#~ msgid "F2"
#~ msgstr "F2"
#, python-format
#~ msgid "HOME"
#~ msgstr "HOME"
#, python-format
#~ msgid "Jump to the bottom"
#~ msgstr "Salta fino in fondo"
#, python-format
#~ msgid "Jump to the top"
#~ msgstr "Salta in cima"
#, python-format
#~ msgid "Keyboard Shortcuts"
#~ msgstr "Scorciatoie tastiera"
#, python-format
#~ msgid "Move down one node"
#~ msgstr "Scendi di un nodo"
#, python-format
#~ msgid "Move up one node"
#~ msgstr "Sali di un nodo"
#, python-format
#~ msgid "Open all nodes"
#~ msgstr "Apri tutti i nodi"
#, python-format
#~ msgid "Open the selected node"
#~ msgstr "Apri il nodo selezionato"
#, python-format
#~ msgid "Refresh"
#~ msgstr "Aggiorna"
#, python-format
#~ msgid "Show Help"
#~ msgstr "Mostra aiuto"
#, python-format
#~ msgid ""
#~ "The action button at the top of the view can be used to open,\n"
#~ " create, edit or delete a node."
#~ msgstr ""
#~ "Il pulsante azione nella parte superiore della vista può essere "
#~ "utilizzato per aprire,\n"
#~ " creare, modificare o eliminare un nodo."
#, python-format
#~ msgid ""
#~ "You can change the structure by moving nodes. It is also possible\n"
#~ " to create new nodes by dragging files and even "
#~ "entire folder\n"
#~ " structures on the view."
#~ msgstr ""
#~ "Puoi cambiare la struttura spostando i nodi. È anche possibile\n"
#~ " creare nuovi nodi trascinando file e anche "
#~ "intere strutture\n"
#~ " di cartelle nella vista."
#, python-format
#~ msgid ""
#~ "You can open a context menu by right-clicking on any node. This\n"
#~ " contains further options for interacting with the "
#~ "node."
#~ msgstr ""
#~ "È possibile aprire un menu contestuale facendo clic con il tasto destro "
#~ "su qualsiasi nodo. Questo\n"
#~ " contiene ulteriori opzioni per interagire con il "
#~ "nodo."
#~ msgid "Add Directory to a DMS Record"
#~ msgstr "Aggiungi cartella a un record DMS"
#, python-format
#~ msgid "Add new root directory"
#~ msgstr "Aggiungi una nuova cartella radice"
#, python-format
#~ msgid "Create:"
#~ msgstr "Crea:"
#, python-format
#~ msgid "Delete:"
#~ msgstr "Elimina:"
#~ msgid "Dms Add Directory Record"
#~ msgstr "Aggiungi record cartella DMS"
#~ msgid "Everyone for Partner DMS"
#~ msgstr "Tutti per il DMS del partner"
#~ msgid "Field Default Group"
#~ msgstr "Campo gruppo predefinito"
#, python-format
#~ msgid "Name:"
#~ msgstr "Nome:"
#~ msgid "Possible storages"
#~ msgstr "Storage possibili"
#, python-format
#~ msgid "Preview/download"
#~ msgstr "Anteprima/download"
#, python-format
#~ msgid "Read:"
#~ msgstr "Leggi:"
#~ msgid "Res"
#~ msgstr "Res"
#~ msgid "Res Model"
#~ msgstr "Modello res"
#, python-format
#~ msgid ""
#~ "Storage %s should need to be assigned to a model in order to relate the "
#~ "directory to a record"
#~ msgstr ""
#~ "Dovrebbe essere necessario assegnare lo storage %s a un modello per "
#~ "correlare la cartella a un record"
#, python-format
#~ msgid ""
#~ "Storage %s should need to be assigned to a model related to the storage"
#~ msgstr ""
#~ "Dovrebbe essere necessario assegnare lo storage %s a un modello correlato "
#~ "allo storage"
#, python-format
#~ msgid "Write:"
#~ msgstr "Scrivi:"
#, python-format
#~ msgid "no"
#~ msgstr "no"
#, python-format
#~ msgid "yes"
#~ msgstr "si"

View file

@ -0,0 +1,8 @@
from . import dms_field_mixin
from . import ir_actions_act_window_view
from . import ir_ui_view
from . import dms_access_group
from . import dms_storage
from . import dms_directory
from . import dms_field_template
from . import res_partner

View file

@ -0,0 +1,55 @@
# Copyright 2024 Tecnativa - Víctor Martínez
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class DmsAccessGroups(models.Model):
_inherit = "dms.access.group"
dms_field_ref = fields.Reference(
selection="_selection_reference_value",
string="DMS field reference",
)
company_id = fields.Many2one(
compute="_compute_company_id",
comodel_name="res.company",
string="Company",
store=True,
)
@api.model
def _selection_reference_value(self):
models = (
self.env["ir.model"]
.sudo()
.search([("transient", "=", False)], order="name asc")
)
return [(model.model, model.name) for model in models]
@api.depends("dms_field_ref")
def _compute_company_id(self):
self.company_id = False
for item in self.filtered("dms_field_ref"):
item.company_id = (
item.dms_field_ref.company_id
if "company_id" in item.dms_field_ref._fields
else False
)
def _get_item_from_dms_field_ref(self, record):
return self.env["dms.access.group"].search(
[("dms_field_ref", "=", "%s,%s" % (record._name, record.id))]
)
@api.constrains("dms_field_ref")
def _check_dms_field_ref(self):
for item in self.filtered("dms_field_ref"):
dms_field_ref = "%s,%s" % (item.dms_field_ref._name, item.dms_field_ref.id)
if self.search(
[("dms_field_ref", "=", dms_field_ref), ("id", "!=", item.id)]
):
raise UserError(
_("There is already an access group created for this record.")
)

View file

@ -0,0 +1,166 @@
# Copyright 2020 Creu Blanca
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.osv import expression
class DmsDirectory(models.Model):
_inherit = "dms.directory"
parent_id = fields.Many2one(default=lambda self: self._default_parent())
@api.model
def _default_parent(self):
return self.env.context.get("default_parent_directory_id", False)
@api.constrains("res_id", "is_root_directory", "storage_id", "res_model")
def _check_resource(self):
for directory in self:
if directory.storage_id.save_type == "attachment":
continue
if (
directory.is_root_directory
and directory.storage_id.model_ids
and not directory.res_id
):
raise ValidationError(
_("Directories of this storage must be related to a record")
)
if not directory.res_id:
continue
if self.search(
[
("storage_id", "=", directory.storage_id.id),
("id", "!=", directory.id),
("res_id", "=", directory.res_id),
("res_model", "=", directory.res_model),
],
limit=1,
):
raise ValidationError(
_("This record is already related in this storage")
)
@api.model
def _build_documents_view_directory(self, directory):
return {
"id": "directory_%s" % directory.id,
"text": directory.name,
"icon": "fa fa-folder-o",
"type": "directory",
"data": {"odoo_id": directory.id, "odoo_model": "dms.directory"},
"children": directory.count_elements > 0,
}
@api.model
def _check_parent_field(self):
if self._parent_name not in self._fields:
raise TypeError("The parent (%s) field does not exist." % self._parent_name)
@api.model
def search_read_parents(
self, domain=False, fields=None, offset=0, limit=None, order=None
):
"""This method finds the top level elements of the hierarchy
for a given search query.
:param domain: a search domain <reference/orm/domains> (default: empty list)
:param fields: a list of fields to read (default: all fields of the model)
:param offset: the number of results to ignore (default: none)
:param limit: maximum number of records to return (default: all)
:param order: a string to define the sort order of the query
(default: none)
:returns: the top level elements for the given search query
"""
if not domain:
domain = []
records = self.search_parents(
domain=domain, offset=offset, limit=limit, order=order
)
if not records:
return []
if fields and fields == ["id"]:
return [{"id": record.id} for record in records]
result = records.read(fields)
if len(result) <= 1:
return result
index = {vals["id"]: vals for vals in result}
return [index[record.id] for record in records if record.id in index]
@api.model
def search_parents(
self, domain=False, offset=0, limit=None, order=None, count=False
):
"""This method finds the top level elements of the
hierarchy for a given search query.
:param domain: a search domain <reference/orm/domains> (default: empty list)
:param offset: the number of results to ignore (default: none)
:param limit: maximum number of records to return (default: all)
:param order: a string to define the sort order of the query
(default: none)
:param count: counts and returns the number of matching records
(default: False)
:returns: the top level elements for the given search query
"""
if not domain:
domain = []
res = self._search_parents(
domain=domain, offset=offset, limit=limit, order=order, count=count
)
return res if count else self.browse(res)
@api.model
def _search_parents(
self, domain=False, offset=0, limit=None, order=None, count=False
):
if not domain:
domain = []
self._check_parent_field()
self.check_access_rights("read")
if expression.is_false(self, domain):
return []
query = self._where_calc(domain)
self._apply_ir_rules(query, "read")
from_clause, where_clause, where_clause_arguments = query.get_sql()
parent_where = where_clause and (" WHERE %s" % where_clause) or ""
parent_query = 'SELECT "%s".id FROM ' % self._table + from_clause + parent_where
no_parent_clause = '"{table}"."{field}" IS NULL'.format(
table=self._table, field=self._parent_name
)
no_access_clause = '"{table}"."{field}" NOT IN ({query})'.format(
table=self._table, field=self._parent_name, query=parent_query
)
parent_clause = "({} OR {})".format(no_parent_clause, no_access_clause)
order_by = self._generate_order_by(order, query)
from_clause, where_clause, where_clause_params = query.get_sql()
where_str = (
where_clause
and (" WHERE {} AND {}".format(where_clause, parent_clause))
or (" WHERE %s" % parent_clause)
)
if count:
# pylint: disable=sql-injection
query_str = "SELECT count(1) FROM " + from_clause + where_str
self._cr.execute(query_str, where_clause_params)
return self._cr.fetchone()[0]
limit_str = limit and " limit %s" or ""
offset_str = offset and " offset %s" or ""
query_str = (
'SELECT "%s".id FROM ' % (self._table)
+ from_clause
+ where_str
+ order_by
+ limit_str
+ offset_str
)
complete_where_clause_params = where_clause_params + where_clause_arguments
if limit:
complete_where_clause_params.append(limit)
if offset:
complete_where_clause_params.append(offset)
# pylint: disable=sql-injection
self._cr.execute(query_str, complete_where_clause_params)
return list({x[0] for x in self._cr.fetchall()})

View file

@ -0,0 +1,80 @@
# Copyright 2020 Creu Blanca
# Copyright 2024 Tecnativa - Víctor Martínez
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, fields, models
from odoo.tools import config
class DMSFieldMixin(models.AbstractModel):
_name = "dms.field.mixin"
_description = "Mixin to use DMS Field"
dms_directory_ids = fields.One2many(
"dms.directory",
"res_id",
string="DMS Directories",
domain=lambda self: [
("res_model", "=", self._name),
("storage_id.save_type", "!=", "attachment"),
],
auto_join=True,
)
@api.model
def models_to_track_dms_field_template(self):
"""Models to be tracked for dms field templates
:args:
:returns: list of models
"""
return self.env["dms.field.template"].sudo().search([]).mapped("model_id.model")
@api.model_create_multi
def create(self, vals_list):
"""Create a dms directory when creating the record if exist a template.
We need to avoid applying a template except when testing functionality
with dms_field* modules to avoid the error that a directory with the same
name already exists (example: create partner).
"""
result = super().create(vals_list)
test_condition = not config["test_enable"] or self.env.context.get(
"test_dms_field"
)
if (
test_condition
and not self.env.context.get("skip_track_dms_field_template")
and self._name in self.models_to_track_dms_field_template()
):
template = self.env["dms.field.template"].with_context(res_model=self._name)
for item in result:
template.with_context(res_id=item.id).create_dms_directory()
return result
def write(self, vals):
"""When modifying a record that has linked directories and changing the
user_id field it is necessary to update the auto-generated access group
(name and explicit_user_ids).
"""
res = super().write(vals)
# Apply sudo() in case the user does not have access to the directory
for item in self.sudo().filtered("dms_directory_ids"):
if "user_id" in vals:
template = self.env["dms.field.template"]._get_template_from_model(
item._name
)
if template:
template.sudo()._get_autogenerated_group(item)
return res
def unlink(self):
"""When deleting a record, we also delete the linked directories and the
auto-generated access group.
"""
# Apply sudo() in case the user does not have access to the directory
for record in self.sudo().filtered("dms_directory_ids"):
group = (
self.env["dms.access.group"].sudo()._get_item_from_dms_field_ref(record)
)
record.sudo().dms_directory_ids.unlink()
group.unlink()
return super().unlink()

View file

@ -0,0 +1,202 @@
# Copyright 2024 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class DmsFieldTemplate(models.Model):
_name = "dms.field.template"
_inherit = "dms.field.mixin"
_description = "Dms Field Template"
name = fields.Char(required=True)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
store=True,
index=True,
)
storage_id = fields.Many2one(
comodel_name="dms.storage",
domain=[("save_type", "!=", "attachment")],
string="Storage",
)
parent_directory_id = fields.Many2one(
comodel_name="dms.directory",
domain="[('storage_id', '=', storage_id)]",
string="Parent directory",
)
model_id = fields.Many2one(
comodel_name="ir.model",
string="Model",
domain=[("transient", "=", False), ("model", "!=", "dms.field.template")],
index=True,
)
model = fields.Char(
compute="_compute_model", compute_sudo=True, store=True, string="Model name"
)
group_ids = fields.Many2many(
comodel_name="dms.access.group",
string="Groups",
)
user_field_id = fields.Many2one(
comodel_name="ir.model.fields",
domain="[('model_id', '=', model_id),('relation', '=', 'res.users')]",
string="User field",
)
directory_format_name = fields.Char(
string="Directory format name",
default="{{object.display_name}}",
help="""You can set expressions to be used for the directory name,
e.g.: {{object.name}}""",
)
@api.depends("model_id")
def _compute_model(self):
for item in self:
item.model = item.model_id.model
def _get_template_from_model(self, model):
return self.search([("model", "=", model)], limit=1)
@api.model_create_multi
def create(self, vals_list):
"""Create dms directory automatically in the creation in install mode."""
result = super().create(vals_list)
if self.env.context.get("install_mode"):
for item in result:
item_ctx = item.with_context(res_model=item._name, res_id=item.id)
item_ctx.create_dms_directory()
return result
@api.model
def create_dms_directory(self):
"""According to the model, create the directory linked to that record
and the subdirectories."""
res_model = self.env.context.get("res_model")
res_id = self.env.context.get("res_id")
record = self.env[res_model].browse(res_id)
directory_model = self.env["dms.directory"].sudo()
if res_model == "dms.field.template":
return directory_model.create(
{
"storage_id": record.storage_id.id,
"res_id": record.id,
"res_model": record._name,
"is_root_directory": True,
"name": record.display_name,
"group_ids": record.group_ids.ids,
}
)
template = self._get_template_from_model(res_model).sudo()
if not template:
raise ValidationError(_("There is no template linked to this model"))
total_directories = directory_model.search_count(
[
("parent_id", "=", self.parent_directory_id.id),
("res_model", "=", res_model),
("res_id", "=", res_id),
]
)
if total_directories > 0:
raise ValidationError(_("There is already a linked directory created."))
# Create root directory + files
dms_directory_ids = template.dms_directory_ids
new_directory = directory_model.create(
template._prepare_directory_vals(dms_directory_ids, record)
)
self._copy_files_from_directory(dms_directory_ids, new_directory)
# Create child directories
self._create_child_directories(new_directory, dms_directory_ids)
return new_directory
def _copy_files_from_directory(self, directory, new_directory):
for file in directory.file_ids:
file.copy({"directory_id": new_directory.id})
def _prepare_autogenerated_group(self, record):
group_name = _("Autogenerated group from %(model)s (%(name)s) #%(id)s") % {
"model": record._description,
"name": record.display_name,
"id": record.id,
}
vals = {
"name": group_name,
# We need to set all the permissions so that the user can manage their
# documents (directories and files)
"perm_create": True,
"perm_write": True,
"perm_unlink": True,
"dms_field_ref": "%s,%s" % (record._name, record.id),
"explicit_user_ids": [(5, 0)],
}
# Apply sudo() because the user may not have permissions to access
# ir.model.fields.
user_field = self.sudo().user_field_id
if user_field:
user = record[user_field.name]
if user:
vals["explicit_user_ids"] += [(4, user.id)]
return vals
def _get_autogenerated_group(self, record):
"""Get the existing auto-generated group or create a new one.
The permissions of the auto-generated group should be changed
to make sure you have the correct data.
"""
group_model = self.env["dms.access.group"]
group_ref = group_model._get_item_from_dms_field_ref(record)
if group_ref:
group_ref.write(self._prepare_autogenerated_group(record))
return group_ref
# Create the autogenerated group linked to the record
return group_model.create(self._prepare_autogenerated_group(record))
def _create_child_directories(self, parent, directory):
# Create child directories (all leves) + files
directory_model = self.env["dms.directory"].sudo()
for child_directory in directory.child_directory_ids:
child = directory_model.create(
{
"name": child_directory.name,
"is_root_directory": False,
"parent_id": parent.id,
}
)
self._copy_files_from_directory(child_directory, child)
self._create_child_directories(child, child_directory)
def _prepare_directory_vals(self, directory, record):
# Groups of the new directory will be those of the template + auto-generate
groups = directory.group_ids
groups += self._get_autogenerated_group(record)
directory_name = self.env["mail.render.mixin"]._render_template(
self.directory_format_name,
record._name,
record.ids,
engine="inline_template",
)[record.id]
vals = {
"storage_id": directory.storage_id.id,
"res_id": record.id,
"res_model": record._name,
"name": directory_name,
"group_ids": [(4, group.id) for group in groups],
}
if not self.parent_directory_id:
vals.update({"is_root_directory": True})
else:
vals.update(
{"parent_id": self.parent_directory_id.id, "inherit_group_ids": False}
)
return vals
@api.constrains("model_id")
def _check_model_id(self):
for template in self:
if self.env["dms.field.template"].search(
[("model_id", "=", template.model_id.id), ("id", "!=", template.id)]
):
raise UserError(
_("There is already a template created for this model.")
)

View file

@ -0,0 +1,66 @@
# Copyright 2020 Creu Blanca
# Copyright 2024 Tecnativa - Víctor Martínez
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class DmsStorage(models.Model):
_inherit = "dms.storage"
field_template_ids = fields.One2many(
comodel_name="dms.field.template",
inverse_name="storage_id",
string="File templated ids",
)
@api.model
def _build_documents_storage(self, storage):
storage_directories = []
model = self.env["dms.directory"]
directories = model.search_parents([["storage_id", "=", storage.id]])
for record in directories:
storage_directories.append(model._build_documents_view_directory(record))
return {
"id": "storage_%s" % storage.id,
"text": storage.name,
"icon": "fa fa-database",
"type": "storage",
"data": {"odoo_id": storage.id, "odoo_model": "dms.storage"},
"children": storage_directories,
}
@api.model
def get_js_tree_data(self):
return [record._build_documents_storage(record) for record in self.search([])]
@api.constrains("model_ids", "save_type")
def _constrain_model_ids(self):
for storage in self:
if storage.save_type == "attachment":
continue
if self.env["dms.directory"].search(
[
("storage_id", "=", storage.id),
("is_root_directory", "=", True),
(
"res_model",
"not in",
storage.mapped("model_ids.model"),
),
]
):
raise ValidationError(
_("Some directories are inconsistent with the storage models")
)
if storage.model_ids and self.env["dms.directory"].search(
[
("storage_id", "=", storage.id),
("is_root_directory", "=", True),
("res_model", "=", False),
]
):
raise ValidationError(
_("There are directories not associated to a record")
)

View file

@ -0,0 +1,13 @@
# Copyright 2020 Creu Blanca
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import fields, models
class IrActionsActWindowView(models.Model):
_inherit = "ir.actions.act_window.view"
view_mode = fields.Selection(
selection_add=[("dms_list", "DMS Tree")], ondelete={"dms_list": "cascade"}
)

View file

@ -0,0 +1,38 @@
# Copyright 2020 Creu Blanca
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import _, fields, models
from odoo.addons.base.models.ir_ui_view import NameManager
class IrUiView(models.Model):
_inherit = "ir.ui.view"
type = fields.Selection(selection_add=[("dms_list", "DMS Tree")])
def _postprocess_tag_dms_list(self, node, name_manager, node_info):
parent = node.getparent()
if parent_name := parent and parent.get("name"):
field = name_manager.model._fields.get(parent_name)
if field:
model_name = field.comodel_name
if model_name not in self.env:
self._raise_view_error(
_("Model not found: %(model)s", model=model_name), node
)
model = self.env[model_name]
new_name_manager = NameManager(model, parent=name_manager)
root_info = {
"view_type": node.tag,
"view_editable": self._editable_node(node, name_manager),
"view_modifiers_from_model": self._modifiers_from_model(node),
}
new_node_info = dict(
root_info,
modifiers={},
editable=self._editable_node(node, new_name_manager),
)
for child in node:
self._postprocess_tag_field(child, new_name_manager, new_node_info)

View file

@ -0,0 +1,9 @@
# Copyright 2024 Tecnativa - Carlos Roca
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import models
class ResPartner(models.Model):
_name = "res.partner"
_inherit = ["res.partner", "dms.field.mixin"]

View file

@ -0,0 +1,16 @@
To use the embedded view in any module, the module must inherit from the mixin
dms.field.mixin (You have an example with res.partner in this module).
Once this is done, in the form view of the model we will have to add the following:
.. code-block:: xml
<field name="dms_directory_ids" mode="dms_list" />
In addition, it will be necessary to create an Embedded DMS template for this model.
#. *Go to Documents > Configuration > Embedded DMS templates* and create a new record.
#. Set a storage, a model (res.partner for example) and the access groups you want.
#. You can also use expressions in "Directory format name", for example: {{object.name}}
#. Click on the "Documents" tab icon and a folder hierarchy will be created.
#. You can set here the hierarchy of directories, subdirectories and files you need, this hierarchy will be used as a base when creating a new record (res.partner for example).

View file

@ -0,0 +1,7 @@
* Enric Tobella <etobella@creublanca.es>
* Jaime Arroyo <jaime.arroyo@creublanca.es>
* `Tecnativa <https://www.tecnativa.com>`_:
* Víctor Martínez
* Carlos Roca

View file

@ -0,0 +1,2 @@
This addon creates a new kind of view and allows to define a folder
related to a record.

View file

@ -0,0 +1,2 @@
- Add drag & drop compatibility to the dms_tree mode
- Multiple selection support (e.g. cut several files and paste to another folder).

View file

@ -0,0 +1,4 @@
#. Go to the form view of an existing partner and click on the "DMS" tab icon, a hierarchy of
folders and files linked to that record will be created.
#. Create a new partner. A hierarchy of folders and files linked to that record will be created.

View file

@ -0,0 +1,3 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_dms_field_template_user,dms_field_template_user,model_dms_field_template,base.group_user,1,0,0,0
access_dms_field_template_manager,dms_field_template_manager,model_dms_field_template,dms.group_dms_manager,1,1,1,1
1 id name model_id/id group_id/id perm_read perm_write perm_create perm_unlink
2 access_dms_field_template_user dms_field_template_user model_dms_field_template base.group_user 1 0 0 0
3 access_dms_field_template_manager dms_field_template_manager model_dms_field_template dms.group_dms_manager 1 1 1 1

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="rule_multi_company_dms_field_template" model="ir.rule">
<field name="name">DMS Field Template multi-company</field>
<field name="model_id" ref="model_dms_field_template" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','in',company_ids)]</field>
</record>
<record id="rule_multi_company_dms_access_group" model="ir.rule">
<field name="name">DMS Access Group multi-company</field>
<field name="model_id" ref="model_dms_access_group" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','in',company_ids)]</field>
</record>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,463 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>DMS Field</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="dms-field">
<h1 class="title">DMS Field</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4c2029fa91a7142bb6adb4fa9c78281736dbf3f2b2bce45b90ae068dba046f41
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/dms/tree/16.0/dms_field"><img alt="OCA/dms" src="https://img.shields.io/badge/github-OCA%2Fdms-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/dms-16-0/dms-16-0-dms_field"><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/dms&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This addon creates a new kind of view and allows to define a folder
related to a record.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p>To use the embedded view in any module, the module must inherit from the mixin
dms.field.mixin (You have an example with res.partner in this module).</p>
<p>Once this is done, in the form view of the model we will have to add the following:</p>
<pre class="code xml literal-block">
<span class="nt">&lt;field</span><span class="w"> </span><span class="na">name=</span><span class="s">&quot;dms_directory_ids&quot;</span><span class="w"> </span><span class="na">mode=</span><span class="s">&quot;dms_list&quot;</span><span class="w"> </span><span class="nt">/&gt;</span>
</pre>
<p>In addition, it will be necessary to create an Embedded DMS template for this model.</p>
<ol class="arabic simple">
<li><em>Go to Documents &gt; Configuration &gt; Embedded DMS templates</em> and create a new record.</li>
<li>Set a storage, a model (res.partner for example) and the access groups you want.</li>
<li>You can also use expressions in “Directory format name”, for example: {{object.name}}</li>
<li>Click on the “Documents” tab icon and a folder hierarchy will be created.</li>
<li>You can set here the hierarchy of directories, subdirectories and files you need, this hierarchy will be used as a base when creating a new record (res.partner for example).</li>
</ol>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-2">Usage</a></h1>
<p>#. Go to the form view of an existing partner and click on the “DMS” tab icon, a hierarchy of
folders and files linked to that record will be created.
#. Create a new partner. A hierarchy of folders and files linked to that record will be created.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-3">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Add drag &amp; drop compatibility to the dms_tree mode</li>
<li>Multiple selection support (e.g. cut several files and paste to another folder).</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/dms/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/dms/issues/new?body=module:%20dms_field%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-6">Authors</a></h2>
<ul class="simple">
<li>Creu Blanca</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-7">Contributors</a></h2>
<ul class="simple">
<li>Enric Tobella &lt;<a class="reference external" href="mailto:etobella&#64;creublanca.es">etobella&#64;creublanca.es</a>&gt;</li>
<li>Jaime Arroyo &lt;<a class="reference external" href="mailto:jaime.arroyo&#64;creublanca.es">jaime.arroyo&#64;creublanca.es</a>&gt;</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Víctor Martínez</li>
<li>Carlos Roca</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/dms/tree/16.0/dms_field">OCA/dms</a> project on GitHub.</p>
<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>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,22 @@
Copyright (c) 2014 Ivan Bozhanov
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,22 @@
Copyright (c) 2014 Orange Hill Development
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 132 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 137 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,22 @@
/** @odoo-module **/
import utils from "web.field_utils";
export function formatBinarySize(value, field, options) {
var new_options = _.defaults(options || {}, {
si: true,
});
var thresh = new_options.si ? 1000 : 1024;
if (Math.abs(value) < thresh) {
return utils.format.float(value, field, options) + " B";
}
var units = new_options.si
? ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
var unit = -1;
var new_value = value;
do {
new_value /= thresh;
++unit;
} while (Math.abs(new_value) >= thresh && unit < units.length - 1);
return utils.format.float(new_value, field, new_options) + " " + units[unit];
}

View file

@ -0,0 +1,81 @@
/** @odoo-module **/
var mapping = [
["file-image-o", /^image\//],
["file-audio-o", /^audio\//],
["file-video-o", /^video\//],
["file-pdf-o", "application/pdf"],
["file-text-o", "text/plain"],
["file-code-o", ["text/html", "text/javascript", "application/javascript"]],
[
"file-archive-o",
[
/^application\/x-(g?tar|xz|compress|bzip2|g?zip)$/,
/^application\/x-(7z|rar|zip)-compressed$/,
/^application\/(zip|gzip|tar)$/,
],
],
[
"file-word-o",
[
/ms-?word/,
"application/vnd.oasis.opendocument.text",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
],
],
[
"file-powerpoint-o",
[
/ms-?powerpoint/,
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
],
],
[
"file-excel-o",
[
/ms-?excel/,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
],
],
["file-o"],
];
function match(mimetype, cond) {
if (Array.isArray(cond)) {
return cond.reduce(function (v, c) {
return v || match(mimetype, c);
}, false);
} else if (cond instanceof RegExp) {
return cond.test(mimetype);
} else if (cond === undefined) {
return true;
}
return mimetype === cond;
}
var cache = {};
function resolve(mimetype) {
if (cache[mimetype]) {
return cache[mimetype];
}
for (var i = 0; i < mapping.length; i++) {
if (match(mimetype, mapping[i][1])) {
cache[mimetype] = mapping[i][0];
return mapping[i][0];
}
}
}
export function mimetype2fa(mimetype, options) {
if (typeof mimetype === "object") {
var new_options = mimetype;
return function (new_mimetype) {
return mimetype2fa(new_mimetype, new_options);
};
}
var icon = resolve(mimetype);
if (icon && options && options.prefix) {
return options.prefix + icon;
}
return icon;
}

View file

@ -0,0 +1,16 @@
// This code is necessary to avoid an incompatibility with the definition
// .o_field_widget input, .o_field_widget textarea {color: inherit} added by
// web_responsive. This causes the text to appear white when editing the name of
// the selected file/directory on field mode (Example: field added on hr.employee by
// hr_dms_field), making it impossible to see the content. It is necessary to maintain
// it in version 16.0, and if migrating to higher versions, check if it remains
// necessary.
.jstree-proton .jstree-clicked {
color: #000000 !important;
i {
color: #ffffff;
}
}
.jstree-proton a.jstree-clicked {
color: #ffffff !important;
}

View file

@ -0,0 +1,37 @@
/** @odoo-module */
/* Copyright 2024 Tecnativa - Carlos Roca
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {addFieldDependencies} from "@web/views/utils";
import {Field} from "@web/views/fields/field";
import {XMLParser} from "@web/core/utils/xml";
export class DmsListArchParser extends XMLParser {
parseFieldNode(node, models, modelName) {
return Field.parseFieldNode(node, models, modelName, "dms_list");
}
parse(arch, models, modelName) {
const fieldNodes = {};
const activeFields = {};
this.visitXML(arch, (node) => {
if (node.tagName === "field") {
const fieldInfo = this.parseFieldNode(node, models, modelName);
fieldNodes[fieldInfo.name] = fieldInfo;
node.setAttribute("field_id", fieldInfo.name);
addFieldDependencies(
activeFields,
models[modelName],
fieldInfo.FieldComponent.fieldDependencies
);
return false;
}
});
for (const [key, field] of Object.entries(fieldNodes)) {
activeFields[key] = field; // TODO process
}
return {
activeFields,
__rawArch: arch,
};
}
}

View file

@ -0,0 +1,538 @@
/** @odoo-module */
/* Copyright 2024 Tecnativa - Carlos Roca
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {Layout} from "@web/search/layout";
import {useService} from "@web/core/utils/hooks";
import {useModel} from "@web/views/model";
const {Component, onRendered} = owl;
import {session} from "@web/session";
import {Deferred} from "@web/core/utils/concurrency";
import {Domain} from "@web/core/domain";
import {mimetype2fa} from "../../utils/mimetype.esm";
import {formatBinarySize} from "../../utils/format_binary_size.esm";
import {patch} from "@web/core/utils/patch";
import {DynamicRecordList} from "@web/views/relational_model";
export const DMSListControllerObject = {
setup() {
this._super(...arguments);
this.orm = useService("orm");
this.actionService = useService("action");
this.http = useService("http");
const {rootState} = this.props.state || {};
this.model =
(this.props.record && this.props.record.model) ||
useModel(this.props.Model, {
resModel: this.props.resModel,
fields: this.props.fields,
activeFields: this.props.archInfo.activeFields,
viewMode: "dms_list",
rootState,
});
this.resModel = this.props.resModel || this.props.record.resModel;
this.rendererActions = {
onDMSCreateEmptyStorages: this.onDMSCreateEmptyStorages.bind(this),
onDMSLoad: this.onDMSLoad.bind(this),
onDMSRenameNode: this.onDMSRenameNode.bind(this),
onDMSMoveNode: this.onDMSMoveNode.bind(this),
onDMSDeleteNode: this.onDMSDeleteNode.bind(this),
onDMSDroppedFile: this.onDMSDroppedFile.bind(this),
};
onRendered(() => {
this.processProps();
});
},
sanitizeDMSModel(model) {
return model;
},
processProps() {
const model = this.sanitizeDMSModel(this.resModel);
var storage_domain = [];
var directory_domain = [];
var autocompute_directory = false;
var show_storage = true;
if (model === "dms.storage") {
if (this.model.root.data && this.model.root.data.id) {
storage_domain = [["id", "=", this.model.root.data.id]];
} else {
storage_domain = [
[
"id",
"in",
this.model.root.records.map((record) => {
return record.resId;
}),
],
];
}
directory_domain = [];
} else if (model === "dms.field.template") {
if (this.model.root.resId) {
storage_domain = [["id", "=", this.model.root.data.storage_id[0]]];
} else {
storage_domain = [["id", "=", 0]];
}
directory_domain = [
[
"root_directory_id",
"in",
this.model.root.data.dms_directory_ids.records.map((record) => {
return record.resId;
}),
],
];
} else {
storage_domain = [["field_template_ids.model", "=", model]];
autocompute_directory = true;
show_storage = false;
}
this.params = {
storage: {
domain: storage_domain,
context: session.user_context,
show: show_storage,
},
directory: {
domain: directory_domain,
context: session.user_context,
autocompute_directory: autocompute_directory,
},
file: {
domain: [],
context: session.user_context,
show: true,
},
};
},
async onDMSLoad(node) {
await this.model.root.load();
this.model.notify();
this.processProps();
var args = this.buildDMSArgs();
var result = false;
if (!node || node.id === "#") {
result = this.loadInitialData(args);
} else {
result = this.loadNode(node, args);
}
return {result, empty_storages: this.empty_storages};
},
loadInitialData(args) {
var self = this;
var data_loaded = new Deferred();
this.empty_storages = [];
this.loadStorages(args).then(
function (storages) {
var loading_data_parts = [];
_.each(
storages,
function (storage, index) {
if (storage.count_storage_directories > 0) {
var directory_loaded = new Deferred();
loading_data_parts.push(directory_loaded);
this.loadDirectoriesSingle(storage.id, args).then(function (
directories
) {
if (directories.length > 0) {
storages[index].directories = directories;
} else if (
self.props.resModel !== "dms.directory" &&
self.props.resModel !== "dms.storage"
) {
self.empty_storages.push(storage);
}
directory_loaded.resolve();
});
} else if (
self.props.resModel !== "dms.directory" &&
self.props.resModel !== "dms.storage"
) {
self.empty_storages.push(storage);
}
}.bind(this)
);
$.when.apply($, loading_data_parts).then(
function () {
if (args.storage.show) {
var result = _.chain(storages)
.map(
function (storage) {
if (!storage.directories) {
return undefined;
}
var children = _.map(
storage.directories || [],
function (directory) {
return this.makeNodeDirectory(
directory,
args.file.show
);
}.bind(this)
);
return this.makeNodeStorage(storage, children);
}.bind(this)
)
.filter(function (node) {
return node;
})
.value();
data_loaded.resolve(result);
} else {
var nodes = [];
_.each(
storages,
function (storage) {
_.each(
storage.directories,
function (directory) {
nodes.push(
this.makeNodeDirectory(
directory,
args.file.show,
storage
)
);
}.bind(this)
);
}.bind(this)
);
data_loaded.resolve(nodes);
}
}.bind(this)
);
// Launch _update_overlay to show the drag and drop
// this._update_overlay();
}.bind(this)
);
return data_loaded;
},
loadNode(node, args) {
var result = new Deferred();
if (node.data && node.data.resModel === "dms.storage") {
this.loadDirectoriesSingle(node.data.data.id, args).then(
function (directories) {
var directory_nodes = _.map(
directories,
function (directory) {
return this.makeNodeDirectory(directory, args.file.show);
}.bind(this)
);
result.resolve(directory_nodes);
}.bind(this)
);
} else if (node.data && node.data.resModel === "dms.directory") {
var files_loaded = new Deferred();
var directories_loaded = new Deferred();
this.loadSubdirectoriesSingle(node.data.data.id, args).then(
function (directories) {
var directory_nodes = _.map(
directories,
function (directory) {
return this.makeNodeDirectory(directory, args.file.show);
}.bind(this)
);
directories_loaded.resolve(directory_nodes);
}.bind(this)
);
if (args.file.show) {
this.loadFilesSingle(node.data.data.id, args).then(
function (files) {
var file_nodes = _.map(
files,
function (file) {
return this.makeNodeFile(file);
}.bind(this)
);
files_loaded.resolve(file_nodes);
}.bind(this)
);
} else {
files_loaded.resolve([]);
}
$.when(directories_loaded, files_loaded).then(function (
directories,
files
) {
result.resolve(directories.concat(files));
});
} else {
result.resolve([]);
}
return result;
},
makeNodeDirectory(directory, showFiles, storage) {
var data = _.extend(directory, {
name: directory.name,
perm_read: directory.permission_read,
perm_create: directory.permission_create,
perm_write: directory.permission_write,
perm_unlink: directory.permission_unlink,
icon_url: directory.icon_url,
count_total_directories: directory.count_total_directories,
count_total_files: directory.count_total_files,
human_size: directory.human_size,
count_elements: directory.count_elements,
});
if (
storage &&
this.resModel !== "dms.directory" &&
this.resModel !== "dms.storage"
) {
// We are assuming this is a record directory, so disabling actions
data.name = storage.name;
data.storage = true;
}
var dt = this.makeDataPoint({
data: data,
resModel: "dms.directory",
});
dt.parent = directory.parent_id ? "directory_" + directory.parent_id[0] : "#";
var directoryNode = {
id: dt.id,
text: directory.name,
icon: "fa fa-folder-o",
type: "directory",
data: dt,
};
if (showFiles) {
directoryNode.children =
directory.count_directories + directory.count_files > 0;
} else {
directoryNode.children = directory.count_directories > 0;
}
return directoryNode;
},
makeNodeFile(file) {
var data = _.extend(file, {
filename: file.name,
display_name: file.name,
binary_size: formatBinarySize(file.size),
perm_read: file.permission_read,
perm_create:
file.permission_create && (!file.is_locked || file.is_lock_editor),
perm_write:
file.permission_write && (!file.is_locked || file.is_lock_editor),
perm_unlink:
file.permission_unlink && (!file.is_locked || file.is_lock_editor),
icon_url: file.icon_url,
});
var dt = this.makeDataPoint({
data: data,
resModel: "dms.file",
});
return {
id: dt.id,
text: dt.data.display_name,
icon: mimetype2fa(dt.data.mimetype, {prefix: "fa fa-"}),
type: "file",
data: dt,
};
},
makeNodeStorage(storage, children) {
var dt = this.makeDataPoint({
data: storage,
resModel: "dms.storage",
});
return {
id: "storage_" + storage.id,
text: storage.name,
icon: "fa fa-database",
type: "storage",
data: dt,
children: children,
};
},
makeDataPoint(dt) {
return new DynamicRecordList(this.model, dt);
},
loadDirectories(operator, value, args) {
return this.orm.call("dms.directory", "search_read_parents", [], {
fields: _.union(args.directory.fields || [], [
"permission_read",
"permission_create",
"permission_write",
"permission_unlink",
"count_directories",
"count_files",
"name",
"parent_id",
"icon_url",
"count_total_directories",
"count_total_files",
"human_size",
"count_elements",
"__last_update",
]),
domain: this.buildDMSDomain(
[["storage_id", operator, value]],
args.directory.domain,
args.directory.autocompute_directory
),
context: args.directory.context || session.user_context,
});
},
loadDirectoriesSingle(storage_id, args) {
return this.loadDirectories("=", storage_id, args);
},
loadSubdirectories(operator, value, args) {
const domain = this.buildDMSDomain(
[["parent_id", operator, value]],
args.directory.domain,
false
);
const fields = _.union(args.directory.fields || [], [
"permission_read",
"permission_create",
"permission_write",
"permission_unlink",
"count_directories",
"count_files",
"name",
"parent_id",
"icon_url",
"count_total_directories",
"count_total_files",
"human_size",
"count_elements",
"__last_update",
]);
return this.orm.searchRead("dms.directory", domain, fields, {
context: args.file.context || session.user_context,
});
},
loadSubdirectoriesSingle(directory_id, args) {
return this.loadSubdirectories("=", directory_id, args);
},
loadFiles(operator, value, args) {
const domain = this.buildDMSDomain(
[["directory_id", operator, value]],
args.file.domain
);
const fields = _.union(args.file.fields || [], [
"permission_read",
"permission_create",
"permission_write",
"permission_unlink",
"icon_url",
"name",
"mimetype",
"directory_id",
"human_size",
"is_locked",
"is_lock_editor",
"extension",
"__last_update",
]);
return this.orm.searchRead("dms.file", domain, fields, {
context: args.file.context || session.user_context,
});
},
loadFilesSingle(directory_id, args) {
return this.loadFiles("=", directory_id, args);
},
loadStorages(args) {
const fields = _.union(args.storage.fields || [], [
"name",
"count_storage_directories",
]);
return this.orm.searchRead("dms.storage", args.storage.domain || [], fields, {
context: args.storage.context || session.user_context,
});
},
buildDMSDomain(base, domain, autocompute_directory) {
var result = new Domain(base);
if (autocompute_directory) {
result = Domain.and([
result,
new Domain([["res_id", "=", this.model.root.resId]]),
]);
} else {
result = Domain.and([result, new Domain(domain || [])]);
}
return result.toList();
},
buildDMSArgs() {
return {
...this.params,
search: {
operator: "ilike",
},
};
},
onDMSCreateEmptyStorages() {
var data = {
model: this.sanitizeDMSModel(this.resModel),
empty_storages: this.empty_storages,
res_id: this.props.record.resId,
};
return this.orm.call("dms.field.template", "create_dms_directory", [], {
context: {
res_id: data.res_id,
res_model: data.model,
},
});
},
onDMSRenameNode(node, text) {
node.data.data.name = text;
return this.orm.write(node.data.resModel, [node.data.data.id], {
name: text,
});
},
onDMSMoveNode(node, newParent) {
var data = {};
if (node.data.resModel === "dms.file") {
data.directory_id = newParent.data.data.id;
} else if (node.data.resModel === "dms.directory") {
data.parent_id = newParent.data.data.id;
}
return this.orm.write(node.data.resModel, [node.data.data.id], data);
},
onDMSDeleteNode(node) {
return this.orm.unlink(node.data.resModel, [node.data.data.id]);
},
async onDMSDroppedFile(directoryId, files) {
const params = {
csrf_token: odoo.csrf_token,
ufile: [...files],
model: "dms.file",
id: 0,
};
const fileData = await this.http.post(
"/web/binary/upload_attachment",
params,
"text"
);
const attachments = JSON.parse(fileData);
if (attachments.error) {
throw new Error(attachments.error);
}
const attachmentIds = attachments.map((a) => a.id);
const ctx = this.props.context || this.props.record.context;
if (!attachmentIds.length) {
return "no_attachments";
}
ctx.default_directory_id = directoryId;
const attachment_datas = await this.orm.call(
"dms.file",
"get_dms_files_from_attachments",
["", attachmentIds]
);
const attachments_args = [];
attachment_datas.forEach((attachment_data) => {
attachments_args.push({
name: attachment_data.name,
content: attachment_data.datas,
mimetype: attachment_data.mimetype,
});
});
return this.orm.call("dms.file", "create", [attachments_args], {
context: ctx,
});
},
};
export class DmsListController extends Component {}
patch(DmsListController.prototype, "DmsListControllerPatch", DMSListControllerObject);
DmsListController.template = "dms_field.View";
DmsListController.components = {Layout};

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="dms_field.View" owl="1">
<Layout display="props.display" className="'h-100 overflow-auto'">
<t t-component="props.Renderer" rendererActions="rendererActions" />
</Layout>
</t>
</templates>

View file

@ -0,0 +1,532 @@
/** @odoo-module */
/* Copyright 2024 Tecnativa - Carlos Roca
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {_lt} from "@web/core/l10n/translation";
import {useService} from "@web/core/utils/hooks";
import {loadCSS, loadJS} from "@web/core/assets";
const {Component, onMounted, onWillStart, useEffect, useRef, useState} = owl;
import {download} from "@web/core/network/download";
import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog";
export class DmsListRenderer extends Component {
setup() {
this.js_tree = useRef("jstree");
this.extra_actions = useRef("extra_actions");
this.dms_add_directory = useRef("dms_add_directory");
this.nodeSelectedState = useState({data: {}});
this.messaging = useService("messaging");
this.notification = useService("notification");
this.dialog = useService("dialog");
this.dragState = useState({
showDragZone: false,
});
this.dropZone = useRef("dropZone");
useEffect(
(el) => {
if (!el) {
return;
}
const highlight = this.highlight.bind(this);
const unhighlight = this.unhighlight.bind(this);
const drop = this.onDrop.bind(this);
el.addEventListener("dragover", highlight);
el.addEventListener("dragleave", unhighlight);
el.addEventListener("drop", drop);
return () => {
el.removeEventListener("dragover", highlight);
el.removeEventListener("dragleave", unhighlight);
el.removeEventListener("drop", drop);
};
},
() => [this.dropZone.el]
);
onWillStart(async () => {
await loadJS("/dms_field/static/lib/jsTree/jstree.js");
await loadCSS("/dms_field/static/lib/jsTree/themes/proton/style.css");
this.config = this.buildTreeConfig();
});
onMounted(() => {
this.$tree = $(this.js_tree.el);
this.$tree.jstree(this.config);
this.startTreeTriggers();
});
}
buildTreeConfig() {
var plugins = [
"conditionalselect",
"massload",
"wholerow",
"state",
"sort",
"search",
"types",
"contextmenu",
];
return {
core: {
widget: this,
animation: 0,
multiple: false,
check_callback: this.checkCallback.bind(this),
themes: {
name: "proton",
responsive: true,
},
data: this.loadData.bind(this),
},
contextmenu: {
items: this.loadContextMenu.bind(this),
},
state: {
key: "documents",
},
conditionalselect: this.checkSelect.bind(this),
plugins: plugins,
sort: function (a, b) {
// Correctly sort the records according to the type of element
// (folder or file).
// Do not use node.icon because they may have (or will have) a
// different icon for each file according to its extension.
var node_a = this.get_node(a);
var node_b = this.get_node(b);
if (node_a.data.resModel === node_b.data.resModel) {
return node_a.text > node_b.text ? 1 : -1;
}
return node_a.data.resModel > node_b.data.resModel ? 1 : -1;
},
};
}
startTreeTriggers() {
this.$tree.on("open_node.jstree", (e, data) => {
if (data.node.data && data.node.data.resModel === "dms.directory") {
data.instance.set_icon(data.node, "fa fa-folder-open-o");
}
});
this.$tree.on("close_node.jstree", (e, data) => {
if (data.node.data && data.node.data.resModel === "dms.directory") {
data.instance.set_icon(data.node, "fa fa-folder-o");
}
});
this.$tree.on("changed.jstree", (e, data) => {
this.treeChanged(data);
});
this.$tree.on("move_node.jstree", (e, data) => {
var jstree = this.$tree.jstree(true);
this.props.rendererActions.onDMSMoveNode(
data.node,
jstree.get_node(data.parent)
);
});
this.$tree.on("rename_node.jstree", (e, data) => {
this.props.rendererActions.onDMSRenameNode(data.node, data.text);
this.updatePreview(data.node);
});
this.$tree.on("delete_node.jstree", (e, data) => {
this.props.rendererActions.onDMSDeleteNode(data.node);
});
this.$tree.on("loaded.jstree", () => {
this.$tree.jstree("open_all");
});
}
treeChanged(data) {
if (
data.action === "select_node" &&
data.selected &&
data.selected.length === 1
) {
this.updatePreview(data.node);
}
}
updatePreview(node) {
var $buttons = $(this.extra_actions.el);
$buttons.empty();
if (
node.data &&
["dms.directory", "dms.file"].indexOf(node.data.resModel) !== -1
) {
this.nodeSelectedState.data = {};
this.nodeSelectedState.data = node.data;
var menu = this.loadContextMenu(node);
_.each(menu, (action) => {
this.generateActionButton(node, action, $buttons);
});
}
}
loadContextMenu(node) {
var menu = {};
var jstree = this.$tree.jstree(true);
if (node.data) {
if (node.data.resModel === "dms.directory") {
menu = this.loadContextMenuDirectoryBefore(jstree, node, menu);
menu = this.loadContextMenuBasic(jstree, node, menu);
menu = this.loadContextMenuDirectory(jstree, node, menu);
} else if (node.data.resModel === "dms.file") {
menu = this.loadContextMenuBasic(jstree, node, menu);
menu = this.loadContextMenuFile(jstree, node, menu);
}
}
return menu;
}
loadContextMenuBasic($jstree, node, menu) {
menu.rename = {
separator_before: false,
separator_after: false,
icon: "fa fa-pencil",
label: _lt("Rename"),
action: () => {
$jstree.edit(node);
},
_disabled: () => {
return !node.data.data.perm_write || node.data.data.storage;
},
};
menu.action = {
separator_before: false,
separator_after: false,
icon: "fa fa-bolt",
label: _lt("Actions"),
action: false,
submenu: {
cut: {
separator_before: false,
separator_after: false,
icon: "fa fa-scissors",
label: _lt("Cut"),
action: () => {
$jstree.cut(node);
},
_disabled: () => {
return !node.data.data.perm_read || node.data.data.storage;
},
},
},
_disabled: () => {
return !node.data.data.perm_read;
},
};
menu.delete = {
separator_before: false,
separator_after: false,
icon: "fa fa-trash-o",
label: _lt("Delete"),
action: () => {
$jstree.delete_node(node);
},
_disabled: () => {
return !node.data.data.perm_unlink || node.data.data.storage;
},
};
menu.open = {
separator_before: false,
separator_after: false,
icon: "fa fa-external-link",
label: _lt("Open"),
action: () => {
this.onDMSOpenRecord(node);
},
};
return menu;
}
loadContextMenuDirectoryBefore($jstree, node, menu) {
menu.add_directory = {
separator_before: false,
separator_after: false,
icon: "fa fa-folder",
label: _lt("Create directory"),
action: () => {
this.onDMSAddDirectory(node);
},
_disabled: () => {
return !node.data.data.perm_create;
},
};
menu.add_file = {
separator_before: false,
separator_after: true,
icon: "fa fa-file",
label: _lt("Create File"),
action: () => {
this.onDMSAddFile(node);
},
_disabled: () => {
return !node.data.data.perm_create;
},
};
return menu;
}
loadContextMenuDirectory($jstree, node, menu) {
if (menu.action && menu.action.submenu) {
menu.action.submenu.paste = {
separator_before: false,
separator_after: false,
icon: "fa fa-clipboard",
label: _lt("Paste"),
action: () => {
$jstree.paste(node);
},
_disabled: () => {
return !$jstree.can_paste() && !node.data.data.perm_create;
},
};
}
return menu;
}
loadContextMenuFile($jstree, node, menu) {
menu.preview = {
separator_before: false,
separator_after: false,
icon: "fa fa-eye",
label: _lt("Preview"),
action: () => {
this.onDMSPreviewFile(node);
},
};
menu.download = {
separator_before: false,
separator_after: false,
icon: "fa fa-download",
label: _lt("Download"),
action: () => {
download({
url: "/web/content",
data: {
id: node.data.data.id,
download: true,
field: "content",
model: "dms.file",
filename_field: "name",
filename: node.data.data.filename,
},
});
},
};
return menu;
}
generateActionButton(node, action, $buttons) {
if (action.action) {
var $button = $("<button>", {
type: "button",
class: "btn btn-secondary " + action.icon,
"data-toggle": "dropdown",
title: action.label,
}).on("click", (event) => {
event.preventDefault();
event.stopPropagation();
if (action._disabled && action._disabled()) {
return;
}
action.action();
});
$buttons.append($button);
}
if (action.submenu) {
_.each(action.submenu, (sub_action) => {
this.generateActionButton(node, sub_action, $buttons);
});
}
}
async loadData(node, callback) {
const {result, empty_storages} = await this.props.rendererActions.onDMSLoad(
node
);
result.then((data) => {
callback.call(this, data);
if (empty_storages.length > 0) {
$(this.dms_add_directory.el).removeClass("o_hidden");
}
});
}
/*
This is used to check that the operation is allowed
*/
checkCallback(operation, node, parent) {
if (operation === "copy_node" || operation === "move_node") {
// Prevent moving a root node
if (node.parent === "#") {
return false;
}
// Prevent moving a child above or below the root
if (parent.id === "#") {
return false;
}
// Prevent moving a child to a settings object
if (parent.data && parent.data.resModel === "dms.storage") {
return false;
}
// Prevent moving a child to a file
if (parent.data && parent.data.resModel === "dms.file") {
return false;
}
}
return true;
}
checkSelect(node) {
if (this.props.filesOnly && node.data.resModel !== "dms.file") {
return false;
}
return !(node.parent === "#" && node.data.resModel === "dms.storage");
}
onDMSAddDirectory(node) {
var context = {
default_parent_directory_id: node.data.data.id,
};
this.dialog.add(FormViewDialog, {
resModel: "dms.directory",
context: context,
title: _lt("Add Directory: ") + node.data.data.name,
onRecordSaved: () => {
const selected_id = this.$tree.find(".jstree-clicked").attr("id");
const model_data = this.$tree.jstree(true)._model.data;
const state = this.$tree.jstree(true).get_state();
const open_res_ids = state.core.open.map(
(id) => model_data[id].data.data.id
);
this.$tree.on("refresh_node.jstree", () => {
const model_data_entries = Object.entries(model_data);
const ids = model_data_entries
.filter(
([, value]) =>
value.data &&
open_res_ids.includes(value.data.data.id) &&
value.data.resModel === "dms.directory"
)
.map((tuple) => tuple[0]);
for (var id of ids) {
this.$tree.jstree(true).open_node(id);
}
});
this.$tree.jstree(true).refresh_node(selected_id);
},
});
}
onDMSAddFile(node) {
var context = {
default_directory_id: node.data.data.id,
};
this.dialog.add(FormViewDialog, {
resModel: "dms.file",
context: context,
title: _lt("Add File: ") + node.data.data.name,
onRecordSaved: () => {
const selected_id = this.$tree.find(".jstree-clicked").attr("id");
const model_data = this.$tree.jstree(true)._model.data;
const state = this.$tree.jstree(true).get_state();
const open_res_ids = state.core.open.map(
(id) => model_data[id].data.data.id
);
this.$tree.on("refresh_node.jstree", () => {
const model_data_entries = Object.entries(model_data);
const ids = model_data_entries
.filter(
([, value]) =>
value.data &&
open_res_ids.includes(value.data.data.id) &&
value.data.model === "dms.directory"
)
.map((tuple) => tuple[0]);
for (var id of ids) {
this.$tree.jstree(true).open_node(id);
}
});
this.$tree.jstree(true).refresh_node(selected_id);
},
});
}
onDMSAddDirectoryRecord() {
this.props.rendererActions.onDMSCreateEmptyStorages().then(() => {
this.$tree.jstree(true).refresh();
$(this.dms_add_directory.el).addClass("o_hidden");
});
}
onDMSOpenRecord(node) {
this.dialog.add(FormViewDialog, {
resModel: node.data.resModel,
title: _lt("Open: ") + node.data.data.name,
resId: node.data.data.id,
});
}
onDMSPreviewFile(node) {
this.messaging.get().then((messaging) => {
const attachmentList = messaging.models.AttachmentList.insert({
selectedAttachment: messaging.models.Attachment.insert({
id: node.data.data.id,
filename: node.data.data.name,
name: node.data.data.name,
mimetype: node.data.data.mimetype,
model_name: node.data.resModel,
}),
});
Component.env.services.dialog = messaging.models.Dialog.insert({
attachmentListOwnerAsAttachmentView: attachmentList,
});
});
}
get showDragZone() {
return (
this.nodeSelectedState.data.resModel === "dms.directory" &&
this.dragState.showDragZone
);
}
highlight(ev) {
ev.stopPropagation();
ev.preventDefault();
this.dragState.showDragZone = true;
}
unhighlight(ev) {
ev.stopPropagation();
ev.preventDefault();
this.dragState.showDragZone = false;
}
async onDrop(ev) {
ev.preventDefault();
const directoryId = this.nodeSelectedState.data.data.id;
const res = await this.props.rendererActions
.onDMSDroppedFile(directoryId, ev.dataTransfer.files)
.catch((error) => {
this.notification.add(error.data.message, {
type: "danger",
});
});
if (res === "no_attachments") {
this.notification.add(_lt("An error occurred during the upload"));
} else {
const selected_id = this.$tree.find(".jstree-clicked").attr("id");
const model_data = this.$tree.jstree(true)._model.data;
const state = this.$tree.jstree(true).get_state();
const open_res_ids = state.core.open.map(
(id) => model_data[id].data.data.id
);
this.$tree.on("refresh_node.jstree", () => {
const model_data_entries = Object.entries(model_data);
const ids = model_data_entries
.filter(
([, value]) =>
value.data &&
open_res_ids.includes(value.data.data.id) &&
value.data.model === "dms.directory"
)
.map((tuple) => tuple[0]);
for (var id of ids) {
this.$tree.jstree(true).open_node(id);
}
});
this.$tree.jstree(true).refresh_node(selected_id);
}
this.unhighlight(ev);
}
}
DmsListRenderer.template = "dms_list.Renderer";

View file

@ -0,0 +1,89 @@
.dms_document_controls {
padding: 5px 0 10px 0;
}
.dms_document_preview {
height: 100% !important;
.o_preview_directory_body {
display: flex;
flex-wrap: wrap;
margin-right: -16px;
margin-left: -16px;
.o_preview_directory_icon {
flex: 0 1 auto;
}
.o_preview_directory_table_body {
flex: 1 1 auto;
}
}
}
.dms_document_col_preview {
position: relative;
.o_dropzone {
width: 100%;
height: 100%;
position: absolute;
background-color: #aaaa;
z-index: 2;
left: 0;
top: 0;
i {
justify-content: center;
display: flex;
align-items: center;
height: 100%;
}
}
}
.dms_treeview {
height: 100%;
.dms_document_container {
height: 100%;
.dms_document_col {
padding: 0;
width: 50%;
}
@media (max-width: 768px) {
width: 100%;
.dms_document_col {
width: 100%;
}
.dms_document_col_preview {
display: none !important;
}
}
@media (min-width: 769px) {
.dms_document_col_tree > div {
border-right-width: 3px;
border-right-style: solid;
border-right-color: #888;
}
}
.dms_document_row {
height: 100% !important;
.dms_document_col_tree > div {
height: 100%;
.dms_document_tree {
height: 100%;
.dms_content,
.dms_tree {
height: 100%;
}
}
}
}
}
}
.vakata-context {
z-index: 9999;
.vakata-context-parent + ul {
list-style: none;
left: 100%;
margin-top: -2.58em;
margin-left: 2px;
}
}

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="dms_list.Renderer" owl="1">
<div class="dms_treeview">
<div class="container-fluid dms_document_container ">
<div class="row o_dms_header btn-group">
<button
class="btn btn-secondary o_dms_add_directory fa fa-database o_hidden"
t-ref="dms_add_directory"
t-on-click="onDMSAddDirectoryRecord"
/>
<div class="o_dms_extra_actions btn-group" t-ref="extra_actions" />
</div>
<div
t-attf-class="row dms_document_row #{env.isSmall ? 'dms_document_mobile' : 'dms_document_desktop'}"
>
<div class="dms_document_col dms_document_col_tree">
<div class="dms_document_tree">
<div class="dms_content">
<div class="dms_tree" t-ref="jstree" />
</div>
</div>
</div>
<div
class="dms_document_col dms_document_col_preview"
t-ref="dropZone"
>
<div t-if="showDragZone" class="o_dropzone">
<i class="fa fa-cloud-upload fa-10x" />
</div>
<div class="dms_document_preview">
<t t-call="dms_list.DocumentTreeViewDirectoryPreview" />
</div>
</div>
</div>
</div>
</div>
</t>
<t t-name="dms_list.DocumentTreeViewDirectoryPreview" owl="1">
<div
class="o_preview_directory"
t-if="Object.entries(nodeSelectedState.data).length !== 0"
t-att-data-directory-id="nodeSelectedState.data.resModel === 'dms.directory' ? nodeSelectedState.data.res_id : ''"
>
<div class="top_info row">
<div class="left_info col-4">
<div class="o_preview_directory_icon" align="center">
<div>
<img
t-if="nodeSelectedState.data.resModel === 'dms.directory'"
class="h-100 w-100"
t-att-src="nodeSelectedState.data.data.icon_url"
/>
<a
t-if="nodeSelectedState.data.resModel === 'dms.file'"
class="o_preview_file"
t-att-data-id="nodeSelectedState.data.id"
>
<img
class="h-100 w-100"
t-att-src="nodeSelectedState.data.data.icon_url"
/>
</a>
</div>
</div>
</div>
<div class="right_info col-8">
<h3>
<t t-esc="nodeSelectedState.data.data.name" />
</h3>
<t t-if="nodeSelectedState.data.resModel === 'dms.directory'">
<p><b>Subdirectories:</b> <span
t-esc="nodeSelectedState.data.data.count_total_directories"
/></p>
<p><b>Files:</b> <span
t-esc="nodeSelectedState.data.data.count_total_files"
/></p>
<p><b>Size:</b> <span
t-if="nodeSelectedState.data.data.human_size"
t-esc="nodeSelectedState.data.data.human_size"
/></p>
<p><b>Elements:</b> <span
t-esc="nodeSelectedState.data.data.count_elements"
/></p>
</t>
<t t-if="nodeSelectedState.data.resModel === 'dms.file'">
<p><b>Size:</b> <span
t-esc="nodeSelectedState.data.data.human_size"
/></p>
</t>
<div class="bottom_buttons">
<a
t-if="nodeSelectedState.data.resModel === 'dms.file'"
class="btn btn-primary"
t-attf-href="/web/content?id=#{nodeSelectedState.data.data.id}&amp;field=content&amp;model=dms.file&amp;filename_field=name&amp;download=true"
>
<i class="fa fa-download" />
Download
</a>
<button
class="btn btn-primary o_preview_file"
t-if="nodeSelectedState.data.resModel === 'dms.file'"
t-on-click="() => this.onDMSPreviewFile(nodeSelectedState)"
>
Open
</button>
</div>
</div>
</div>
</div>
</t>
</templates>

View file

@ -0,0 +1,33 @@
/** @odoo-module */
import {registry} from "@web/core/registry";
import {DmsListController} from "./dms_list_controller.esm";
import {DmsListArchParser} from "./dms_list_arch_parser.esm";
import {RelationalModel} from "@web/views/relational_model";
import {DmsListRenderer} from "./dms_list_renderer.esm";
export const dmsListView = {
type: "dms_list",
display_name: "Dms Tree",
icon: "fa fa-file-o",
multiRecord: true,
Controller: DmsListController,
ArchParser: DmsListArchParser,
Renderer: DmsListRenderer,
Model: RelationalModel,
props(genericProps, view) {
const {ArchParser} = view;
const {arch, relatedModels, resModel} = genericProps;
const archInfo = new ArchParser().parse(arch, relatedModels, resModel);
return {
...genericProps,
Model: view.Model,
Renderer: view.Renderer,
archInfo,
};
},
};
registry.category("views").add("dms_list", dmsListView);

View file

@ -0,0 +1,27 @@
/** @odoo-module */
/* Copyright 2024 Tecnativa - Carlos Roca
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {X2ManyField} from "@web/views/fields/x2many/x2many_field";
import {patch} from "@web/core/utils/patch";
import {DmsListRenderer} from "../../dms_list/dms_list_renderer.esm";
import {DMSListControllerObject} from "../../dms_list/dms_list_controller.esm";
patch(X2ManyField.prototype, "dms_field.X2ManyField", {
...DMSListControllerObject,
get rendererProps() {
const archInfo = this.activeField.views[this.viewMode];
const props = {
archInfo,
list: this.list,
openRecord: this.openRecord.bind(this),
};
if (this.viewMode === "dms_list") {
props.archInfo = archInfo;
props.readonly = this.props.readonly;
props.rendererActions = this.rendererActions;
return props;
}
return this._super(...arguments);
},
});
X2ManyField.components = {...X2ManyField.components, DmsListRenderer};

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t
t-name="dms_field.X2ManyField"
t-inherit="web.X2ManyField"
t-inherit-mode="extension"
owl="1"
>
<xpath expr="//ListRenderer" position="after">
<DmsListRenderer t-elif="viewMode == 'dms_list'" t-props="rendererProps" />
</xpath>
</t>
</templates>

View file

@ -0,0 +1 @@
from . import test_dms_field

View file

@ -0,0 +1,246 @@
# Copyright 2020 Creu Blanca
# Copyright 2024 Tecnativa - Víctor Martínez
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import fields
from odoo.exceptions import UserError, ValidationError
from odoo.tests import new_test_user
from odoo.tools import mute_logger
from odoo.addons.base.tests.common import BaseCommon
class TestDmsField(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, test_dms_field=True))
cls.user_a = new_test_user(cls.env, login="test-user-a")
cls.group = cls.env["res.groups"].create(
{"name": "Test group", "users": [(4, cls.user_a.id)]}
)
cls.user_b = new_test_user(cls.env, login="test-user-b")
cls.template = cls.env.ref("dms_field.field_template_partner")
cls.template.group_ids.group_ids = [(4, cls.group.id)]
cls.template.group_ids.explicit_user_ids = [(4, cls.user_b.id)]
cls.storage = cls.template.storage_id
cls.directory = cls.template.dms_directory_ids
cls.subdirectory_1 = cls.env["dms.directory"].create(
{
"name": "Test subdirectory 1",
"parent_id": cls.directory.id,
"storage_id": cls.storage.id,
}
)
cls.subdirectory_2 = cls.env["dms.directory"].create(
{
"name": "Test subdirectory 2",
"parent_id": cls.directory.id,
"storage_id": cls.storage.id,
}
)
cls.partner = (
cls.env["res.partner"]
.with_context(skip_track_dms_field_template=True)
.create({"name": "DEMO Partner"})
)
def _create_directory_vals(self, record):
return {
"storage_id": self.storage.id,
"is_root_directory": True,
"name": record.display_name,
"res_model": record._name,
"res_id": record.id,
}
def test_check_constrain_multi_directory(self):
self.env["dms.directory"].create(self._create_directory_vals(self.partner))
with self.assertRaises(ValidationError):
self.env["dms.directory"].create(self._create_directory_vals(self.partner))
def test_check_constrain_not_root(self):
directory = self.env["dms.directory"].create(
self._create_directory_vals(self.partner)
)
with self.assertRaises(ValidationError):
self.env["dms.directory"].create(
{
"parent_id": directory.id,
"name": "Second Directory",
"res_model": self.partner._name,
"res_id": self.partner.id,
}
)
def test_js_tree(self):
self.assertTrue(
any(
r["id"] == "storage_%s" % self.storage.id
for r in self.storage.get_js_tree_data()
)
)
def test_dms_access_group_constrains_dms_field_ref(self):
group = self.env["dms.access.group"].create(
{
"name": "Test 1",
"dms_field_ref": "%s,%s" % (self.partner._name, self.partner.id),
}
)
with self.assertRaises(UserError):
group.copy({"name": "Test 2"})
def test_dms_access_group_company_dms_field_ref_01(self):
self.partner.company_id = False
group = self.env["dms.access.group"].create(
{
"name": "Test 1",
"dms_field_ref": "%s,%s" % (self.partner._name, self.partner.id),
}
)
self.assertFalse(group.company_id)
def test_dms_access_group_company_dms_field_ref_02(self):
self.company = self.env.company
self.partner.company_id = self.company
group = self.env["dms.access.group"].create(
{
"name": "Test 1",
"dms_field_ref": "%s,%s" % (self.partner._name, self.partner.id),
}
)
self.assertEqual(group.company_id, self.company)
def test_template_directory(self):
self.assertTrue(self.template.dms_directory_ids)
self.assertIn(
self.template.group_ids, self.template.dms_directory_ids.group_ids
)
@mute_logger("odoo.models.unlink")
def test_creation_process_01(self):
self.assertFalse(self.partner.dms_directory_ids)
template = self.env["dms.field.template"].with_context(
res_model=self.partner._name, res_id=self.partner.id
)
template.create_dms_directory()
self.partner.invalidate_model()
self.assertEqual(self.partner.dms_directory_ids.name, self.partner.display_name)
child_names = self.partner.dms_directory_ids.mapped("child_directory_ids.name")
directory_0 = self.partner.dms_directory_ids[0]
self.assertFalse(directory_0.parent_id)
self.assertTrue(directory_0.is_root_directory)
self.assertTrue(directory_0.inherit_group_ids)
self.assertIn(self.template.group_ids, directory_0.group_ids)
self.assertIn(self.group, directory_0.group_ids.group_ids)
self.assertIn(self.partner, directory_0.mapped("group_ids.dms_field_ref"))
group_custom = directory_0.group_ids.filtered("dms_field_ref")
self.assertTrue(group_custom.perm_create)
self.assertTrue(group_custom.perm_write)
self.assertTrue(group_custom.perm_unlink)
self.assertIn(self.user_b, directory_0.group_ids.explicit_user_ids)
self.assertIn(self.user_a, directory_0.group_ids.users)
self.assertIn(self.user_b, directory_0.group_ids.users)
self.assertIn(self.subdirectory_1.name, child_names)
self.assertIn(self.subdirectory_2.name, child_names)
with self.assertRaises(ValidationError):
template.create_dms_directory()
# Remove folder: El grupo de acceso todavía existe
old_groups = directory_0.group_ids
directory_0.unlink()
dms_field_ref_value = "%s,%s" % (self.partner._name, self.partner.id)
total = self.env["dms.access.group"].search_count(
[("dms_field_ref", "=", dms_field_ref_value)]
)
self.assertEqual(total, 1)
# Create directory again (access groups are the same)
template.create_dms_directory()
self.partner.invalidate_model()
directory_0 = self.partner.dms_directory_ids[0]
self.assertEqual(directory_0.group_ids, old_groups)
def test_creation_process_01_with_parent(self):
self.assertFalse(self.partner.dms_directory_ids)
self.template.parent_directory_id = fields.first(
self.template.storage_id.root_directory_ids
)
template = self.env["dms.field.template"].with_context(
res_model=self.partner._name, res_id=self.partner.id
)
template.create_dms_directory()
self.partner.invalidate_model()
self.assertEqual(self.partner.dms_directory_ids.name, self.partner.display_name)
directory_0 = self.partner.dms_directory_ids[0]
self.assertEqual(directory_0.parent_id, self.template.parent_directory_id)
self.assertFalse(directory_0.is_root_directory)
self.assertFalse(directory_0.inherit_group_ids)
self.assertIn(self.template.group_ids, directory_0.group_ids)
self.assertIn(self.group, directory_0.group_ids.group_ids)
self.assertIn(self.user_b, directory_0.group_ids.explicit_user_ids)
self.assertIn(self.user_a, directory_0.group_ids.users)
self.assertIn(self.user_b, directory_0.group_ids.users)
def test_creation_process_02(self):
partner_1 = self.env["res.partner"].create({"name": "Test partner 1"})
partner_1.invalidate_model()
directory_1 = partner_1.dms_directory_ids[0]
self.assertFalse(directory_1.parent_id)
self.assertTrue(directory_1.is_root_directory)
self.assertTrue(directory_1.inherit_group_ids)
partner_2 = (
self.env["res.partner"]
.with_context(skip_track_dms_field_template=True)
.create({"name": "Test partner 2"})
)
partner_2.invalidate_model()
self.assertFalse(partner_2.dms_directory_ids)
def test_creation_process_02_with_parent(self):
self.template.parent_directory_id = fields.first(
self.template.storage_id.root_directory_ids
)
partner_1 = self.env["res.partner"].create({"name": "Test partner 1"})
partner_1.invalidate_model()
directory_1 = partner_1.dms_directory_ids[0]
self.assertEqual(directory_1.parent_id, self.template.parent_directory_id)
self.assertFalse(directory_1.is_root_directory)
self.assertFalse(directory_1.inherit_group_ids)
partner_2 = (
self.env["res.partner"]
.with_context(skip_track_dms_field_template=True)
.create({"name": "Test partner 2"})
)
partner_2.invalidate_model()
self.assertFalse(partner_2.dms_directory_ids)
def test_creation_process_03(self):
self.template.directory_format_name = "{{object.name}}-{{object.ref}}"
partner_1 = self.env["res.partner"].create(
{"name": "TEST-PARTNER1", "ref": "CUSTOM-REF"}
)
partner_1.invalidate_model()
self.assertEqual(
partner_1.dms_directory_ids.name, "%s-%s" % (partner_1.name, partner_1.ref)
)
def test_parents(self):
directory = self.env["dms.directory"].create(
self._create_directory_vals(self.partner)
)
self.assertEqual(
directory.search_read_parents([("id", "=", directory.id)], fields=["id"])[
0
],
{"id": directory.id},
)
self.assertEqual(
directory.search_read_parents(
[("id", "=", directory.id)], fields=["id", "name"]
)[0],
{"id": directory.id, "name": directory.name},
)
self.assertIn(
{"id": directory.id, "name": directory.name},
directory.search_read_parents(fields=["id", "name"]),
)

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_dms_access_groups_tree" model="ir.ui.view">
<field name="name">dms_access_groups.tree</field>
<field name="model">dms.access.group</field>
<field name="inherit_id" ref="dms.view_dms_access_groups_tree" />
<field name="arch" type="xml">
<field name="count_directories" position="after">
<field name="dms_field_ref" optional="hide" />
</field>
</field>
</record>
<record id="view_dms_access_groups_form" model="ir.ui.view">
<field name="name">dms_access.group.form</field>
<field name="model">dms.access.group</field>
<field name="inherit_id" ref="dms.view_dms_access_groups_form" />
<field name="arch" type="xml">
<field name="perm_create" position="after">
<field name="dms_field_ref" />
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Creu Blanca
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<odoo>
<record model="ir.ui.view" id="dms_directory_dms_tree_view">
<field name="name">dms.directory.dms_tree (in dms_field)</field>
<field name="model">dms.directory</field>
<field name="type">dms_list</field>
<field name="arch" type="xml">
<dms_list>
<field name="name" />
</dms_list>
</field>
</record>
</odoo>

View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2024 Tecnativa - Víctor Martínez
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_dms_field_template_tree" model="ir.ui.view">
<field name="model">dms.field.template</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="storage_id" />
<field name="model_id" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_dms_field_template_form" model="ir.ui.view">
<field name="model">dms.field.template</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<h1><field name="name" /></h1>
</div>
<group name="main_group">
<field name="company_id" groups="base.group_multi_company" />
<field name="dms_directory_ids" invisible="1" />
<field name="storage_id" required="1" />
<field
name="parent_directory_id"
attrs="{'invisible':[('storage_id', '=', False)]}"
/>
<field name="model_id" required="1" />
<field
name="user_field_id"
attrs="{'invisible':[('model_id', '=', False)]}"
/>
<field
name="directory_format_name"
attrs="{'invisible':[('model_id', '=', False)]}"
required="1"
/>
<field name="group_ids" widget="many2many_tags" />
</group>
<notebook position="inside">
<page
name="dms"
string="Documents"
attrs="{'invisible':['|', ('storage_id', '=', False), ('model_id', '=', False)]}"
>
<field
name="dms_directory_ids"
mode="dms_list"
attrs="{'invisible': [('id', '=', False)]}"
/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_dms_field_template" model="ir.actions.act_window">
<field name="name">Embedded DMS templates</field>
<field name="res_model">dms.field.template</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
id="menu_dms_field_template"
name="Embedded DMS templates"
parent="dms.cat_menu_dms_config_organizing"
sequence="6"
action="action_dms_field_template"
/>
</odoo>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Creu Blanca
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<odoo>
<record model="ir.ui.view" id="dms_storage_dms_tree_view">
<field name="name">dms.storage.dms_tree (in dms_field)</field>
<field name="model">dms.storage</field>
<field name="type">dms_list</field>
<field name="arch" type="xml">
<dms_list>
<field name="name" />
</dms_list>
</field>
</record>
<record model="ir.actions.act_window" id="dms_storage_act_window">
<field name="name">Documents</field>
<field name="res_model">dms.storage</field>
<field name="view_mode">dms_list</field>
<field name="domain">[]</field>
<field name="context">{}</field>
</record>
<record model="ir.ui.menu" id="dms_storage_menu">
<field name="name">Documents</field>
<field name="parent_id" ref="dms.main_menu_dms" />
<field name="action" ref="dms_storage_act_window" />
<field name="sequence" eval="99" />
</record>
</odoo>

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Dms_field Module - dms_field
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for dms_field. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [dms](../../odoo-bringout-oca-dms-dms)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon dms_field or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-dms-dms_field"
# or
uv pip install odoo-bringout-oca-dms-dms_field"
```

View file

@ -0,0 +1,20 @@
# Models
Detected core models and extensions in dms_field.
```mermaid
classDiagram
class dms_field_mixin
class dms_field_template
class res_partner
class dms_access_group
class dms_directory
class dms_field_mixin
class dms_storage
class ir_actions_act_window_view
class ir_ui_view
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: dms_field. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon dms_field
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,41 @@
# Security
Access control and security definitions in dms_field.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../dms_field/security/ir.model.access.csv)**
- 2 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[security.xml](../dms_field/security/security.xml)**
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../dms_field/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
- **[security.xml](../dms_field/security/security.xml)**
- Security groups, categories, and XML-based rules
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon dms_field
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,43 @@
[project]
name = "odoo-bringout-oca-dms-dms_field"
version = "16.0.0"
description = "DMS Field -
Create DMS View and allow to use them inside a record"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-dms-dms>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["dms_field"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]