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

Fixed #21380 -- Added a way to set different permission for static direc... #1873

Closed
wants to merge 1 commit into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -294,12 +294,6 @@ def copy_file(self, path, prefixed_path, source_storage):
self.log("Pretending to copy '%s'" % source_path, level=1)
else:
self.log("Copying '%s'" % source_path, level=1)
if self.local:
full_path = self.storage.path(prefixed_path)
try:
os.makedirs(os.path.dirname(full_path))
except OSError:
pass
with source_storage.open(path) as source_file:
self.storage.save(prefixed_path, source_file)
if not prefixed_path in self.copied_files:
Expand Down
13 changes: 9 additions & 4 deletions django/core/files/storage.py
Expand Up @@ -147,7 +147,8 @@ class FileSystemStorage(Storage):
Standard filesystem storage
"""

def __init__(self, location=None, base_url=None, file_permissions_mode=None):
def __init__(self, location=None, base_url=None, file_permissions_mode=None,
directory_permissions_mode=None):
if location is None:
location = settings.MEDIA_ROOT
self.base_location = location
Expand All @@ -159,6 +160,10 @@ def __init__(self, location=None, base_url=None, file_permissions_mode=None):
file_permissions_mode if file_permissions_mode is not None
else settings.FILE_UPLOAD_PERMISSIONS
)
self.directory_permissions_mode = (
directory_permissions_mode if directory_permissions_mode is not None
else settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS
)

def _open(self, name, mode='rb'):
return File(open(self.path(name), mode))
Expand All @@ -173,12 +178,12 @@ def _save(self, name, content):
directory = os.path.dirname(full_path)
if not os.path.exists(directory):
try:
if settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS is not None:
if self.directory_permissions_mode is not None:
# os.makedirs applies the global umask, so we reset it,
# for consistency with FILE_UPLOAD_PERMISSIONS behavior.
# for consistency with file_permissions_mode behavior.
old_umask = os.umask(0)
try:
os.makedirs(directory, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS)
os.makedirs(directory, self.directory_permissions_mode)
finally:
os.umask(old_umask)
else:
Expand Down
18 changes: 11 additions & 7 deletions docs/ref/contrib/staticfiles.txt
Expand Up @@ -60,26 +60,30 @@ by the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
by default.

By default, collected files receive permissions from
:setting:`FILE_UPLOAD_PERMISSIONS`. If you would like different permissions for
these files, you can subclass either of the :ref:`static files storage
classes <staticfiles-storages>` and specify the ``file_permissions_mode``
parameter. For example::
:setting:`FILE_UPLOAD_PERMISSIONS` and collected directories receive permissions
from :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`. If you would like different
permissions for these files and/or directories, you can subclass either of the
:ref:`static files storage classes <staticfiles-storages>` and specify the
``file_permissions_mode`` parameter for files permissions and
``directory_permissions_mode`` parameter for directories permissions. For example::

from django.contrib.staticfiles import storage

class MyStaticFilesStorage(storage.StaticFilesStorage):
def __init__(self, *args, **kwargs):
kwargs['file_permissions_mode'] = 0o640
kwargs['directory_permissions_mode'] = 0o760
super(CustomStaticFilesStorage, self).__init__(*args, **kwargs)

Then set the :setting:`STATICFILES_STORAGE` setting to
``'path.to.MyStaticFilesStorage'``.

.. versionadded:: 1.7

The ability to override ``file_permissions_mode`` is new in Django 1.7.
Previously the file permissions always used
:setting:`FILE_UPLOAD_PERMISSIONS`.
The ability to override ``file_permissions_mode`` and
``directory_permissions_mode`` is new in Django 1.7. Previously the file
permissions always used :setting:`FILE_UPLOAD_PERMISSIONS` and the directory
permissions always used :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`.

.. highlight:: console

Expand Down
14 changes: 13 additions & 1 deletion docs/ref/files/storage.txt
Expand Up @@ -29,7 +29,8 @@ Django provides two convenient ways to access the current storage class:
The FileSystemStorage Class
---------------------------

.. class:: FileSystemStorage([location=None, base_url=None, file_permissions_mode=None])
.. class:: FileSystemStorage([location=None, base_url=None, file_permissions_mode=None,
directory_permissions_mode=None])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if there's a way to multiline parameters that doesn't break sphinx. I've removed the newline.


The :class:`~django.core.files.storage.FileSystemStorage` class implements
basic file storage on a local filesystem. It inherits from
Expand All @@ -46,6 +47,17 @@ The FileSystemStorage Class
The ``file_permissions_mode`` attribute was added. Previously files
always received :setting:`FILE_UPLOAD_PERMISSIONS` permissions.

.. attribute:: directory_permissions_mode

The file system permissions that the directory will receive when it is
saved. Defaults to :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`.

.. versionadded:: 1.7

The ``directory_permissions_mode`` attribute was added. Previously
directories always received :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`
permissions.

.. note::

The ``FileSystemStorage.delete()`` method will not raise
Expand Down
11 changes: 8 additions & 3 deletions docs/ref/settings.txt
Expand Up @@ -1139,9 +1139,14 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS

Default: ``None``

The numeric mode to apply to directories created in the process of
uploading files. This value mirrors the functionality and caveats of
the :setting:`FILE_UPLOAD_PERMISSIONS` setting.
The numeric mode to apply to directories created in the process of uploading files.

This setting also determines the default permissions for collected static directories
when using the :djadmin:`collectstatic` management command. See
:djadmin:`collectstatic` for details on overridding it.

This value mirrors the functionality and caveats of the :setting:`FILE_UPLOAD_PERMISSIONS`
setting.

.. setting:: FILE_UPLOAD_PERMISSIONS

Expand Down
5 changes: 3 additions & 2 deletions docs/releases/1.7.txt
Expand Up @@ -237,9 +237,10 @@ Minor features
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

* The :ref:`static files storage classes <staticfiles-storages>` may be
subclassed to override the permissions that collected static files receive by
setting the
subclassed to override the permissions that collected static files and directories
receive by setting the
:attr:`~django.core.files.storage.FileSystemStorage.file_permissions_mode`
and :attr:`~django.core.files.storage.FileSystemStorage.directory_permissions_mode`
parameter. See :djadmin:`collectstatic` for example usage.

:mod:`django.contrib.syndication`
Expand Down
33 changes: 31 additions & 2 deletions tests/staticfiles_tests/tests.py
Expand Up @@ -814,6 +814,7 @@ class CustomStaticFilesStorage(storage.StaticFilesStorage):
"""
def __init__(self, *args, **kwargs):
kwargs['file_permissions_mode'] = 0o640
kwargs['directory_permissions_mode'] = 0o740
super(CustomStaticFilesStorage, self).__init__(*args, **kwargs)


