Skip to content

Commit

Permalink
Add FileSystemOverwriteStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
craigds committed Feb 13, 2024
1 parent 70fa8d9 commit 5112692
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 0 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
django-storages CHANGELOG
=========================

UNRELEASED
**********

Filesystem
----------

Add new ``FileSystemOverwriteStorage`` backend - similar to Django's existing filesystem storage,
but overwrites existing files instead of always saving with unique names. (`#1252`_)

.. _#1252: https://github.com/jschneier/django-storages/pull/1252

1.14.2 (2023-10-08)
*******************

Expand Down
21 changes: 21 additions & 0 deletions docs/backends/filesystem.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Filesystem
==========

django-storages contains an alternative filesystem storage backend.

Unlike Django's builtin filesystem storage, this one always overwrites files of the same name, and never renames files.


Usage
*****

::

from storages.backends.filesystem import FileSystemOverwriteStorage
storage = FileSystemOverwriteStorage(location='/media/photos')
storage.save("myfile.txt", ContentFile("content 1"))
# This will overwrite the previous file, *not* create a new file.
storage.save("myfile.txt", ContentFile("content 2"))

27 changes: 27 additions & 0 deletions storages/backends/filesystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import os
import pathlib

from django.core.exceptions import SuspiciousFileOperation
from django.core.files.storage import FileSystemStorage


class FileSystemOverwriteStorage(FileSystemStorage):
"""
Filesystem storage that never renames files.
Files uploaded via this storage class will automatically overwrite any files of the same name.
"""

# Don't throw errors if the file already exists when saving.
# https://manpages.debian.org/bullseye/manpages-dev/open.2.en.html#O_EXCL
OS_OPEN_FLAGS = FileSystemStorage.OS_OPEN_FLAGS & ~os.O_EXCL

# Don't check what files already exist; just use the original name.
def get_available_name(self, name, max_length=None):
# Do validate it though (just like FileSystemStorage does)
name = str(name).replace("\\", "/")
dir_name, _ = os.path.split(name)
if ".." in pathlib.PurePath(dir_name).parts:
raise SuspiciousFileOperation(
"Detected path traversal attempt in '%s'" % dir_name
)
return name
37 changes: 37 additions & 0 deletions tests/test_filesystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import tempfile

import pytest
from django.core.exceptions import SuspiciousFileOperation
from django.core.files.base import ContentFile

from storages.backends import filesystem


@pytest.fixture()
def storage():
with tempfile.TemporaryDirectory() as tmpdirname:
yield filesystem.FileSystemOverwriteStorage(location=tmpdirname)


def test_save_overwrite(storage):
content = ContentFile("content")
name = "testfile.txt"
storage.save(name, content)

assert storage.exists(name)
assert storage.size(name) == len(content)

content2 = ContentFile("content2")
storage.save(name, content2)
# No rename was done; the same file was overwritten
assert storage.exists(name)
assert storage.size(name) == len(content2)


def test_filename_validate(storage):
content = ContentFile("content")
with pytest.raises(SuspiciousFileOperation):
storage.save("/badfile.txt", content)

with pytest.raises(SuspiciousFileOperation):
storage.save("foo/../../../badfile.txt", content)

0 comments on commit 5112692

Please sign in to comment.