Skip to content
Merged
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
8 changes: 7 additions & 1 deletion django/core/management/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def handle(self, app_or_project, name, target=None, **options):
if self.verbosity >= 2:
self.stdout.write('Creating %s' % new_path)
try:
shutil.copymode(old_path, new_path)
self.apply_umask(old_path, new_path)
self.make_writeable(new_path)
except OSError:
self.stderr.write(
Expand Down Expand Up @@ -345,6 +345,12 @@ def is_url(self, template):
scheme = template.split(':', 1)[0].lower()
return scheme in self.url_schemes

def apply_umask(self, old_path, new_path):
current_umask = os.umask(0)
os.umask(current_umask)
current_mode = stat.S_IMODE(os.stat(old_path).st_mode)
os.chmod(new_path, current_mode & ~current_umask)

def make_writeable(self, filename):
"""
Make sure that the file is writeable.
Expand Down
33 changes: 30 additions & 3 deletions tests/admin_scripts/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import re
import shutil
import socket
import stat
import subprocess
import sys
import tempfile
Expand All @@ -32,6 +33,7 @@
LiveServerTestCase, SimpleTestCase, TestCase, override_settings,
)
from django.test.utils import captured_stderr, captured_stdout
from django.utils.version import PY39

custom_templates_dir = os.path.join(os.path.dirname(__file__), 'custom_templates')

Expand Down Expand Up @@ -95,7 +97,7 @@ def _ext_backend_paths(self):
paths.append(os.path.dirname(backend_dir))
return paths

def run_test(self, args, settings_file=None, apps=None):
def run_test(self, args, settings_file=None, apps=None, umask=None):
base_dir = os.path.dirname(self.test_dir)
# The base dir for Django's tests is one level up.
tests_dir = os.path.dirname(os.path.dirname(__file__))
Expand Down Expand Up @@ -124,11 +126,13 @@ def run_test(self, args, settings_file=None, apps=None):
cwd=self.test_dir,
env=test_environ,
text=True,
# subprocess.run()'s umask was added in Python 3.9.
**({'umask': umask} if umask and PY39 else {}),
)
return p.stdout, p.stderr

def run_django_admin(self, args, settings_file=None):
return self.run_test(['-m', 'django', *args], settings_file)
def run_django_admin(self, args, settings_file=None, umask=None):
return self.run_test(['-m', 'django', *args], settings_file, umask=umask)

def run_manage(self, args, settings_file=None, manage_py=None):
template_manage_py = (
Expand Down Expand Up @@ -2297,6 +2301,29 @@ def test_custom_project_template_exclude_directory(self):
not_excluded = os.path.join(testproject_dir, project_name)
self.assertIs(os.path.exists(not_excluded), True)

@unittest.skipIf(
sys.platform == 'win32',
'Windows only partially supports umasks and chmod.',
)
@unittest.skipUnless(PY39, "subprocess.run()'s umask was added in Python 3.9.")
def test_honor_umask(self):
_, err = self.run_django_admin(['startproject', 'testproject'], umask=0o077)
self.assertNoOutput(err)
testproject_dir = os.path.join(self.test_dir, 'testproject')
self.assertIs(os.path.isdir(testproject_dir), True)
tests = [
(['manage.py'], 0o700),
(['testproject'], 0o700),
(['testproject', 'settings.py'], 0o600),
]
for paths, expected_mode in tests:
file_path = os.path.join(testproject_dir, *paths)
with self.subTest(paths[-1]):
self.assertEqual(
stat.S_IMODE(os.stat(file_path).st_mode),
expected_mode,
)


class StartApp(AdminScriptTestCase):

Expand Down