summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Schwartz <wkschwartz@gmail.com>2021-01-04 12:04:28 -0600
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-04-01 14:33:30 +0200
commit9ee693bd6cf4074f04ec51c6f3cfe87cad392f12 (patch)
tree893e5fba17d522305a35e7f2380164cd57563472
parentcfe47b7686df0c4c87270a83d6d7f933323ed7e6 (diff)
Fixed #32316 -- Deferred accessing __file__.
Deferred accessing the module-global variable __file__ because the Python import API does not guarantee it always exists—in particular, it does not exist in certain "frozen" environments. The following changes advanced this goal. Thanks to Carlton Gibson, Tom Forbes, Mariusz Felisiak, and Shreyas Ravi for review and feedback.
-rw-r--r--django/contrib/auth/password_validation.py9
-rw-r--r--django/forms/renderers.py4
-rw-r--r--django/utils/version.py4
-rw-r--r--django/views/debug.py18
-rw-r--r--tests/version/tests.py22
5 files changed, 46 insertions, 11 deletions
diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py
index 845f4d86d5..7c9e75e62e 100644
--- a/django/contrib/auth/password_validation.py
+++ b/django/contrib/auth/password_validation.py
@@ -8,7 +8,7 @@ from django.conf import settings
from django.core.exceptions import (
FieldDoesNotExist, ImproperlyConfigured, ValidationError,
)
-from django.utils.functional import lazy
+from django.utils.functional import cached_property, lazy
from django.utils.html import format_html, format_html_join
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _, ngettext
@@ -167,9 +167,14 @@ class CommonPasswordValidator:
https://gist.github.com/roycewilliams/281ce539915a947a23db17137d91aeb7
The password list must be lowercased to match the comparison in validate().
"""
- DEFAULT_PASSWORD_LIST_PATH = Path(__file__).resolve().parent / 'common-passwords.txt.gz'
+
+ @cached_property
+ def DEFAULT_PASSWORD_LIST_PATH(self):
+ return Path(__file__).resolve().parent / 'common-passwords.txt.gz'
def __init__(self, password_list_path=DEFAULT_PASSWORD_LIST_PATH):
+ if password_list_path is CommonPasswordValidator.DEFAULT_PASSWORD_LIST_PATH:
+ password_list_path = self.DEFAULT_PASSWORD_LIST_PATH
try:
with gzip.open(password_list_path, 'rt', encoding='utf-8') as f:
self.passwords = {x.strip() for x in f}
diff --git a/django/forms/renderers.py b/django/forms/renderers.py
index dcf3d92302..ce3b7097e6 100644
--- a/django/forms/renderers.py
+++ b/django/forms/renderers.py
@@ -7,8 +7,6 @@ from django.template.loader import get_template
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
-ROOT = Path(__file__).parent
-
@functools.lru_cache()
def get_default_renderer():
@@ -33,7 +31,7 @@ class EngineMixin:
def engine(self):
return self.backend({
'APP_DIRS': True,
- 'DIRS': [ROOT / self.backend.app_dirname],
+ 'DIRS': [Path(__file__).parent / self.backend.app_dirname],
'NAME': 'djangoforms',
'OPTIONS': {},
})
diff --git a/django/utils/version.py b/django/utils/version.py
index 50be432942..d8437ad07e 100644
--- a/django/utils/version.py
+++ b/django/utils/version.py
@@ -77,6 +77,10 @@ def get_git_changeset():
This value isn't guaranteed to be unique, but collisions are very unlikely,
so it's sufficient for generating the development version numbers.
"""
+ # Repository may not be found if __file__ is undefined, e.g. in a frozen
+ # module.
+ if '__file__' not in globals():
+ return None
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
git_log = subprocess.run(
'git log --pretty=format:%ct --quiet -1 HEAD',
diff --git a/django/views/debug.py b/django/views/debug.py
index 1b8637874a..67bb5de20b 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -26,7 +26,15 @@ DEBUG_ENGINE = Engine(
libraries={'i18n': 'django.templatetags.i18n'},
)
-CURRENT_DIR = Path(__file__).parent
+
+def builtin_template_path(name):
+ """
+ Return a path to a builtin template.
+
+ Avoid calling this function at the module level or in a class-definition
+ because __file__ may not exist, e.g. in frozen environments.
+ """
+ return Path(__file__).parent / 'templates' / name
class ExceptionCycleWarning(UserWarning):
@@ -248,11 +256,11 @@ class ExceptionReporter:
@property
def html_template_path(self):
- return CURRENT_DIR / 'templates' / 'technical_500.html'
+ return builtin_template_path('technical_500.html')
@property
def text_template_path(self):
- return CURRENT_DIR / 'templates' / 'technical_500.txt'
+ return builtin_template_path('technical_500.txt')
def __init__(self, request, exc_type, exc_value, tb, is_email=False):
self.request = request
@@ -534,7 +542,7 @@ def technical_404_response(request, exception):
module = obj.__module__
caller = '%s.%s' % (module, caller)
- with Path(CURRENT_DIR, 'templates', 'technical_404.html').open(encoding='utf-8') as fh:
+ with builtin_template_path('technical_404.html').open(encoding='utf-8') as fh:
t = DEBUG_ENGINE.from_string(fh.read())
reporter_filter = get_default_exception_reporter_filter()
c = Context({
@@ -553,7 +561,7 @@ def technical_404_response(request, exception):
def default_urlconf(request):
"""Create an empty URLconf 404 error response."""
- with Path(CURRENT_DIR, 'templates', 'default_urlconf.html').open(encoding='utf-8') as fh:
+ with builtin_template_path('default_urlconf.html').open(encoding='utf-8') as fh:
t = DEBUG_ENGINE.from_string(fh.read())
c = Context({
'version': get_docs_version(),
diff --git a/tests/version/tests.py b/tests/version/tests.py
index bfa4af0496..7cf4dce0a6 100644
--- a/tests/version/tests.py
+++ b/tests/version/tests.py
@@ -1,17 +1,37 @@
+from unittest import skipUnless
+
+import django.utils.version
from django import get_version
from django.test import SimpleTestCase
-from django.utils.version import get_complete_version, get_version_tuple
+from django.utils.version import (
+ get_complete_version, get_git_changeset, get_version_tuple,
+)
class VersionTests(SimpleTestCase):
def test_development(self):
+ get_git_changeset.cache_clear()
ver_tuple = (1, 4, 0, 'alpha', 0)
# This will return a different result when it's run within or outside
# of a git clone: 1.4.devYYYYMMDDHHMMSS or 1.4.
ver_string = get_version(ver_tuple)
self.assertRegex(ver_string, r'1\.4(\.dev[0-9]+)?')
+ @skipUnless(
+ hasattr(django.utils.version, '__file__'),
+ 'test_development() checks the same when __file__ is already missing, '
+ 'e.g. in a frozen environments'
+ )
+ def test_development_no_file(self):
+ get_git_changeset.cache_clear()
+ version_file = django.utils.version.__file__
+ try:
+ del django.utils.version.__file__
+ self.test_development()
+ finally:
+ django.utils.version.__file__ = version_file
+
def test_releases(self):
tuples_to_strings = (
((1, 4, 0, 'alpha', 1), '1.4a1'),