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
4 changes: 2 additions & 2 deletions django/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.utils.version import get_version
from django.utils.version import VersionTuple, get_version

VERSION = (6, 2, 0, "alpha", 0)
VERSION = VersionTuple(6, 2, 0, "alpha", 0)

__version__ = get_version(VERSION)

Expand Down
3 changes: 2 additions & 1 deletion django/core/mail/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.core.mail import InvalidMailer
from django.utils.deprecation import RemovedInDjango2028Warning, warn_about_external_use
from django.utils.module_loading import qualname

# RemovedInDjango2028Warning.
_NOT_PROVIDED = object()
Expand Down Expand Up @@ -57,7 +58,7 @@ def __init__(
# backend or a super.__init__() call from a custom subclass).
# Use something more precise than self.__class__.__name__,
# which is often just "EmailBackend".
class_name = f"{type(self).__module__}.{type(self).__qualname__}"
class_name = qualname(type(self))
warn_about_external_use(
f"{class_name}.__init__() does not support {kwarg_names}. "
"In Django 2028, BaseEmailBackend will raise a TypeError "
Expand Down
20 changes: 10 additions & 10 deletions django/db/migrations/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
from django.db.models.deletion import DatabaseOnDelete
from django.utils.functional import LazyObject, Promise
from django.utils.module_loading import qualname
from django.utils.version import get_docs_version

FUNCTION_TYPES = (types.FunctionType, types.BuiltinFunctionType, types.MethodType)
Expand Down Expand Up @@ -202,14 +203,15 @@ def serialize(self):

module_name = self.value.__module__

if "<" not in self.value.__qualname__: # Qualname can include <locals>
return "%s.%s" % (module_name, self.value.__qualname__), {
"import %s" % self.value.__module__
}
try:
name = qualname(self.value)
except ValueError as e:
raise ValueError(
"Could not find function %s in %s.\n"
% (self.value.__name__, module_name)
) from e

raise ValueError(
"Could not find function %s in %s.\n" % (self.value.__name__, module_name)
)
return name, {"import %s" % module_name}


class FunctoolsPartialSerializer(BaseSerializer):
Expand Down Expand Up @@ -342,9 +344,7 @@ def serialize(self):
if module == builtins.__name__:
return self.value.__name__, set()
else:
return "%s.%s" % (module, self.value.__qualname__), {
"import %s" % module
}
return qualname(self.value), {"import %s" % module}


class UUIDSerializer(BaseSerializer):
Expand Down
22 changes: 22 additions & 0 deletions django/db/models/functions/datetime.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

from django.conf import settings
from django.db.models.expressions import Func
from django.db.models.fields import (
Expand All @@ -17,6 +19,7 @@
YearLte,
)
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango2029Warning, django_file_prefixes


class TimezoneMixin:
Expand All @@ -35,6 +38,25 @@ def get_tzname(self):
tzname = timezone._get_timezone_name(self.tzinfo)
return tzname

def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.tzinfo is None and settings.USE_TZ:
warnings.warn(
f"The {self.__class__.__name__}() database function's tzinfo "
"argument is not provided, which can lead to inconsistent "
"behavior if TIME_ZONE changes. In Django 2029, the current "
"time zone will be captured in migrations when tzinfo is "
"omitted. Pass an explicit tzinfo to suppress this warning.",
category=RemovedInDjango2029Warning,
skip_file_prefixes=django_file_prefixes(),
)
# RemovedInDjango2029Warning: when the deprecation ends, replace
# the warning with the following, so that the current timezone is
# captured in migrations instead of being silently resolved at run
# time (get_tzname() is left unchanged for query-time usage):
# kwargs["tzinfo"] = timezone.get_current_timezone()
return path, args, kwargs


class Extract(TimezoneMixin, Transform):
lookup_name = None
Expand Down
5 changes: 2 additions & 3 deletions django/tasks/backends/immediate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.json import normalize_json
from django.utils.module_loading import qualname

from .base import BaseTaskBackend

Expand Down Expand Up @@ -59,9 +60,7 @@ def _execute_task(self, task_result):
exception_type = type(e)
task_result.errors.append(
TaskError(
exception_class_path=(
f"{exception_type.__module__}.{exception_type.__qualname__}"
),
exception_class_path=qualname(exception_type),
traceback="".join(format_exception(e)),
)
)
Expand Down
4 changes: 2 additions & 2 deletions django/tasks/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from django.db.models.enums import TextChoices
from django.utils.json import normalize_json
from django.utils.module_loading import import_string
from django.utils.module_loading import import_string, qualname
from django.utils.translation import pgettext_lazy

from .exceptions import TaskResultMismatch
Expand Down Expand Up @@ -145,7 +145,7 @@ def get_backend(self):

@property
def module_path(self):
return f"{self.func.__module__}.{self.func.__qualname__}"
return qualname(self.func)


def task(
Expand Down
6 changes: 4 additions & 2 deletions django/urls/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from django.utils.datastructures import MultiValueDict
from django.utils.functional import cached_property
from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
from django.utils.module_loading import qualname
from django.utils.regex_helper import _lazy_re_compile, normalize
from django.utils.translation import get_language

Expand Down Expand Up @@ -495,9 +496,10 @@ def lookup_str(self):
callback = callback.func
if hasattr(callback, "view_class"):
callback = callback.view_class
elif not hasattr(callback, "__name__"):
try:
return qualname(callback)
except ValueError:
return callback.__module__ + "." + callback.__class__.__name__
return callback.__module__ + "." + callback.__qualname__


class URLResolver:
Expand Down
10 changes: 8 additions & 2 deletions django/utils/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from collections import Counter
from inspect import iscoroutinefunction

from django.middleware import MiddlewareMixin as _MiddlewareMixin
from django.utils.inspect import signature
from django.utils.warnings import django_file_prefixes

Expand All @@ -23,14 +22,21 @@ class RemovedInDjango2029Warning(PendingDeprecationWarning):


def __getattr__(name):
# RemovedInDjango2029Warning: remove the whole if-block.
if name == "MiddlewareMixin":
warnings.warn(
"Importing MiddlewareMixin from django.utils.deprecation is deprecated. "
"Import from django.middleware.MiddlewareMixin instead.",
RemovedInDjango2029Warning,
stacklevel=2,
)
return _MiddlewareMixin
# Imported here, not at module level, so that merely importing this
# module doesn't require django.middleware's own dependencies. That
# matters this early, e.g. while a build backend is reading
# django.__version__ before Django's own dependencies are installed.
from django.middleware import MiddlewareMixin

return MiddlewareMixin
if name == "RemovedInDjango70Warning":
warnings.warn(
"RemovedInDjango2028Warning should be used instead of "
Expand Down
20 changes: 20 additions & 0 deletions django/utils/module_loading.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import os
import sys
import types
from importlib import import_module
from importlib.util import find_spec as importlib_find

Expand Down Expand Up @@ -118,3 +119,22 @@ def module_dir(module):
if filename is not None:
return os.path.dirname(filename)
raise ValueError("Cannot determine directory containing %s" % module)


def qualname(value):
"""
Return the fully qualified name (dotted module path) for a class, function,
type, or module (e.g. 'django.db.models.Model').
"""
if isinstance(value, types.ModuleType):
return value.__name__
if not hasattr(value, "__module__") or value.__module__ is None:
msg = f"Cannot determine module path for {value!r}: no __module__ attribute."
raise ValueError(msg)
if not hasattr(value, "__qualname__"):
msg = f"Cannot determine module path for {value!r}: no __qualname__ attribute."
raise ValueError(msg)
if "<" in value.__qualname__:
msg = f"Cannot determine module path for {value!r}: local or anonymous object."
raise ValueError(msg)
return f"{value.__module__}.{value.__qualname__}"
Loading
Loading