Skip to content

Commit

Permalink
Merge branch 'master' into pre-commit-ci-update-config
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger authored Aug 22, 2024
2 parents b153a00 + b0fdd24 commit 88e2dfb
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 17 deletions.
43 changes: 43 additions & 0 deletions dbbackup/checks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from datetime import datetime

from django.core.checks import Tags, Warning, register

Expand Down Expand Up @@ -35,6 +36,46 @@
"settings.DBBACKUP_ADMINS",
id="dbbackup.W006",
)
W007 = Warning(
"Invalid FILENAME_TEMPLATE parameter",
hint="settings.DBBACKUP_FILENAME_TEMPLATE must not contain slashes ('/'). "
"Did you mean to change the value for 'location'?",
id="dbbackup.W007",
)
W008 = Warning(
"Invalid MEDIA_FILENAME_TEMPLATE parameter",
hint="settings.DBBACKUP_MEDIA_FILENAME_TEMPLATE must not contain slashes ('/')"
"Did you mean to change the value for 'location'?",
id="dbbackup.W007",
)


def check_filename_templates():
return _check_filename_template(
settings.FILENAME_TEMPLATE,
W007,
"db",
) + _check_filename_template(
settings.MEDIA_FILENAME_TEMPLATE,
W008,
"media",
)


def _check_filename_template(filename_template, check_code, content_type) -> list:
if callable(filename_template):
params = {
"servername": "localhost",
"datetime": datetime.now().strftime(settings.DATE_FORMAT),
"databasename": "default",
"extension": "dump",
"content_type": content_type,
}
filename_template = filename_template(params)

if "/" in filename_template:
return [check_code]
return []


@register(Tags.compatibility)
Expand Down Expand Up @@ -64,4 +105,6 @@ def check_settings(app_configs, **kwargs):
if getattr(settings, "FAILURE_RECIPIENTS", None) is not None:
errors.append(W006)

errors += check_filename_templates()

return errors
3 changes: 1 addition & 2 deletions dbbackup/management/commands/mediabackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import os
import tarfile

from django.core.files.storage import get_storage_class
from django.core.management.base import CommandError

from ... import utils
from ...storage import StorageError, get_storage
from ...storage import StorageError, get_storage, get_storage_class
from ._base import BaseDbBackupCommand, make_option


Expand Down
4 changes: 1 addition & 3 deletions dbbackup/management/commands/mediarestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

import tarfile

from django.core.files.storage import get_storage_class

from ... import utils
from ...storage import get_storage
from ...storage import get_storage, get_storage_class
from ._base import BaseDbBackupCommand, make_option


Expand Down
16 changes: 13 additions & 3 deletions dbbackup/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@
GPG_ALWAYS_TRUST = getattr(settings, "DBBACKUP_GPG_ALWAYS_TRUST", False)
GPG_RECIPIENT = GPG_ALWAYS_TRUST = getattr(settings, "DBBACKUP_GPG_RECIPIENT", None)

STORAGE = getattr(
settings, "DBBACKUP_STORAGE", "django.core.files.storage.FileSystemStorage"
)
STORAGE = getattr(settings, "DBBACKUP_STORAGE", None)
STORAGE_OPTIONS = getattr(settings, "DBBACKUP_STORAGE_OPTIONS", {})
# https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-STORAGES
STORAGES_DBBACKUP_ALIAS = "dbbackup"
DJANGO_STORAGES = getattr(settings, "STORAGES", {})
django_dbbackup_storage = DJANGO_STORAGES.get(STORAGES_DBBACKUP_ALIAS, {})

if not STORAGE:
STORAGE = (
django_dbbackup_storage.get("BACKEND")
or "django.core.files.storage.FileSystemStorage"
)
if not STORAGE_OPTIONS:
STORAGE_OPTIONS = django_dbbackup_storage.get("OPTIONS") or STORAGE_OPTIONS

CONNECTORS = getattr(settings, "DBBACKUP_CONNECTORS", {})

Expand Down
28 changes: 27 additions & 1 deletion dbbackup/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import logging

from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import get_storage_class

from . import settings, utils

