Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into github-actions-test…
Browse files Browse the repository at this point in the history
…-callstring

* upstream/main:
  Refs django#10941 -- Added tests in querystring template tag.
  Refs django#10941 -- Added helper and refactored tests for querystring template tag.
  Fixed #35530 -- Deprecated request.user fallback in auth.login and auth.alogin.
  Refs #35530 -- Added basic test cases for auth.login.
  Fixed #35038 -- Created AlterConstraint operation.
  Refs #35038 -- Added test for drop and recreation of a constraint.
  Fixed #35897 -- Removed unnecessary escaping in template's get_exception_info().
  Refs #21286 -- Fixed serializer test with primary key TextField.
  Added parallelism support via --jobs to docs build Makefile rule.
  Made reverse() docs more self-contained.
  Upgraded to Python 3.12, Ubuntu 24.04, and enabled fail_on_warning for docs builds.
  Removed trailing whitespace in docs.
  Added stub release notes and release date for 5.1.4, 5.0.10, and 4.2.17.
  Fixed #35921 -- Fixed failure when running tests in parallel on postgres.
  Fixed #35939 -- Linked documentation of Permission.content_type to the ContentType model.
  Refs #33735 -- Captured stderr during ASGITest.test_file_response.
  • Loading branch information
ArneTR committed Nov 29, 2024
2 parents 91c0f49 + 15ca754 commit 6bc47f4
Show file tree
Hide file tree
Showing 36 changed files with 687 additions and 77 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ permissions:

jobs:
docs:
# OS must be the same as on djangoproject.com.
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
name: docs
steps:
- name: Eco-CI Init
Expand Down
5 changes: 3 additions & 2 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
version: 2

build:
os: ubuntu-20.04
os: ubuntu-24.04
tools:
python: "3.8"
python: "3.12"

sphinx:
configuration: docs/conf.py
fail_on_warning: true

python:
install:
Expand Down
21 changes: 21 additions & 0 deletions django/contrib/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import inspect
import re
import warnings

from django.apps import apps as django_apps
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.middleware.csrf import rotate_token
from django.utils.crypto import constant_time_compare
from django.utils.deprecation import RemovedInDjango61Warning
from django.utils.module_loading import import_string
from django.views.decorators.debug import sensitive_variables

Expand Down Expand Up @@ -154,9 +156,19 @@ def login(request, user, backend=None):
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
# RemovedInDjango61Warning: When the deprecation ends, replace with:
# session_auth_hash = user.get_session_auth_hash()
session_auth_hash = ""
# RemovedInDjango61Warning.
if user is None:
user = request.user
warnings.warn(
"Fallback to request.user when user is None will be removed.",
RemovedInDjango61Warning,
stacklevel=2,
)

# RemovedInDjango61Warning.
if hasattr(user, "get_session_auth_hash"):
session_auth_hash = user.get_session_auth_hash()

Expand Down Expand Up @@ -187,9 +199,18 @@ def login(request, user, backend=None):

async def alogin(request, user, backend=None):
"""See login()."""
# RemovedInDjango61Warning: When the deprecation ends, replace with:
# session_auth_hash = user.get_session_auth_hash()
session_auth_hash = ""
# RemovedInDjango61Warning.
if user is None:
warnings.warn(
"Fallback to request.user when user is None will be removed.",
RemovedInDjango61Warning,
stacklevel=2,
)
user = await request.auser()
# RemovedInDjango61Warning.
if hasattr(user, "get_session_auth_hash"):
session_auth_hash = user.get_session_auth_hash()

Expand Down
62 changes: 60 additions & 2 deletions django/db/migrations/autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ def _detect_changes(self, convert_apps=None, graph=None):
self.generate_altered_unique_together()
self.generate_added_indexes()
self.generate_added_constraints()
self.generate_altered_constraints()
self.generate_altered_db_table()

self._sort_migrations()
Expand Down Expand Up @@ -1450,6 +1451,19 @@ def generate_renamed_indexes(self):
),
)

def _constraint_should_be_dropped_and_recreated(
self, old_constraint, new_constraint
):
old_path, old_args, old_kwargs = old_constraint.deconstruct()
new_path, new_args, new_kwargs = new_constraint.deconstruct()

for attr in old_constraint.non_db_attrs:
old_kwargs.pop(attr, None)
for attr in new_constraint.non_db_attrs:
new_kwargs.pop(attr, None)

return (old_path, old_args, old_kwargs) != (new_path, new_args, new_kwargs)

def create_altered_constraints(self):
option_name = operations.AddConstraint.option_name
for app_label, model_name in sorted(self.kept_model_keys):
Expand All @@ -1461,14 +1475,41 @@ def create_altered_constraints(self):

