Skip to content
This repository was archived by the owner on Mar 19, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0707ff6
Renamed set_language()'s next variable to avoid clash with builtin.
myusko Dec 21, 2019
6c9c823
Renamed docs/README to README.rst.
Someoneece Jan 8, 2020
66e16dc
Removed unused lines in ImageFieldTests.test_pickle().
hramezani Nov 10, 2019
aaea9de
Refs #21238 -- Added more tests for pickling FileField and ImageField.
hramezani Jan 9, 2020
f600e3f
Fixed #21238 -- Fixed restoring attributes when pickling FileField an…
hramezani Nov 10, 2019
eef3ea8
Fixed #31148 -- Added error messages on update()/delete() operations …
hramezani Jan 9, 2020
ceecd05
Improved ReST formatting in docs/README.rst.
KHMANJUNATH Jan 9, 2020
eb629f4
Fixed #30995 -- Allowed converter.to_url() to raise ValueError to ind…
jcushman Dec 21, 2019
aa6c620
More accurate terminology ("logger" instead of "logging handler") in …
Apr 18, 2019
4c1b401
Added file cleanup in FileFieldTests.test_pickle().
carltongibson Jan 9, 2020
5166097
Fixed #31154 -- Added support for using enumeration types in templates.
adamchainz Jan 9, 2020
581ba5a
Refs #23004 -- Allowed exception reporter filters to customize settin…
carltongibson Jan 9, 2020
e2d9d66
Fixed #23004 -- Added request.META filtering to SafeExceptionReporter…
carltongibson Jan 9, 2020
8b3e714
Fixed #30980 -- Improved error message when checking uniqueness of ad…
AdamDonna Jan 6, 2020
6f7998a
Fixed #31155 -- Fixed a system check for the longest choice when a na…
felixxm Jan 11, 2020
1f4b9f4
Removed unused ExceptionReporterFilter class.
carltongibson Jan 9, 2020
77d335e
Fixed #31160 -- Fixed admin CSS for ordered lists' descendants in uno…
owenh000 Jan 13, 2020
20debf0
Fixed typo in docs/ref/django-admin.txt.
blueyed Jan 13, 2020
4fe4865
Fixed <span> nesting in technical 500 template.
blueyed Jan 14, 2020
927c903
Refs #31097 -- Added release notes for 2f565f84aca136d9cc4e4d061f3196…
carltongibson Jan 14, 2020
63e6ee1
Fixed #29871 -- Allowed setting pk=None on a child model to create a …
ChetanKhanna Oct 28, 2019
59b4e99
Refs #31136 -- Made QuerySet.values()/values_list() group only by sel…
felixxm Jan 15, 2020
b5a62bd
Refs #27468 -- Added explicit tests for django.utils.crypto.salted_hm…
claudep Jan 8, 2020
c5e373d
Fixed obsolete comment in django.utils.crypto.salted_hmac().
felixxm Jan 15, 2020
d202846
Refs #29998 -- Corrected auto-created OneToOneField parent_link in MT…
felixxm Jan 15, 2020
29c126b
Fixed #31124 -- Fixed setting of get_FOO_display() when overriding in…
carltongibson Jan 7, 2020
7400da4
Clarified backport policy for regressions.
carltongibson Jan 14, 2020
bf77669
Fixed #29998 -- Allowed multiple OneToOneFields to the parent model.
felixxm Jan 16, 2020
1e0dcd6
Used constant instead of hard-coded value for recursive relationship.
adamchainz Jan 16, 2020
d08d4f4
Fixed #30765 -- Made cache_page decorator take precedence over max-ag…
fcurella Sep 26, 2019
266c853
Fixed #31162 -- Prevented error logs when using WKT strings in lookups.
felixxm Jan 16, 2020
a5a28de
Added apps.py to project from tutorials in reusable apps docs.
AnaelMobilia Jan 16, 2020
13e4abf
Fixed #30752 -- Allowed using ExceptionReporter subclasses in error r…
Sep 7, 2019
2f9bde3
fix nested filtered relations
ferrants Jan 6, 2020
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: 4 additions & 0 deletions django/conf/global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,10 @@ def gettext_noop(s):
# Custom logging configuration.
LOGGING = {}

