diff --git a/auto_backup_fs_file/README.rst b/auto_backup_fs_file/README.rst new file mode 100644 index 00000000000..855dd392262 --- /dev/null +++ b/auto_backup_fs_file/README.rst @@ -0,0 +1,298 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +=================== +Auto Backup Fs File +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:ee3d4c88955253f4037b88f12085fc335206fe54512032059d08dddf4c753c51 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/16.0/auto_backup_fs_file + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-auto_backup_fs_file + :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/server-tools&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module enhances the database backup functionality in Odoo by +introducing support for storing backups as files using the ``fsspec`` +library. It is designed to address the need for reliable and flexible +backup solutions, enabling businesses to safeguard their data by storing +backups on various filesystems, including cloud storage providers, +network drives, and local secure storage. + +By integrating with the ``fs_file`` addon from the OCA storage +repository, this module allows users to automate the backup process and +store the resulting files in a configured filesystem. It provides an +easy-to-use interface for configuring backup methods, managing stored +backups, and ensuring data integrity for disaster recovery or +multi-environment setups. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +BUSINESS NEED: This module addresses the critical need for safeguarding +Odoo instance data by enabling automated backups to a filesystem +supported by the ``fsspec`` library. Businesses often require reliable +and accessible backup solutions to ensure data integrity and recovery in +case of system failures or data loss. This module is particularly useful +in contexts where organizations need to store backups on cloud storage, +network drives, or other custom filesystems supported by ``fsspec``. + +Practical examples include: + +- Backing up Odoo data to cloud storage providers like AWS S3, Google + Cloud Storage, or Azure Blob Storage. +- Storing backups on a secure local or remote filesystem for disaster + recovery purposes. +- Automating backup processes in multi-environment setups, such as + multi-company or multi-website configurations. + +APPROACH: The module extends the backup functionality from the +``auto_backup`` module by introducing a method that allows storing the +resulting backup using an ``fsspec`` implementation. This is achieved +through the integration of the ``fs_file`` from `storage +repository `__. The module leverages the +``fsspec`` library to provide a flexible and extensible interface for +interacting with various filesystems. It automates the backup process by +exporting Odoo instance data and storing it in the specified filesystem. +Additionally, it allows users to download the backups for local storage +or further processing. + +Backup file cleanup is handled automatically based on the **Days to +Keep** configuration. When expired backup records are removed, the +physical backup files are not deleted synchronously. Instead, the module +delegates file deletion to the ``fs_attachment`` garbage collector (GC), +which marks files for deferred removal and physically deletes them +during Odoo's autovacuum cron cycle. This two-phase approach ensures +transactional safety: files are only removed once the GC confirms no +database record still references them. + +USEFUL INFORMATION: + +- **Dependencies**: This module depends on the ``fsspec`` library, its + relevant filesystem implementations, and the ``fs_file`` addon from + OCA/storage. Ensure the required ``fsspec`` plugins are installed for + your target filesystem. + +Installation +============ + +This addon itself does not introduce any dependencies, but its +dependencies may require additional packages. + +Configuration +============= + +1. **Review Documentation for Dependencies** Before configuring the + module, ensure you have reviewed the documentation for the following + modules: + +- ``fs_attachment`` +- ``fs_storage`` These modules provide the necessary setup for file + storage and attachment handling. + +2. **Configure File Storage** + +- Navigate to **Settings** > **Technical** > **FS Storage**. +- Create or select an existing storage configuration. +- Ensure the storage is properly set up and tested for accessibility. + +3. **Link Backup File field to Storage** + +- While configuring the file storage in **Settings** > **Technical** > + **FS Storage**, ensure that the ``backup_file`` from the + ``db.backup.fs.file`` model is listed under the ``Field`` field. +- This step is part of the storage configuration process. +- Save the changes after verifying the setup. + +.. image:: https://raw.githubusercontent.com/OCA/server-tools/16.0/auto_backup_fs_file/images/file_storage_configuration.png + :alt: Example of File Storage Configuration + +4. **Verify Configuration** + +- Perform a test backup to ensure the files are being stored in the + correct location. +- Check the logs for any errors or warnings. + +By following these steps, you will ensure that the module is properly +configured for storing backups in the desired file storage system. + +Usage +===== + +This module extends the functionality of the database backup system in +Odoo by introducing a new backup method: **Fs File**. This method allows +storing database backups as files using an FSSPEC implementation. + +How to Use the Module +--------------------- + +1. Configure the Backup Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Navigate to **Settings** > **Technical** > **Database Structure** > + **Automated Backups**. +2. Create or edit a backup configuration. +3. In the **Backup Method** field, select **Fs File**. +4. Configure other fields as needed, such as the backup format and + retention settings. +5. Save the configuration. + +2. Perform a Backup +~~~~~~~~~~~~~~~~~~~ + +1. From the list of backup configurations, select the one configured + with the **Fs File** method. +2. Click the **Backup Now** button to initiate the backup process. +3. The backup will be stored as a file in the configured FSSPEC storage. + +3. View Fs File Backups +~~~~~~~~~~~~~~~~~~~~~~~ + +1. Open the backup configuration form view. +2. In the top-right corner, you will see a **Backups** stat button (if + backups exist). +3. Click the **Backups** button to view the list of Fs File backups + associated with the configuration. + +4. Manage Fs File Backups +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- In the Fs File backups list view, you can see details such as the + backup filename and associated database backup configuration. +- Use this view to manage or download backups as needed. + +5. Cleanup and File Deletion +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Backup retention is controlled by the **Days to Keep** field on the +backup configuration. When this value is greater than 0, the automatic +cleanup process removes expired backup records during each backup run. + +When a backup record is deleted (either by automatic cleanup or manually +from the list view), the physical backup file in the filesystem storage +is **not removed immediately**. Instead, the file is marked for deferred +deletion by the ``fs_attachment`` garbage collector (GC), which runs +periodically via Odoo's autovacuum cron. Physical files are only removed +from the storage backend once the GC confirms no database record +references them. + +This means: + +- **Immediately after deletion**: the database record is gone, but the + file may still exist in the storage backend for a short period. +- **After the next autovacuum cycle**: the file is permanently deleted + from the storage backend. + +This behavior requires the storage's ``autovacuum_gc`` flag to be +enabled (the default). If disabled, files must be managed manually. + +Screenshots +~~~~~~~~~~~ + +- **Backup Configuration Form View** |Backup Configuration Form| + +- **Fs File Backups List View** |Fs File Backups List| + +Notes +~~~~~ + +- Ensure that the FSSPEC storage is properly configured before using + the **Fs File** method. +- This module adds a new stat button in the backup configuration form + view to quickly access Fs File backups. + +.. |Backup Configuration Form| image:: https://raw.githubusercontent.com/OCA/server-tools/16.0/auto_backup_fs_file/static/description/db_backup_form_view.png +.. |Fs File Backups List| image:: https://raw.githubusercontent.com/OCA/server-tools/16.0/auto_backup_fs_file/static/description/db_backup_fs_file_tree_view.png + +Known issues / Roadmap +====================== + +- **Folder field behavior**: The ``folder`` field on the ``db.backup`` + model specifies the backup storage directory. For records using the + ``fs_file`` method, storage is controlled by the ``fs_file`` field's + settings. The ``folder`` field is hidden (``invisible``) and no + longer required when ``method='fs_file'``, so it no longer interferes + with ``fs_file`` configurations. No auto-sync between both fields is + performed since the methods are mutually exclusive. + +- **Design limitation**: The current implementation has a design + constraint due to ``fs_storage`` addon limitations. Since storage + setting targets the ``db.backup.fs.file`` model, only one storage + backend can effectively be used. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Binhex + +Contributors +------------ + +- Rolando Pérez Rebollo r.perez@binhex.cloud + +Other credits +------------- + +The development of this module has been financially supported by: + +- Binhex + +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/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/auto_backup_fs_file/__init__.py b/auto_backup_fs_file/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/auto_backup_fs_file/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/auto_backup_fs_file/__manifest__.py b/auto_backup_fs_file/__manifest__.py new file mode 100644 index 00000000000..3b6c2a56411 --- /dev/null +++ b/auto_backup_fs_file/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2025 r.perez@binhex.cloud +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Auto Backup Fs File", + "summary": """Store backups using some FSSPEC implementation""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Binhex,Odoo Community Association (OCA)", + "category": "Tools", + "website": "https://github.com/OCA/server-tools", + "depends": ["auto_backup", "fs_file", "mail"], + "development_status": "Alpha", + "data": [ + "data/mail_activity_type_data.xml", + "data/fs_storage_data.xml", + "security/ir.model.access.csv", + "views/db_backup_views.xml", + "views/db_backup_fs_file_views.xml", + ], +} diff --git a/auto_backup_fs_file/data/fs_storage_data.xml b/auto_backup_fs_file/data/fs_storage_data.xml new file mode 100644 index 00000000000..4eed67e9fb2 --- /dev/null +++ b/auto_backup_fs_file/data/fs_storage_data.xml @@ -0,0 +1,13 @@ + + + + + Odoo Filesystem Auto Backups Backend + odoofs + odoobackupfs + auto_backup_fs_file.field_db_backup_fs_file__backup_file + + diff --git a/auto_backup_fs_file/data/mail_activity_type_data.xml b/auto_backup_fs_file/data/mail_activity_type_data.xml new file mode 100644 index 00000000000..ef95e81f9c1 --- /dev/null +++ b/auto_backup_fs_file/data/mail_activity_type_data.xml @@ -0,0 +1,10 @@ + + + + + Download Backup + fa-download + db.backup.fs.file + + diff --git a/auto_backup_fs_file/i18n/auto_backup_fs_file.pot b/auto_backup_fs_file/i18n/auto_backup_fs_file.pot new file mode 100644 index 00000000000..6b3ef2b1c35 --- /dev/null +++ b/auto_backup_fs_file/i18n/auto_backup_fs_file.pot @@ -0,0 +1,313 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auto_backup_fs_file +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_ids +msgid "Activities" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_state +msgid "Activity State" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__backup_file +msgid "Backup File" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__name +msgid "Backup Filename" +msgstr "" + +#. module: auto_backup_fs_file +#: model_terms:ir.ui.view,arch_db:auto_backup_fs_file.view_backup_conf_form +msgid "Backups" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup__method +msgid "Choose the storage method for this backup." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__create_uid +msgid "Created by" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__create_date +msgid "Created on" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__db_backup_id +msgid "DB Backup" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model,name:auto_backup_fs_file.model_db_backup +msgid "Database Backup" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model,name:auto_backup_fs_file.model_db_backup_fs_file +msgid "Database Backup Files" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__display_name +msgid "Display Name" +msgstr "" + +#. module: auto_backup_fs_file +#: model:mail.activity.type,name:auto_backup_fs_file.mail_act_download_backup +msgid "Download Backup" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields.selection,name:auto_backup_fs_file.selection__db_backup__method__fs_file +msgid "Fs File" +msgstr "" + +#. module: auto_backup_fs_file +#. odoo-python +#: code:addons/auto_backup_fs_file/models/db_backup.py:0 +#, python-format +msgid "Fs File Backup - %s" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__fs_file_backup_count +msgid "Fs File Backup Count" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.actions.act_window,name:auto_backup_fs_file.db_backup_fs_file_act_window +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__fs_file_backup_ids +#: model:ir.ui.menu,name:auto_backup_fs_file.db_backup_fs_file_menu +msgid "Fs File Backups" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__has_message +msgid "Has Message" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__id +msgid "ID" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_error +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__is_expired +msgid "Indicates whether the backup has exceeded its storage time." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__is_expired +msgid "Is Expired" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__write_date +msgid "Last Updated on" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_ids +msgid "Messages" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__method +msgid "Method" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__responsible_id +msgid "Responsible" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__smart_search +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__smart_search +msgid "Smart Search" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__backup_file +msgid "The file that contains the database backup" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup__responsible_id +msgid "User to be notified." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: auto_backup_fs_file +#. odoo-python +#: code:addons/auto_backup_fs_file/models/db_backup.py:0 +#, python-format +msgid "" +"You must configure a FS Storage for the '%(model_description)s' model - or " +"'Backup File' - field to use the 'Fs File' backup method." +msgstr "" diff --git a/auto_backup_fs_file/i18n/es.po b/auto_backup_fs_file/i18n/es.po new file mode 100644 index 00000000000..ce977d6f5d1 --- /dev/null +++ b/auto_backup_fs_file/i18n/es.po @@ -0,0 +1,293 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auto_backup_fs_file +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-18 13:18+0000\n" +"PO-Revision-Date: 2025-11-18 13:18+0000\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: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_ids +msgid "Activities" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_state +msgid "Activity State" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__backup_file +msgid "Backup File" +msgstr "Archivo de copia de seguridad" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__name +msgid "Backup Filename" +msgstr "Nombre del archivo de copia de seguridad" + +#. module: auto_backup_fs_file +#: model_terms:ir.ui.view,arch_db:auto_backup_fs_file.view_backup_conf_form +msgid "Backups" +msgstr "Copias de seguridad" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup__method +msgid "Choose the storage method for this backup." +msgstr "Elija el método de almacenamiento para esta copia de seguridad." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__create_uid +msgid "Created by" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__create_date +msgid "Created on" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__db_backup_id +msgid "DB Backup" +msgstr "Copia de seguridad de la base de datos" + +#. module: auto_backup_fs_file +#: model:ir.model,name:auto_backup_fs_file.model_db_backup +msgid "Database Backup" +msgstr "Copia de seguridad de la base de datos" + +#. module: auto_backup_fs_file +#: model:ir.model,name:auto_backup_fs_file.model_db_backup_fs_file +msgid "Database Backup Files" +msgstr "Archivos de copia de seguridad de la base de datos" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__display_name +msgid "Display Name" +msgstr "" + +#. module: auto_backup_fs_file +#: model:mail.activity.type,name:auto_backup_fs_file.mail_act_download_backup +msgid "Download Backup" +msgstr "Descargar copia de seguridad" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_follower_ids +msgid "Followers" +msgstr "Seguidores" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_partner_ids +msgid "Followers (Partners)" +msgstr "Seguidores (Contactos)" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields.selection,name:auto_backup_fs_file.selection__db_backup__method__fs_file +msgid "Fs File" +msgstr "Archivo FS" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__fs_file_backup_count +msgid "Fs File Backup Count" +msgstr "Conteo de copias de seguridad de archivos FS" + +#. module: auto_backup_fs_file +#: model:ir.actions.act_window,name:auto_backup_fs_file.db_backup_fs_file_act_window +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__fs_file_backup_ids +#: model:ir.ui.menu,name:auto_backup_fs_file.db_backup_fs_file_menu +msgid "Fs File Backups" +msgstr "Copias de seguridad de archivos FS" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__has_message +msgid "Has Message" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__id +msgid "ID" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_error +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__is_expired +msgid "Indicates whether the backup has exceeded its storage time." +msgstr "Indica si la copia de seguridad ha superado su tiempo de almacenamiento." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__is_expired +msgid "Is Expired" +msgstr "¿Está caducado?" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__write_date +msgid "Last Updated on" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_ids +msgid "Messages" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__method +msgid "Method" +msgstr "Método" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__responsible_id +msgid "Responsible" +msgstr "Responsable" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__backup_file +msgid "The file that contains the database backup" +msgstr "El archivo que contiene la copia de seguridad de la base de datos" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup__responsible_id +msgid "User to be notified." +msgstr "Usuario a ser notificado." + +#. module: auto_backup_fs_file +#. odoo-python +#: code:addons/auto_backup_fs_file/models/db_backup.py:0 +#, python-format +msgid "" +"You must configure a FS Storage for the '%(model_description)s' model - or " +"'Backup File' - field to use the 'Fs File' backup method." +msgstr "Debe configurar un almacenamiento FS para el modelo '%(model_description)s'" +" o para el campo 'Archivo de copia de seguridad' para utilizar el método de copia de seguridad 'Fs File'." diff --git a/auto_backup_fs_file/i18n/it.po b/auto_backup_fs_file/i18n/it.po new file mode 100644 index 00000000000..76b7f47905b --- /dev/null +++ b/auto_backup_fs_file/i18n/it.po @@ -0,0 +1,323 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * auto_backup_fs_file +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2026-02-10 11:50+0000\n" +"Last-Translator: mymage \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.15.2\n" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_needaction +msgid "Action Needed" +msgstr "Azione richiesta" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_ids +msgid "Activities" +msgstr "Attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Decorazione eccezione attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_state +msgid "Activity State" +msgstr "Stato attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_type_icon +msgid "Activity Type Icon" +msgstr "Icona tipo attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_attachment_count +msgid "Attachment Count" +msgstr "Conteggio allegati" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__backup_file +msgid "Backup File" +msgstr "File di backup" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__name +msgid "Backup Filename" +msgstr "Nome file backup" + +#. module: auto_backup_fs_file +#: model_terms:ir.ui.view,arch_db:auto_backup_fs_file.view_backup_conf_form +msgid "Backups" +msgstr "Backup" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup__method +msgid "Choose the storage method for this backup." +msgstr "Scegliere il metodo di archiviazione per questo backup." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__db_backup_id +msgid "DB Backup" +msgstr "Backup DB" + +#. module: auto_backup_fs_file +#: model:ir.model,name:auto_backup_fs_file.model_db_backup +msgid "Database Backup" +msgstr "Backup database" + +#. module: auto_backup_fs_file +#: model:ir.model,name:auto_backup_fs_file.model_db_backup_fs_file +msgid "Database Backup Files" +msgstr "File backup database" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: auto_backup_fs_file +#: model:mail.activity.type,name:auto_backup_fs_file.mail_act_download_backup +msgid "Download Backup" +msgstr "Scarica backup" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_follower_ids +msgid "Followers" +msgstr "Seguito da" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_partner_ids +msgid "Followers (Partners)" +msgstr "Seguito da (partner)" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Icona Font Awesome es. fa-tasks" + +#. module: auto_backup_fs_file +#: model:ir.model.fields.selection,name:auto_backup_fs_file.selection__db_backup__method__fs_file +msgid "Fs File" +msgstr "File FS" + +#. module: auto_backup_fs_file +#. odoo-python +#: code:addons/auto_backup_fs_file/models/db_backup.py:0 +#, python-format +msgid "Fs File Backup - %s" +msgstr "Backup file FS - %s" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__fs_file_backup_count +msgid "Fs File Backup Count" +msgstr "Conteggio backup file FS" + +#. module: auto_backup_fs_file +#: model:ir.actions.act_window,name:auto_backup_fs_file.db_backup_fs_file_act_window +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__fs_file_backup_ids +#: model:ir.ui.menu,name:auto_backup_fs_file.db_backup_fs_file_menu +msgid "Fs File Backups" +msgstr "Backup file FS" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__has_message +msgid "Has Message" +msgstr "Ha un messaggio" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__id +msgid "ID" +msgstr "ID" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_icon +msgid "Icon" +msgstr "Icona" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Icona per indicare un'attività eccezione." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Se selezionata, nuovi messaggi richiedono attenzione." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_error +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "Se selezionata, alcuni messaggi hanno un errore di consegna." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__is_expired +msgid "Indicates whether the backup has exceeded its storage time." +msgstr "Indica se il backup ha superato il suo orario di archivio." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__is_expired +msgid "Is Expired" +msgstr "È scatuto" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_is_follower +msgid "Is Follower" +msgstr "Segue" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_error +msgid "Message Delivery error" +msgstr "Errore di consegna messaggio" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_ids +msgid "Messages" +msgstr "Messaggi" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__method +msgid "Method" +msgstr "Metodo" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Scadenza mia attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Scadenza prossima attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_summary +msgid "Next Activity Summary" +msgstr "Riepilogo prossima attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_type_id +msgid "Next Activity Type" +msgstr "Tipo prossima attività" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_needaction_counter +msgid "Number of Actions" +msgstr "Numero di azioni" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_error_counter +msgid "Number of errors" +msgstr "Numero di errori" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Numero di messaggi che richiedono un'azione" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Numero di messaggi con errore di consegna" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__responsible_id +msgid "Responsible" +msgstr "Responsabile" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__activity_user_id +msgid "Responsible User" +msgstr "Utente responsabile" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__message_has_sms_error +msgid "SMS Delivery error" +msgstr "Errore consegna SMS" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup__smart_search +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__smart_search +msgid "Smart Search" +msgstr "Ricerca intelligente" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Stato in base alle attività\n" +"Scaduto: la data richiesta è trascorsa\n" +"Oggi: la data attività è oggi\n" +"Pianificato: attività future." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__backup_file +msgid "The file that contains the database backup" +msgstr "Il file che contiene il backup del database" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Tipo di attività eccezione sul record." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup__responsible_id +msgid "User to be notified." +msgstr "Utente da avvisare." + +#. module: auto_backup_fs_file +#: model:ir.model.fields,field_description:auto_backup_fs_file.field_db_backup_fs_file__website_message_ids +msgid "Website Messages" +msgstr "Messaggi sito web" + +#. module: auto_backup_fs_file +#: model:ir.model.fields,help:auto_backup_fs_file.field_db_backup_fs_file__website_message_ids +msgid "Website communication history" +msgstr "Cronologia comunicazioni sito web" + +#. module: auto_backup_fs_file +#. odoo-python +#: code:addons/auto_backup_fs_file/models/db_backup.py:0 +#, python-format +msgid "" +"You must configure a FS Storage for the '%(model_description)s' model - or " +"'Backup File' - field to use the 'Fs File' backup method." +msgstr "" +"È necessario configurare un archivio FS per il modello '%(model_description)" +"s', o il campo 'File di backup', per utilizzare il metodo di backup 'File " +"FS'." diff --git a/auto_backup_fs_file/models/__init__.py b/auto_backup_fs_file/models/__init__.py new file mode 100644 index 00000000000..d0cb75951a9 --- /dev/null +++ b/auto_backup_fs_file/models/__init__.py @@ -0,0 +1,2 @@ +from . import db_backup_fs_file +from . import db_backup diff --git a/auto_backup_fs_file/models/db_backup.py b/auto_backup_fs_file/models/db_backup.py new file mode 100644 index 00000000000..f7290c04d95 --- /dev/null +++ b/auto_backup_fs_file/models/db_backup.py @@ -0,0 +1,146 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from markupsafe import Markup + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.service import db + +from odoo.addons.fs_file.fields import FSFileValue + + +class DbBackup(models.Model): + _inherit = "db.backup" + + method = fields.Selection( + selection_add=[("fs_file", "Fs File")], ondelete={"fs_file": "cascade"} + ) + + fs_file_backup_ids = fields.One2many( + comodel_name="db.backup.fs.file", + inverse_name="db_backup_id", + string="Fs File Backups", + ) + + fs_file_backup_count = fields.Integer(compute="_compute_fs_file_backup_count") + + responsible_id = fields.Many2one("res.users", help="User to be notified.") + + folder = fields.Char(required=False, default=False) + + @api.constrains("method", "folder") + def _check_folder_required_for_method(self): + """folder is only required for local and sftp methods, not fs_file.""" + for record in self: + if record.method and record.method != "fs_file" and not record.folder: + raise ValidationError( + _("Folder is required for local and SFTP backup methods.") + ) + + @api.model + def _get_fs_storage(self): + """Get the fs_storage to be used for fs_file backups.""" + DbBackupFsFile = self.env["db.backup.fs.file"] + return DbBackupFsFile.fs_storage() + + def _compute_name(self): + res = super()._compute_name() + for record in self.filtered(lambda r: r.method == "fs_file"): + record.name = _("Fs File Backup - %s", record._get_fs_storage().name) + return res + + @api.depends("fs_file_backup_ids") + def _compute_fs_file_backup_count(self): + """Compute the count of fs_file backups.""" + for record in self: + record.fs_file_backup_count = len(record.fs_file_backup_ids) + + @api.constrains("method") + def _check_fs_file_backup_storage(self): + """Ensure that fs_file method has a storage configured.""" + for record in self.filtered(lambda r: r.method == "fs_file"): + if not record._get_fs_storage(): + raise ValidationError( + _( + "You must configure a FS Storage for the " + "'%(model_description)s' model - or 'Backup File' - field" + " to use the 'Fs File'" + " backup method.", + model_description=self.fs_file_backup_ids._description, + ) + ) + + def action_backup(self): + """Override the action_backup method to add the fs_file method.""" + fs_backups = self.filtered(lambda it: it.method == "fs_file") + successful_fs = self.browse() + dbname = self.env.cr.dbname + for fs_backup in fs_backups: + with fs_backup.backup_log(): + filename = self.filename( + fields.Datetime.now(), ext=fs_backup.backup_format + ) + backup = self.env["db.backup.fs.file"].create( + { + "name": filename, + "db_backup_id": fs_backup.id, + "backup_file": FSFileValue( + name=filename, + value=b"init file", + ), + } + ) + with backup.backup_file.open("wb", new_version=False) as f: + db.dump_db(dbname, f, fs_backup.backup_format) + user_to_notify = fs_backup.responsible_id or self.env.user + file_metadata = backup.read(["backup_file"])[0].get("backup_file") + base_url = ( + self.env["ir.config_parameter"].sudo().get_param("web.base.url") + ) + activity_note = Markup( + _( + "The database backup '%(backup_name)s' is ready." + " You can download it from the attachment link: " + "" + "%(download_link)s", + backup_name=backup.name, + download_link=f"{base_url}{file_metadata.get('url')}", + ) + ) + backup.activity_schedule( + "auto_backup_fs_file.mail_act_download_backup", + date_deadline=fields.Date.today(), + note=activity_note, + summary=_("Database backup is ready to download."), + user_id=user_to_notify.id, + ) + successful_fs |= fs_backup + res = super().action_backup() + successful_fs.cleanup() + return res + + def action_open_fs_backups_view(self): + self.ensure_one() + action = self.env["ir.actions.actions"]._for_xml_id( + "auto_backup_fs_file.db_backup_fs_file_act_window" + ) + action["domain"] = [("db_backup_id", "=", self.id)] + return action + + def cleanup(self): + """Extend cleanup to fs_file backups. + + Physical file removal is handled by the fs_attachment GC stack: + unlink() on db.backup.fs.file cascades to ir.attachment.unlink(), + which marks the file in fs.file.gc for deferred deletion by the + autovacuum job. + """ + for db_backup_conf in self.filtered( + lambda record: record.method == "fs_file" and record.days_to_keep + ): + with db_backup_conf.cleanup_log(): + to_delete = db_backup_conf.fs_file_backup_ids.filtered("is_expired") + to_delete.unlink() + res = super().cleanup() + return res diff --git a/auto_backup_fs_file/models/db_backup_fs_file.py b/auto_backup_fs_file/models/db_backup_fs_file.py new file mode 100644 index 00000000000..d5f5fabfefa --- /dev/null +++ b/auto_backup_fs_file/models/db_backup_fs_file.py @@ -0,0 +1,73 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + +from odoo.addons.fs_file import fields as fs_fields + + +class DbBackupFsFile(models.Model): + _name = "db.backup.fs.file" + _inherit = ["mail.thread", "mail.activity.mixin"] + _description = "Database Backup Files" + + name = fields.Char("Backup Filename", required=True) + db_backup_id = fields.Many2one("db.backup", string="DB Backup", required=True) + backup_file = fs_fields.FSFile( + required=False, + help="The file that contains the database backup", + ) + is_expired = fields.Boolean( + compute="_compute_is_expired", + help="Indicates whether the backup has exceeded its storage time.", + ) + + @api.depends("db_backup_id.days_to_keep", "create_date") + def _compute_is_expired(self): + """Compute whether the backup has exceeded its storage time.""" + for record in self: + days_to_keep = record.db_backup_id.days_to_keep + if days_to_keep: + expiration_date = fields.Datetime.add( + record.create_date, days=days_to_keep + ) + record.is_expired = fields.Datetime.now() > expiration_date + else: + record.is_expired = False + + def unlink(self): + """Unlink backing ir.attachment records before deleting the DB record. + + Odoo's base Model.unlink() does cascade-delete ir.attachment via raw + SQL, but we make this explicit to ensure the fs_attachment GC stack + (_fs_mark_for_gc) is triggered reliably. The physical file on the + external storage is removed by the autovacuum GC job + (fs_file_gc._gc_files), not synchronously here. + """ + attachments = self.env["ir.attachment"].search( + [ + ("res_model", "=", self._name), + ("res_id", "in", self.ids), + ("res_field", "=", "backup_file"), + ] + ) + attachments.unlink() + return super().unlink() + + @api.model + def fs_storage(self): + FsStorage = self.env["fs.storage"] + fs_storages = FsStorage.search([]) + fs_storage = fs_storages.filtered( + lambda item: item.field_xmlids + and "auto_backup_fs_file.field_db_backup_fs_file__backup_file" + in item.field_xmlids + ) + if not fs_storage: + fs_storage = fs_storages.filtered( + lambda item: item.model_xmlids + and "auto_backup_fs_file.model_db_backup_fs_file" in item.model_xmlids + ) + if fs_storage: + return fs_storage + return False diff --git a/auto_backup_fs_file/pyproject.toml b/auto_backup_fs_file/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/auto_backup_fs_file/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/auto_backup_fs_file/readme/CONFIGURE.md b/auto_backup_fs_file/readme/CONFIGURE.md new file mode 100644 index 00000000000..26b4c2ae26a --- /dev/null +++ b/auto_backup_fs_file/readme/CONFIGURE.md @@ -0,0 +1,23 @@ +1. **Review Documentation for Dependencies** + Before configuring the module, ensure you have reviewed the documentation for the following modules: + - `fs_attachment` + - `fs_storage` + These modules provide the necessary setup for file storage and attachment handling. + +2. **Configure File Storage** + - Navigate to **Settings** > **Technical** > **FS Storage**. + - Create or select an existing storage configuration. + - Ensure the storage is properly set up and tested for accessibility. + +3. **Link Backup File field to Storage** + - While configuring the file storage in **Settings** > **Technical** > **FS Storage**, ensure that the `backup_file` from the `db.backup.fs.file` model is listed under the `Field` field. + - This step is part of the storage configuration process. + - Save the changes after verifying the setup. + + ![Example of File Storage Configuration](../images/file_storage_configuration.png) + +4. **Verify Configuration** + - Perform a test backup to ensure the files are being stored in the correct location. + - Check the logs for any errors or warnings. + +By following these steps, you will ensure that the module is properly configured for storing backups in the desired file storage system. diff --git a/auto_backup_fs_file/readme/CONTEXT.md b/auto_backup_fs_file/readme/CONTEXT.md new file mode 100644 index 00000000000..476024d83ee --- /dev/null +++ b/auto_backup_fs_file/readme/CONTEXT.md @@ -0,0 +1,15 @@ +BUSINESS NEED: +This module addresses the critical need for safeguarding Odoo instance data by enabling automated backups to a filesystem supported by the `fsspec` library. Businesses often require reliable and accessible backup solutions to ensure data integrity and recovery in case of system failures or data loss. This module is particularly useful in contexts where organizations need to store backups on cloud storage, network drives, or other custom filesystems supported by `fsspec`. + +Practical examples include: +- Backing up Odoo data to cloud storage providers like AWS S3, Google Cloud Storage, or Azure Blob Storage. +- Storing backups on a secure local or remote filesystem for disaster recovery purposes. +- Automating backup processes in multi-environment setups, such as multi-company or multi-website configurations. + +APPROACH: +The module extends the backup functionality from the `auto_backup` module by introducing a method that allows storing the resulting backup using an `fsspec` implementation. This is achieved through the integration of the `fs_file` from [storage repository](https://github.com/OCA/storage). The module leverages the `fsspec` library to provide a flexible and extensible interface for interacting with various filesystems. It automates the backup process by exporting Odoo instance data and storing it in the specified filesystem. Additionally, it allows users to download the backups for local storage or further processing. + + Backup file cleanup is handled automatically based on the **Days to Keep** configuration. When expired backup records are removed, the physical backup files are not deleted synchronously. Instead, the module delegates file deletion to the `fs_attachment` garbage collector (GC), which marks files for deferred removal and physically deletes them during Odoo's autovacuum cron cycle. This two-phase approach ensures transactional safety: files are only removed once the GC confirms no database record still references them. + +USEFUL INFORMATION: +- **Dependencies**: This module depends on the `fsspec` library, its relevant filesystem implementations, and the `fs_file` addon from OCA/storage. Ensure the required `fsspec` plugins are installed for your target filesystem. diff --git a/auto_backup_fs_file/readme/CONTRIBUTORS.md b/auto_backup_fs_file/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..f8e1ad8bfa1 --- /dev/null +++ b/auto_backup_fs_file/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Rolando Pérez Rebollo diff --git a/auto_backup_fs_file/readme/CREDITS.md b/auto_backup_fs_file/readme/CREDITS.md new file mode 100644 index 00000000000..95aa18652f8 --- /dev/null +++ b/auto_backup_fs_file/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +- Binhex diff --git a/auto_backup_fs_file/readme/DESCRIPTION.md b/auto_backup_fs_file/readme/DESCRIPTION.md new file mode 100644 index 00000000000..c1ea900109a --- /dev/null +++ b/auto_backup_fs_file/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module enhances the database backup functionality in Odoo by introducing support for storing backups as files using the `fsspec` library. It is designed to address the need for reliable and flexible backup solutions, enabling businesses to safeguard their data by storing backups on various filesystems, including cloud storage providers, network drives, and local secure storage. + +By integrating with the `fs_file` addon from the OCA storage repository, this module allows users to automate the backup process and store the resulting files in a configured filesystem. It provides an easy-to-use interface for configuring backup methods, managing stored backups, and ensuring data integrity for disaster recovery or multi-environment setups. diff --git a/auto_backup_fs_file/readme/INSTALL.md b/auto_backup_fs_file/readme/INSTALL.md new file mode 100644 index 00000000000..bf5358ca486 --- /dev/null +++ b/auto_backup_fs_file/readme/INSTALL.md @@ -0,0 +1 @@ +This addon itself does not introduce any dependencies, but its dependencies may require additional packages. diff --git a/auto_backup_fs_file/readme/ROADMAP.md b/auto_backup_fs_file/readme/ROADMAP.md new file mode 100644 index 00000000000..26e6ecbd01a --- /dev/null +++ b/auto_backup_fs_file/readme/ROADMAP.md @@ -0,0 +1,3 @@ +- **Folder field behavior**: The `folder` field on the `db.backup` model specifies the backup storage directory. For records using the `fs_file` method, storage is controlled by the `fs_file` field's settings. The `folder` field is hidden (`invisible`) and no longer required when `method='fs_file'`, so it no longer interferes with `fs_file` configurations. No auto-sync between both fields is performed since the methods are mutually exclusive. + +- **Design limitation**: The current implementation has a design constraint due to `fs_storage` addon limitations. Since storage setting targets the `db.backup.fs.file` model, only one storage backend can effectively be used. diff --git a/auto_backup_fs_file/readme/USAGE.md b/auto_backup_fs_file/readme/USAGE.md new file mode 100644 index 00000000000..0f2a7459c5e --- /dev/null +++ b/auto_backup_fs_file/readme/USAGE.md @@ -0,0 +1,48 @@ +This module extends the functionality of the database backup system in Odoo by introducing a new backup method: **Fs File**. This method allows storing database backups as files using an FSSPEC implementation. + +## How to Use the Module + +### 1. Configure the Backup Method +1. Navigate to **Settings** > **Technical** > **Database Structure** > **Automated Backups**. +2. Create or edit a backup configuration. +3. In the **Backup Method** field, select **Fs File**. +4. Configure other fields as needed, such as the backup format and retention settings. +5. Save the configuration. + +### 2. Perform a Backup +1. From the list of backup configurations, select the one configured with the **Fs File** method. +2. Click the **Backup Now** button to initiate the backup process. +3. The backup will be stored as a file in the configured FSSPEC storage. + +### 3. View Fs File Backups +1. Open the backup configuration form view. +2. In the top-right corner, you will see a **Backups** stat button (if backups exist). +3. Click the **Backups** button to view the list of Fs File backups associated with the configuration. + +### 4. Manage Fs File Backups +- In the Fs File backups list view, you can see details such as the backup filename and associated database backup configuration. +- Use this view to manage or download backups as needed. + +### 5. Cleanup and File Deletion + +Backup retention is controlled by the **Days to Keep** field on the backup configuration. When this value is greater than 0, the automatic cleanup process removes expired backup records during each backup run. + +When a backup record is deleted (either by automatic cleanup or manually from the list view), the physical backup file in the filesystem storage is **not removed immediately**. Instead, the file is marked for deferred deletion by the `fs_attachment` garbage collector (GC), which runs periodically via Odoo's autovacuum cron. Physical files are only removed from the storage backend once the GC confirms no database record references them. + +This means: +- **Immediately after deletion**: the database record is gone, but the file may still exist in the storage backend for a short period. +- **After the next autovacuum cycle**: the file is permanently deleted from the storage backend. + +This behavior requires the storage's `autovacuum_gc` flag to be enabled (the default). If disabled, files must be managed manually. + +### Screenshots +- **Backup Configuration Form View** + ![Backup Configuration Form](../static/description/db_backup_form_view.png) + +- **Fs File Backups List View** + ![Fs File Backups List](../static/description/db_backup_fs_file_tree_view.png) + +### Notes +- Ensure that the FSSPEC storage is properly configured before using the **Fs File** method. +- This module adds a new stat button in the backup configuration form view to quickly access Fs File backups. + diff --git a/auto_backup_fs_file/security/ir.model.access.csv b/auto_backup_fs_file/security/ir.model.access.csv new file mode 100644 index 00000000000..9ababe18d50 --- /dev/null +++ b/auto_backup_fs_file/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_db_backup_fs_file_read,Read db.backup fs.file,model_db_backup_fs_file,base.group_erp_manager,1,0,0,0 +access_db_backup_fs_file_write,Write db.backup fs.file,model_db_backup_fs_file,base.group_system,1,1,1,1 diff --git a/auto_backup_fs_file/static/description/db_backup_form_view.png b/auto_backup_fs_file/static/description/db_backup_form_view.png new file mode 100644 index 00000000000..98881da236a Binary files /dev/null and b/auto_backup_fs_file/static/description/db_backup_form_view.png differ diff --git a/auto_backup_fs_file/static/description/db_backup_fs_file_tree_view.png b/auto_backup_fs_file/static/description/db_backup_fs_file_tree_view.png new file mode 100644 index 00000000000..ca37b316188 Binary files /dev/null and b/auto_backup_fs_file/static/description/db_backup_fs_file_tree_view.png differ diff --git a/auto_backup_fs_file/static/description/file_storage_configuration.png b/auto_backup_fs_file/static/description/file_storage_configuration.png new file mode 100644 index 00000000000..5bb8f8ddf34 Binary files /dev/null and b/auto_backup_fs_file/static/description/file_storage_configuration.png differ diff --git a/auto_backup_fs_file/static/description/icon.png b/auto_backup_fs_file/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/auto_backup_fs_file/static/description/icon.png differ diff --git a/auto_backup_fs_file/static/description/index.html b/auto_backup_fs_file/static/description/index.html new file mode 100644 index 00000000000..9876acc30c1 --- /dev/null +++ b/auto_backup_fs_file/static/description/index.html @@ -0,0 +1,664 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Auto Backup Fs File

