2
0
Эх сурвалжийг харах

Deprecated SortedDict (replaced with collections.OrderedDict)

Thanks Loic Bistuer for the review.
Curtis Maloney 11 жил өмнө
parent
commit
07876cf02b

+ 4 - 4
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.http.response import HttpResponseBase
 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 @@ class ModelAdmin(BaseModelAdmin):
         # 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 @@ class ModelAdmin(BaseModelAdmin):
         # 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
         ])

+ 3 - 3
django/contrib/admin/views/main.py

@@ -1,3 +1,4 @@
+from collections import OrderedDict
 import sys
 import warnings
 
@@ -7,7 +8,6 @@ from django.core.urlresolvers import reverse
 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 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
 
     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

+ 3 - 2
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 @@ class PasswordChangeForm(SetPasswordForm):
             )
         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']
 ])

+ 8 - 8
django/contrib/auth/hashers.py

@@ -2,13 +2,13 @@ from __future__ import unicode_literals
 
 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 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
     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 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher):
         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 @@ class SHA1PasswordHasher(BasePasswordHasher):
     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 @@ class MD5PasswordHasher(BasePasswordHasher):
     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 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
     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 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
         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 @@ class CryptPasswordHasher(BasePasswordHasher):
     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)),

+ 11 - 9
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.shortcuts import redirect
 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 @@ class WizardView(TemplateView):
         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 @@ class WizardView(TemplateView):
         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 @@ class WizardView(TemplateView):
         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 @@ class WizardView(TemplateView):
         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 @@ class WizardView(TemplateView):
         """
         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):
         """

+ 5 - 4
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.utils import six
 from django.contrib.staticfiles import utils
 from django.contrib.staticfiles.storage import AppStaticStorage
 
-_finders = SortedDict()
+_finders = OrderedDict()
 
 
 class BaseFinder(object):
@@ -47,7 +48,7 @@ class FileSystemFinder(BaseFinder):
         # 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 @@ class AppDirectoriesFinder(BaseFinder):
         # 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:

+ 2 - 2
django/contrib/staticfiles/management/commands/collectstatic.py

@@ -2,12 +2,12 @@ from __future__ import unicode_literals
 
 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 @@ class Command(NoArgsCommand):
         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

+ 3 - 3
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.cache import (get_cache, InvalidCacheBackendError,
 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 @@ class CachedFilesMixin(object):
         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 @@ class CachedFilesMixin(object):
 
     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:
 

+ 5 - 4
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 @@ class Command(BaseCommand):
         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('.')

+ 3 - 3
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 @@ class Command(NoArgsCommand):
             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 @@ class Command(NoArgsCommand):
         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:

+ 2 - 2
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.base import NoArgsCommand
 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 @@ class Command(NoArgsCommand):
             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
         )

+ 2 - 2
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 @@ class Collector(object):
                     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):

+ 15 - 6
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 @@ import sys
 
 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 @@ __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
 
 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 @@ class AppCache(object):
         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 @@ class AppCache(object):
             # 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

+ 5 - 5
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.fields.proxy import OrderWrt
 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 @@ class Options(object):
         # 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 @@ class Options(object):
         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 @@ class Options(object):
         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 @@ class Options(object):
         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():

+ 10 - 10
django/db/models/sql/query.py

@@ -7,9 +7,9 @@ databases). The abstraction barrier only works one way: this module has to know
 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 @@ class Query(object):
         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 @@ class Query(object):
 
         # 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 @@ class Query(object):
             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 @@ class Query(object):
         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 @@ class Query(object):
             # 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 @@ class Query(object):
                     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 @@ class Query(object):
         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 @@ class Query(object):
         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 @@ class Query(object):
         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
             ])

+ 2 - 2
django/forms/forms.py

@@ -4,6 +4,7 @@ Form classes
 
 from __future__ import unicode_literals
 
+from collections import OrderedDict
 import copy
 import warnings
 
@@ -11,7 +12,6 @@ from django.core.exceptions import ValidationError
 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):
     """

+ 4 - 4
django/forms/models.py

@@ -5,6 +5,7 @@ and database field objects.
 
 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.util import ErrorList
 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)]
         )

+ 3 - 2
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):

+ 5 - 1
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 @@ class SortedDict(dict):
         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)

+ 4 - 4
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 importlib import import_module
 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)

+ 3 - 0
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
 ---
 

+ 3 - 4
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

+ 4 - 0
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.
 

+ 7 - 0
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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 19 - 19
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 @@ class ExtraRegressTests(TestCase):
         # 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 @@ class ExtraRegressTests(TestCase):
         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)]
         )
 

+ 4 - 4
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.where import WhereNode, EverythingNode, NothingNode
 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 @@ class Queries1Tests(BaseQuerysetTest):
         )
 
     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 @@ class Queries1Tests(BaseQuerysetTest):
         # 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 @@ class ValuesQuerysetTests(BaseQuerysetTest):
 
     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')