# Default exception reporter class used in case none has been
# specifically assigned to the HttpRequest instance.
DEFAULT_EXCEPTION_REPORTER = 'django.views.debug.ExceptionReporter'

# Default exception reporter filter class used in case none has been
# specifically assigned to the HttpRequest instance.
DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFilter'
Expand Down
24 changes: 15 additions & 9 deletions django/contrib/admin/checks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import collections
from itertools import chain

from django.apps import apps
Expand Down Expand Up @@ -985,15 +986,20 @@ def _check_action_permission_methods(self, obj):

def _check_actions_uniqueness(self, obj):
"""Check that every action has a unique __name__."""
names = [name for _, name, _ in obj._get_base_actions()]
if len(names) != len(set(names)):
return [checks.Error(
'__name__ attributes of actions defined in %s must be '
'unique.' % obj.__class__,
obj=obj.__class__,
id='admin.E130',
)]
return []
errors = []
names = collections.Counter(name for _, name, _ in obj._get_base_actions())
for name, count in names.items():
if count > 1:
errors.append(checks.Error(
'__name__ attributes of actions defined in %s must be '
'unique. Name %r is not unique.' % (
obj.__class__.__name__,
name,
),
obj=obj.__class__,
id='admin.E130',
))
return errors


class InlineModelAdminChecks(BaseModelAdminChecks):
Expand Down
2 changes: 1 addition & 1 deletion django/contrib/admin/static/admin/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ h5 {
letter-spacing: 1px;
}

