From 7e2d61a9724644d6d1c7ce9361d9fd5be3e2ab86 Mon Sep 17 00:00:00 2001 From: Vajrasky Kok Date: Tue, 5 Nov 2013 18:02:54 +0800 Subject: [PATCH] Fixed #21380 -- Added a way to set different permission for static directories. Previously when collecting static files, the directories would receive permissions from the global umask. Now the default permission comes from FILE_UPLOAD_DIRECTORY_PERMISSIONS and there's an option to specify the permissions by subclassing any of the static files storage classes and setting the directory_permissions_mode parameter. --- .../management/commands/collectstatic.py | 6 ---- django/core/files/storage.py | 13 +++++--- docs/ref/contrib/staticfiles.txt | 18 ++++++---- docs/ref/files/storage.txt | 13 +++++++- docs/ref/settings.txt | 14 +++++--- docs/releases/1.7.txt | 7 ++-- tests/staticfiles_tests/tests.py | 33 +++++++++++++++++-- 7 files changed, 77 insertions(+), 27 deletions(-) diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index c1e9fa811ba..67fa6229c4a 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -294,12 +294,6 @@ Type 'yes' to continue, or 'no' to cancel: """ 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: diff --git a/django/core/files/storage.py b/django/core/files/storage.py index aae3d7e9841..e8db352d3ea 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -149,7 +149,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 @@ -161,6 +162,10 @@ class FileSystemStorage(Storage): 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)) @@ -175,12 +180,12 @@ class FileSystemStorage(Storage): 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: diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index c65533fa0f8..32023212690 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -60,16 +60,19 @@ 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 ` 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 ` and specify the +``file_permissions_mode`` and/or ``directory_permissions_mode`` parameters, +respectively. 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 @@ -77,9 +80,10 @@ Then set the :setting:`STATICFILES_STORAGE` setting to .. 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 diff --git a/docs/ref/files/storage.txt b/docs/ref/files/storage.txt index bd8fa567874..6d367b70fc3 100644 --- a/docs/ref/files/storage.txt +++ b/docs/ref/files/storage.txt @@ -29,7 +29,7 @@ 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]) The :class:`~django.core.files.storage.FileSystemStorage` class implements basic file storage on a local filesystem. It inherits from @@ -46,6 +46,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 diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index c99a7e43473..a1207fb0f81 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1135,9 +1135,15 @@ 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 overriding it. + +This value mirrors the functionality and caveats of the +:setting:`FILE_UPLOAD_PERMISSIONS` setting. .. setting:: FILE_UPLOAD_PERMISSIONS @@ -1157,7 +1163,7 @@ 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. +:djadmin:`collectstatic` for details on overriding it. .. warning:: diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 8d130f95854..9cb907adeda 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -256,10 +256,11 @@ Minor features ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The :ref:`static files storage classes ` 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` - parameter. See :djadmin:`collectstatic` for example usage. + and :attr:`~django.core.files.storage.FileSystemStorage.directory_permissions_mode` + parameters. See :djadmin:`collectstatic` for example usage. :mod:`django.contrib.syndication` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/staticfiles_tests/tests.py b/tests/staticfiles_tests/tests.py index 39ce49817c3..e7c13833734 100644 --- a/tests/staticfiles_tests/tests.py +++ b/tests/staticfiles_tests/tests.py @@ -823,6 +823,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) @@ -839,21 +840,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) + @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 - self.assertEqual(file_mode, 0o655) + 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)