old_constraints = old_model_state.options[option_name]
new_constraints = new_model_state.options[option_name]
add_constraints = [c for c in new_constraints if c not in old_constraints]
rem_constraints = [c for c in old_constraints if c not in new_constraints]

alt_constraints = []
alt_constraints_name = []

for old_c in old_constraints:
for new_c in new_constraints:
old_c_dec = old_c.deconstruct()
new_c_dec = new_c.deconstruct()
if (
old_c_dec != new_c_dec
and old_c.name == new_c.name
and not self._constraint_should_be_dropped_and_recreated(
old_c, new_c
)
):
alt_constraints.append(new_c)
alt_constraints_name.append(new_c.name)

add_constraints = [
c
for c in new_constraints
if c not in old_constraints and c.name not in alt_constraints_name
]
rem_constraints = [
c
for c in old_constraints
if c not in new_constraints and c.name not in alt_constraints_name
]

self.altered_constraints.update(
{
(app_label, model_name): {
"added_constraints": add_constraints,
"removed_constraints": rem_constraints,
"altered_constraints": alt_constraints,
}
}
)
Expand Down Expand Up @@ -1503,6 +1544,23 @@ def generate_removed_constraints(self):
),
)

def generate_altered_constraints(self):
for (
app_label,
model_name,
), alt_constraints in self.altered_constraints.items():
dependencies = self._get_dependencies_for_model(app_label, model_name)
for constraint in alt_constraints["altered_constraints"]:
self.add_operation(
app_label,
operations.AlterConstraint(
model_name=model_name,
name=constraint.name,
constraint=constraint,
),
dependencies=dependencies,
)

@staticmethod
def _get_dependencies_for_foreign_key(app_label, model_name, field, project_state):
remote_field_model = None
Expand Down
2 changes: 2 additions & 0 deletions django/db/migrations/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .models import (
AddConstraint,
AddIndex,
AlterConstraint,
AlterIndexTogether,
AlterModelManagers,
AlterModelOptions,
Expand Down Expand Up @@ -36,6 +37,7 @@
"RenameField",
"AddConstraint",
"RemoveConstraint",
"AlterConstraint",
"SeparateDatabaseAndState",
"RunSQL",
"RunPython",
Expand Down
54 changes: 54 additions & 0 deletions django/db/migrations/operations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,12 @@ def reduce(self, operation, app_label):
and self.constraint.name == operation.name
):
return []
if (
isinstance(operation, AlterConstraint)
and self.model_name_lower == operation.model_name_lower
and self.constraint.name == operation.name
):
return [AddConstraint(self.model_name, operation.constraint)]
return super().reduce(operation, app_label)


Expand Down Expand Up @@ -1274,3 +1280,51 @@ def describe(self):
@property
def migration_name_fragment(self):
return "remove_%s_%s" % (self.model_name_lower, self.name.lower())


class AlterConstraint(IndexOperation):
category = OperationCategory.ALTERATION
option_name = "constraints"

def __init__(self, model_name, name, constraint):
self.model_name = model_name
self.name = name
self.constraint = constraint

def state_forwards(self, app_label, state):
state.alter_constraint(
app_label, self.model_name_lower, self.name, self.constraint
)

def database_forwards(self, app_label, schema_editor, from_state, to_state):
pass

def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass

def deconstruct(self):
return (
self.__class__.__name__,
[],
{
"model_name": self.model_name,
"name": self.name,
"constraint": self.constraint,
},
)

def describe(self):
return f"Alter constraint {self.name} on {self.model_name}"

@property
def migration_name_fragment(self):
return "alter_%s_%s" % (self.model_name_lower, self.constraint.name.lower())

def reduce(self, operation, app_label):
if (
isinstance(operation, (AlterConstraint, RemoveConstraint))
and self.model_name_lower == operation.model_name_lower
and self.name == operation.name
):
return [operation]
return super().reduce(operation, app_label)
13 changes: 13 additions & 0 deletions django/db/migrations/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ def _remove_option(self, app_label, model_name, option_name, obj_name):
model_state.options[option_name] = [obj for obj in objs if obj.name != obj_name]
self.reload_model(app_label, model_name, delay=True)

def _alter_option(self, app_label, model_name, option_name, obj_name, alt_obj):
model_state = self.models[app_label, model_name]
objs = model_state.options[option_name]
model_state.options[option_name] = [
obj if obj.name != obj_name else alt_obj for obj in objs
]
self.reload_model(app_label, model_name, delay=True)

def add_index(self, app_label, model_name, index):
self._append_option(app_label, model_name, "indexes", index)