ul li {
ul > li {
list-style-type: square;
padding: 1px 0;
}
Expand Down
9 changes: 8 additions & 1 deletion django/contrib/gis/gdal/raster/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ def __init__(self, ds_input, write=False):

# If input is a valid file path, try setting file as source.
if isinstance(ds_input, str):
if (
not ds_input.startswith(VSI_FILESYSTEM_BASE_PATH) and
not os.path.exists(ds_input)
):
raise GDALException(
'Unable to read raster source input "%s".' % ds_input
)
try:
# GDALOpen will auto-detect the data source type.
self._ptr = capi.open_ds(force_bytes(ds_input), self._write)
Expand Down Expand Up @@ -225,7 +232,7 @@ def vsi_buffer(self):

@cached_property
def is_vsi_based(self):
return self.name.startswith(VSI_FILESYSTEM_BASE_PATH)
return self._ptr and self.name.startswith(VSI_FILESYSTEM_BASE_PATH)

@property
def name(self):
Expand Down
5 changes: 4 additions & 1 deletion django/db/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def __new__(cls, name, bases, attrs, **kwargs):
continue
# Locate OneToOneField instances.
for field in base._meta.local_fields:
if isinstance(field, OneToOneField):
if isinstance(field, OneToOneField) and field.remote_field.parent_link:
related = resolve_relation(new_class, field.remote_field.model)
parent_links[make_model_tuple(related)] = field

Expand Down Expand Up @@ -569,6 +569,9 @@ def _get_pk_val(self, meta=None):
return getattr(self, meta.pk.attname)

def _set_pk_val(self, value):
for parent_link in self._meta.parents.values():
if parent_link and parent_link != self._meta.pk:
setattr(self, parent_link.target_field.attname, value)
return setattr(self, self._meta.pk.attname, value)

pk = property(_get_pk_val, _set_pk_val)
Expand Down
1 change: 1 addition & 0 deletions django/db/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __new__(metacls, classname, bases, classdict):
# that is passed in as "self" as the value to use when looking up the
# label in the choices.
cls.label = property(lambda self: cls._value2label_map_.get(self.value))
cls.do_not_call_in_templates = True
return enum.unique(cls)

def __contains__(cls, member):
Expand Down
10 changes: 7 additions & 3 deletions django/db/models/fields/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,10 @@ def _check_choices(self):
):
break
if self.max_length is not None and group_choices:
choice_max_length = max(
choice_max_length = max([
choice_max_length,
*(len(value) for value, _ in group_choices if isinstance(value, str)),
)
])
except (TypeError, ValueError):
# No groups, choices in the form [value, display]
value, human_name = group_name, group_choices
Expand Down Expand Up @@ -764,7 +764,11 @@ def contribute_to_class(self, cls, name, private_only=False):
if not getattr(cls, self.attname, None):
setattr(cls, self.attname, self.descriptor_class(self))
if self.choices is not None:
if not hasattr(cls, 'get_%s_display' % self.name):
# Don't override a get_FOO_display() method defined explicitly on
# this class, but don't check methods derived from inheritance, to
# allow overriding inherited choices. For more complex inheritance
# structures users should override contribute_to_class().
if 'get_%s_display' % self.name not in cls.__dict__:
setattr(
cls,
'get_%s_display' % self.name,
Expand Down
20 changes: 15 additions & 5 deletions django/db/models/fields/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,21 @@ def close(self):
file.close()

def __getstate__(self):
# FieldFile needs access to its associated model field and an instance
# it's attached to in order to work properly, but the only necessary
# data to be pickled is the file's name itself. Everything else will
# be restored later, by FileDescriptor below.
return {'name': self.name, 'closed': False, '_committed': True, '_file': None}
# FieldFile needs access to its associated model field, an instance and
# the file's name. Everything else will be restored later, by
# FileDescriptor below.
return {
'name': self.name,
'closed': False,
'_committed': True,
'_file': None,
'instance': self.instance,
'field': self.field,
}

def __setstate__(self, state):
self.__dict__.update(state)
self.storage = self.field.storage


class FileDescriptor:
Expand Down
34 changes: 25 additions & 9 deletions django/db/models/fields/related.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,11 @@ def resolve_related_fields(self):
for index in range(len(self.from_fields)):
from_field_name = self.from_fields[index]
to_field_name = self.to_fields[index]
from_field = (self if from_field_name == 'self'
else self.opts.get_field(from_field_name))
from_field = (
self
if from_field_name == RECURSIVE_RELATIONSHIP_CONSTANT
else self.opts.get_field(from_field_name)
)
to_field = (self.remote_field.model._meta.pk if to_field_name is None
else self.remote_field.model._meta.get_field(to_field_name))
related_fields.append((from_field, to_field))
Expand Down Expand Up @@ -810,8 +813,13 @@ def __init__(self, to, on_delete, related_name=None, related_query_name=None,
)
kwargs.setdefault('db_index', True)

super().__init__(to, on_delete, from_fields=['self'], to_fields=[to_field], **kwargs)

super().__init__(
to,
on_delete,
from_fields=[RECURSIVE_RELATIONSHIP_CONSTANT],
to_fields=[to_field],
**kwargs,
)
self.db_constraint = db_constraint

def check(self, **kwargs):
Expand Down Expand Up @@ -1276,8 +1284,11 @@ def _check_relationship_model(self, from_model=None, **kwargs):
"through_fields keyword argument.") % (self, from_model_name),
hint=(
'If you want to create a recursive relationship, '
'use ForeignKey("self", symmetrical=False, through="%s").'
) % relationship_model_name,
'use ForeignKey("%s", symmetrical=False, through="%s").'
) % (
RECURSIVE_RELATIONSHIP_CONSTANT,
relationship_model_name,
),
obj=self,
id='fields.E334',
)
Expand All @@ -1293,8 +1304,11 @@ def _check_relationship_model(self, from_model=None, **kwargs):
"through_fields keyword argument." % (self, to_model_name),
hint=(
'If you want to create a recursive relationship, '
'use ForeignKey("self", symmetrical=False, through="%s").'
) % relationship_model_name,
'use ForeignKey("%s", symmetrical=False, through="%s").'
) % (
RECURSIVE_RELATIONSHIP_CONSTANT,
relationship_model_name,
),
obj=self,
id='fields.E335',
)
Expand Down Expand Up @@ -1561,7 +1575,9 @@ def contribute_to_class(self, cls, name, **kwargs):
# automatically. The funky name reduces the chance of an accidental
# clash.
if self.remote_field.symmetrical and (
self.remote_field.model == "self" or self.remote_field.model == cls._meta.object_name):
self.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT or
self.remote_field.model == cls._meta.object_name
):
self.remote_field.related_name = "%s_rel_+" % name
elif self.remote_field.is_hidden():
# If the backwards relation is disabled, replace the original
Expand Down
6 changes: 1 addition & 5 deletions django/db/models/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from django.apps import apps
from django.conf import settings
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.core.exceptions import FieldDoesNotExist
from django.db import connections
from django.db.models import Manager
from django.db.models.fields import AutoField
Expand Down Expand Up @@ -251,10 +251,6 @@ def _prepare(self, model):
field = already_created[0]
field.primary_key = True
self.setup_pk(field)
if not field.remote_field.parent_link:
raise ImproperlyConfigured(
'Add parent_link=True to %s.' % field,
)
else:
auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True)
model.add_to_class('id', auto)
Expand Down
2 changes: 2 additions & 0 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ def in_bulk(self, id_list=None, *, field_name='pk'):