+ +

Alpha License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

This module enhances the database backup functionality in Odoo by +introducing support for storing backups as files using the fsspec +library. It is designed to address the need for reliable and flexible +backup solutions, enabling businesses to safeguard their data by storing +backups on various filesystems, including cloud storage providers, +network drives, and local secure storage.

+

By integrating with the fs_file addon from the OCA storage +repository, this module allows users to automate the backup process and +store the resulting files in a configured filesystem. It provides an +easy-to-use interface for configuring backup methods, managing stored +backups, and ensuring data integrity for disaster recovery or +multi-environment setups.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Use Cases / Context

+

BUSINESS NEED: This module addresses the critical need for safeguarding +Odoo instance data by enabling automated backups to a filesystem +supported by the fsspec library. Businesses often require reliable +and accessible backup solutions to ensure data integrity and recovery in +case of system failures or data loss. This module is particularly useful +in contexts where organizations need to store backups on cloud storage, +network drives, or other custom filesystems supported by fsspec.

+

Practical examples include:

+
    +
  • Backing up Odoo data to cloud storage providers like AWS S3, Google +Cloud Storage, or Azure Blob Storage.
  • +
  • Storing backups on a secure local or remote filesystem for disaster +recovery purposes.
  • +
  • Automating backup processes in multi-environment setups, such as +multi-company or multi-website configurations.
  • +
