Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FileSystemOverwriteStorage #1252

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Loading