def delete(self):
"""Delete the records in the current QuerySet."""
self._not_support_combined_queries('delete')
assert not self.query.is_sliced, \
"Cannot use 'limit' or 'offset' with delete."

Expand Down Expand Up @@ -756,6 +757,7 @@ def update(self, **kwargs):
Update all elements in the current QuerySet, setting all the given
fields to the appropriate values.
"""
self._not_support_combined_queries('update')
assert not self.query.is_sliced, \
"Cannot update a query once a slice has been taken."
self._for_write = True
Expand Down
41 changes: 29 additions & 12 deletions django/db/models/sql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,6 @@ def join(self, join, reuse=None, reuse_with_filtered_relation=False):
reuse_alias = reuse_aliases[-1]
self.ref_alias(reuse_alias)
return reuse_alias

# No reuse is possible, so we need a new alias.
alias, _ = self.table_alias(join.table_name, create=True, filtered_relation=join.filtered_relation)
if join.join_type:
Expand Down Expand Up @@ -1408,13 +1407,23 @@ def build_filtered_relation_q(self, q_object, reuse, branch_negated=False, curre
def add_filtered_relation(self, filtered_relation, alias):
filtered_relation.alias = alias
lookups = dict(get_children_from_q(filtered_relation.condition))
for lookup in chain((filtered_relation.relation_name,), lookups):
relation_lookup_parts, relation_field_parts, _ = self.solve_lookup_type(filtered_relation.relation_name)
if relation_lookup_parts:
raise ValueError(
"FilteredRelation's relation_name doesn't support lookups "
"(got %r)." % filtered_relation.relation_name
)
for lookup in chain(lookups):
lookup_parts, field_parts, _ = self.solve_lookup_type(lookup)
shift = 2 if not lookup_parts else 1
if len(field_parts) > (shift + len(lookup_parts)):
lookup_path = field_parts[:-shift]
for _ in relation_field_parts:
if lookup_path:
lookup_path = lookup_path[1:]
if lookup_path:
raise ValueError(
"FilteredRelation's condition doesn't support nested "
"relations (got %r)." % lookup
"on clauses deeper than the relation (got %r for %r)." % (lookup, filtered_relation.relation_name)
)
self._filtered_relations[filtered_relation.alias] = filtered_relation

Expand Down Expand Up @@ -1448,7 +1457,14 @@ def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False):
field = self.annotation_select[name].output_field
elif name in self._filtered_relations and pos == 0:
filtered_relation = self._filtered_relations[name]
field = opts.get_field(filtered_relation.relation_name)
if LOOKUP_SEP in filtered_relation.relation_name:
parts = filtered_relation.relation_name.split(LOOKUP_SEP)
filtered_relation_path, field, _, _ = self.names_to_path(
parts, opts, allow_many, fail_on_missing
)
path.extend(filtered_relation_path[:-1])
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the nested FilterRelation relation_name

else:
field = opts.get_field(filtered_relation.relation_name)
if field is not None:
# Fields that contain one-to-many relations with a generic
# model (like a GenericForeignKey) cannot generate reverse
Expand Down Expand Up @@ -2093,13 +2109,6 @@ def set_values(self, fields):
self.clear_deferred_loading()
self.clear_select_fields()

if self.group_by is True:
self.add_fields((f.attname for f in self.model._meta.concrete_fields), False)
# Disable GROUP BY aliases to avoid orphaning references to the
# SELECT clause which is about to be cleared.
self.set_group_by(allow_aliases=False)
self.clear_select_fields()

if fields:
field_names = []
extra_names = []
Expand All @@ -2121,6 +2130,14 @@ def set_values(self, fields):
self.set_annotation_mask(annotation_names)
else:
field_names = [f.attname for f in self.model._meta.concrete_fields]
# Selected annotations must be known before setting the GROUP BY
# clause.
if self.group_by is True:
self.add_fields((f.attname for f in self.model._meta.concrete_fields), False)
# Disable GROUP BY aliases to avoid orphaning references to the
# SELECT clause which is about to be cleared.
self.set_group_by(allow_aliases=False)
self.clear_select_fields()

self.values_select = tuple(field_names)
self.add_fields(field_names, True)
Expand Down
23 changes: 14 additions & 9 deletions django/middleware/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class UpdateCacheMiddleware(MiddlewareMixin):
"""
def __init__(self, get_response=None):
self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
self.page_timeout = None
self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
self.cache = caches[self.cache_alias]
Expand All @@ -89,15 +90,18 @@ def process_response(self, request, response):
if 'private' in response.get('Cache-Control', ()):
return response

