Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #21219 -- Added a way to set different permission for static fi…

…les.

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.
  • Loading branch information...
commit 9eecb9169566db263e243e4522b08ea1403ee95f 1 parent c052699
Vajrasky Kok vajrasky authored timgraham committed
10 django/core/files/storage.py
View
@@ -147,7 +147,7 @@ class FileSystemStorage(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:
location = settings.MEDIA_ROOT
self.base_location = location
@@ -155,6 +155,10 @@ def __init__(self, location=None, base_url=None):
if base_url is None:
base_url = settings.MEDIA_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'):
return File(open(self.path(name), mode))
@@ -232,8 +236,8 @@ def _save(self, name, content):
# OK, the file save worked. Break out of the loop.
break
- if settings.FILE_UPLOAD_PERMISSIONS is not None:
- os.chmod(full_path, settings.FILE_UPLOAD_PERMISSIONS)
+ if self.file_permissions_mode is not None:
+ os.chmod(full_path, self.file_permissions_mode)
return name
28 docs/ref/contrib/staticfiles.txt
View
@@ -32,8 +32,6 @@ following settings:
Management Commands
===================
-.. highlight:: console
-
``django.contrib.staticfiles`` exposes three management commands.
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 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:
.. django-admin-option:: --noinput
@@ -174,6 +196,8 @@ Example usage::
django-admin.py runserver --insecure
+.. _staticfiles-storages:
+
Storages
========
13 docs/ref/files/storage.txt
View
@@ -29,13 +29,23 @@ Django provides two convenient ways to access the current storage 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
basic file storage on a local filesystem. It inherits from
:class:`~django.core.files.storage.Storage` and provides implementations
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::
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
system.
-
.. method:: get_valid_name(name)
Returns a filename based on the ``name`` parameter that's suitable
4 docs/ref/settings.txt
View
@@ -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
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::
**Always prefix the mode with a 0.**
9 docs/releases/1.7.txt
View
@@ -226,6 +226,15 @@ Minor features
:class:`~django.middleware.http.ConditionalGetMiddleware` to handle
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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5 tests/file_storage/tests.py
View
@@ -442,7 +442,6 @@ def setUp(self):
self.umask = 0o027
self.old_umask = os.umask(self.umask)
self.storage_dir = tempfile.mkdtemp()
- self.storage = FileSystemStorage(self.storage_dir)
def tearDown(self):
shutil.rmtree(self.storage_dir)
@@ -450,24 +449,28 @@ def tearDown(self):
@override_settings(FILE_UPLOAD_PERMISSIONS=0o654)
def test_file_upload_permissions(self):
+ self.storage = FileSystemStorage(self.storage_dir)
name = self.storage.save("the_file", ContentFile("data"))
actual_mode = os.stat(self.storage.path(name))[0] & 0o777
self.assertEqual(actual_mode, 0o654)
@override_settings(FILE_UPLOAD_PERMISSIONS=None)
def test_file_upload_default_permissions(self):
+ self.storage = FileSystemStorage(self.storage_dir)
fname = self.storage.save("some_file", ContentFile("data"))
mode = os.stat(self.storage.path(fname))[0] & 0o777
self.assertEqual(mode, 0o666 & ~self.umask)
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
def test_file_upload_directory_permissions(self):
+ self.storage = FileSystemStorage(self.storage_dir)
name = self.storage.save("the_directory/the_file", ContentFile("data"))
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
self.assertEqual(dir_mode, 0o765)
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
def test_file_upload_directory_default_permissions(self):
+ self.storage = FileSystemStorage(self.storage_dir)
name = self.storage.save("the_directory/the_file", ContentFile("data"))
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
self.assertEqual(dir_mode, 0o777 & ~self.umask)
44 tests/staticfiles_tests/tests.py
View
@@ -7,6 +7,7 @@
import shutil
import sys
import tempfile
+import unittest
from django.template import loader, Context
from django.conf import settings
@@ -21,6 +22,7 @@
from django.utils import six
from django.contrib.staticfiles import finders, storage
+from django.contrib.staticfiles.management.commands import collectstatic
TEST_ROOT = os.path.dirname(upath(__file__))
TEST_SETTINGS = {
@@ -804,3 +806,45 @@ def test_app_with_non_ascii_characters_in_path(self):
st.path('bar')
finally:
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)
Please sign in to comment.
Something went wrong with that request. Please try again.