Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-mm committed Jan 8, 2025
2 parents 25e9042 + a9c79b4 commit a386b28
Show file tree
Hide file tree
Showing 26 changed files with 239 additions and 77 deletions.
10 changes: 8 additions & 2 deletions django/contrib/admin/templatetags/admin_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ def link_in_col(is_first, field_name, cl):
for field_index, field_name in enumerate(cl.list_display):
empty_value_display = cl.model_admin.get_empty_value_display()
row_classes = ["field-%s" % _coerce_field_name(field_name, field_index)]
link_to_changelist = link_in_col(first, field_name, cl)
try:
f, attr, value = lookup_field(field_name, result, cl.model_admin)
except ObjectDoesNotExist:
Expand All @@ -240,14 +241,19 @@ def link_in_col(is_first, field_name, cl):
else:
result_repr = field_val
else:
result_repr = display_for_field(value, f, empty_value_display)
result_repr = display_for_field(
value,
f,
empty_value_display,
avoid_link=link_to_changelist,
)
if isinstance(
f, (models.DateField, models.TimeField, models.ForeignKey)
):
row_classes.append("nowrap")
row_class = mark_safe(' class="%s"' % " ".join(row_classes))
# If list_display_links not defined, add the link tag to the first field
if link_in_col(first, field_name, cl):
if link_to_changelist:
table_tag = "th" if first else "td"
first = False

Expand Down
4 changes: 2 additions & 2 deletions django/contrib/admin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ def help_text_for_field(name, model):
return help_text


def display_for_field(value, field, empty_value_display):
def display_for_field(value, field, empty_value_display, avoid_link=False):
from django.contrib.admin.templatetags.admin_list import _boolean_icon

if getattr(field, "flatchoices", None):
Expand All @@ -452,7 +452,7 @@ def display_for_field(value, field, empty_value_display):
return formats.number_format(value, field.decimal_places)
elif isinstance(field, (models.IntegerField, models.FloatField)):
return formats.number_format(value)
elif isinstance(field, models.FileField) and value:
elif isinstance(field, models.FileField) and value and not avoid_link:
return format_html('<a href="{}">{}</a>', value.url, value)
elif isinstance(field, models.JSONField) and value:
try:
Expand Down
2 changes: 1 addition & 1 deletion django/core/serializers/xml_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def start_object(self, obj):
if not self.use_natural_primary_keys or not hasattr(obj, "natural_key"):
obj_pk = obj.pk
if obj_pk is not None:
attrs["pk"] = str(obj_pk)
attrs["pk"] = obj._meta.pk.value_to_string(obj)

self.xml.startElement("object", attrs)