Expand Down Expand Up @@ -285,3 +284,30 @@ def clean_old_backups(
if keep_filter(filename):
continue
self.delete_file(filename)


def get_storage_class(path=None):
"""
Return the configured storage class.
:param path: Path in Python dot style to module containing the storage
class. If empty, the default storage class will be used.
:type path: str or None
:returns: Storage class
:rtype: :class:`django.core.files.storage.Storage`
"""
from django.utils.module_loading import import_string

if path:
# this is a workaround to keep compatibility with Django >= 5.1 (django.core.files.storage.get_storage_class is removed)
return import_string(path)

try:
from django.core.files.storage import DefaultStorage

return DefaultStorage
except Exception:
from django.core.files.storage import get_storage_class

return get_storage_class()
3 changes: 1 addition & 2 deletions dbbackup/tests/commands/test_mediabackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
import os
import tempfile

from django.core.files.storage import get_storage_class
from django.test import TestCase

from dbbackup.management.commands.mediabackup import Command as DbbackupCommand
from dbbackup.storage import get_storage
from dbbackup.storage import get_storage, get_storage_class
from dbbackup.tests.utils import DEV_NULL, HANDLED_FILES, add_public_gpg


Expand Down
12 changes: 12 additions & 0 deletions dbbackup/tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@
]
)

# For testing the new storages setting introduced in Django 4.2
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
"OPTIONS": {},
},
"dbbackup": {
"BACKEND": DBBACKUP_STORAGE,
"OPTIONS": DBBACKUP_STORAGE_OPTIONS,
},
}

LOGGING = {
"version": 1,
"disable_existing_loggers": False,
Expand Down
24 changes: 24 additions & 0 deletions dbbackup/tests/test_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,27 @@ def test_Failure_recipients_warning(self):
expected_errors = [checks.W006]
errors = checks.check_settings(DbbackupConfig)
self.assertEqual(expected_errors, errors)

@patch("dbbackup.checks.settings.FILENAME_TEMPLATE", "foo/bar-{datetime}.ext")
def test_db_filename_template_with_slash(self):
expected_errors = [checks.W007]
errors = checks.check_settings(DbbackupConfig)
self.assertEqual(expected_errors, errors)

@patch("dbbackup.checks.settings.FILENAME_TEMPLATE", lambda _: "foo/bar")
def test_db_filename_template_callable_with_slash(self):
expected_errors = [checks.W007]
errors = checks.check_settings(DbbackupConfig)
self.assertEqual(expected_errors, errors)

@patch("dbbackup.checks.settings.MEDIA_FILENAME_TEMPLATE", "foo/bar-{datetime}.ext")
def test_media_filename_template_with_slash(self):
expected_errors = [checks.W008]
errors = checks.check_settings(DbbackupConfig)
self.assertEqual(expected_errors, errors)

@patch("dbbackup.checks.settings.MEDIA_FILENAME_TEMPLATE", lambda _: "foo/bar")
def test_media_filename_template_callable_with_slash(self):
expected_errors = [checks.W008]
errors = checks.check_settings(DbbackupConfig)
self.assertEqual(expected_errors, errors)
51 changes: 50 additions & 1 deletion dbbackup/tests/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.test import TestCase

from dbbackup import utils
from dbbackup.storage import Storage, get_storage
from dbbackup.storage import Storage, get_storage, get_storage_class
from dbbackup.tests.utils import HANDLED_FILES, FakeStorage

DEFAULT_STORAGE_PATH = "django.core.files.storage.FileSystemStorage"
Expand All @@ -30,6 +30,55 @@ def test_set_options(self, *args):
("django.core.files.storage", "django.core.files.storage.filesystem"),
)

def test_get_storage_class(self):
storage_class = get_storage_class(DEFAULT_STORAGE_PATH)
self.assertIn(
storage_class.__module__,
("django.core.files.storage", "django.core.files.storage.filesystem"),
)
self.assertIn(storage_class.__name__, ("FileSystemStorage", "DefaultStorage"))

storage_class = get_storage_class("dbbackup.tests.utils.FakeStorage")
self.assertEqual(storage_class.__module__, "dbbackup.tests.utils")
self.assertEqual(storage_class.__name__, "FakeStorage")

def test_default_storage_class(self):
storage_class = get_storage_class()
self.assertIn(
storage_class.__module__,
("django.core.files.storage", "django.core.files.storage.filesystem"),
)
self.assertIn(storage_class.__name__, ("FileSystemStorage", "DefaultStorage"))

def test_invalid_storage_class_path(self):
with self.assertRaises(ImportError):
get_storage_class("invalid.path.to.StorageClass")

def test_storages_settings(self):
from .settings import STORAGES

