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
12 changes: 1 addition & 11 deletions django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,7 @@
},
// Return the current time while accounting for the server timezone.
now: function () {
const serverOffset = document.body.dataset.adminUtcOffset;
if (serverOffset) {
const localNow = new Date();
const localOffset = localNow.getTimezoneOffset() * -60;
localNow.setTime(
localNow.getTime() + 1000 * (serverOffset - localOffset),
);
return localNow;
} else {
return new Date();
}
return CalendarNamespace.serverToday();
},
// Add a warning when the time zone in the browser and backend do not match.
addTimezoneWarning: function (inp) {
Expand Down
16 changes: 14 additions & 2 deletions django/contrib/admin/static/admin/js/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,21 @@ depends on core.js for utility functions like removeChildren or quickElement
true,
);
},
// Return the current date adjusted for the server timezone.
serverToday: function () {
const today = new Date();
const serverOffset = document.body.dataset.adminUtcOffset;
if (serverOffset) {
const localOffset = today.getTimezoneOffset() * -60;
today.setTime(
today.getTime() + 1000 * (serverOffset - localOffset),
);
}
return today;
},
draw: function (month, year, div_id, callback, selected) {
// month = 1-12, year = 1-9999
const today = new Date();
const today = CalendarNamespace.serverToday();
const todayDay = today.getDate();
const todayMonth = today.getMonth() + 1;
const todayYear = today.getFullYear();
Expand Down Expand Up @@ -267,7 +279,7 @@ depends on core.js for utility functions like removeChildren or quickElement
// calendar is clicked
this.div_id = div_id;
this.callback = callback;
this.today = new Date();
this.today = CalendarNamespace.serverToday();
this.currentMonth = this.today.getMonth() + 1;
this.currentYear = this.today.getFullYear();
if (typeof selected !== "undefined") {
Expand Down
11 changes: 4 additions & 7 deletions django/contrib/admin/templatetags/base.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from inspect import getfullargspec

from django.template.exceptions import TemplateSyntaxError
from django.template.library import InclusionNode, parse_bits
from django.utils.inspect import lazy_annotations
from django.utils.inspect import getfullargspec


class InclusionAdminNode(InclusionNode):
Expand All @@ -13,10 +11,9 @@ class InclusionAdminNode(InclusionNode):

def __init__(self, name, parser, token, func, template_name, takes_context=True):
self.template_name = template_name
with lazy_annotations():
params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = (
getfullargspec(func)
)
params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(
func
)
if takes_context:
if params and params[0] == "context":
del params[0]
Expand Down
5 changes: 2 additions & 3 deletions django/template/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
from django.utils.deprecation import RemovedInDjango70Warning, django_file_prefixes
from django.utils.formats import localize
from django.utils.html import conditional_escape
from django.utils.inspect import lazy_annotations, signature
from django.utils.inspect import getfullargspec, signature
from django.utils.regex_helper import _lazy_re_compile
from django.utils.safestring import SafeData, SafeString, mark_safe
from django.utils.text import get_text_list, smart_split, unescape_string_literal
Expand Down Expand Up @@ -843,8 +843,7 @@ def args_check(name, func, provided):
# Check to see if a decorator is providing the real function.
func = inspect.unwrap(func)

with lazy_annotations():
args, _, _, defaults, _, _, _ = inspect.getfullargspec(func)
args, _, _, defaults, _, _, _ = getfullargspec(func)
alen = len(args)
dlen = len(defaults or [])
# Not enough OR Too many
Expand Down
61 changes: 29 additions & 32 deletions django/template/library.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from collections.abc import Iterable
from functools import wraps
from importlib import import_module
from inspect import getfullargspec, unwrap
from inspect import unwrap

from django.utils.html import conditional_escape
from django.utils.inspect import lazy_annotations
from django.utils.inspect import getfullargspec

from .base import Node, Template, token_kwargs
from .exceptions import TemplateSyntaxError
Expand Down Expand Up @@ -111,16 +111,15 @@ def hello(*args, **kwargs):
"""

def dec(func):
with lazy_annotations():
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
function_name = name or func.__name__

if takes_context:
Expand Down Expand Up @@ -175,16 +174,15 @@ def hello(content):

def dec(func):
nonlocal end_name
with lazy_annotations():
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
function_name = name or func.__name__

if end_name is None:
Expand Down Expand Up @@ -264,16 +262,15 @@ def show_results(poll):
"""

def dec(func):
with lazy_annotations():
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
(
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
_,
) = getfullargspec(unwrap(func))
function_name = name or func.__name__

if takes_context:
Expand Down
35 changes: 26 additions & 9 deletions django/utils/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
import threading
from contextlib import contextmanager

from django.utils.version import PY314
from django.utils.version import PY314, PY315

if PY314:
import annotationlib

lock = threading.Lock()
safe_signature_from_callable = functools.partial(
inspect._signature_from_callable,
annotation_format=annotationlib.Format.FORWARDREF,
)
if not PY315:
lock = threading.Lock()
safe_signature_from_callable = functools.partial(
inspect._signature_from_callable,
annotation_format=annotationlib.Format.FORWARDREF,
)


@functools.lru_cache(maxsize=512)
Expand Down Expand Up @@ -106,12 +107,12 @@ def lazy_annotations():
compatibility with Python 3.14+ deferred evaluation, patch the module-level
helper to provide the annotation_format that we are using elsewhere.

This private helper could be removed when there is an upstream solution for
https://github.com/python/cpython/issues/141560.
This private helper should only be used for Python 3.14, as
https://github.com/python/cpython/issues/141560 was fixed in 3.15.

This context manager is not reentrant.
"""
if not PY314:
if PY315 or not PY314:
yield
return
with lock:
Expand All @@ -123,6 +124,22 @@ def lazy_annotations():
inspect._signature_from_callable = original_helper


def getfullargspec(*args, annotation_format=None, **kwargs):
"""
A wrapper around inspect.getfullargspec that leaves deferred annotations
unevaluated on Python 3.14+, since they are not used in our case.
"""
if PY315:
return inspect.getfullargspec(
*args, **kwargs, annotation_format=annotationlib.Format.FORWARDREF
)
if PY314:
with lazy_annotations():
return inspect.getfullargspec(*args, **kwargs)
else:
return inspect.getfullargspec(*args, **kwargs)


def signature(obj):
"""
A wrapper around inspect.signature that leaves deferred annotations
Expand Down
31 changes: 31 additions & 0 deletions js_tests/admin/DateTimeShortcuts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,34 @@ QUnit.test("today link has aria-label with current date", function (assert) {
const expectedAriaLabel = `Today (${formattedDate})`;
assert.equal(todayLink.attr("aria-label"), expectedAriaLabel);
});

QUnit.test("calendar today highlight with server offset", function (assert) {
const $ = django.jQuery;
const calDiv = $('<div id="test-calendar"></div>');
$("#qunit-fixture").append(calDiv);

// Simulate a server timezone that is 24 hours ahead of the browser.
const localOffset = new Date().getTimezoneOffset() * -60;
const serverOffset = localOffset + 86400;
$("body").attr("data-admin-utc-offset", serverOffset);

const expectedDate = new Date();
expectedDate.setTime(
expectedDate.getTime() + 1000 * (serverOffset - localOffset),
);

CalendarNamespace.draw(
expectedDate.getMonth() + 1,
expectedDate.getFullYear(),
"test-calendar",
function () {},
);

const todayCells = calDiv.find("td.today");
assert.equal(todayCells.length, 1, "Exactly one cell marked as today");
assert.equal(
todayCells.find("a").text(),
String(expectedDate.getDate()),
"Today cell matches server-adjusted date",
);
});
2 changes: 1 addition & 1 deletion tests/migrations/test_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3097,7 +3097,7 @@ def test_alter_field_reloads_state_fk_with_to_field_related_name_target_type_cha
def test_alter_field_reloads_state_on_transitive_attname_to_field_type_change(
self,
):
app_label = "test_alflrstfattnamettc"
app_label = "test_alflrstatftc"
project_state = self.apply_operations(
app_label,
ProjectState(),
Expand Down
Loading