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 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.
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.
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.
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.
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.
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).
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.
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.
To interact with the filesystem you can simply use the filesystem client
provided by the field value.
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’.
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')