# Try to get the timeout from the "max-age" section of the "Cache-
# Control" header before reverting to using the default cache_timeout
# length.
timeout = get_max_age(response)
# Page timeout takes precedence over the "max-age" and the default
# cache timeout.
timeout = self.page_timeout
if timeout is None:
timeout = self.cache_timeout
elif timeout == 0:
# max-age was set to 0, don't bother caching.
return response
# The timeout from the "max-age" section of the "Cache-Control"
# header takes precedence over the default cache timeout.
timeout = get_max_age(response)
if timeout is None:
timeout = self.cache_timeout
elif timeout == 0:
# max-age was set to 0, don't cache.
return response
patch_response_headers(response, timeout)
if timeout and response.status_code == 200:
cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
Expand Down Expand Up @@ -160,7 +164,7 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
Also used as the hook point for the cache decorator, which is generated
using the decorator-from-middleware utility.
"""
def __init__(self, get_response=None, cache_timeout=None, **kwargs):
def __init__(self, get_response=None, cache_timeout=None, page_timeout=None, **kwargs):
self.get_response = get_response
# We need to differentiate between "provided, but using default value",
# and "not provided". If the value is provided using a default, then
Expand All @@ -186,4 +190,5 @@ def __init__(self, get_response=None, cache_timeout=None, **kwargs):
if cache_timeout is None:
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
self.cache_timeout = cache_timeout
self.page_timeout = page_timeout
self.cache = caches[self.cache_alias]
Loading