From 07876cf02b6db453ca0397c29c225668872fa96d Mon Sep 17 00:00:00 2001 From: Curtis Maloney Date: Sat, 3 Aug 2013 15:41:15 +1000 Subject: [PATCH] Deprecated SortedDict (replaced with collections.OrderedDict) Thanks Loic Bistuer for the review. --- django/contrib/admin/options.py | 8 ++-- django/contrib/admin/views/main.py | 6 +-- django/contrib/auth/forms.py | 5 ++- django/contrib/auth/hashers.py | 16 ++++---- django/contrib/formtools/wizard/views.py | 20 +++++----- django/contrib/staticfiles/finders.py | 9 +++-- .../management/commands/collectstatic.py | 4 +- django/contrib/staticfiles/storage.py | 6 +-- django/core/management/commands/dumpdata.py | 9 +++-- django/core/management/commands/inspectdb.py | 6 +-- django/core/management/commands/syncdb.py | 4 +- django/db/models/deletion.py | 4 +- django/db/models/loading.py | 21 +++++++--- django/db/models/options.py | 10 ++--- django/db/models/sql/query.py | 20 +++++----- django/forms/forms.py | 4 +- django/forms/models.py | 8 ++-- django/middleware/locale.py | 5 ++- django/utils/datastructures.py | 6 ++- django/utils/translation/trans_real.py | 8 ++-- docs/internals/deprecation.txt | 3 ++ docs/ref/models/querysets.txt | 7 ++-- docs/ref/utils.txt | 4 ++ docs/releases/1.7.txt | 7 ++++ tests/extra_regress/tests.py | 38 +++++++++---------- tests/queries/tests.py | 8 ++-- 26 files changed, 139 insertions(+), 107 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 94e654865028a..8a6b65358e244 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import copy import operator from functools import partial, reduce, update_wrapper @@ -29,7 +30,6 @@ from django.shortcuts import get_object_or_404 from django.template.response import SimpleTemplateResponse, TemplateResponse from django.utils.decorators import method_decorator -from django.utils.datastructures import SortedDict from django.utils.html import escape, escapejs from django.utils.safestring import mark_safe from django.utils import six @@ -672,7 +672,7 @@ def get_actions(self, request): # want *any* actions enabled on this page. from django.contrib.admin.views.main import _is_changelist_popup if self.actions is None or _is_changelist_popup(request): - return SortedDict() + return OrderedDict() actions = [] @@ -693,8 +693,8 @@ def get_actions(self, request): # get_action might have returned None, so filter any of those out. actions = filter(None, actions) - # Convert the actions into a SortedDict keyed by name. - actions = SortedDict([ + # Convert the actions into an OrderedDict keyed by name. + actions = OrderedDict([ (name, (func, name, desc)) for func, name, desc in actions ]) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index f766031bf87b0..3325747a9fcb9 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import sys import warnings @@ -7,7 +8,6 @@ from django.db import models from django.db.models.fields import FieldDoesNotExist from django.utils import six -from django.utils.datastructures import SortedDict from django.utils.deprecation import RenameMethodsBase from django.utils.encoding import force_str, force_text from django.utils.translation import ugettext, ugettext_lazy @@ -319,13 +319,13 @@ def get_ordering(self, request, queryset): def get_ordering_field_columns(self): """ - Returns a SortedDict of ordering field column numbers and asc/desc + Returns an OrderedDict of ordering field column numbers and asc/desc """ # We must cope with more than one column having the same underlying sort # field, so we base things on column numbers. ordering = self._get_default_ordering() - ordering_fields = SortedDict() + ordering_fields = OrderedDict() if ORDER_VAR not in self.params: # for ordering specified on ModelAdmin or model Meta, we don't know # the right column numbers absolutely, because there might be more diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index e3f03cc536efc..c5c2db456e9fb 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,9 +1,10 @@ from __future__ import unicode_literals +from collections import OrderedDict + from django import forms from django.forms.util import flatatt from django.template import loader -from django.utils.datastructures import SortedDict from django.utils.encoding import force_bytes from django.utils.html import format_html, format_html_join from django.utils.http import urlsafe_base64_encode @@ -324,7 +325,7 @@ def clean_old_password(self): ) return old_password -PasswordChangeForm.base_fields = SortedDict([ +PasswordChangeForm.base_fields = OrderedDict([ (k, PasswordChangeForm.base_fields[k]) for k in ['old_password', 'new_password1', 'new_password2'] ]) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index 45ebc0f980843..06aec646367e6 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -2,13 +2,13 @@ import base64 import binascii +from collections import OrderedDict import hashlib import importlib from django.dispatch import receiver from django.conf import settings from django.test.signals import setting_changed -from django.utils.datastructures import SortedDict from django.utils.encoding import force_bytes, force_str, force_text from django.core.exceptions import ImproperlyConfigured from django.utils.crypto import ( @@ -243,7 +243,7 @@ def verify(self, password, encoded): def safe_summary(self, encoded): algorithm, iterations, salt, hash = encoded.split('$', 3) assert algorithm == self.algorithm - return SortedDict([ + return OrderedDict([ (_('algorithm'), algorithm), (_('iterations'), iterations), (_('salt'), mask_hash(salt)), @@ -320,7 +320,7 @@ def safe_summary(self, encoded): algorithm, empty, algostr, work_factor, data = encoded.split('$', 4) assert algorithm == self.algorithm salt, checksum = data[:22], data[22:] - return SortedDict([ + return OrderedDict([ (_('algorithm'), algorithm), (_('work factor'), work_factor), (_('salt'), mask_hash(salt)), @@ -368,7 +368,7 @@ def verify(self, password, encoded): def safe_summary(self, encoded): algorithm, salt, hash = encoded.split('$', 2) assert algorithm == self.algorithm - return SortedDict([ + return OrderedDict([ (_('algorithm'), algorithm), (_('salt'), mask_hash(salt, show=2)), (_('hash'), mask_hash(hash)), @@ -396,7 +396,7 @@ def verify(self, password, encoded): def safe_summary(self, encoded): algorithm, salt, hash = encoded.split('$', 2) assert algorithm == self.algorithm - return SortedDict([ + return OrderedDict([ (_('algorithm'), algorithm), (_('salt'), mask_hash(salt, show=2)), (_('hash'), mask_hash(hash)), @@ -429,7 +429,7 @@ def verify(self, password, encoded): def safe_summary(self, encoded): assert encoded.startswith('sha1$$') hash = encoded[6:] - return SortedDict([ + return OrderedDict([ (_('algorithm'), self.algorithm), (_('hash'), mask_hash(hash)), ]) @@ -462,7 +462,7 @@ def verify(self, password, encoded): return constant_time_compare(encoded, encoded_2) def safe_summary(self, encoded): - return SortedDict([ + return OrderedDict([ (_('algorithm'), self.algorithm), (_('hash'), mask_hash(encoded, show=3)), ]) @@ -496,7 +496,7 @@ def verify(self, password, encoded): def safe_summary(self, encoded): algorithm, salt, data = encoded.split('$', 2) assert algorithm == self.algorithm - return SortedDict([ + return OrderedDict([ (_('algorithm'), algorithm), (_('salt'), salt), (_('hash'), mask_hash(data, show=3)), diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py index c137f1aeafef1..9c81f77ed2799 100644 --- a/django/contrib/formtools/wizard/views.py +++ b/django/contrib/formtools/wizard/views.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import re from django import forms @@ -5,7 +6,6 @@ from django.core.urlresolvers import reverse from django.forms import formsets, ValidationError from django.views.generic import TemplateView -from django.utils.datastructures import SortedDict from django.utils.decorators import classonlymethod from django.utils.translation import ugettext as _ from django.utils import six @@ -158,7 +158,7 @@ def get_initkwargs(cls, form_list=None, initial_dict=None, form_list = form_list or kwargs.pop('form_list', getattr(cls, 'form_list', None)) or [] - computed_form_list = SortedDict() + computed_form_list = OrderedDict() assert len(form_list) > 0, 'at least one form is needed' @@ -206,7 +206,7 @@ def get_form_list(self): The form_list is always generated on the fly because condition methods could use data from other (maybe previous forms). """ - form_list = SortedDict() + form_list = OrderedDict() for form_key, form_class in six.iteritems(self.form_list): # try to fetch the value from condition list, by default, the form # gets passed to the new list. @@ -498,9 +498,10 @@ def get_next_step(self, step=None): if step is None: step = self.steps.current form_list = self.get_form_list() - key = form_list.keyOrder.index(step) + 1 - if len(form_list.keyOrder) > key: - return form_list.keyOrder[key] + keys = list(form_list.keys()) + key = keys.index(step) + 1 + if len(keys) > key: + return keys[key] return None def get_prev_step(self, step=None): @@ -512,9 +513,10 @@ def get_prev_step(self, step=None): if step is None: step = self.steps.current form_list = self.get_form_list() - key = form_list.keyOrder.index(step) - 1 + keys = list(form_list.keys()) + key = keys.index(step) - 1 if key >= 0: - return form_list.keyOrder[key] + return keys[key] return None def get_step_index(self, step=None): @@ -524,7 +526,7 @@ def get_step_index(self, step=None): """ if step is None: step = self.steps.current - return self.get_form_list().keyOrder.index(step) + return list(self.get_form_list().keys()).index(step) def get_context_data(self, form, **kwargs): """ diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index 7d266d95a0db7..d4efd1a8d82fa 100644 --- a/django/contrib/staticfiles/finders.py +++ b/django/contrib/staticfiles/finders.py @@ -1,8 +1,9 @@ +from collections import OrderedDict import os + from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import default_storage, Storage, FileSystemStorage -from django.utils.datastructures import SortedDict from django.utils.functional import empty, memoize, LazyObject from django.utils.module_loading import import_by_path from django.utils._os import safe_join @@ -11,7 +12,7 @@ from django.contrib.staticfiles import utils from django.contrib.staticfiles.storage import AppStaticStorage -_finders = SortedDict() +_finders = OrderedDict() class BaseFinder(object): @@ -47,7 +48,7 @@ def __init__(self, apps=None, *args, **kwargs): # List of locations with static files self.locations = [] # Maps dir paths to an appropriate storage instance - self.storages = SortedDict() + self.storages = OrderedDict() if not isinstance(settings.STATICFILES_DIRS, (list, tuple)): raise ImproperlyConfigured( "Your STATICFILES_DIRS setting is not a tuple or list; " @@ -118,7 +119,7 @@ def __init__(self, apps=None, *args, **kwargs): # The list of apps that are handled self.apps = [] # Mapping of app module paths to storage instances - self.storages = SortedDict() + self.storages = OrderedDict() if apps is None: apps = settings.INSTALLED_APPS for app in apps: diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 7c3de80e93808..c1e9fa811ba9d 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -2,12 +2,12 @@ import os import sys +from collections import OrderedDict from optparse import make_option from django.core.files.storage import FileSystemStorage from django.core.management.base import CommandError, NoArgsCommand from django.utils.encoding import smart_text -from django.utils.datastructures import SortedDict from django.utils.six.moves import input from django.contrib.staticfiles import finders, storage @@ -97,7 +97,7 @@ def collect(self): else: handler = self.copy_file - found_files = SortedDict() + found_files = OrderedDict() for finder in finders.get_finders(): for path, storage in finder.list(self.ignore_patterns): # Prefix the relative path if the source storage contains it diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index f6d0a37a11185..1242afe4113f2 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from collections import OrderedDict import hashlib from importlib import import_module import os @@ -16,7 +17,6 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage, get_storage_class -from django.utils.datastructures import SortedDict from django.utils.encoding import force_bytes, force_text from django.utils.functional import LazyObject from django.utils._os import upath @@ -64,7 +64,7 @@ def __init__(self, *args, **kwargs): except InvalidCacheBackendError: # Use the default backend self.cache = default_cache - self._patterns = SortedDict() + self._patterns = OrderedDict() for extension, patterns in self.patterns: for pattern in patterns: if isinstance(pattern, (tuple, list)): @@ -202,7 +202,7 @@ def converter(matchobj): def post_process(self, paths, dry_run=False, **options): """ - Post process the given SortedDict of files (called from collectstatic). + Post process the given OrderedDict of files (called from collectstatic). Processing is actually two separate operations: diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 100c1872a76d4..fd9418a728ea3 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -1,10 +1,11 @@ +from collections import OrderedDict +from optparse import make_option + from django.core.exceptions import ImproperlyConfigured from django.core.management.base import BaseCommand, CommandError from django.core import serializers from django.db import router, DEFAULT_DB_ALIAS -from django.utils.datastructures import SortedDict -from optparse import make_option class Command(BaseCommand): option_list = BaseCommand.option_list + ( @@ -66,11 +67,11 @@ def handle(self, *app_labels, **options): if len(app_labels) == 0: if primary_keys: raise CommandError("You can only use --pks option with one model") - app_list = SortedDict((app, None) for app in get_apps() if app not in excluded_apps) + app_list = OrderedDict((app, None) for app in get_apps() if app not in excluded_apps) else: if len(app_labels) > 1 and primary_keys: raise CommandError("You can only use --pks option with one model") - app_list = SortedDict() + app_list = OrderedDict() for label in app_labels: try: app_label, model_label = label.split('.') diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index dc26bb11d2949..2cfea028ec1e5 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -1,12 +1,12 @@ from __future__ import unicode_literals +from collections import OrderedDict import keyword import re from optparse import make_option from django.core.management.base import NoArgsCommand, CommandError from django.db import connections, DEFAULT_DB_ALIAS -from django.utils.datastructures import SortedDict class Command(NoArgsCommand): @@ -69,7 +69,7 @@ def handle_inspection(self, options): used_column_names = [] # Holds column names used in the table so far for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)): comment_notes = [] # Holds Field notes, to be displayed in a Python comment. - extra_params = SortedDict() # Holds Field parameters such as 'db_column'. + extra_params = OrderedDict() # Holds Field parameters such as 'db_column'. column_name = row[0] is_relation = i in relations @@ -193,7 +193,7 @@ def get_field_type(self, connection, table_name, row): description, this routine will return the given field type name, as well as any additional keyword parameters and notes for the field. """ - field_params = SortedDict() + field_params = OrderedDict() field_notes = [] try: diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index 7c8d8b5d8f67f..d51699e95a5e0 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from importlib import import_module from optparse import make_option import itertools @@ -9,7 +10,6 @@ from django.core.management.color import no_style from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal, emit_pre_sync_signal from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS -from django.utils.datastructures import SortedDict class Command(NoArgsCommand): @@ -76,7 +76,7 @@ def model_installed(model): return not ((converter(opts.db_table) in tables) or (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables)) - manifest = SortedDict( + manifest = OrderedDict( (app_name, list(filter(model_installed, model_list))) for app_name, model_list in all_models ) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index e0bfb9d879158..f4c64f7b7dbd0 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -1,8 +1,8 @@ +from collections import OrderedDict from operator import attrgetter from django.db import connections, transaction, IntegrityError from django.db.models import signals, sql -from django.utils.datastructures import SortedDict from django.utils import six @@ -234,7 +234,7 @@ def sort(self): found = True if not found: return - self.data = SortedDict([(model, self.data[model]) + self.data = OrderedDict([(model, self.data[model]) for model in sorted_models]) def delete(self): diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 83682035d2fea..f858f25c1d70c 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -1,5 +1,7 @@ "Utilities for loading models and the modules that contain them." +from collections import OrderedDict +import copy import imp from importlib import import_module import os @@ -7,7 +9,6 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.utils.datastructures import SortedDict from django.utils.module_loading import module_has_submodule from django.utils._os import upath from django.utils import six @@ -17,6 +18,14 @@ MODELS_MODULE_NAME = 'models' +class ModelDict(OrderedDict): + """ + We need to special-case the deepcopy for this, as the keys are modules, + which can't be deep copied. + """ + def __deepcopy__(self, memo): + return self.__class__([(key, copy.deepcopy(value, memo)) + for key, value in self.items()]) class UnavailableApp(Exception): pass @@ -31,14 +40,14 @@ class AppCache(object): # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531. __shared_state = dict( # Keys of app_store are the model modules for each application. - app_store=SortedDict(), + app_store=ModelDict(), # Mapping of installed app_labels to model modules for that app. app_labels={}, # Mapping of app_labels to a dictionary of model names to model code. # May contain apps that are not installed. - app_models=SortedDict(), + app_models=ModelDict(), # Mapping of app_labels to errors raised when trying to import the app. app_errors={}, @@ -244,12 +253,12 @@ def get_models(self, app_mod=None, if app_mod: if app_mod in self.app_store: app_list = [self.app_models.get(self._label_for(app_mod), - SortedDict())] + ModelDict())] else: app_list = [] else: if only_installed: - app_list = [self.app_models.get(app_label, SortedDict()) + app_list = [self.app_models.get(app_label, ModelDict()) for app_label in six.iterkeys(self.app_labels)] else: app_list = six.itervalues(self.app_models) @@ -298,7 +307,7 @@ def register_models(self, app_label, *models): # Store as 'name: model' pair in a dictionary # in the app_models dictionary model_name = model._meta.model_name - model_dict = self.app_models.setdefault(app_label, SortedDict()) + model_dict = self.app_models.setdefault(app_label, ModelDict()) if model_name in model_dict: # The same model may be imported via different paths (e.g. # appname.models and project.appname.models). We use the source diff --git a/django/db/models/options.py b/django/db/models/options.py index d072d67fb3275..95cefec1bf372 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from collections import OrderedDict import re from bisect import bisect import warnings @@ -11,7 +12,6 @@ from django.db.models.loading import get_models, app_cache_ready from django.utils import six from django.utils.functional import cached_property -from django.utils.datastructures import SortedDict from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible from django.utils.translation import activate, deactivate_all, get_language, string_concat @@ -58,7 +58,7 @@ def __init__(self, meta, app_label=None): # concrete models, the concrete_model is always the class itself. self.concrete_model = None self.swappable = None - self.parents = SortedDict() + self.parents = OrderedDict() self.auto_created = False # To handle various inheritance situations, we need to track where @@ -332,7 +332,7 @@ def get_m2m_with_model(self): return list(six.iteritems(self._m2m_cache)) def _fill_m2m_cache(self): - cache = SortedDict() + cache = OrderedDict() for parent in self.parents: for field, model in parent._meta.get_m2m_with_model(): if model: @@ -474,7 +474,7 @@ def get_all_related_objects_with_model(self, local_only=False, return [t for t in cache.items() if all(p(*t) for p in predicates)] def _fill_related_objects_cache(self): - cache = SortedDict() + cache = OrderedDict() parent_list = self.get_parent_list() for parent in self.parents: for obj, model in parent._meta.get_all_related_objects_with_model(include_hidden=True): @@ -519,7 +519,7 @@ def get_all_related_m2m_objects_with_model(self): return list(six.iteritems(cache)) def _fill_related_many_to_many_cache(self): - cache = SortedDict() + cache = OrderedDict() parent_list = self.get_parent_list() for parent in self.parents: for obj, model in parent._meta.get_all_related_m2m_objects_with_model(): diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index fa7ae51d9cdef..de431204bded1 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -7,9 +7,9 @@ all about the internals of models in order to get the information it needs. """ +from collections import OrderedDict import copy -from django.utils.datastructures import SortedDict from django.utils.encoding import force_text from django.utils.tree import Node from django.utils import six @@ -142,7 +142,7 @@ def __init__(self, model, where=WhereNode): self.select_related = False # SQL aggregate-related attributes - self.aggregates = SortedDict() # Maps alias -> SQL aggregate function + self.aggregates = OrderedDict() # Maps alias -> SQL aggregate function self.aggregate_select_mask = None self._aggregate_select_cache = None @@ -152,7 +152,7 @@ def __init__(self, model, where=WhereNode): # These are for extensions. The contents are more or less appended # verbatim to the appropriate clause. - self.extra = SortedDict() # Maps col_alias -> (col_sql, params). + self.extra = OrderedDict() # Maps col_alias -> (col_sql, params). self.extra_select_mask = None self._extra_select_cache = None @@ -741,7 +741,7 @@ def relabel_column(col): self.group_by = [relabel_column(col) for col in self.group_by] self.select = [SelectInfo(relabel_column(s.col), s.field) for s in self.select] - self.aggregates = SortedDict( + self.aggregates = OrderedDict( (key, relabel_column(col)) for key, col in self.aggregates.items()) # 2. Rename the alias in the internal table/alias datastructures. @@ -795,7 +795,7 @@ def bump_prefix(self, exceptions=()): assert current < ord('Z') prefix = chr(current + 1) self.alias_prefix = prefix - change_map = SortedDict() + change_map = OrderedDict() for pos, alias in enumerate(self.tables): if alias in exceptions: continue @@ -1638,7 +1638,7 @@ def add_extra(self, select, select_params, where, params, tables, order_by): # dictionary with their parameters in 'select_params' so that # subsequent updates to the select dictionary also adjust the # parameters appropriately. - select_pairs = SortedDict() + select_pairs = OrderedDict() if select_params: param_iter = iter(select_params) else: @@ -1651,7 +1651,7 @@ def add_extra(self, select, select_params, where, params, tables, order_by): entry_params.append(next(param_iter)) pos = entry.find("%s", pos + 2) select_pairs[name] = (entry, entry_params) - # This is order preserving, since self.extra_select is a SortedDict. + # This is order preserving, since self.extra_select is an OrderedDict. self.extra.update(select_pairs) if where or params: self.where.add(ExtraWhere(where, params), AND) @@ -1760,7 +1760,7 @@ def set_extra_mask(self, names): self._extra_select_cache = None def _aggregate_select(self): - """The SortedDict of aggregate columns that are not masked, and should + """The OrderedDict of aggregate columns that are not masked, and should be used in the SELECT clause. This result is cached for optimization purposes. @@ -1768,7 +1768,7 @@ def _aggregate_select(self): if self._aggregate_select_cache is not None: return self._aggregate_select_cache elif self.aggregate_select_mask is not None: - self._aggregate_select_cache = SortedDict([ + self._aggregate_select_cache = OrderedDict([ (k, v) for k, v in self.aggregates.items() if k in self.aggregate_select_mask ]) @@ -1781,7 +1781,7 @@ def _extra_select(self): if self._extra_select_cache is not None: return self._extra_select_cache elif self.extra_select_mask is not None: - self._extra_select_cache = SortedDict([ + self._extra_select_cache = OrderedDict([ (k, v) for k, v in self.extra.items() if k in self.extra_select_mask ]) diff --git a/django/forms/forms.py b/django/forms/forms.py index 31e51e7c340d8..9905eee7c72d4 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals +from collections import OrderedDict import copy import warnings @@ -11,7 +12,6 @@ from django.forms.fields import Field, FileField from django.forms.util import flatatt, ErrorDict, ErrorList from django.forms.widgets import Media, media_property, TextInput, Textarea -from django.utils.datastructures import SortedDict from django.utils.html import conditional_escape, format_html from django.utils.encoding import smart_text, force_text, python_2_unicode_compatible from django.utils.safestring import mark_safe @@ -55,7 +55,7 @@ def get_declared_fields(bases, attrs, with_base_fields=True): if hasattr(base, 'declared_fields'): fields = list(six.iteritems(base.declared_fields)) + fields - return SortedDict(fields) + return OrderedDict(fields) class DeclarativeFieldsMetaclass(type): """ diff --git a/django/forms/models.py b/django/forms/models.py index 83954b0b22a19..a5b82e521d743 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals +from collections import OrderedDict import warnings from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError @@ -15,7 +16,6 @@ from django.forms.widgets import (SelectMultiple, HiddenInput, MultipleHiddenInput, media_property, CheckboxSelectMultiple) from django.utils.encoding import smart_text, force_text -from django.utils.datastructures import SortedDict from django.utils import six from django.utils.text import get_text_list, capfirst from django.utils.translation import ugettext_lazy as _, ugettext, string_concat @@ -142,7 +142,7 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, localized_fields=None, labels=None, help_texts=None, error_messages=None): """ - Returns a ``SortedDict`` containing form fields for the given model. + Returns a ``OrderedDict`` containing form fields for the given model. ``fields`` is an optional list of field names. If provided, only the named fields will be included in the returned fields. @@ -199,9 +199,9 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, field_list.append((f.name, formfield)) else: ignored.append(f.name) - field_dict = SortedDict(field_list) + field_dict = OrderedDict(field_list) if fields: - field_dict = SortedDict( + field_dict = OrderedDict( [(f, field_dict.get(f)) for f in fields if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored)] ) diff --git a/django/middleware/locale.py b/django/middleware/locale.py index 4e0a4753ce38d..cd5d2cce71a91 100644 --- a/django/middleware/locale.py +++ b/django/middleware/locale.py @@ -1,12 +1,13 @@ "This is the locale selecting middleware that will look at accept headers" +from collections import OrderedDict + from django.conf import settings from django.core.urlresolvers import (is_valid_path, get_resolver, LocaleRegexURLResolver) from django.http import HttpResponseRedirect from django.utils.cache import patch_vary_headers from django.utils import translation -from django.utils.datastructures import SortedDict class LocaleMiddleware(object): @@ -19,7 +20,7 @@ class LocaleMiddleware(object): """ def __init__(self): - self._supported_languages = SortedDict(settings.LANGUAGES) + self._supported_languages = OrderedDict(settings.LANGUAGES) self._is_language_prefix_patterns_used = False for url_pattern in get_resolver(None).url_patterns: if isinstance(url_pattern, LocaleRegexURLResolver): diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 3b1638392c08b..d6447ec0c7909 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,7 +1,7 @@ import copy +import warnings from django.utils import six - class MergeDict(object): """ A simple class for creating new "virtual" dictionaries that actually look @@ -124,6 +124,10 @@ def __new__(cls, *args, **kwargs): return instance def __init__(self, data=None): + warnings.warn( + "SortedDict is deprecated and will be removed in Django 1.9.", + PendingDeprecationWarning, stacklevel=2 + ) if data is None or isinstance(data, dict): data = data or [] super(SortedDict, self).__init__(data) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 91a73f1e68fcf..11463d8f9bc5b 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -1,6 +1,7 @@ """Translation helper functions.""" from __future__ import unicode_literals +from collections import OrderedDict import locale import os import re @@ -10,7 +11,6 @@ from threading import local import warnings -from django.utils.datastructures import SortedDict from django.utils.encoding import force_str, force_text from django.utils.functional import memoize from django.utils._os import upath @@ -369,7 +369,7 @@ def get_supported_language_variant(lang_code, supported=None, strict=False): """ if supported is None: from django.conf import settings - supported = SortedDict(settings.LANGUAGES) + supported = OrderedDict(settings.LANGUAGES) if lang_code: # if fr-CA is not supported, try fr-ca; if that fails, fallback to fr. generic_lang_code = lang_code.split('-')[0] @@ -396,7 +396,7 @@ def get_language_from_path(path, supported=None, strict=False): """ if supported is None: from django.conf import settings - supported = SortedDict(settings.LANGUAGES) + supported = OrderedDict(settings.LANGUAGES) regex_match = language_code_prefix_re.match(path) if not regex_match: return None @@ -418,7 +418,7 @@ def get_language_from_request(request, check_path=False): """ global _accepted from django.conf import settings - supported = SortedDict(settings.LANGUAGES) + supported = OrderedDict(settings.LANGUAGES) if check_path: lang_code = get_language_from_path(request.path_info, supported) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 3d6cd48b006c2..9b0dbf8ffab87 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -423,6 +423,9 @@ these changes. * FastCGI support via the ``runfcgi`` management command will be removed. Please deploy your project using WSGI. +* ``django.utils.datastructures.SortedDict`` will be removed. Use + :class:`collections.OrderedDict` from the Python standard library instead. + 2.0 --- diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 95dad202faab2..c29ac987a6426 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -977,14 +977,13 @@ of the arguments is required, but you should use at least one of them. ``select_params`` parameter. Since ``select_params`` is a sequence and the ``select`` attribute is a dictionary, some care is required so that the parameters are matched up correctly with the extra select pieces. - In this situation, you should use a - :class:`django.utils.datastructures.SortedDict` for the ``select`` - value, not just a normal Python dictionary. + In this situation, you should use a :class:`collections.OrderedDict` for + the ``select`` value, not just a normal Python dictionary. This will work, for example:: Blog.objects.extra( - select=SortedDict([('a', '%s'), ('b', '%s')]), + select=OrderedDict([('a', '%s'), ('b', '%s')]), select_params=('one', 'two')) The only thing to be careful about when using select parameters in diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 6b614e0c83302..599797bedb93c 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -105,6 +105,10 @@ to distinguish caches by the ``Accept-language`` header. .. class:: SortedDict +.. deprecated:: 1.7 + ``SortedDict`` is deprecated and will be removed in Django 1.9. Use + :class:`collections.OrderedDict` instead. + The :class:`django.utils.datastructures.SortedDict` class is a dictionary that keeps its keys in the order in which they're inserted. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index d21cb4542893c..91b932aff1a6b 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -167,6 +167,13 @@ on all Python versions. Since ``unittest2`` became the standard library's Python versions, this module isn't useful anymore. It has been deprecated. Use :mod:`unittest` instead. +``django.utils.datastructures.SortedDict`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As :class:`~collections.OrderedDict` was added to the standard library in +Python 2.7, :class:`~django.utils.datastructures.SortedDict` is no longer +needed and has been deprecated. + Custom SQL location for models package ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/extra_regress/tests.py b/tests/extra_regress/tests.py index 921f33aab9944..ae8f1b44234c1 100644 --- a/tests/extra_regress/tests.py +++ b/tests/extra_regress/tests.py @@ -1,10 +1,10 @@ from __future__ import unicode_literals +from collections import OrderedDict import datetime from django.contrib.auth.models import User from django.test import TestCase -from django.utils.datastructures import SortedDict from .models import TestObject, Order, RevisionableModel @@ -74,7 +74,7 @@ def test_extra_stay_tied(self): # select portions. Applies when portions are updated or otherwise # moved around. qs = User.objects.extra( - select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), + select=OrderedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), select_params=(1, 3) ) qs = qs.extra(select={"beta": 4}) @@ -180,100 +180,100 @@ def test_values_with_extra(self): obj.save() self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values()), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values()), [{'bar': 'second', 'third': 'third', 'second': 'second', 'whiz': 'third', 'foo': 'first', 'id': obj.pk, 'first': 'first'}] ) # Extra clauses after an empty values clause are still included self.assertEqual( - list(TestObject.objects.values().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + list(TestObject.objects.values().extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), [{'bar': 'second', 'third': 'third', 'second': 'second', 'whiz': 'third', 'foo': 'first', 'id': obj.pk, 'first': 'first'}] ) # Extra columns are ignored if not mentioned in the values() clause self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second')), [{'second': 'second', 'first': 'first'}] ) # Extra columns after a non-empty values() clause are ignored self.assertEqual( - list(TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + list(TestObject.objects.values('first', 'second').extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), [{'second': 'second', 'first': 'first'}] ) # Extra columns can be partially returned self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second', 'foo')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second', 'foo')), [{'second': 'second', 'foo': 'first', 'first': 'first'}] ) # Also works if only extra columns are included self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('foo', 'whiz')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('foo', 'whiz')), [{'foo': 'first', 'whiz': 'third'}] ) # Values list works the same way # All columns are returned for an empty values_list() self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()), [('first', 'second', 'third', obj.pk, 'first', 'second', 'third')] ) # Extra columns after an empty values_list() are still included self.assertEqual( - list(TestObject.objects.values_list().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + list(TestObject.objects.values_list().extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), [('first', 'second', 'third', obj.pk, 'first', 'second', 'third')] ) # Extra columns ignored completely if not mentioned in values_list() self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')), [('first', 'second')] ) # Extra columns after a non-empty values_list() clause are ignored completely self.assertEqual( - list(TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + list(TestObject.objects.values_list('first', 'second').extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), [('first', 'second')] ) self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('second', flat=True)), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('second', flat=True)), ['second'] ) # Only the extra columns specified in the values_list() are returned self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second', 'whiz')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second', 'whiz')), [('first', 'second', 'third')] ) # ...also works if only extra columns are included self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('foo','whiz')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('foo','whiz')), [('first', 'third')] ) self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', flat=True)), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', flat=True)), ['third'] ) # ... and values are returned in the order they are specified self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz','foo')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz','foo')), [('third', 'first')] ) self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first','id')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first','id')), [('first', obj.pk)] ) self.assertEqual( - list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', 'first', 'bar', 'id')), + list(TestObject.objects.extra(select=OrderedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', 'first', 'bar', 'id')), [('third', 'first', 'second', obj.pk)] ) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 025fcd8608128..6e03b0d7f6d6a 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from collections import OrderedDict import datetime from operator import attrgetter import pickle @@ -14,7 +15,6 @@ from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature from django.test.utils import str_prefix -from django.utils.datastructures import SortedDict from .models import ( Annotation, Article, Author, Celebrity, Child, Cover, Detail, DumbCategory, @@ -499,7 +499,7 @@ def test_ticket4358(self): ) def test_ticket2902(self): - # Parameters can be given to extra_select, *if* you use a SortedDict. + # Parameters can be given to extra_select, *if* you use an OrderedDict. # (First we need to know which order the keys fall in "naturally" on # your system, so we can put things in the wrong way around from @@ -513,7 +513,7 @@ def test_ticket2902(self): # This slightly odd comparison works around the fact that PostgreSQL will # return 'one' and 'two' as strings, not Unicode objects. It's a side-effect of # using constants here and not a real concern. - d = Item.objects.extra(select=SortedDict(s), select_params=params).values('a', 'b')[0] + d = Item.objects.extra(select=OrderedDict(s), select_params=params).values('a', 'b')[0] self.assertEqual(d, {'a': 'one', 'b': 'two'}) # Order by the number of tags attached to an item. @@ -1987,7 +1987,7 @@ def test_flat_values_list(self): def test_extra_values(self): # testing for ticket 14930 issues - qs = Number.objects.extra(select=SortedDict([('value_plus_x', 'num+%s'), + qs = Number.objects.extra(select=OrderedDict([('value_plus_x', 'num+%s'), ('value_minus_x', 'num-%s')]), select_params=(1, 2)) qs = qs.order_by('value_minus_x')