Expand Down
20 changes: 7 additions & 13 deletions django/core/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from django.core.exceptions import ValidationError
from django.utils.deconstruct import deconstructible
from django.utils.encoding import punycode
from django.utils.ipv6 import is_valid_ipv6_address
from django.utils.regex_helper import _lazy_re_compile
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -76,14 +75,14 @@ class DomainNameValidator(RegexValidator):
# Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1.
domain_re = r"(?:\.(?!-)[a-z" + ul + r"0-9-]{1,63}(?<!-))*"
# Top-level domain.
tld_re = (
tld_no_fqdn_re = (
r"\." # dot
r"(?!-)" # can't start with a dash
r"(?:[a-z" + ul + "-]{2,63}" # domain label
r"|xn--[a-z0-9]{1,59})" # or punycode label
r"(?<!-)" # can't end with a dash
r"\.?" # may have a trailing dot
)
tld_re = tld_no_fqdn_re + r"\.?"
ascii_only_hostname_re = r"[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
ascii_only_domain_re = r"(?:\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-))*"
ascii_only_tld_re = (
Expand Down Expand Up @@ -210,6 +209,10 @@ def validate_integer(value):
class EmailValidator:
message = _("Enter a valid email address.")
code = "invalid"
hostname_re = DomainNameValidator.hostname_re
domain_re = DomainNameValidator.domain_re
tld_no_fqdn_re = DomainNameValidator.tld_no_fqdn_re

user_regex = _lazy_re_compile(
# dot-atom
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z"
Expand All @@ -219,8 +222,7 @@ class EmailValidator:
re.IGNORECASE,
)
domain_regex = _lazy_re_compile(
# max length for domain name labels is 63 characters per RFC 1034
r"((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z",
r"^" + hostname_re + domain_re + tld_no_fqdn_re + r"\Z",
re.IGNORECASE,
)
literal_regex = _lazy_re_compile(
Expand Down Expand Up @@ -252,14 +254,6 @@ def __call__(self, value):
if domain_part not in self.domain_allowlist and not self.validate_domain_part(
domain_part
):
# Try for possible IDN domain-part
try:
domain_part = punycode(domain_part)
except UnicodeError:
pass
else:
if self.validate_domain_part(domain_part):
return
raise ValidationError(self.message, code=self.code, params={"value": value})

def validate_domain_part(self, domain_part):
Expand Down
10 changes: 10 additions & 0 deletions django/db/models/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1861,6 +1861,16 @@ def get_source_expressions(self):
return [self.expression]

def as_sql(self, compiler, connection, template=None, **extra_context):
if isinstance(self.expression, ColPairs):
sql_parts = []
params = []
for col in self.expression.get_cols():
copy = self.copy()
copy.set_source_expressions([col])
sql, col_params = compiler.compile(copy)
sql_parts.append(sql)
params.extend(col_params)
return ", ".join(sql_parts), params
template = template or self.template
if connection.features.supports_order_by_nulls_modifier:
if self.nulls_last:
Expand Down
25 changes: 25 additions & 0 deletions django/db/models/fields/composite.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from django.core import checks
from django.db.models import NOT_PROVIDED, Field
from django.db.models.expressions import ColPairs
Expand All @@ -13,6 +15,11 @@
from django.utils.functional import cached_property


class AttributeSetter:
def __init__(self, name, value):
setattr(self, name, value)


class CompositeAttribute:
def __init__(self, field):
self.field = field
Expand Down Expand Up @@ -130,6 +137,24 @@ def _check_field_name(self):
)
]

def value_to_string(self, obj):
values = []
vals = self.value_from_object(obj)
for field, value in zip(self.fields, vals):
obj = AttributeSetter(field.attname, value)
values.append(field.value_to_string(obj))
return json.dumps(values, ensure_ascii=False)

def to_python(self, value):
if isinstance(value, str):
# Assume we're deserializing.
vals = json.loads(value)
value = [
field.to_python(val)
for field, val in zip(self.fields, vals, strict=True)
]
return value


CompositePrimaryKey.register_lookup(TupleExact)
CompositePrimaryKey.register_lookup(TupleGreaterThan)
Expand Down
3 changes: 1 addition & 2 deletions django/db/models/sql/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1117,10 +1117,9 @@ def find_ordering_name(
)
return results
targets, alias, _ = self.query.trim_joins(targets, joins, path)
target_fields = composite.unnest(targets)
return [
(OrderBy(transform_function(t, alias), descending=descending), False)
for t in target_fields
for t in targets
]

def _setup_joins(self, pieces, opts, alias):
Expand Down
5 changes: 0 additions & 5 deletions docs/faq/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,9 @@ Then, please post it in one of the following channels:
discussions.
* The |django-users| mailing list. This is for email-based discussions.
* The `Django Discord server`_ for chat-based discussions.
* The `#django IRC channel`_ on the Libera.Chat IRC network. This is for
chat-based discussions. If you're new to IRC, see the `Libera.Chat
documentation`_ for different ways to connect.

.. _`"Using Django"`: https://forum.djangoproject.com/c/users/6
.. _`Django Discord server`: https://discord.gg/xcRH6mN4fa
.. _#django IRC channel: https://web.libera.chat/#django
.. _Libera.Chat documentation: https://libera.chat/guides/connect

In all these channels please abide by the `Django Code of Conduct`_. In
summary, being friendly and patient, considerate, respectful, and careful in
Expand Down
10 changes: 5 additions & 5 deletions docs/faq/install.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ For each version of Python, only the latest micro release (A.B.C) is officially
supported. You can find the latest micro version for each series on the `Python
download page <https://www.python.org/downloads/>`_.

Typically, we will support a Python version up to and including the first
Django LTS release whose security support ends after security support for that
version of Python ends. For example, Python 3.9 security support ends in
October 2025 and Django 4.2 LTS security support ends in April 2026. Therefore
Django 4.2 is the last version to support Python 3.9.
We will support a Python version up to and including the first Django LTS
release whose security support ends after security support for that version of
Python ends. For example, Python 3.9 security support ends in October 2025 and
Django 4.2 LTS security support ends in April 2026. Therefore Django 4.2 is the
last version to support Python 3.9.

What Python version should I use with Django?
=============================================
Expand Down
8 changes: 4 additions & 4 deletions docs/internals/contributing/bugs-and-features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Otherwise, before reporting a bug or requesting a new feature on the
`searching`_ or running `custom queries`_ in the ticket tracker.

* Don't use the ticket system to ask support questions. Use the
|django-users| list or the `#django`_ IRC channel for that.
|django-users| list or the `Django Discord server`_ for that.

* Don't reopen issues that have been marked "wontfix" without finding consensus
to do so on the `Django Forum`_ or |django-developers| list.
Expand All @@ -39,8 +39,8 @@ particular:
* **Do** read the :doc:`FAQ </faq/index>` to see if your issue might
be a well-known question.

* **Do** ask on |django-users| or `#django`_ *first* if you're not sure if
what you're seeing is a bug.
* **Do** ask on |django-users| or the `Django Discord server`_ *first* if
you're not sure if what you're seeing is a bug.

* **Do** write complete, reproducible, specific bug reports. You must
include a clear, concise description of the problem, and a set of
Expand Down Expand Up @@ -166,5 +166,5 @@ Votes on technical matters should be announced and held in public on the

.. _searching: https://code.djangoproject.com/search
.. _custom queries: https://code.djangoproject.com/query
.. _#django: https://web.libera.chat/#django
.. _Django Forum: https://forum.djangoproject.com/
.. _Django Discord server: https://discord.gg/xcRH6mN4fa
7 changes: 3 additions & 4 deletions docs/internals/contributing/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ a great ecosystem to work in:
friendly and helpful atmosphere. If you're new to the Django community,
you should read the `posting guidelines`_.

* Join the `Django Discord server`_ or the `#django IRC channel`_ on
Libera.Chat to discuss and answer questions. By explaining Django to other
users, you're going to learn a lot about the framework yourself.
* Join the `Django Discord server`_ to discuss and answer questions. By
explaining Django to other users, you're going to learn a lot about the
framework yourself.

* Blog about Django. We syndicate all the Django blogs we know about on
the `community page`_; if you'd like to see your blog on that page you
Expand All @@ -45,7 +45,6 @@ a great ecosystem to work in:
build it!

.. _posting guidelines: https://code.djangoproject.com/wiki/UsingTheMailingList
.. _#django IRC channel: https://web.libera.chat/#django
.. _community page: https://www.djangoproject.com/community/
.. _Django Discord server: https://discord.gg/xcRH6mN4fa
.. _Django forum: https://forum.djangoproject.com/
Expand Down
3 changes: 0 additions & 3 deletions docs/internals/howto-release-django.txt
Original file line number Diff line number Diff line change
Expand Up @@ -561,9 +561,6 @@ Now you're ready to actually put the release out there. To do this:
message body should include the vulnerability details, for example, the
announcement blog post text. Include a link to the announcement blog post.

#. Add a link to the blog post in the topic of the ``#django`` IRC channel:
``/msg chanserv TOPIC #django new topic goes here``.

Post-release
============

Expand Down
8 changes: 4 additions & 4 deletions docs/intro/contributing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ so that it can be of use to the widest audience.
.. admonition:: Where to get help:

If you're having trouble going through this tutorial, please post a message
on the `Django Forum`_, |django-developers|, or drop by
`#django-dev on irc.libera.chat`__ to chat with other Django users who
might be able to help.
on the `Django Forum`_, |django-developers|, or drop by the
`Django Discord server`_ to chat with other Django users who might be able
to help.

__ https://web.libera.chat/#django-dev
.. _Dive Into Python: https://diveintopython3.net/
.. _Django Forum: https://forum.djangoproject.com/
.. _Django Discord server: https://discord.gg/xcRH6mN4fa

What does this tutorial cover?
------------------------------
Expand Down
1 change: 0 additions & 1 deletion docs/intro/tutorial08.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ resolve the issue yourself, there are options available to you.
Toolbar’s is `on GitHub <https://github.com/django-commons/django-debug-toolbar/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc>`_.
#. Consult the `Django Forum <https://forum.djangoproject.com/>`_.
#. Join the `Django Discord server <https://discord.gg/xcRH6mN4fa>`_.
#. Join the #Django IRC channel on `Libera.chat <https://libera.chat/>`_.

Installing other third-party packages
=====================================
Expand Down
6 changes: 3 additions & 3 deletions docs/intro/whatsnext.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ ticket system and use your feedback to improve the documentation for everybody.

Note, however, that tickets should explicitly relate to the documentation,
rather than asking broad tech-support questions. If you need help with your
particular Django setup, try the |django-users| mailing list or the `#django
IRC channel`_ instead.
particular Django setup, try the |django-users| mailing list or the
`Django Discord server`_ instead.

.. _ticket system: https://code.djangoproject.com/
.. _#django IRC channel: https://web.libera.chat/#django
.. _Django Discord server: https://discord.gg/xcRH6mN4fa

In plain text
-------------
Expand Down
10 changes: 9 additions & 1 deletion tests/admin_changelist/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.contrib.auth.models import User
from django.core.paginator import Paginator

from .models import Band, Child, Event, GrandChild, Parent, ProxyUser, Swallow
from .models import Band, Child, Event, Genre, GrandChild, Parent, ProxyUser, Swallow

site = admin.AdminSite(name="admin")

Expand Down Expand Up @@ -157,6 +157,14 @@ class NoListDisplayLinksParentAdmin(admin.ModelAdmin):
site.register(Parent, NoListDisplayLinksParentAdmin)


class ListDisplayLinksGenreAdmin(admin.ModelAdmin):
list_display = ["name", "file"]
list_display_links = ["file"]


site.register(Genre, ListDisplayLinksGenreAdmin)


class SwallowAdmin(admin.ModelAdmin):
actions = None # prevent ['action_checkbox'] + list(list_display)
list_display = ("origin", "load", "speed", "swallowonetoone")
Expand Down
1 change: 1 addition & 0 deletions tests/admin_changelist/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __html__(self):

class Genre(models.Model):
name = models.CharField(max_length=20)
file = models.FileField(upload_to="documents/", blank=True, null=True)


class Band(models.Model):
Expand Down
10 changes: 10 additions & 0 deletions tests/admin_changelist/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,16 @@ def test_no_list_display_links(self):
link = reverse("admin:admin_changelist_parent_change", args=(p.pk,))
self.assertNotContains(response, '<a href="%s">' % link)

def test_link_field_display_links(self):
self.client.force_login(self.superuser)
g = Genre.objects.create(name="Blues", file="documents/blues_history.txt")
response = self.client.get(reverse("admin:admin_changelist_genre_changelist"))
self.assertContains(
response,
'<a href="/admin/admin_changelist/genre/%s/change/">'
"documents/blues_history.txt</a>" % g.pk,
)

def test_clear_all_filters_link(self):
self.client.force_login(self.superuser)
url = reverse("admin:auth_user_changelist")
Expand Down
8 changes: 8 additions & 0 deletions tests/composite_pk/fixtures/tenant.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,13 @@
"tenant_id": 2,
"id": "ffffffff-ffff-ffff-ffff-ffffffffffff"
}
},
{
"pk": [1, "2022-01-12T05:55:14.956"],
"model": "composite_pk.timestamped",
"fields": {
"id": 1,
"created": "2022-01-12T05:55:14.956"
}
}
]
3 changes: 2 additions & 1 deletion tests/composite_pk/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from .tenant import Comment, Post, Tenant, Token, User
from .tenant import Comment, Post, Tenant, TimeStamped, Token, User

__all__ = [
"Comment",
"Post",
"Tenant",
"TimeStamped",
"Token",
"User",
]
Loading

0 comments on commit a386b28

Please sign in to comment.