+

APPROACH: The module extends the backup functionality from the +auto_backup module by introducing a method that allows storing the +resulting backup using an fsspec implementation. This is achieved +through the integration of the fs_file from storage +repository. The module leverages the +fsspec library to provide a flexible and extensible interface for +interacting with various filesystems. It automates the backup process by +exporting Odoo instance data and storing it in the specified filesystem. +Additionally, it allows users to download the backups for local storage +or further processing.

+

Backup file cleanup is handled automatically based on the Days to +Keep configuration. When expired backup records are removed, the +physical backup files are not deleted synchronously. Instead, the module +delegates file deletion to the fs_attachment garbage collector (GC), +which marks files for deferred removal and physically deletes them +during Odoo’s autovacuum cron cycle. This two-phase approach ensures +transactional safety: files are only removed once the GC confirms no +database record still references them.

+

USEFUL INFORMATION:

+
    +
  • Dependencies: This module depends on the fsspec library, its +relevant filesystem implementations, and the fs_file addon from +OCA/storage. Ensure the required fsspec plugins are installed for +your target filesystem.
  • +
+
+
+

Installation

+

This addon itself does not introduce any dependencies, but its +dependencies may require additional packages.

+
+
+

Configuration

+
    +
  1. Review Documentation for Dependencies Before configuring the +module, ensure you have reviewed the documentation for the following +modules:
  2. +