self.assertIsInstance(STORAGES, dict)
self.assertEqual(
STORAGES["dbbackup"]["BACKEND"], "dbbackup.tests.utils.FakeStorage"
)

from dbbackup.settings import DJANGO_STORAGES, STORAGE

self.assertIsInstance(DJANGO_STORAGES, dict)
self.assertEqual(DJANGO_STORAGES, STORAGES)
self.assertEqual(STORAGES["dbbackup"]["BACKEND"], STORAGE)

storage = get_storage()
self.assertEqual(storage.storage.__class__.__module__, "dbbackup.tests.utils")
self.assertEqual(storage.storage.__class__.__name__, "FakeStorage")

def test_storages_settings_options(self):
from dbbackup.settings import STORAGE_OPTIONS

from .settings import STORAGES

self.assertEqual(STORAGES["dbbackup"]["OPTIONS"], STORAGE_OPTIONS)


class StorageTest(TestCase):
def setUp(self):
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ Unreleased
* Default HOST to localhost for postgres databases. https://github.com/jazzband/django-dbbackup/issues/520
* Add PostgreSQL Schema support by @angryfoxx in https://github.com/jazzband/django-dbbackup/pull/507
* Fix restore of database from S3 storage by reintroducing inputfile.seek(0) to utils.uncompress_file
* Add warning for filenames with slashes in them
* Fix bug where dbbackup management command would not respect settings.py:DBBACKUP_DATABASES
* Remove usage of deprecated 'get_storage_class' function in newer Django versions
* Add support for new STORAGES (Django 4.2+) setting under the 'dbbackup' alias

4.1.0 (2024-01-14)
------------------
Expand Down
32 changes: 31 additions & 1 deletion docs/storage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,32 @@ mainly based on Django Storage API and extends its possibilities.

You can choose your backup storage backend by setting ``settings.DBBACKUP_STORAGE``,
it must be the full path of a storage class. For example:
``django.core.files.storage.FileSystemStorage`` to use file system storage.
``django.core.files.storage.FileSystemStorage`` to use file system storage.
Below, we'll list some of the available solutions and their options.


The storage's option are gathered in ``settings.DBBACKUP_STORAGE_OPTIONS`` which
is a dictionary of keywords representing how to configure it.

Since Django 4.2 there is a new way to configure storages using the STORAGES setting. E.g.:::

STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
"OPTIONS": {
...your_options_here
},
},
"staticfiles": {
...
},
}

instead of the previous DEFAULT_FILE_STORAGE and STATICFILES_STORAGE settings.
If ``settings.DBBACKUP_STORAGE`` is not set, django-dbbackup will look for the ``dbbackup`` key in the ``STORAGES`` dictionary setting.
Otherwise, the default storage will be used.


.. warning::

Do not configure backup storage with the same configuration as your media
Expand Down Expand Up @@ -43,6 +63,16 @@ settings below. ::
DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage'
DBBACKUP_STORAGE_OPTIONS = {'location': '/my/backup/dir/'}

Alternatively, starting from Django 4.2, you can use the STORAGES setting: ::

STORAGES = {
"dbbackup": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
"OPTIONS": {
"location": "/my/backup/dir/",
},
},
}

Available settings
~~~~~~~~~~~~~~~~~~
Expand Down
9 changes: 5 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py{37,38,39,310,311,312}-django{32,42,50,master},lint,docs,functional
envlist = py{37,38,39,310,311,312}-django{32,42,50,51,master},lint,docs,functional

[testenv]
passenv = *
Expand All @@ -10,6 +10,7 @@ deps =
django32: django>=3.2,<3.3
django42: django>=4.2,<4.3
django50: django>=5.0,<5.1
django51: django>=5.1,<5.2
djangomaster: https://github.com/django/django/archive/master.zip
commands = {posargs:coverage run runtests.py}

Expand All @@ -19,9 +20,9 @@ python =
3.7: py37-django{32},functional
3.8: py38-django{32,42},functional
3.9: py39-django{32,42},functional
3.10: py310-django{32,42,50},functional
3.11: py311-django{42,50},functional
3.12: py312-django{42,50},functional
3.10: py310-django{32,42,50,51},functional
3.11: py311-django{42,50,51},functional
3.12: py312-django{42,50,51},functional

[testenv:lint]
basepython = python
Expand Down

0 comments on commit 88e2dfb

Please sign in to comment.