Metadata-Version: 2.1
Name: odoo-addon-fs_folder
Version: 18.0.2.0.0
Requires-Python: >=3.10
Requires-Dist: odoo-addon-fs_storage==18.0.*
Requires-Dist: odoo==18.0.*
Summary: A module to link to Odoo records and manage from record forms forlders from external file systems
Home-page: https://github.com/OCA/storage
License: LGPL-3
Author: ACSONE SA/NV,Odoo Community Association (OCA)
Author-email: support@odoo-community.org
Classifier: Programming Language :: Python
Classifier: Framework :: Odoo
Classifier: Framework :: Odoo :: 18.0
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
Description-Content-Type: text/x-rst

.. image:: https://odoo-community.org/readme-banner-image
   :target: https://odoo-community.org/get-involved?utm_source=readme
   :alt: Odoo Community Association

=========
Fs Folder
=========

.. 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   !! This file is generated by oca-gen-addon-readme !!
   !! changes will be overwritten.                   !!
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   !! source digest: sha256:4085455207c434600a5db8b91a0ae7f89808a7061387200fa7147fda9e52715c
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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/license-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%2Fstorage-lightgray.png?logo=github
    :target: https://github.com/OCA/storage/tree/18.0/fs_folder
    :alt: OCA/storage
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
    :target: https://translation.odoo-community.org/projects/storage-18-0/storage-18-0-fs_folder
    :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/storage&target_branch=18.0
    :alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

If you need to link some specific models to a specific folder into an
external filesystem and be able to manage the content of this folder
from the model form view, this module is for you.

**Table of contents**

.. contents::
   :local:

Use Cases / Context
===================

Some organizations have a need to manage documents in an external
filesystem next to odoo records. This module provides a generic way to
do it through the use of a specialized field ``FsFolder`` that can be
used in any Odoo model.

Usage
=====

The module is a technical module providing a new field type to Odoo. It
is not intended to be used directly by end-users. It is intended to be
used by developers to create new modules that need to manage files in an
external filesystem.

The **FsFolder** field type is a specialized field type that can be used
in any Odoo model. When an Odoo developer declares a field of this type
on a model and the related form view, the field will display a
specialized widget that allows the user to interact with a folder in an
external filesystem.

The field is linked to a filesystem backend that must be configured in
Odoo. The backend configuration is available in the 'Settings' ->
'Technical' -> 'FS Storage' menu. It is possible to define a default
backend for all the fields of this type in the Odoo configuration or to
define a specific backend for each model or field.

In the following documentation, we will see:

- how to declare a field of this type in a model
- how the security is managed
- how you can interact with the filesystem
- how you can customize the field behavior

Field declaration
-----------------

To declare a field of this type in a model, you need to import the field
type and declare it in the model definition. By default, the field
declaration requires no parampeters.

.. code:: python

   from odoo import models
   from odoo.addons.fs_folder import fields as fs_fields

   class MyModel(models.Model):
       _name = 'my.model'

       fs_folder_field = fs_fields.FsFolder()

The new field type comes with a specific way to initialize the field.
Even if the field is not yet initialized, the value of the field will be
an instance of the ``FsFolderValue`` class. But as for empty recordsets
in Odoo, the value will be considered as ``False`` in a boolean context
or ``None`` in a null context.

.. code:: python


   from odoo.addons.fs_folder.fields import FsFolderValue

   record = self.env['my.model'].create({})
   assert isinstance(record.fs_folder_field, FsFolderValue)
   assert not record.fs_folder_field
   assert record.fs_folder_field is None

To initialize the field, you can call the ``initialize`` method on the
field value. This method will create the folder in the filesystem if it
does not exist yet.

.. code:: python


   record.fs_folder_field.initialize()

The creation of the folder in the filesystem requires 2 informations:

- The path where the folder will be created
- The name of the folder

By default, the path is the root folder of the filesystem. In some cases
you may want to create the folder in a specific subfolder of the
filesystem depending on the record data (or not). In this case, you can
define into the field definition a method that will be called to get the
path where the folder will be created.

.. code:: python

   import fsspec

   class MyModel(models.Model):
       _name = 'my.model'

       fs_folder_field = fs_fields.FsFolder(
           create_parent_get='get_folder_path',
       )

       def get_folder_path(self, fs: fsspec.AbstractFileSystem) -> dict[int, list[str]]:
           result = {}
           for record in self:
               result[record.id] = ['my', 'subfolder']
           return result

In this example, the ``get_folder_path`` method will be called to get
the path where the folder will be created. The method must return a
dictionary where the key is the record id and the value is a list of
strings representing the path where the folder will be created. (The
list of strings will be joined with the approprisate separator defined
by the specific filesystem used to create the folder).

In the same way, you can define a method to get the folder name (by
default the folder name is the record display name).

.. code:: python


   class MyModel(models.Model):
       _name = 'my.model'

       reference = fields.Char(required=True)

       fs_folder_field = fs_fields.FsFolder(
           create_name_get='get_folder_name',
       )

       def get_folder_name(self, fs: fsspec.AbstractFileSystem) -> dict[int, str]:
           result = {}
           for record in self:
               result[record.id] = record.reference
           return result