+
    +
  • fs_attachment
  • +
  • fs_storage These modules provide the necessary setup for file +storage and attachment handling.
  • +
+
    +
  1. Configure File Storage
  2. +
+
    +
  • Navigate to Settings > Technical > FS Storage.
  • +
  • Create or select an existing storage configuration.
  • +
  • Ensure the storage is properly set up and tested for accessibility.
  • +
+
    +
  1. Link Backup File field to Storage
  2. +
+
    +
  • While configuring the file storage in Settings > Technical > +FS Storage, ensure that the backup_file from the +db.backup.fs.file model is listed under the Field field.
  • +
  • This step is part of the storage configuration process.
  • +
  • Save the changes after verifying the setup.
  • +
+Example of File Storage Configuration +
    +
  1. Verify Configuration
  2. +
+
    +
  • Perform a test backup to ensure the files are being stored in the +correct location.
  • +
  • Check the logs for any errors or warnings.
  • +
+

By following these steps, you will ensure that the module is properly +configured for storing backups in the desired file storage system.

+
+
+

Usage

+

This module extends the functionality of the database backup system in +Odoo by introducing a new backup method: Fs File. This method allows +storing database backups as files using an FSSPEC implementation.

+
+

How to Use the Module

+
+

1. Configure the Backup Method

