diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 830ba25408b4..09c9b95d26d5 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -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' diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index 0c32301284b4..ba754bd873b7 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -1,3 +1,4 @@ +import collections from itertools import chain from django.apps import apps @@ -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): diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index 3e27dd41f06c..cb527f1618ab 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -94,7 +94,7 @@ h5 { letter-spacing: 1px; } -ul li { +ul > li { list-style-type: square; padding: 1px 0; } diff --git a/django/contrib/gis/gdal/raster/source.py b/django/contrib/gis/gdal/raster/source.py index 05475434c426..33d8b3069f08 100644 --- a/django/contrib/gis/gdal/raster/source.py +++ b/django/contrib/gis/gdal/raster/source.py @@ -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) @@ -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): diff --git a/django/db/models/base.py b/django/db/models/base.py index 844c01e95eeb..24453e218a43 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -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 @@ -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) diff --git a/django/db/models/enums.py b/django/db/models/enums.py index f48143ddaf52..51821a2b458f 100644 --- a/django/db/models/enums.py +++ b/django/db/models/enums.py @@ -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): diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index aa21a151bc15..6bd95f542ce8 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -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 @@ -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, diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index b03d54366aec..7ee38d937f67 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -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: diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index f6c5ae258597..75b533b0a86c 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -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)) @@ -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): @@ -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', ) @@ -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', ) @@ -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 diff --git a/django/db/models/options.py b/django/db/models/options.py index a375f6ba1dd8..08c80bb6c852 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -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 @@ -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) diff --git a/django/db/models/query.py b/django/db/models/query.py index 38c13584d133..f8be008a62ea 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -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." @@ -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 diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index f96a0a6e2d8b..cd304ea2b2c7 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -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: @@ -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 @@ -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]) + 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 @@ -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 = [] @@ -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) diff --git a/django/middleware/cache.py b/django/middleware/cache.py index 6b320f1db5ad..9705270b5927 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -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] @@ -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) @@ -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 @@ -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] diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 120e0396af99..6e3f2443c974 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -632,11 +632,18 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): candidate_subs = kwargs # Convert the candidate subs to text using Converter.to_url(). text_candidate_subs = {} + match = True for k, v in candidate_subs.items(): if k in converters: - text_candidate_subs[k] = converters[k].to_url(v) + try: + text_candidate_subs[k] = converters[k].to_url(v) + except ValueError: + match = False + break else: text_candidate_subs[k] = str(v) + if not match: + continue # WSGI provides decoded URLs, without %xx escapes, and the URL # resolver operates on such URLs. First substitute arguments # without quoting to build a decoded URL and look for a match. diff --git a/django/utils/crypto.py b/django/utils/crypto.py index eeb55af0667d..4ec1cfcf77a7 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -26,8 +26,7 @@ def salted_hmac(key_salt, value, secret=None): # passing the key_salt and our base key through a pseudo-random function and # SHA1 works nicely. key = hashlib.sha1(key_salt + secret).digest() - - # If len(key_salt + secret) > sha_constructor().block_size, the above + # If len(key_salt + secret) > block size of the hash algorithm, the above # line is redundant and could be replaced by key = key_salt + secret, since # the hmac module does the same thing for keys longer than the block size. # However, we need to ensure that we *always* do this. diff --git a/django/utils/log.py b/django/utils/log.py index e40d87159c68..717c15814cdb 100644 --- a/django/utils/log.py +++ b/django/utils/log.py @@ -86,7 +86,7 @@ def __init__(self, include_html=False, email_backend=None, reporter_class=None): super().__init__() self.include_html = include_html self.email_backend = email_backend - self.reporter_class = import_string(reporter_class or 'django.views.debug.ExceptionReporter') + self.reporter_class = import_string(reporter_class or settings.DEFAULT_EXCEPTION_REPORTER) def emit(self, record): try: diff --git a/django/views/debug.py b/django/views/debug.py index 3b37e8da1a5f..1761d6904a1c 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -25,10 +25,6 @@ libraries={'i18n': 'django.templatetags.i18n'}, ) -HIDDEN_SETTINGS = _lazy_re_compile('API|TOKEN|KEY|SECRET|PASS|SIGNATURE', flags=re.IGNORECASE) - -CLEANSED_SUBSTITUTE = '********************' - CURRENT_DIR = Path(__file__).parent @@ -46,48 +42,12 @@ def __repr__(self): return repr(self._wrapped) -def cleanse_setting(key, value): - """ - Cleanse an individual setting key/value of sensitive content. If the value - is a dictionary, recursively cleanse the keys in that dictionary. - """ - try: - if HIDDEN_SETTINGS.search(key): - cleansed = CLEANSED_SUBSTITUTE - else: - if isinstance(value, dict): - cleansed = {k: cleanse_setting(k, v) for k, v in value.items()} - else: - cleansed = value - except TypeError: - # If the key isn't regex-able, just return as-is. - cleansed = value - - if callable(cleansed): - # For fixing #21345 and #23070 - cleansed = CallableSettingWrapper(cleansed) - - return cleansed - - -def get_safe_settings(): - """ - Return a dictionary of the settings module with values of sensitive - settings replaced with stars (*********). - """ - settings_dict = {} - for k in dir(settings): - if k.isupper(): - settings_dict[k] = cleanse_setting(k, getattr(settings, k)) - return settings_dict - - def technical_500_response(request, exc_type, exc_value, tb, status_code=500): """ Create a technical server error response. The last three arguments are the values returned from sys.exc_info() and friends. """ - reporter = ExceptionReporter(request, exc_type, exc_value, tb) + reporter = get_exception_reporter_class(request)(request, exc_type, exc_value, tb) if request.is_ajax(): text = reporter.get_traceback_text() return HttpResponse(text, status=status_code, content_type='text/plain; charset=utf-8') @@ -107,27 +67,58 @@ def get_exception_reporter_filter(request): return getattr(request, 'exception_reporter_filter', default_filter) -class ExceptionReporterFilter: - """ - Base for all exception reporter filter classes. All overridable hooks - contain lenient default behaviors. - """ - - def get_post_parameters(self, request): - if request is None: - return {} - else: - return request.POST - - def get_traceback_frame_variables(self, request, tb_frame): - return list(tb_frame.f_locals.items()) +def get_exception_reporter_class(request): + default_exception_reporter_class = import_string(settings.DEFAULT_EXCEPTION_REPORTER) + return getattr(request, 'exception_reporter_class', default_exception_reporter_class) -class SafeExceptionReporterFilter(ExceptionReporterFilter): +class SafeExceptionReporterFilter: """ Use annotations made by the sensitive_post_parameters and sensitive_variables decorators to filter out sensitive information. """ + cleansed_substitute = '********************' + hidden_settings = _lazy_re_compile('API|TOKEN|KEY|SECRET|PASS|SIGNATURE', flags=re.I) + + def cleanse_setting(self, key, value): + """ + Cleanse an individual setting key/value of sensitive content. If the + value is a dictionary, recursively cleanse the keys in that dictionary. + """ + try: + if self.hidden_settings.search(key): + cleansed = self.cleansed_substitute + elif isinstance(value, dict): + cleansed = {k: self.cleanse_setting(k, v) for k, v in value.items()} + else: + cleansed = value + except TypeError: + # If the key isn't regex-able, just return as-is. + cleansed = value + + if callable(cleansed): + cleansed = CallableSettingWrapper(cleansed) + + return cleansed + + def get_safe_settings(self): + """ + Return a dictionary of the settings module with values of sensitive + settings replaced with stars (*********). + """ + settings_dict = {} + for k in dir(settings): + if k.isupper(): + settings_dict[k] = self.cleanse_setting(k, getattr(settings, k)) + return settings_dict + + def get_safe_request_meta(self, request): + """ + Return a dictionary of request.META with sensitive values redacted. + """ + if not hasattr(request, 'META'): + return {} + return {k: self.cleanse_setting(k, v) for k, v in request.META.items()} def is_active(self, request): """ @@ -149,7 +140,7 @@ def get_cleansed_multivaluedict(self, request, multivaluedict): multivaluedict = multivaluedict.copy() for param in sensitive_post_parameters: if param in multivaluedict: - multivaluedict[param] = CLEANSED_SUBSTITUTE + multivaluedict[param] = self.cleansed_substitute return multivaluedict def get_post_parameters(self, request): @@ -166,13 +157,13 @@ def get_post_parameters(self, request): if sensitive_post_parameters == '__ALL__': # Cleanse all parameters. for k in cleansed: - cleansed[k] = CLEANSED_SUBSTITUTE + cleansed[k] = self.cleansed_substitute return cleansed else: # Cleanse only the specified parameters. for param in sensitive_post_parameters: if param in cleansed: - cleansed[param] = CLEANSED_SUBSTITUTE + cleansed[param] = self.cleansed_substitute return cleansed else: return request.POST @@ -215,12 +206,12 @@ def get_traceback_frame_variables(self, request, tb_frame): if sensitive_variables == '__ALL__': # Cleanse all variables for name in tb_frame.f_locals: - cleansed[name] = CLEANSED_SUBSTITUTE + cleansed[name] = self.cleansed_substitute else: # Cleanse specified variables for name, value in tb_frame.f_locals.items(): if name in sensitive_variables: - value = CLEANSED_SUBSTITUTE + value = self.cleansed_substitute else: value = self.cleanse_special_types(request, value) cleansed[name] = value @@ -236,8 +227,8 @@ def get_traceback_frame_variables(self, request, tb_frame): # the sensitive_variables decorator's frame, in case the variables # associated with those arguments were meant to be obfuscated from # the decorated function's frame. - cleansed['func_args'] = CLEANSED_SUBSTITUTE - cleansed['func_kwargs'] = CLEANSED_SUBSTITUTE + cleansed['func_args'] = self.cleansed_substitute + cleansed['func_kwargs'] = self.cleansed_substitute return cleansed.items() @@ -302,9 +293,10 @@ def get_traceback_data(self): 'unicode_hint': unicode_hint, 'frames': frames, 'request': self.request, + 'request_meta': self.filter.get_safe_request_meta(self.request), 'user_str': user_str, 'filtered_POST_items': list(self.filter.get_post_parameters(self.request).items()), - 'settings': get_safe_settings(), + 'settings': self.filter.get_safe_settings(), 'sys_executable': sys.executable, 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], 'server_time': timezone.now(), @@ -506,6 +498,7 @@ def technical_404_response(request, exception): with Path(CURRENT_DIR, 'templates', 'technical_404.html').open(encoding='utf-8') as fh: t = DEBUG_ENGINE.from_string(fh.read()) + reporter_filter = get_default_exception_reporter_filter() c = Context({ 'urlconf': urlconf, 'root_urlconf': settings.ROOT_URLCONF, @@ -513,7 +506,7 @@ def technical_404_response(request, exception): 'urlpatterns': tried, 'reason': str(exception), 'request': request, - 'settings': get_safe_settings(), + 'settings': reporter_filter.get_safe_settings(), 'raising_view_name': caller, }) return HttpResponseNotFound(t.render(c), content_type='text/html') diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 9658bd6ba257..773cf0c2c674 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -20,7 +20,7 @@ def cache_page(timeout, *, cache=None, key_prefix=None): into account on caching -- just like the middleware does. """ return decorator_from_middleware_with_args(CacheMiddleware)( - cache_timeout=timeout, cache_alias=cache, key_prefix=key_prefix + page_timeout=timeout, cache_alias=cache, key_prefix=key_prefix, ) diff --git a/django/views/i18n.py b/django/views/i18n.py index cbe61bcf885f..17eb4f9f6140 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -31,26 +31,31 @@ def set_language(request): redirect to the page in the request (the 'next' parameter) without changing any state. """ - next = request.POST.get('next', request.GET.get('next')) + next_url = request.POST.get('next', request.GET.get('next')) if ( - (next or not request.is_ajax()) and + (next_url or not request.is_ajax()) and not url_has_allowed_host_and_scheme( - url=next, allowed_hosts={request.get_host()}, require_https=request.is_secure(), + url=next_url, + allowed_hosts={request.get_host()}, + require_https=request.is_secure(), ) ): - next = request.META.get('HTTP_REFERER') - next = next and unquote(next) # HTTP_REFERER may be encoded. + next_url = request.META.get('HTTP_REFERER') + # HTTP_REFERER may be encoded. + next_url = next_url and unquote(next_url) if not url_has_allowed_host_and_scheme( - url=next, allowed_hosts={request.get_host()}, require_https=request.is_secure(), + url=next_url, + allowed_hosts={request.get_host()}, + require_https=request.is_secure(), ): - next = '/' - response = HttpResponseRedirect(next) if next else HttpResponse(status=204) + next_url = '/' + response = HttpResponseRedirect(next_url) if next_url else HttpResponse(status=204) if request.method == 'POST': lang_code = request.POST.get(LANGUAGE_QUERY_PARAMETER) if lang_code and check_for_language(lang_code): - if next: - next_trans = translate_url(next, lang_code) - if next_trans != next: + if next_url: + next_trans = translate_url(next_url, lang_code) + if next_trans != next_url: response = HttpResponseRedirect(next_trans) if hasattr(request, 'session'): # Storing the language in the session is deprecated. diff --git a/django/views/templates/technical_500.html b/django/views/templates/technical_500.html index b01b00c8e6b6..3e3d3b332527 100644 --- a/django/views/templates/technical_500.html +++ b/django/views/templates/technical_500.html @@ -208,7 +208,7 @@
{{ var.1|pprint }}