In this example, the ``get_folder_name`` method will be called to get
the folder name. The method must return a dictionary where the key is
the record id and the value is the folder name.

For advanced use cases, you can also define the method that will ensure
the creation of the folder in the filesystem and assign the value to the
field. This method must return a list of ``FsFolderValue``.

.. code:: python


   class MyModel(models.Model):
       _name = 'my.model'

       reference = fields.Char(required=True)

       fs_folder_field = fs_fields.FsFolder(
           create_get='create_folder',
       )

       def create_folder(self, fs: fsspec.AbstractFileSystem) -> list[FsFolderValue]:
           result = []
           value_adapter = self.env["fs.folder.field.value.adapter"]
           storage_code = self.env[
                   "fs.storage"
               ].get_default_storage_code_for_fs_content(self._name, 'fs_folder_field')
           for record in self:
               path = f'my/subfolder/{record.reference}'
               fs.mkdir(path)
               record.fs_folder_field = value_adapter._created_folder_name_to_stored_value(
                   path, storage_code, fs
               )
               result.append(record.fs_folder_field)
           return result

Last but not least, 2 additional method hooks are available to customize
the behavior of the field:

- create_additional_kwargs_get: This method will be called to get
  additional keyword arguments to pass to the ``fs.mkdir`` method when
  creating the folder.
- create_post_process: This method will be called after the folder
  creation to do some additional processing.

Security
--------

   **Important:** The security of the field should be managed by the
   filesystem backend.

In the python code
~~~~~~~~~~~~~~~~~~

The initialization of the field value is only allowed if the user has
write access to the record. From the field value, the user can get
access to the filesystem client and interact with the filesystem. The
filesystem is rooted in the folder itself. The user can only interact
with the folder and its children without being able to go up in the
filesystem.

In the form view through widget
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The addon comes with a specific widget that will be used to display the
field in the form view. The widget will display the folder content and
allow the user to interact with it. The user can create, delete, rename,
download and upload files and folders. The user can also navigate into
the filesystem. All theses operations are made available by methods
provided by the abstract model 'fs.folder.field.web.api'. A first level
of security is provided by this API. Any operation modifying the
filesystem will allowed only if the user has write access to the record
and any operation reading the filesystem will be allowed only if the
user has read access to the record.

Interacting with the filesystem
-------------------------------

To interact with the filesystem you can simply use the filesystem client
provided by the field value.

.. code:: python


   record.fs_folder_field.fs.mkdir('my/subfolder')
   with record.fs_folder_field.fs.open('my/subfolder/myfile.txt', 'w') as f:
       f.write('Hello, world!')

The api also provides higl-level methods to interact with the filesystem
and ease the access to the files. For example, if you need to provide a
download link to a file, you can use the ``get_url_for_download`` method
from the abstract model 'fs.folder.field.web.api'.

.. code:: python

   api = self.env['fs.folder.field.web.api']
   url = api.get_url_for_download(record.id,  record._name, "fs_folder_field", 'my/subfolder/myfile.txt')

Customizing the field behavior
------------------------------

As specified above, you can define some methods to customize the field
behavior. During the process of creating a directory in a filesystem, 1
additional mechanism comes into play to guarantee the conformity of the
directory created.

Folder name conformity
~~~~~~~~~~~~~~~~~~~~~~

When it comes to creating a folder in the filesystem, we must prevent
the use of special characters that could cause problems. This is done by
default by the field when creating the folder by replacing the
characters that are not allowed by the filesystem by an underscore. You
can control this behavior on the 'fs.storage' form view. A field is
available to disable this behavior and another one to define the
character to use as a replacement. If the behavior is disabled, the
field will raise an error if the folder name is not conform. (This
applies to the full path of the folder).

Other customizations
~~~~~~~~~~~~~~~~~~~~

In addition you can:

- override the adapter that will be used to convert the field value to a
  stored value and vice versa. The adapter is a model that must extend
  the 'fs.folder.field.value.adapter' abstract model.
- extend/override the api model used by the widget to interact with the
  filesystem. The api model must extend the 'fs.folder.field.web.api'
  abstract model.
- extend/override the widget itself

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/storage/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/storage/issues/new?body=module:%20fs_folder%0Aversion:%2018.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
-------

* ACSONE SA/NV

Contributors
------------

- Laurent Mignon laurent.mignon@acsone.eu
- Enric Tobella Alomar enric.tobella@dixmit.com

Other credits
-------------

The development of this module has been financially supported by:

- ACSONE SA/NV
- BRUGEL (Brussels Regional Public Service for Energy)

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.

.. |maintainer-lmignon| image:: https://github.com/lmignon.png?size=40px
    :target: https://github.com/lmignon
    :alt: lmignon

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-lmignon| 

This module is part of the `OCA/storage <https://github.com/OCA/storage/tree/18.0/fs_folder>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