+
    +
  1. Navigate to Settings > Technical > Database Structure > +Automated Backups.
  2. +
  3. Create or edit a backup configuration.
  4. +
  5. In the Backup Method field, select Fs File.
  6. +
  7. Configure other fields as needed, such as the backup format and +retention settings.
  8. +
  9. Save the configuration.
  10. +
+
+
+

2. Perform a Backup

+
    +
  1. From the list of backup configurations, select the one configured +with the Fs File method.
  2. +
  3. Click the Backup Now button to initiate the backup process.
  4. +
  5. The backup will be stored as a file in the configured FSSPEC storage.
  6. +
+
+
+

3. View Fs File Backups

+
    +
  1. Open the backup configuration form view.
  2. +
  3. In the top-right corner, you will see a Backups stat button (if +backups exist).
  4. +
  5. Click the Backups button to view the list of Fs File backups +associated with the configuration.
  6. +
+
+
+

4. Manage Fs File Backups

+
    +
  • In the Fs File backups list view, you can see details such as the +backup filename and associated database backup configuration.
  • +
  • Use this view to manage or download backups as needed.
  • +
+
+
+

5. Cleanup and File Deletion

+

Backup retention is controlled by the Days to Keep field on the +backup configuration. When this value is greater than 0, the automatic +cleanup process removes expired backup records during each backup run.