Expand All @@ -237,6 +245,11 @@ def add_constraint(self, app_label, model_name, constraint):
def remove_constraint(self, app_label, model_name, constraint_name):
self._remove_option(app_label, model_name, "constraints", constraint_name)

def alter_constraint(self, app_label, model_name, constraint_name, constraint):
self._alter_option(
app_label, model_name, "constraints", constraint_name, constraint
)

def add_field(self, app_label, model_name, name, field, preserve_default):
# If preserve default is off, don't use the default for future state.
if not preserve_default:
Expand Down
2 changes: 2 additions & 0 deletions django/db/models/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class BaseConstraint:
violation_error_code = None
violation_error_message = None

non_db_attrs = ("violation_error_code", "violation_error_message")

# RemovedInDjango60Warning: When the deprecation ends, replace with:
# def __init__(
# self, *, name, violation_error_code=None, violation_error_message=None
Expand Down
10 changes: 5 additions & 5 deletions django/template/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@

from django.template.context import BaseContext
from django.utils.formats import localize
from django.utils.html import conditional_escape, escape
from django.utils.html import conditional_escape
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 @@ -247,10 +247,10 @@ def get_exception_info(self, exception, token):
for num, next in enumerate(linebreak_iter(self.source)):
if start >= upto and end <= next:
line = num
before = escape(self.source[upto:start])
during = escape(self.source[start:end])
after = escape(self.source[end:next])
source_lines.append((num, escape(self.source[upto:next])))
before = self.source[upto:start]
during = self.source[start:end]
after = self.source[end:next]
source_lines.append((num, self.source[upto:next]))
upto = next
total = len(source_lines)

Expand Down
3 changes: 2 additions & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ SPHINXBUILD ?= sphinx-build
PAPER ?=
BUILDDIR ?= _build
LANGUAGE ?=
JOBS ?= auto

# Set the default language.
ifndef LANGUAGE
Expand All @@ -21,7 +22,7 @@ LANGUAGEOPT = $(firstword $(subst _, ,$(LANGUAGE)))
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees -D language=$(LANGUAGEOPT) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees -D language=$(LANGUAGEOPT) --jobs $(JOBS) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

Expand Down
2 changes: 1 addition & 1 deletion docs/internals/contributing/writing-documentation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Output is printed to the terminal, but can also be found in
The execution of the command requires an internet connection and takes
several minutes to complete, because the command tests all the links
that are found in the documentation.

Entries that have a status of "working" are fine, those that are "unchecked" or
"ignored" have been skipped because they either cannot be checked or have
matched ignore rules in the configuration.
Expand Down
4 changes: 4 additions & 0 deletions docs/internals/deprecation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ details on these changes.
* The ``all`` keyword argument of ``django.contrib.staticfiles.finders.find()``
will be removed.

* The fallback to ``request.user`` when ``user`` is ``None`` in
``django.contrib.auth.login()`` and ``django.contrib.auth.alogin()`` will be
removed.

.. _deprecation-removed-in-6.0:

6.0
Expand Down
6 changes: 3 additions & 3 deletions docs/ref/contrib/auth.txt
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,8 @@ fields:

.. attribute:: content_type

Required. A reference to the ``django_content_type`` database table,
which contains a record for each installed model.
Required. A foreign key to the
:class:`~django.contrib.contenttypes.models.ContentType` model.

.. attribute:: codename

Expand Down Expand Up @@ -679,7 +679,7 @@ The following backends are available in :mod:`django.contrib.auth.backends`:
user permissions and group permissions. Returns an empty set if
:attr:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or
:attr:`~django.contrib.auth.models.CustomUser.is_active` is ``False``.

.. versionchanged:: 5.2

``aget_all_permissions()`` function was added.
Expand Down
2 changes: 1 addition & 1 deletion docs/ref/forms/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1416,7 +1416,7 @@ Methods of ``BoundField``

.. method:: BoundField.render(template_name=None, context=None, renderer=None)

The render method is called by ``as_field_group``. All arguments are
The render method is called by ``as_field_group``. All arguments are
optional and default to:

* ``template_name``: :attr:`.BoundField.template_name`
Expand Down
2 changes: 1 addition & 1 deletion docs/ref/forms/fields.txt
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ be ignored in favor of the value from the form's initial data.
.. attribute:: Field.template_name

The ``template_name`` argument allows a custom template to be used when the
field is rendered with :meth:`~django.forms.BoundField.as_field_group`. By
field is rendered with :meth:`~django.forms.BoundField.as_field_group`. By
default this value is set to ``"django/forms/field.html"``. Can be changed per
field by overriding this attribute or more generally by overriding the default
template, see also :ref:`overriding-built-in-field-templates`.
Expand Down
Loading

0 comments on commit 6bc47f4

Please sign in to comment.