Fixed #21219 -- Added a way to set different permission for static files.
Previously, when collecting static files, the files would receive permission from FILE_UPLOAD_PERMISSIONS. Now, there's an option to give different permission from uploaded files permission by subclassing any of the static files storage classes and setting the file_permissions_mode parameter. Thanks dblack at atlassian.com for the suggestion.
This commit is contained in:
parent
c052699be3
commit
9eecb91695
@ -147,7 +147,7 @@ class FileSystemStorage(Storage):
|
|||||||
Standard filesystem storage
|
Standard filesystem storage
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, location=None, base_url=None):
|
def __init__(self, location=None, base_url=None, file_permissions_mode=None):
|
||||||
if location is None:
|
if location is None:
|
||||||
location = settings.MEDIA_ROOT
|
location = settings.MEDIA_ROOT
|
||||||
self.base_location = location
|
self.base_location = location
|
||||||
@ -155,6 +155,10 @@ class FileSystemStorage(Storage):
|
|||||||
if base_url is None:
|
if base_url is None:
|
||||||
base_url = settings.MEDIA_URL
|
base_url = settings.MEDIA_URL
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
|
self.file_permissions_mode = (
|
||||||
|
file_permissions_mode if file_permissions_mode is not None
|
||||||
|
else settings.FILE_UPLOAD_PERMISSIONS
|
||||||
|
)
|
||||||
|
|
||||||
def _open(self, name, mode='rb'):
|
def _open(self, name, mode='rb'):
|
||||||
return File(open(self.path(name), mode))
|
return File(open(self.path(name), mode))
|
||||||
@ -232,8 +236,8 @@ class FileSystemStorage(Storage):
|
|||||||
# OK, the file save worked. Break out of the loop.
|
# OK, the file save worked. Break out of the loop.
|
||||||
break
|
break
|
||||||
|
|
||||||
if settings.FILE_UPLOAD_PERMISSIONS is not None:
|
if self.file_permissions_mode is not None:
|
||||||
os.chmod(full_path, settings.FILE_UPLOAD_PERMISSIONS)
|
os.chmod(full_path, self.file_permissions_mode)
|
||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
@ -32,8 +32,6 @@ following settings:
|
|||||||
Management Commands
|
Management Commands
|
||||||
===================
|
===================
|
||||||
|
|
||||||
.. highlight:: console
|
|
||||||
|
|
||||||
``django.contrib.staticfiles`` exposes three management commands.
|
``django.contrib.staticfiles`` exposes three management commands.
|
||||||
|
|
||||||
collectstatic
|
collectstatic
|
||||||
@ -61,6 +59,30 @@ receives all command line options of :djadmin:`collectstatic`. This is used
|
|||||||
by the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
|
by the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
|
||||||
by default.
|
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::
|
||||||
|
|
||||||
|
from django.contrib.staticfiles import storage
|
||||||
|
|
||||||
|
class MyStaticFilesStorage(storage.StaticFilesStorage):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['file_permissions_mode'] = 0o640
|
||||||
|
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`.
|
||||||
|
|
||||||
|
.. highlight:: console
|
||||||
|
|
||||||
Some commonly used options are:
|
Some commonly used options are:
|
||||||
|
|
||||||
.. django-admin-option:: --noinput
|
.. django-admin-option:: --noinput
|
||||||
@ -174,6 +196,8 @@ Example usage::
|
|||||||
|
|
||||||
django-admin.py runserver --insecure
|
django-admin.py runserver --insecure
|
||||||
|
|
||||||
|
.. _staticfiles-storages:
|
||||||
|
|
||||||
Storages
|
Storages
|
||||||
========
|
========
|
||||||
|
|
||||||
|
@ -29,13 +29,23 @@ Django provides two convenient ways to access the current storage class:
|
|||||||
The FileSystemStorage Class
|
The FileSystemStorage Class
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
.. class:: FileSystemStorage
|
.. class:: FileSystemStorage([location=None, base_url=None, file_permissions_mode=None])
|
||||||
|
|
||||||
The :class:`~django.core.files.storage.FileSystemStorage` class implements
|
The :class:`~django.core.files.storage.FileSystemStorage` class implements
|
||||||
basic file storage on a local filesystem. It inherits from
|
basic file storage on a local filesystem. It inherits from
|
||||||
:class:`~django.core.files.storage.Storage` and provides implementations
|
:class:`~django.core.files.storage.Storage` and provides implementations
|
||||||
for all the public methods thereof.
|
for all the public methods thereof.
|
||||||
|
|
||||||
|
.. attribute:: file_permissions_mode
|
||||||
|
|
||||||
|
The file system permissions that the file will receive when it is
|
||||||
|
saved. Defaults to :setting:`FILE_UPLOAD_PERMISSIONS`.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
The ``file_permissions_mode`` attribute was added. Previously files
|
||||||
|
always received :setting:`FILE_UPLOAD_PERMISSIONS` permissions.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The ``FileSystemStorage.delete()`` method will not raise
|
The ``FileSystemStorage.delete()`` method will not raise
|
||||||
@ -81,7 +91,6 @@ The Storage Class
|
|||||||
available for new content to be written to on the target storage
|
available for new content to be written to on the target storage
|
||||||
system.
|
system.
|
||||||
|
|
||||||
|
|
||||||
.. method:: get_valid_name(name)
|
.. method:: get_valid_name(name)
|
||||||
|
|
||||||
Returns a filename based on the ``name`` parameter that's suitable
|
Returns a filename based on the ``name`` parameter that's suitable
|
||||||
|
@ -1159,6 +1159,10 @@ dependent behavior. On most platforms, temporary files will have a mode
|
|||||||
of ``0600``, and files saved from memory will be saved using the
|
of ``0600``, and files saved from memory will be saved using the
|
||||||
system's standard umask.
|
system's standard umask.
|
||||||
|
|
||||||
|
This setting also determines the default permissions for collected static files
|
||||||
|
when using the :djadmin:`collectstatic` management command. See
|
||||||
|
:djadmin:`collectstatic` for details on overridding it.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
**Always prefix the mode with a 0.**
|
**Always prefix the mode with a 0.**
|
||||||
|
@ -226,6 +226,15 @@ Minor features
|
|||||||
:class:`~django.middleware.http.ConditionalGetMiddleware` to handle
|
:class:`~django.middleware.http.ConditionalGetMiddleware` to handle
|
||||||
conditional ``GET`` requests for sitemaps which set ``lastmod``.
|
conditional ``GET`` requests for sitemaps which set ``lastmod``.
|
||||||
|
|
||||||
|
:mod:`django.contrib.staticfiles`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* The :ref:`static files storage classes <staticfiles-storages>` may be
|
||||||
|
subclassed to override the permissions that collected static files receive by
|
||||||
|
setting the
|
||||||
|
:attr:`~django.core.files.storage.FileSystemStorage.file_permissions_mode`
|
||||||
|
parameter. See :djadmin:`collectstatic` for example usage.
|
||||||
|
|
||||||
:mod:`django.contrib.syndication`
|
:mod:`django.contrib.syndication`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -442,7 +442,6 @@ class FileStoragePermissions(unittest.TestCase):
|
|||||||
self.umask = 0o027
|
self.umask = 0o027
|
||||||
self.old_umask = os.umask(self.umask)
|
self.old_umask = os.umask(self.umask)
|
||||||
self.storage_dir = tempfile.mkdtemp()
|
self.storage_dir = tempfile.mkdtemp()
|
||||||
self.storage = FileSystemStorage(self.storage_dir)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(self.storage_dir)
|
shutil.rmtree(self.storage_dir)
|
||||||
@ -450,24 +449,28 @@ class FileStoragePermissions(unittest.TestCase):
|
|||||||
|
|
||||||
@override_settings(FILE_UPLOAD_PERMISSIONS=0o654)
|
@override_settings(FILE_UPLOAD_PERMISSIONS=0o654)
|
||||||
def test_file_upload_permissions(self):
|
def test_file_upload_permissions(self):
|
||||||
|
self.storage = FileSystemStorage(self.storage_dir)
|
||||||
name = self.storage.save("the_file", ContentFile("data"))
|
name = self.storage.save("the_file", ContentFile("data"))
|
||||||
actual_mode = os.stat(self.storage.path(name))[0] & 0o777
|
actual_mode = os.stat(self.storage.path(name))[0] & 0o777
|
||||||
self.assertEqual(actual_mode, 0o654)
|
self.assertEqual(actual_mode, 0o654)
|
||||||
|
|
||||||
@override_settings(FILE_UPLOAD_PERMISSIONS=None)
|
@override_settings(FILE_UPLOAD_PERMISSIONS=None)
|
||||||
def test_file_upload_default_permissions(self):
|
def test_file_upload_default_permissions(self):
|
||||||
|
self.storage = FileSystemStorage(self.storage_dir)
|
||||||
fname = self.storage.save("some_file", ContentFile("data"))
|
fname = self.storage.save("some_file", ContentFile("data"))
|
||||||
mode = os.stat(self.storage.path(fname))[0] & 0o777
|
mode = os.stat(self.storage.path(fname))[0] & 0o777
|
||||||
self.assertEqual(mode, 0o666 & ~self.umask)
|
self.assertEqual(mode, 0o666 & ~self.umask)
|
||||||
|
|
||||||
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
|
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
|
||||||
def test_file_upload_directory_permissions(self):
|
def test_file_upload_directory_permissions(self):
|
||||||
|
self.storage = FileSystemStorage(self.storage_dir)
|
||||||
name = self.storage.save("the_directory/the_file", ContentFile("data"))
|
name = self.storage.save("the_directory/the_file", ContentFile("data"))
|
||||||
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
|
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
|
||||||
self.assertEqual(dir_mode, 0o765)
|
self.assertEqual(dir_mode, 0o765)
|
||||||
|
|
||||||
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
|
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
|
||||||
def test_file_upload_directory_default_permissions(self):
|
def test_file_upload_directory_default_permissions(self):
|
||||||
|
self.storage = FileSystemStorage(self.storage_dir)
|
||||||
name = self.storage.save("the_directory/the_file", ContentFile("data"))
|
name = self.storage.save("the_directory/the_file", ContentFile("data"))
|
||||||
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
|
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
|
||||||
self.assertEqual(dir_mode, 0o777 & ~self.umask)
|
self.assertEqual(dir_mode, 0o777 & ~self.umask)
|
||||||
|
@ -7,6 +7,7 @@ import posixpath
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
from django.template import loader, Context
|
from django.template import loader, Context
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -21,6 +22,7 @@ from django.utils._os import rmtree_errorhandler, upath
|
|||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from django.contrib.staticfiles import finders, storage
|
from django.contrib.staticfiles import finders, storage
|
||||||
|
from django.contrib.staticfiles.management.commands import collectstatic
|
||||||
|
|
||||||
TEST_ROOT = os.path.dirname(upath(__file__))
|
TEST_ROOT = os.path.dirname(upath(__file__))
|
||||||
TEST_SETTINGS = {
|
TEST_SETTINGS = {
|
||||||
@ -804,3 +806,45 @@ class TestAppStaticStorage(TestCase):
|
|||||||
st.path('bar')
|
st.path('bar')
|
||||||
finally:
|
finally:
|
||||||
sys.getfilesystemencoding = old_enc_func
|
sys.getfilesystemencoding = old_enc_func
|
||||||
|
|
||||||
|
|
||||||
|
class CustomStaticFilesStorage(storage.StaticFilesStorage):
|
||||||
|
"""
|
||||||
|
Used in TestStaticFilePermissions
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['file_permissions_mode'] = 0o640
|
||||||
|
super(CustomStaticFilesStorage, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.platform.startswith('win'),
|
||||||
|
"Windows only partially supports chmod.")
|
||||||
|
class TestStaticFilePermissions(BaseCollectionTestCase, StaticFilesTestCase):
|
||||||
|
|
||||||
|
command_params = {'interactive': False,
|
||||||
|
'post_process': True,
|
||||||
|
'verbosity': '0',
|
||||||
|
'ignore_patterns': ['*.ignoreme'],
|
||||||
|
'use_default_ignore_patterns': True,
|
||||||
|
'clear': False,
|
||||||
|
'link': False,
|
||||||
|
'dry_run': False}
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
collectstatic.Command().execute(**self.command_params)
|
||||||
|
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
|
||||||
|
file_mode = os.stat(test_file)[0] & 0o777
|
||||||
|
self.assertEqual(file_mode, 0o655)
|
||||||
|
|
||||||
|
@override_settings(FILE_UPLOAD_PERMISSIONS=0o655,
|
||||||
|
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")
|
||||||
|
file_mode = os.stat(test_file)[0] & 0o777
|
||||||
|
self.assertEqual(file_mode, 0o640)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user