diff --git a/fs_attachment/README.rst b/fs_attachment/README.rst index be909f69c9..3bc5efba28 100644 --- a/fs_attachment/README.rst +++ b/fs_attachment/README.rst @@ -160,6 +160,18 @@ configure the way attachments will be stored in the filesystem. - application/javascript are stored in database whatever their size - text/css are stored in database whatever their size + A key can also be a ``.`` pair to force the storage in + database of the attachments of a specific binary field, whatever their + mimetype. It's useful for fields like the menu icons, that are read on + every page load, without having to keep all the images in database: + + {"image/": 51200, "application/javascript": 0, "text/css": 0, + "ir.ui.menu.web_icon_data": 0} + + A field rule is checked before the mimetype rules: if the attachment + matches it and is within its limit, it is stored in database, + otherwise the mimetype rules apply. + This option is only available on the filesystem storage that is used as default for attachments. diff --git a/fs_attachment/models/fs_storage.py b/fs_attachment/models/fs_storage.py index fdffe76e15..35be667634 100644 --- a/fs_attachment/models/fs_storage.py +++ b/fs_attachment/models/fs_storage.py @@ -66,7 +66,10 @@ class FsStorage(models.Model): "* images mimetypes (image/png, image/jpeg, ...) below 50KB are stored " "in database\n" "* application/javascript are stored in database whatever their size \n" - "* text/css are stored in database whatever their size", + "* text/css are stored in database whatever their size\n" + "A key can also be a '.' pair (e.g. " + "'ir.ui.menu.web_icon_data') to force the storage in database of the " + "attachments of a specific binary field, whatever their mimetype.", default=lambda self: self._default_force_db_for_default_attachment_rules, ) use_filename_obfuscation = fields.Boolean( diff --git a/fs_attachment/models/ir_attachment.py b/fs_attachment/models/ir_attachment.py index 3227f7c7c7..5b2ccafa0c 100644 --- a/fs_attachment/models/ir_attachment.py +++ b/fs_attachment/models/ir_attachment.py @@ -152,6 +152,27 @@ def _get_storage_force_db_config(self): self._storage() ) + def _get_storage_force_db_rules(self): + """Split the force db config into mimetype rules and field rules + + A key containing a "/" is the beginning of a mimetype. A key without + "/" but with a "." is a ``.`` pair (e.g. + ``ir.ui.menu.web_icon_data``). + + :return: a tuple ``(mimetype_rules, field_rules)`` where + ``mimetype_rules`` is a dict ``{mimetype_key: limit}`` and + ``field_rules`` a dict ``{(res_model, res_field): limit}`` + """ + mimetype_rules = {} + field_rules = {} + for key, limit in self._get_storage_force_db_config().items(): + if "/" not in key and "." in key: + res_model, res_field = key.rsplit(".", 1) + field_rules[(res_model, res_field)] = limit + else: + mimetype_rules[key] = limit + return mimetype_rules, field_rules + def _store_in_db_instead_of_object_storage_domain(self): """Return a domain for attachments that must be forced to DB @@ -165,13 +186,18 @@ def _store_in_db_instead_of_object_storage_domain(self): ``_store_in_db_instead_of_object_storage``. """ domain = [] - storage_config = self._get_storage_force_db_config() - for mimetype_key, limit in storage_config.items(): + mimetype_rules, field_rules = self._get_storage_force_db_rules() + for mimetype_key, limit in mimetype_rules.items(): part = [("mimetype", "=like", f"{mimetype_key}%")] if limit: part = Domain.AND([part, [("file_size", "<=", limit)]]) # OR simplifies to [(1, '=', 1)] if a domain being OR'ed is empty domain = Domain.OR([domain, part]) if domain else part + for (res_model, res_field), limit in field_rules.items(): + part = [("res_model", "=", res_model), ("res_field", "=", res_field)] + if limit: + part = Domain.AND([part, [("file_size", "<=", limit)]]) + domain = Domain.OR([domain, part]) if domain else part return domain def _store_in_db_instead_of_object_storage(self, data, mimetype): @@ -204,6 +230,16 @@ def _store_in_db_instead_of_object_storage(self, data, mimetype): value is the limit in size below which attachments are kept in DB. 0 means no limit. + A key can also be a ``.`` pair to force the storage in + DB of the attachments of a specific binary field, whatever their + mimetype, for instance:: + + {"image/": 51200, "ir.ui.menu.web_icon_data": 0} + + A field rule is checked before the mimetype rules: if the attachment + matches it and is within its limit, it is stored in DB, otherwise the + mimetype rules apply. + These limits are applied only if the storage is the default one for attachments (see ``_storage``). @@ -214,8 +250,16 @@ def _store_in_db_instead_of_object_storage(self, data, mimetype): """ if self._is_storage_disabled(): return True - storage_config = self._get_storage_force_db_config() - for mimetype_key, limit in storage_config.items(): + mimetype_rules, field_rules = self._get_storage_force_db_rules() + field_key = ( + self.env.context.get("attachment_res_model"), + self.env.context.get("attachment_res_field"), + ) + if field_key in field_rules: + limit = field_rules[field_key] + if not limit or len(data) <= limit: + return True + for mimetype_key, limit in mimetype_rules.items(): if mimetype.startswith(mimetype_key): if not limit: return True diff --git a/fs_attachment/readme/USAGE.md b/fs_attachment/readme/USAGE.md index 6283ddb40d..59bfda6828 100644 --- a/fs_attachment/readme/USAGE.md +++ b/fs_attachment/readme/USAGE.md @@ -70,6 +70,18 @@ configure the way attachments will be stored in the filesystem. - application/javascript are stored in database whatever their size - text/css are stored in database whatever their size + A key can also be a `.` pair to force the storage in + database of the attachments of a specific binary field, whatever their + mimetype. It's useful for fields like the menu icons, that are read on + every page load, without having to keep all the images in database: + + > {"image/": 51200, "application/javascript": 0, "text/css": 0, + > "ir.ui.menu.web_icon_data": 0} + + A field rule is checked before the mimetype rules: if the attachment + matches it and is within its limit, it is stored in database, + otherwise the mimetype rules apply. + This option is only available on the filesystem storage that is used as default for attachments. diff --git a/fs_attachment/readme/newsfragments/force_db_field.feature b/fs_attachment/readme/newsfragments/force_db_field.feature new file mode 100644 index 0000000000..c59ecb4200 --- /dev/null +++ b/fs_attachment/readme/newsfragments/force_db_field.feature @@ -0,0 +1,3 @@ +Allow `.` keys in `force_db_for_default_attachment_rules` to keep +the attachments of a specific binary field (e.g. `ir.ui.menu.web_icon_data`) in +the database whatever their mimetype. diff --git a/fs_attachment/static/description/index.html b/fs_attachment/static/description/index.html index 561f035b25..3c2b1a9cf1 100644 --- a/fs_attachment/static/description/index.html +++ b/fs_attachment/static/description/index.html @@ -3,7 +3,7 @@ -Base Attachment Object Store +README.rst