mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Backup update (#10586)
* Update django-dbbackup version * Specify STORAGES option for dbbackup * Add more backup configuration * Support custom date formats * Add connector options * Extend functionality of invoke backup * Add extra options for restore task * Add invoke task for finding additional backups * Small tweaks * Add docs around backup / restore * Fix typo * Add example for GCS storage * More docs
This commit is contained in:
		
							
								
								
									
										133
									
								
								src/backend/InvenTree/InvenTree/backup.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/backend/InvenTree/InvenTree/backup.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| """Configuration options for InvenTree backup / restore functionality. | ||||
|  | ||||
| We use the django-dbbackup library to handle backup and restore operations. | ||||
|  | ||||
| Ref: https://archmonger.github.io/django-dbbackup/latest/configuration/ | ||||
| """ | ||||
|  | ||||
| import InvenTree.config | ||||
|  | ||||
|  | ||||
| def get_backup_connector_options() -> dict: | ||||
|     """Options which are specific to the selected backup connector. | ||||
|  | ||||
|     These options apply to the database connector, not to the backup storage. | ||||
|  | ||||
|     Ref: https://archmonger.github.io/django-dbbackup/latest/databases/ | ||||
|     """ | ||||
|     default_options = {'EXCLUDE': ['django_session']} | ||||
|  | ||||
|     # Allow user to specify custom options here if necessary | ||||
|     connector_options = InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_CONNECTOR_OPTIONS', | ||||
|         'backup_connector_options', | ||||
|         default_value=default_options, | ||||
|         typecast=dict, | ||||
|     ) | ||||
|  | ||||
|     return connector_options | ||||
|  | ||||
|  | ||||
| def get_backup_storage_backend() -> str: | ||||
|     """Return the backup storage backend string.""" | ||||
|     backend = InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_STORAGE', | ||||
|         'backup_storage', | ||||
|         'django.core.files.storage.FileSystemStorage', | ||||
|     ) | ||||
|  | ||||
|     # Validate that the selected backend is valid | ||||
|     # It must be able to be imported, and a class must be found | ||||
|     # It also must be a subclass of django.core.files.storage.Storage | ||||
|     try: | ||||
|         from django.core.files.storage import Storage | ||||
|         from django.utils.module_loading import import_string | ||||
|  | ||||
|         backend_class = import_string(backend) | ||||
|  | ||||
|         if not issubclass(backend_class, Storage): | ||||
|             raise TypeError( | ||||
|                 f"Backup storage backend '{backend}' is not a valid Storage class" | ||||
|             ) | ||||
|     except Exception as e: | ||||
|         raise ImportError(f"Could not load backup storage backend '{backend}': {e}") | ||||
|  | ||||
|     return backend | ||||
|  | ||||
|  | ||||
| def get_backup_storage_options() -> dict: | ||||
|     """Return the backup storage options dictionary.""" | ||||
|     # Default backend options which are used for FileSystemStorage | ||||
|     default_options = {'location': InvenTree.config.get_backup_dir()} | ||||
|  | ||||
|     options = InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_OPTIONS', | ||||
|         'backup_options', | ||||
|         default_value=default_options, | ||||
|         typecast=dict, | ||||
|     ) | ||||
|  | ||||
|     if not isinstance(options, dict): | ||||
|         raise ValueError('Backup storage options must be a dictionary') | ||||
|  | ||||
|     return options | ||||
|  | ||||
|  | ||||
| def backup_email_on_error() -> bool: | ||||
|     """Return whether to send emails to admins on backup failure.""" | ||||
|     return InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_SEND_EMAIL', | ||||
|         'backup_send_email', | ||||
|         default_value=False, | ||||
|         typecast=bool, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def backup_email_prefix() -> str: | ||||
|     """Return the email subject prefix for backup emails.""" | ||||
|     return InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_EMAIL_PREFIX', | ||||
|         'backup_email_prefix', | ||||
|         default_value='[InvenTree Backup]', | ||||
|         typecast=str, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def backup_gpg_recipient() -> str: | ||||
|     """Return the GPG recipient for encrypted backups.""" | ||||
|     return InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_GPG_RECIPIENT', | ||||
|         'backup_gpg_recipient', | ||||
|         default_value='', | ||||
|         typecast=str, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def backup_date_format() -> str: | ||||
|     """Return the date format string for database backups.""" | ||||
|     return InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_DATE_FORMAT', | ||||
|         'backup_date_format', | ||||
|         default_value='%Y-%m-%d-%H%M%S', | ||||
|         typecast=str, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def backup_filename_template() -> str: | ||||
|     """Return the filename template for database backups.""" | ||||
|     return InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_DATABASE_FILENAME_TEMPLATE', | ||||
|         'backup_database_filename_template', | ||||
|         default_value='InvenTree-db-{datetime}.{extension}', | ||||
|         typecast=str, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def backup_media_filename_template() -> str: | ||||
|     """Return the filename template for media backups.""" | ||||
|     return InvenTree.config.get_setting( | ||||
|         'INVENTREE_BACKUP_MEDIA_FILENAME_TEMPLATE', | ||||
|         'backup_media_filename_template', | ||||
|         default_value='InvenTree-media-{datetime}.{extension}', | ||||
|         typecast=str, | ||||
|     ) | ||||
| @@ -24,6 +24,7 @@ from django.http import Http404, HttpResponseGone | ||||
| import structlog | ||||
| from corsheaders.defaults import default_headers as default_cors_headers | ||||
|  | ||||
| import InvenTree.backup | ||||
| from InvenTree.cache import get_cache_config, is_global_cache_enabled | ||||
| from InvenTree.config import ( | ||||
|     get_boolean_setting, | ||||
| @@ -247,22 +248,32 @@ if DEBUG and 'collectstatic' not in sys.argv: | ||||
|         STATICFILES_DIRS.append(BASE_DIR.joinpath('plugin', 'samples', 'static')) | ||||
|  | ||||
| # Database backup options | ||||
| # Ref: https://django-dbbackup.readthedocs.io/en/master/configuration.html | ||||
| DBBACKUP_SEND_EMAIL = False | ||||
| DBBACKUP_STORAGE = get_setting( | ||||
|     'INVENTREE_BACKUP_STORAGE', | ||||
|     'backup_storage', | ||||
|     'django.core.files.storage.FileSystemStorage', | ||||
| ) | ||||
| # Ref: https://archmonger.github.io/django-dbbackup/latest/configuration/ | ||||
|  | ||||
| # Default backup configuration | ||||
| DBBACKUP_STORAGE_OPTIONS = get_setting( | ||||
|     'INVENTREE_BACKUP_OPTIONS', | ||||
|     'backup_options', | ||||
|     default_value={'location': config.get_backup_dir()}, | ||||
|     typecast=dict, | ||||
| ) | ||||
| # For core backup functionality, refer to the STORAGES["dbbackup"] entry (below) | ||||
|  | ||||
| DBBACKUP_DATE_FORMAT = InvenTree.backup.backup_date_format() | ||||
| DBBACKUP_FILENAME_TEMPLATE = InvenTree.backup.backup_filename_template() | ||||
| DBBACKUP_MEDIA_FILENAME_TEMPLATE = InvenTree.backup.backup_media_filename_template() | ||||
|  | ||||
| DBBACKUP_GPG_RECIPIENT = InvenTree.backup.backup_gpg_recipient() | ||||
|  | ||||
| DBBACKUP_SEND_EMAIL = InvenTree.backup.backup_email_on_error() | ||||
| DBBACKUP_EMAIL_SUBJECT_PREFIX = InvenTree.backup.backup_email_prefix() | ||||
|  | ||||
| DBBACKUP_CONNECTORS = {'default': InvenTree.backup.get_backup_connector_options()} | ||||
|  | ||||
| # Data storage options | ||||
| STORAGES = { | ||||
|     'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'}, | ||||
|     'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'}, | ||||
|     'dbbackup': { | ||||
|         'BACKEND': InvenTree.backup.get_backup_storage_backend(), | ||||
|         'OPTIONS': InvenTree.backup.get_backup_storage_options(), | ||||
|     }, | ||||
| } | ||||
|  | ||||
| # Enable django admin interface? | ||||
| INVENTREE_ADMIN_ENABLED = get_boolean_setting( | ||||
|     'INVENTREE_ADMIN_ENABLED', config_key='admin_enabled', default_value=True | ||||
| ) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ django-anymail[amazon_ses,postal]       # Email backend for various providers | ||||
| django-allauth[mfa,socialaccount,saml,openid]   # SSO for external providers via OpenID | ||||
| django-cleanup                          # Automated deletion of old / unused uploaded files | ||||
| django-cors-headers                     # CORS headers extension for DRF | ||||
| django-dbbackup                         # Backup / restore of database and media files | ||||
| django-dbbackup>=5.0.0                  # Backup / restore of database and media files | ||||
| django-error-report-2                   # Error report viewer for the admin interface | ||||
| django-filter                           # Extended filtering options | ||||
| django-flags                            # Feature flags | ||||
|   | ||||
| @@ -438,9 +438,9 @@ django-cors-headers==4.9.0 \ | ||||
|     --hash=sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449 \ | ||||
|     --hash=sha256:fe5d7cb59fdc2c8c646ce84b727ac2bca8912a247e6e68e1fb507372178e59e8 | ||||
|     # via -r src/backend/requirements.in | ||||
| django-dbbackup==4.3.0 \ | ||||
|     --hash=sha256:3549c8ccfdb167f20ca2c26eb0dfa55c79aa3f31ea4e4dbfa0816bc18ceec6dc \ | ||||
|     --hash=sha256:b4003b353d49d914ffbc033c793198426572d0a2b137ec795ddb2fb82225b960 | ||||
| django-dbbackup==5.0.0 \ | ||||
|     --hash=sha256:a0301b14a4bb3c7243a2fde76d09f8f572f16cd7639f75f4cd42d898fc1b82a2 \ | ||||
|     --hash=sha256:aa9cc88e1413adfec0e547dd91e0afed6dbb91a02459697663a9b988dbc71f18 | ||||
|     # via -r src/backend/requirements.in | ||||
| django-error-report-2==0.4.2 \ | ||||
|     --hash=sha256:1dd99c497af09b7ea99f5fbaf910501838150a9d5390796ea00e187bc62f6c1b \ | ||||
| @@ -1295,10 +1295,6 @@ python3-saml==1.16.0 \ | ||||
|     --hash=sha256:97c9669aecabc283c6e5fb4eb264f446b6e006f5267d01c9734f9d8bffdac133 \ | ||||
|     --hash=sha256:c49097863c278ff669a337a96c46dc1f25d16307b4bb2679d2d1733cc4f5176a | ||||
|     # via django-allauth | ||||
| pytz==2025.2 \ | ||||
|     --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \ | ||||
|     --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 | ||||
|     # via django-dbbackup | ||||
| pyyaml==6.0.3 \ | ||||
|     --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ | ||||
|     --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user