Expand All @@ -830,21 +831,49 @@ class TestStaticFilePermissions(BaseCollectionTestCase, StaticFilesTestCase):
'link': False,
'dry_run': False}

def setUp(self):
self.umask = 0o027
self.old_umask = os.umask(self.umask)
super(TestStaticFilePermissions, self).setUp()

def tearDown(self):
os.umask(self.old_umask)
super(TestStaticFilePermissions, self).tearDown()

# Don't run collectstatic command in this test class.
def run_collectstatic(self, **kwargs):
pass

@override_settings(FILE_UPLOAD_PERMISSIONS=0o655)
def test_collect_static_files_default_permissions(self):
@override_settings(FILE_UPLOAD_PERMISSIONS=0o655,
FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
def test_collect_static_files_permissions(self):
collectstatic.Command().execute(**self.command_params)
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
file_mode = os.stat(test_file)[0] & 0o777
dir_mode = os.stat(test_dir)[0] & 0o777
self.assertEqual(file_mode, 0o655)
self.assertEqual(dir_mode, 0o765)

@override_settings(FILE_UPLOAD_PERMISSIONS=None,
FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
def test_collect_static_files_default_permissions(self):
collectstatic.Command().execute(**self.command_params)
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
file_mode = os.stat(test_file)[0] & 0o777
dir_mode = os.stat(test_dir)[0] & 0o777
self.assertEqual(file_mode, 0o666 & ~self.umask)
self.assertEqual(dir_mode, 0o777 & ~self.umask)

@override_settings(FILE_UPLOAD_PERMISSIONS=0o655,
FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765,
STATICFILES_STORAGE='staticfiles_tests.tests.CustomStaticFilesStorage')
def test_collect_static_files_subclass_of_static_storage(self):
collectstatic.Command().execute(**self.command_params)
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
file_mode = os.stat(test_file)[0] & 0o777
dir_mode = os.stat(test_dir)[0] & 0o777
self.assertEqual(file_mode, 0o640)
self.assertEqual(dir_mode, 0o740)