+

When a backup record is deleted (either by automatic cleanup or manually +from the list view), the physical backup file in the filesystem storage +is not removed immediately. Instead, the file is marked for deferred +deletion by the fs_attachment garbage collector (GC), which runs +periodically via Odoo’s autovacuum cron. Physical files are only removed +from the storage backend once the GC confirms no database record +references them.

+

This means:

+
    +
  • Immediately after deletion: the database record is gone, but the +file may still exist in the storage backend for a short period.
  • +
  • After the next autovacuum cycle: the file is permanently deleted +from the storage backend.
  • +
+

This behavior requires the storage’s autovacuum_gc flag to be +enabled (the default). If disabled, files must be managed manually.

+
+
+

Screenshots

+
    +
  • Backup Configuration Form View Backup Configuration Form
  • +
  • Fs File Backups List View Fs File Backups List
  • +
+
+
+

Notes

+
    +
  • Ensure that the FSSPEC storage is properly configured before using +the Fs File method.
  • +
  • This module adds a new stat button in the backup configuration form +view to quickly access Fs File backups.
  • +
+
+
+
+
+

Known issues / Roadmap

+
    +
  • Folder field behavior: The folder field on the db.backup +model specifies the backup storage directory. For records using the +fs_file method, storage is controlled by the fs_file field’s +settings. The folder field is hidden (invisible) and no +longer required when method='fs_file', so it no longer interferes +with fs_file configurations. No auto-sync between both fields is +performed since the methods are mutually exclusive.
  • +
  • Design limitation: The current implementation has a design +constraint due to fs_storage addon limitations. Since storage +setting targets the db.backup.fs.file model, only one storage +backend can effectively be used.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub 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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Binhex
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The development of this module has been financially supported by:

+
    +
  • Binhex
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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/server-tools project on GitHub.

+

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

+
+
+
+
+ + diff --git a/auto_backup_fs_file/tests/__init__.py b/auto_backup_fs_file/tests/__init__.py new file mode 100644 index 00000000000..0f334ca0fa9 --- /dev/null +++ b/auto_backup_fs_file/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_auto_backup_fs_file diff --git a/auto_backup_fs_file/tests/test_auto_backup_fs_file.py b/auto_backup_fs_file/tests/test_auto_backup_fs_file.py new file mode 100644 index 00000000000..29d7b82e43d --- /dev/null +++ b/auto_backup_fs_file/tests/test_auto_backup_fs_file.py @@ -0,0 +1,172 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later[](https://www.gnu.org/licenses/agpl). + +import io + +from odoo import fields +from odoo.exceptions import ValidationError +from odoo.service import db +from odoo.tests import tagged + +from odoo.addons.base.tests.common import BaseCommon + + +@tagged("post_install", "-at_install") +class TestAutoBackupFsFile(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.DbBackup = cls.env["db.backup"] + cls.DbBackupFsFile = cls.env["db.backup.fs.file"] + cls.FsStorage = cls.env["fs.storage"] + + cls.env.ref("auto_backup_fs_file.fs_storage_auto_backup").unlink() + + # Create a test FS Storage + cls.test_storage = cls.FsStorage.create( + { + "name": "Test Storage", + "protocol": "memory", # Use in-memory for testing + "code": "mem_dir", + "directory_path": "/tmp/", + } + ) + + def setUp(self): + super().setUp() + # Patch db.dump_db to avoid actual DB dumping (active for the entire test) + self.patch( + db, + "dump_db", + lambda dbname, stream, backup_format: io.BytesIO(b"fake_backup_data"), + ) + + def _create_backup_config(self): + # Create a test backup configuration with fs_file method + return self.DbBackup.create( + { + "method": "fs_file", + "backup_format": "zip", + "days_to_keep": 7, + "responsible_id": self.env.user.id, + } + ) + + def _action_backup(self, backup_config): + backup_config.action_backup() + + def test_ordinary_flow(self): + """Test the ordinary flow of creating a backup configuration and performing + backups.""" + # Create backup configuration, field not linked to storage yet + with self.assertRaises(ValidationError): + self._create_backup_config() + + self.test_storage.field_xmlids = ( + "auto_backup_fs_file.field_db_backup_fs_file__backup_file" + ) + backup_config = self._create_backup_config() + self.assertEqual( + backup_config.name, + f"Fs File Backup - {backup_config._get_fs_storage().name}", + ) + self.assertFalse(backup_config.folder) + + # Test computation of fs_file_backup_count + self.assertEqual(backup_config.fs_file_backup_count, 0) + + # Test backup generation and activity creation + self._action_backup( + backup_config + ) # No need for _action_backup_with_time_freeze + self.assertEqual(backup_config.fs_file_backup_count, 1) + + # Check activity scheduled + activity = self.env["mail.activity"].search( + [ + ("res_model", "=", "db.backup.fs.file"), + ("res_id", "=", backup_config.fs_file_backup_ids.id), + ( + "activity_type_id", + "=", + self.env.ref("auto_backup_fs_file.mail_act_download_backup").id, + ), + ] + ) + self.assertTrue(activity) + self.assertEqual(activity.user_id, self.env.user) + self.assertFalse( + backup_config.fs_file_backup_ids.is_expired + ) # Without active mock, not expired + backup_config.cleanup() + self.assertEqual(backup_config.fs_file_backup_count, 1) + + # Get the fs_backup for expiry testing + fs_backup = backup_config.fs_file_backup_ids + + # Compute the expiration date + computed_now = fields.Datetime.add( + fs_backup.create_date, days=backup_config.days_to_keep, seconds=10 + ) + + def fake_now(): + return computed_now + + # Use self.patch to mock ONLY Datetime.now() for the duration of the test + self.patch(fields.Datetime, "now", fake_now) + fs_backup.invalidate_recordset(["is_expired"]) + + self.assertTrue( + fs_backup.is_expired + ) # Triggers _compute_is_expired with mocked now + backup_config.cleanup() # Will use the computed is_expired + + self.assertEqual(backup_config.fs_file_backup_count, 0) + + # Verify the backing ir.attachment is also removed (GC chain fires via unlink) + attachment = self.env["ir.attachment"].search( + [ + ("res_model", "=", "db.backup.fs.file"), + ("res_field", "=", "backup_file"), + ] + ) + self.assertFalse(attachment) + + def test_action_backup_triggers_cleanup(self): + """cleanup() must fire for fs_file records via action_backup(), not just + when called directly. Regression test for the bug where action_backup() + only passed local/sftp records to successful.cleanup(), making fs_file + cleanup dead code in production.""" + self.test_storage.field_xmlids = ( + "auto_backup_fs_file.field_db_backup_fs_file__backup_file" + ) + backup_config = self._create_backup_config() + + # Run first backup — creates record at create_date = now + self._action_backup(backup_config) + self.assertEqual(backup_config.fs_file_backup_count, 1) + first_backup = backup_config.fs_file_backup_ids + + # Make the first backup expired by setting an old create_date directly + # (avoids patching Datetime.now which doesn't control ORM create_date) + old_date = fields.Datetime.add( + fields.Datetime.now(), days=-backup_config.days_to_keep - 1 + ) + self.env.cr.execute( + "UPDATE db_backup_fs_file SET create_date = %s WHERE id = %s", + (old_date, first_backup.id), + ) + first_backup.invalidate_recordset(["is_expired", "create_date"]) + self.assertTrue(first_backup.is_expired) + + # Run second backup via action_backup() — this is the PRODUCTION call path. + # cleanup() must be triggered automatically for the expired first backup. + self._action_backup(backup_config) + + # First backup must be gone (cleanup deleted it) + self.assertFalse( + first_backup.exists(), + "Expired backup was not deleted by action_backup()", + ) + # A new backup should have been created + self.assertTrue(backup_config.fs_file_backup_ids) diff --git a/auto_backup_fs_file/views/db_backup_fs_file_views.xml b/auto_backup_fs_file/views/db_backup_fs_file_views.xml new file mode 100644 index 00000000000..e1652283de5 --- /dev/null +++ b/auto_backup_fs_file/views/db_backup_fs_file_views.xml @@ -0,0 +1,63 @@ + + + + + + db.backup.fs.file.form.view + db.backup.fs.file + +
+
+ + + + + + + + +
+ + + +
+ + + + + + db.backup.fs.file.search.view + db.backup.fs.file + + + + + + + + + db.backup.fs.file.tree.view + db.backup.fs.file + + + + + + + + + + + Fs File Backups + db.backup.fs.file + + + + Fs File Backups + + + + + + diff --git a/auto_backup_fs_file/views/db_backup_views.xml b/auto_backup_fs_file/views/db_backup_views.xml new file mode 100644 index 00000000000..0c025afa525 --- /dev/null +++ b/auto_backup_fs_file/views/db_backup_views.xml @@ -0,0 +1,52 @@ + + + + + + db.backup + + + +
+ +
+
+ + {'invisible': [('method', '=', 'fs_file')]} + + + + + +
+ + + + db.backup + + + + + + + + +
diff --git a/setup/auto_backup_fs_file/odoo/addons/auto_backup_fs_file b/setup/auto_backup_fs_file/odoo/addons/auto_backup_fs_file new file mode 120000 index 00000000000..3fe0d5b3486 --- /dev/null +++ b/setup/auto_backup_fs_file/odoo/addons/auto_backup_fs_file @@ -0,0 +1 @@ +../../../../auto_backup_fs_file \ No newline at end of file diff --git a/setup/auto_backup_fs_file/setup.py b/setup/auto_backup_fs_file/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/auto_backup_fs_file/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)