From 2b281cc35ed9d997614ca3c416928d7fabfef1ad Mon Sep 17 00:00:00 2001
From: Claude Paroz <claude@2xlibre.net>
Date: Sat, 7 Jan 2017 12:11:46 +0100
Subject: [PATCH] Refs #23919 -- Removed most of remaining six usage

Thanks Tim Graham for the review.
---
 django/contrib/admin/options.py               |   5 +-
 django/contrib/admin/sites.py                 |   3 +-
 .../contrib/admin/templatetags/admin_urls.py  |   3 +-
 django/contrib/auth/decorators.py             |   2 +-
 .../management/commands/createsuperuser.py    |   1 -
 django/contrib/auth/views.py                  |   2 +-
 django/contrib/contenttypes/checks.py         |   3 +-
 .../contenttypes/management/__init__.py       |   3 +-
 .../commands/remove_stale_contenttypes.py     |   4 +-
 .../contrib/gis/db/backends/oracle/adapter.py |   1 -
 django/contrib/gis/gdal/datasource.py         |   1 -
 django/contrib/gis/gdal/feature.py            |   1 -
 django/contrib/gis/gdal/geometries.py         |   8 +-
 django/contrib/gis/gdal/layer.py              |   1 -
 django/contrib/gis/gdal/raster/band.py        |   4 +-
 django/contrib/gis/geos/collections.py        |   1 -
 django/contrib/gis/geos/coordseq.py           |   1 -
 django/contrib/gis/geos/factory.py            |   3 +-
 django/contrib/gis/geos/geometry.py           |   5 +-
 django/contrib/gis/geos/linestring.py         |   1 -
 django/contrib/gis/geos/mutable_list.py       |   2 -
 django/contrib/gis/geos/point.py              |   1 -
 django/contrib/gis/geos/polygon.py            |   1 -
 django/contrib/gis/geos/prototypes/io.py      |   5 +-
 django/contrib/gis/geos/prototypes/misc.py    |   1 -
 django/contrib/gis/measure.py                 |   4 +-
 django/contrib/gis/utils/ogrinspect.py        |   1 -
 django/contrib/messages/storage/cookie.py     |   3 +-
 django/contrib/sessions/backends/cache.py     |   1 -
 django/contrib/sessions/serializers.py        |   7 +-
 django/contrib/sitemaps/__init__.py           |   5 +-
 django/contrib/staticfiles/finders.py         |   4 +-
 django/contrib/staticfiles/handlers.py        |   5 +-
 .../management/commands/collectstatic.py      |   1 -
 django/contrib/staticfiles/storage.py         |   8 +-
 django/contrib/staticfiles/views.py           |   2 +-
 django/contrib/syndication/views.py           |   6 +-
 django/core/cache/backends/db.py              |   6 +-
 django/core/cache/backends/filebased.py       |   6 +-
 django/core/cache/backends/locmem.py          |   7 +-
 django/core/files/storage.py                  |   2 +-
 django/core/mail/message.py                   |   5 +-
 django/core/management/__init__.py            |   4 +-
 django/core/management/commands/flush.py      |   1 -
 .../management/commands/makemigrations.py     |   4 +-
 .../management/commands/squashmigrations.py   |   3 +-
 django/core/management/templates.py           |   2 +-
 django/core/paginator.py                      |   3 +-
 django/core/serializers/__init__.py           |   3 +-
 django/core/serializers/base.py               |   9 +-
 django/core/serializers/python.py             |   3 +-
 django/core/servers/basehttp.py               |   2 +-
 django/core/validators.py                     |   2 +-
 django/db/backends/base/base.py               |  10 +-
 django/db/backends/base/creation.py           |   3 +-
 django/db/backends/oracle/base.py             |   2 +-
 django/db/backends/oracle/creation.py         |   1 -
 django/db/backends/postgresql/client.py       |   3 +-
 django/db/backends/sqlite3/creation.py        |   1 -
 django/db/migrations/loader.py                |   4 +-
 django/db/migrations/questioner.py            |   1 -
 django/db/migrations/serializer.py            |   5 +-
 django/db/models/base.py                      |   4 +-
 django/db/models/deletion.py                  |  19 ++-
 django/db/models/lookups.py                   |   1 -
 django/db/models/options.py                   |   3 +-
 django/db/models/query.py                     |   4 +-
 django/db/models/sql/compiler.py              |   1 -
 django/db/models/sql/query.py                 |  17 ++-
 django/db/models/sql/subqueries.py            |   5 +-
 django/dispatch/dispatcher.py                 |   1 -
 django/forms/fields.py                        |   2 +-
 django/forms/forms.py                         |   3 +-
 django/forms/formsets.py                      |   1 -
 django/forms/models.py                        |   5 +-
 django/forms/widgets.py                       |   5 +-
 django/http/cookie.py                         |  11 +-
 django/http/multipartparser.py                |  10 +-
 django/http/request.py                        |   8 +-
 django/http/response.py                       |   9 +-
 django/middleware/common.py                   |   2 +-
 django/middleware/csrf.py                     |   3 +-
 django/template/defaulttags.py                |   5 +-
 django/template/loader_tags.py                |   5 +-
 django/templatetags/static.py                 |   3 +-
 django/test/client.py                         |   2 +-
 django/test/runner.py                         |   2 +-
 django/test/selenium.py                       |   3 +-
 django/test/testcases.py                      |   9 +-
 django/test/utils.py                          |   6 +-
 django/urls/base.py                           |   2 +-
 django/utils/_os.py                           |   1 -
 django/utils/autoreload.py                    |   7 +-
 django/utils/crypto.py                        |   1 -
 django/utils/datastructures.py                |   6 +-
 django/utils/dateparse.py                     |   9 +-
 django/utils/encoding.py                      |   3 +-
 django/utils/feedgenerator.py                 |   4 +-
 django/utils/functional.py                    |   4 +-
 django/utils/html.py                          |   9 +-
 django/utils/html_parser.py                   |   8 +-
 django/utils/http.py                          |   8 +-
 django/utils/ipv6.py                          |   1 -
 django/utils/regex_helper.py                  |   1 -
 django/utils/termcolors.py                    |   4 +-
 django/utils/text.py                          |   7 +-
 django/utils/translation/template.py          |   2 +-
 django/views/generic/base.py                  |   3 +-
 django/views/i18n.py                          |   3 +-
 django/views/static.py                        |   2 +-
 tests/admin_scripts/tests.py                  |   2 +-
 tests/admin_views/admin.py                    |   2 +-
 tests/admin_views/tests.py                    |   2 +-
 tests/aggregation_regress/tests.py            |   3 +-
 tests/auth_tests/test_deprecated_views.py     |   2 +-
 tests/auth_tests/test_management.py           |  51 +++----
 tests/auth_tests/test_views.py                |   2 +-
 tests/backends/tests.py                       |   1 -
 tests/base/models.py                          |   5 +-
 tests/cache/tests.py                          |  14 +-
 tests/check_framework/tests.py                |   2 +-
 tests/contenttypes_tests/tests.py             |  10 +-
 tests/db_typecasts/tests.py                   |   3 +-
 tests/delete/tests.py                         |   1 -
 tests/deprecation/tests.py                    |  15 +--
 tests/file_storage/tests.py                   |  11 +-
 tests/file_uploads/tests.py                   |   3 +-
 tests/fixtures/tests.py                       |  14 +-
 tests/fixtures_regress/tests.py               |   2 +-
 tests/gis_tests/gdal_tests/test_geom.py       |   8 +-
 tests/gis_tests/gdal_tests/test_raster.py     |   3 +-
 tests/gis_tests/geoapp/tests.py               |   4 +-
 tests/gis_tests/geos_tests/test_geos.py       |  15 +--
 tests/gis_tests/inspectapp/tests.py           |   2 +-
 tests/gis_tests/test_data.py                  |   3 +-
 tests/httpwrappers/tests.py                   |  41 +++---
 tests/i18n/test_compilation.py                |   2 +-
 tests/i18n/test_extraction.py                 |   2 +-
 tests/inspectdb/tests.py                      |   2 +-
 tests/logging_tests/tests.py                  |   4 +-
 tests/m2m_through_regress/tests.py            |   3 +-
 tests/mail/tests.py                           |   2 +-
 tests/middleware/tests.py                     |   9 +-
 tests/migrate_signals/tests.py                |   7 +-
 tests/migrations/models.py                    |   3 +-
 tests/migrations/test_commands.py             | 125 +++++++++---------
 tests/migrations/test_writer.py               |   5 +-
 tests/model_fields/models.py                  |   3 +-
 tests/model_fields/test_binaryfield.py        |   3 +-
 tests/model_forms/models.py                   |   1 -
 tests/model_forms/tests.py                    |   3 +-
 tests/multiple_database/tests.py              |   2 +-
 tests/pagination/tests.py                     |   3 +-
 tests/queries/tests.py                        |   1 -
 tests/requests/tests.py                       |   6 +-
 tests/serializers/test_data.py                |   3 +-
 tests/serializers/test_yaml.py                |   2 +-
 tests/serializers/tests.py                    |   2 +-
 tests/servers/tests.py                        |   4 +-
 tests/sessions_tests/tests.py                 |  14 +-
 tests/sitemaps_tests/test_utils.py            |   3 +-
 tests/staticfiles_tests/test_forms.py         |   3 +-
 tests/staticfiles_tests/test_liveserver.py    |   2 +-
 tests/staticfiles_tests/test_management.py    |  31 +++--
 tests/staticfiles_tests/test_storage.py       |  10 +-
 tests/swappable_models/tests.py               |   5 +-
 .../syntax_tests/test_static.py               |   3 +-
 tests/template_tests/templatetags/custom.py   |   3 +-
 .../template_tests/templatetags/inclusion.py  |   3 +-
 tests/test_client/views.py                    |   2 +-
 tests/test_client_regress/views.py            |   2 +-
 tests/test_runner/test_debug_sql.py           |   4 +-
 tests/test_utils/tests.py                     |   4 +-
 tests/timezones/tests.py                      |  10 +-
 tests/user_commands/tests.py                  |   2 +-
 tests/utils_tests/test_autoreload.py          |   3 +-
 tests/utils_tests/test_baseconv.py            |   1 -
 tests/utils_tests/test_datastructures.py      |  12 +-
 tests/utils_tests/test_lazyobject.py          |   3 +-
 tests/view_tests/tests/test_debug.py          |   6 +-
 180 files changed, 421 insertions(+), 559 deletions(-)

diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index eaa9916056..2d4bbbb933 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -36,7 +36,6 @@ from django.http import HttpResponseRedirect
 from django.http.response import HttpResponseBase
 from django.template.response import SimpleTemplateResponse, TemplateResponse
 from django.urls import reverse
-from django.utils import six
 from django.utils.decorators import method_decorator
 from django.utils.encoding import force_text
 from django.utils.html import format_html
@@ -92,7 +91,7 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = {
 csrf_protect_m = method_decorator(csrf_protect)
 
 
-class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
+class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
     """Functionality common to both ModelAdmin and InlineAdmin."""
 
     raw_id_fields = ()
@@ -805,7 +804,7 @@ class ModelAdmin(BaseModelAdmin):
         tuple (name, description).
         """
         choices = [] + default_choices
-        for func, name, description in six.itervalues(self.get_actions(request)):
+        for func, name, description in self.get_actions(request).values():
             choice = (name, description % model_format_dict(self.opts))
             choices.append(choice)
         return choices
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index b3297d04f6..14b0da7df4 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -9,7 +9,6 @@ from django.db.models.base import ModelBase
 from django.http import Http404, HttpResponseRedirect
 from django.template.response import TemplateResponse
 from django.urls import NoReverseMatch, reverse
-from django.utils import six
 from django.utils.text import capfirst
 from django.utils.translation import ugettext as _, ugettext_lazy
 from django.views.decorators.cache import never_cache
@@ -169,7 +168,7 @@ class AdminSite(object):
         """
         Get all the enabled actions as an iterable of (name, func).
         """
-        return six.iteritems(self._actions)
+        return iter(self._actions.items())
 
     @property
     def empty_value_display(self):
diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py
index 8e665ec9de..097fbcf040 100644
--- a/django/contrib/admin/templatetags/admin_urls.py
+++ b/django/contrib/admin/templatetags/admin_urls.py
@@ -1,8 +1,9 @@
+from urllib.parse import parse_qsl, urlparse, urlunparse
+
 from django import template
 from django.contrib.admin.utils import quote
 from django.urls import Resolver404, get_script_prefix, resolve
 from django.utils.http import urlencode
-from django.utils.six.moves.urllib.parse import parse_qsl, urlparse, urlunparse
 
 register = template.Library()
 
diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
index f6e4c1d5b2..807ea8a3f6 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -1,11 +1,11 @@
 from functools import wraps
+from urllib.parse import urlparse
 
 from django.conf import settings
 from django.contrib.auth import REDIRECT_FIELD_NAME
 from django.core.exceptions import PermissionDenied
 from django.shortcuts import resolve_url
 from django.utils.decorators import available_attrs
-from django.utils.six.moves.urllib.parse import urlparse
 
 
 def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py
index b9f13b7b05..7a6d0b0231 100644
--- a/django/contrib/auth/management/commands/createsuperuser.py
+++ b/django/contrib/auth/management/commands/createsuperuser.py
@@ -11,7 +11,6 @@ from django.core import exceptions
 from django.core.management.base import BaseCommand, CommandError
 from django.db import DEFAULT_DB_ALIAS
 from django.utils.encoding import force_str
-from django.utils.six.moves import input
 from django.utils.text import capfirst
 
 
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index 1a004e7a84..bf95d11a10 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -1,4 +1,5 @@
 import warnings
+from urllib.parse import urlparse, urlunparse
 
 from django.conf import settings
 # Avoid shadowing the login() and logout() views below.
@@ -20,7 +21,6 @@ from django.utils.decorators import method_decorator
 from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.encoding import force_text
 from django.utils.http import is_safe_url, urlsafe_base64_decode
-from django.utils.six.moves.urllib.parse import urlparse, urlunparse
 from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.cache import never_cache
 from django.views.decorators.csrf import csrf_protect
diff --git a/django/contrib/contenttypes/checks.py b/django/contrib/contenttypes/checks.py
index 2355a5e943..d21df40f46 100644
--- a/django/contrib/contenttypes/checks.py
+++ b/django/contrib/contenttypes/checks.py
@@ -1,7 +1,6 @@
 from itertools import chain
 
 from django.apps import apps
-from django.utils import six
 
 
 def check_generic_foreign_keys(app_configs=None, **kwargs):
@@ -13,7 +12,7 @@ def check_generic_foreign_keys(app_configs=None, **kwargs):
         models = chain.from_iterable(app_config.get_models() for app_config in app_configs)
     errors = []
     fields = (
-        obj for model in models for obj in six.itervalues(vars(model))
+        obj for model in models for obj in vars(model).values()
         if isinstance(obj, GenericForeignKey)
     )
     for field in fields:
diff --git a/django/contrib/contenttypes/management/__init__.py b/django/contrib/contenttypes/management/__init__.py
index d0d5b52f09..6799ef8a23 100644
--- a/django/contrib/contenttypes/management/__init__.py
+++ b/django/contrib/contenttypes/management/__init__.py
@@ -1,7 +1,6 @@
 from django.apps import apps as global_apps
 from django.db import DEFAULT_DB_ALIAS, migrations, router, transaction
 from django.db.utils import IntegrityError
-from django.utils import six
 
 
 class RenameContentType(migrations.RunPython):
@@ -126,7 +125,7 @@ def create_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT
             app_label=app_label,
             model=model_name,
         )
-        for (model_name, model) in six.iteritems(app_models)
+        for (model_name, model) in app_models.items()
         if model_name not in content_types
     ]
     ContentType.objects.using(using).bulk_create(cts)
diff --git a/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py b/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py
index 2a3b23b0d0..e5f77dc7df 100644
--- a/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py
+++ b/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py
@@ -3,8 +3,6 @@ from django.contrib.contenttypes.models import ContentType
 from django.core.management import BaseCommand
 from django.db import DEFAULT_DB_ALIAS, router
 from django.db.models.deletion import Collector
-from django.utils import six
-from django.utils.six.moves import input
 
 from ...management import get_contenttypes_and_models
 
@@ -32,7 +30,7 @@ class Command(BaseCommand):
             if not app_models:
                 continue
             to_remove = [
-                ct for (model_name, ct) in six.iteritems(content_types)
+                ct for (model_name, ct) in content_types.items()
                 if model_name not in app_models
             ]
             # Confirm that the content type is stale before deletion.
diff --git a/django/contrib/gis/db/backends/oracle/adapter.py b/django/contrib/gis/db/backends/oracle/adapter.py
index 11eb0424aa..7c43391f69 100644
--- a/django/contrib/gis/db/backends/oracle/adapter.py
+++ b/django/contrib/gis/db/backends/oracle/adapter.py
@@ -2,7 +2,6 @@ from cx_Oracle import CLOB
 
 from django.contrib.gis.db.backends.base.adapter import WKTAdapter
 from django.contrib.gis.geos import GeometryCollection, Polygon
-from django.utils.six.moves import range
 
 
 class OracleSpatialAdapter(WKTAdapter):
diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py
index 1ff2326a63..3008da804c 100644
--- a/django/contrib/gis/gdal/datasource.py
+++ b/django/contrib/gis/gdal/datasource.py
@@ -41,7 +41,6 @@ from django.contrib.gis.gdal.error import GDALException, OGRIndexError
 from django.contrib.gis.gdal.layer import Layer
 from django.contrib.gis.gdal.prototypes import ds as capi
 from django.utils.encoding import force_bytes, force_text
-from django.utils.six.moves import range
 
 
 # For more information, see the OGR C API source code:
diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py
index 8975b9c076..47e8bb1ae3 100644
--- a/django/contrib/gis/gdal/feature.py
+++ b/django/contrib/gis/gdal/feature.py
@@ -4,7 +4,6 @@ from django.contrib.gis.gdal.field import Field
 from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType
 from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api
 from django.utils.encoding import force_bytes, force_text
-from django.utils.six.moves import range
 
 
 # For more information, see the OGR C API source code:
diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py
index 28ef2907ac..50c1d9ff65 100644
--- a/django/contrib/gis/gdal/geometries.py
+++ b/django/contrib/gis/gdal/geometries.py
@@ -51,9 +51,7 @@ from django.contrib.gis.gdal.geomtype import OGRGeomType
 from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api
 from django.contrib.gis.gdal.srs import CoordTransform, SpatialReference
 from django.contrib.gis.geometry.regex import hex_regex, json_regex, wkt_regex
-from django.utils import six
 from django.utils.encoding import force_bytes
-from django.utils.six.moves import range
 
 
 # For more information, see the OGR C API source code:
@@ -71,7 +69,7 @@ class OGRGeometry(GDALBase):
 
         # If HEX, unpack input to a binary buffer.
         if str_instance and hex_regex.match(geom_input):
-            geom_input = six.memoryview(a2b_hex(geom_input.upper().encode()))
+            geom_input = memoryview(a2b_hex(geom_input.upper().encode()))
             str_instance = False
 
         # Constructing the geometry,
@@ -96,7 +94,7 @@ class OGRGeometry(GDALBase):
                 # (e.g., 'Point', 'POLYGON').
                 OGRGeomType(geom_input)
                 g = capi.create_geom(OGRGeomType(geom_input).num)
-        elif isinstance(geom_input, six.memoryview):
+        elif isinstance(geom_input, memoryview):
             # WKB was passed in
             g = self._from_wkb(geom_input)
         elif isinstance(geom_input, OGRGeomType):
@@ -353,7 +351,7 @@ class OGRGeometry(GDALBase):
         buf = (c_ubyte * sz)()
         capi.to_wkb(self.ptr, byteorder, byref(buf))
         # Returning a buffer of the string at the pointer.
-        return six.memoryview(string_at(buf, sz))
+        return memoryview(string_at(buf, sz))
 
     @property
     def wkt(self):
diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py
index fbbede81b0..119abf9459 100644
--- a/django/contrib/gis/gdal/layer.py
+++ b/django/contrib/gis/gdal/layer.py
@@ -14,7 +14,6 @@ from django.contrib.gis.gdal.prototypes import (
 )
 from django.contrib.gis.gdal.srs import SpatialReference
 from django.utils.encoding import force_bytes, force_text
-from django.utils.six.moves import range
 
 
 # For more information, see the OGR C API source code:
diff --git a/django/contrib/gis/gdal/raster/band.py b/django/contrib/gis/gdal/raster/band.py
index bb65cf197e..141dc0e5b6 100644
--- a/django/contrib/gis/gdal/raster/band.py
+++ b/django/contrib/gis/gdal/raster/band.py
@@ -4,9 +4,7 @@ from django.contrib.gis.gdal.base import GDALBase
 from django.contrib.gis.gdal.error import GDALException
 from django.contrib.gis.gdal.prototypes import raster as capi
 from django.contrib.gis.shortcuts import numpy
-from django.utils import six
 from django.utils.encoding import force_text
-from django.utils.six.moves import range
 
 from .const import GDAL_INTEGER_TYPES, GDAL_PIXEL_TYPES, GDAL_TO_CTYPES
 
@@ -207,7 +205,7 @@ class GDALBand(GDALBase):
             access_flag = 1
 
             # Instantiate ctypes array holding the input data
-            if isinstance(data, (bytes, six.memoryview)) or (numpy and isinstance(data, numpy.ndarray)):
+            if isinstance(data, (bytes, memoryview)) or (numpy and isinstance(data, numpy.ndarray)):
                 data_array = ctypes_array.from_buffer_copy(data)
             else:
                 data_array = ctypes_array(*data)
diff --git a/django/contrib/gis/geos/collections.py b/django/contrib/gis/geos/collections.py
index 4935dbd1c3..e964a1de8f 100644
--- a/django/contrib/gis/geos/collections.py
+++ b/django/contrib/gis/geos/collections.py
@@ -12,7 +12,6 @@ from django.contrib.gis.geos.libgeos import geos_version_info, get_pointer_arr
 from django.contrib.gis.geos.linestring import LinearRing, LineString
 from django.contrib.gis.geos.point import Point
 from django.contrib.gis.geos.polygon import Polygon
-from django.utils.six.moves import range
 
 
 class GeometryCollection(GEOSGeometry):
diff --git a/django/contrib/gis/geos/coordseq.py b/django/contrib/gis/geos/coordseq.py
index 8722874099..6908b040c0 100644
--- a/django/contrib/gis/geos/coordseq.py
+++ b/django/contrib/gis/geos/coordseq.py
@@ -10,7 +10,6 @@ from django.contrib.gis.geos.base import GEOSBase
 from django.contrib.gis.geos.error import GEOSException
 from django.contrib.gis.geos.libgeos import CS_PTR
 from django.contrib.gis.shortcuts import numpy
-from django.utils.six.moves import range
 
 
 class GEOSCoordSeq(GEOSBase):
diff --git a/django/contrib/gis/geos/factory.py b/django/contrib/gis/geos/factory.py
index 42cd10c756..54c19aab37 100644
--- a/django/contrib/gis/geos/factory.py
+++ b/django/contrib/gis/geos/factory.py
@@ -1,5 +1,4 @@
 from django.contrib.gis.geos.geometry import GEOSGeometry, hex_regex, wkt_regex
-from django.utils import six
 
 
 def fromfile(file_h):
@@ -25,7 +24,7 @@ def fromfile(file_h):
     else:
         return GEOSGeometry(buf)
 
-    return GEOSGeometry(six.memoryview(buf))
+    return GEOSGeometry(memoryview(buf))
 
 
 def fromstr(string, **kwargs):
diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py
index f7bdf8937f..f49ce2e0de 100644
--- a/django/contrib/gis/geos/geometry.py
+++ b/django/contrib/gis/geos/geometry.py
@@ -17,7 +17,6 @@ from django.contrib.gis.geos.prepared import PreparedGeometry
 from django.contrib.gis.geos.prototypes.io import (
     ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w,
 )
-from django.utils import six
 from django.utils.deconstruct import deconstructible
 from django.utils.encoding import force_bytes, force_text
 
@@ -67,7 +66,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
         elif isinstance(geo_input, GEOM_PTR):
             # When the input is a pointer to a geometry (GEOM_PTR).
             g = geo_input
-        elif isinstance(geo_input, six.memoryview):
+        elif isinstance(geo_input, memoryview):
             # When the input is a buffer (WKB).
             g = wkb_r().read(geo_input)
         elif isinstance(geo_input, GEOSGeometry):
@@ -149,7 +148,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
     def __setstate__(self, state):
         # Instantiating from the tuple state that was pickled.
         wkb, srid = state
-        ptr = wkb_r().read(six.memoryview(wkb))
+        ptr = wkb_r().read(memoryview(wkb))
         if not ptr:
             raise GEOSException('Invalid Geometry loaded from pickled state.')
         self.ptr = ptr
diff --git a/django/contrib/gis/geos/linestring.py b/django/contrib/gis/geos/linestring.py
index 7bfc004370..6caf6bef34 100644
--- a/django/contrib/gis/geos/linestring.py
+++ b/django/contrib/gis/geos/linestring.py
@@ -4,7 +4,6 @@ from django.contrib.gis.geos.error import GEOSException
 from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin
 from django.contrib.gis.geos.point import Point
 from django.contrib.gis.shortcuts import numpy
-from django.utils.six.moves import range
 
 
 class LineString(LinearGeometryMixin, GEOSGeometry):
diff --git a/django/contrib/gis/geos/mutable_list.py b/django/contrib/gis/geos/mutable_list.py
index 8896ebc518..082f3b161a 100644
--- a/django/contrib/gis/geos/mutable_list.py
+++ b/django/contrib/gis/geos/mutable_list.py
@@ -10,8 +10,6 @@ Author: Aryeh Leib Taurog.
 """
 from functools import total_ordering
 
-from django.utils.six.moves import range
-
 
 @total_ordering
 class ListMixin(object):
diff --git a/django/contrib/gis/geos/point.py b/django/contrib/gis/geos/point.py
index fadf5eb414..72de47f9f6 100644
--- a/django/contrib/gis/geos/point.py
+++ b/django/contrib/gis/geos/point.py
@@ -4,7 +4,6 @@ from django.contrib.gis import gdal
 from django.contrib.gis.geos import prototypes as capi
 from django.contrib.gis.geos.error import GEOSException
 from django.contrib.gis.geos.geometry import GEOSGeometry
-from django.utils.six.moves import range
 
 
 class Point(GEOSGeometry):
diff --git a/django/contrib/gis/geos/polygon.py b/django/contrib/gis/geos/polygon.py
index 0b4de0d3c3..58f1b28c29 100644
--- a/django/contrib/gis/geos/polygon.py
+++ b/django/contrib/gis/geos/polygon.py
@@ -4,7 +4,6 @@ from django.contrib.gis.geos import prototypes as capi
 from django.contrib.gis.geos.geometry import GEOSGeometry
 from django.contrib.gis.geos.libgeos import GEOM_PTR, get_pointer_arr
 from django.contrib.gis.geos.linestring import LinearRing
-from django.utils.six.moves import range
 
 
 class Polygon(GEOSGeometry):
diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py
index 1cc0ccb8f3..b8b1a06dba 100644
--- a/django/contrib/gis/geos/prototypes/io.py
+++ b/django/contrib/gis/geos/prototypes/io.py
@@ -7,7 +7,6 @@ from django.contrib.gis.geos.prototypes.errcheck import (
     check_geom, check_sized_string, check_string,
 )
 from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p
-from django.utils import six
 from django.utils.encoding import force_bytes
 
 
@@ -147,7 +146,7 @@ class _WKBReader(IOBase):
 
     def read(self, wkb):
         "Returns a _pointer_ to C GEOS Geometry object from the given WKB."
-        if isinstance(wkb, six.memoryview):
+        if isinstance(wkb, memoryview):
             wkb_s = bytes(wkb)
             return wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
         elif isinstance(wkb, (bytes, str)):
@@ -240,7 +239,7 @@ class WKBWriter(IOBase):
             # Fix GEOS output for empty polygon.
             # See https://trac.osgeo.org/geos/ticket/680.
             wkb = wkb[:-8] + b'\0' * 4
-        return six.memoryview(wkb)
+        return memoryview(wkb)
 
     def write_hex(self, geom):
         "Returns the HEXEWKB representation of the given geometry."
diff --git a/django/contrib/gis/geos/prototypes/misc.py b/django/contrib/gis/geos/prototypes/misc.py
index 4a82888d09..2d890c3d31 100644
--- a/django/contrib/gis/geos/prototypes/misc.py
+++ b/django/contrib/gis/geos/prototypes/misc.py
@@ -7,7 +7,6 @@ from ctypes import POINTER, c_double, c_int
 from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory
 from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string
 from django.contrib.gis.geos.prototypes.geom import geos_char_p
-from django.utils.six.moves import range
 
 __all__ = ['geos_area', 'geos_distance', 'geos_length', 'geos_isvalidreason']
 
diff --git a/django/contrib/gis/measure.py b/django/contrib/gis/measure.py
index 8691d009a6..a9b9e8a75f 100644
--- a/django/contrib/gis/measure.py
+++ b/django/contrib/gis/measure.py
@@ -38,8 +38,6 @@ and Geoff Biggs' PhD work on dimensioned units for robotics.
 from decimal import Decimal
 from functools import total_ordering
 
-from django.utils import six
-
 __all__ = ['A', 'Area', 'D', 'Distance']
 
 NUMERIC_TYPES = (int, float, Decimal)
@@ -187,7 +185,7 @@ class MeasureBase(object):
         """
         val = 0.0
         default_unit = self.STANDARD_UNIT
-        for unit, value in six.iteritems(kwargs):
+        for unit, value in kwargs.items():
             if not isinstance(value, float):
                 value = float(value)
             if unit in self.UNITS:
diff --git a/django/contrib/gis/utils/ogrinspect.py b/django/contrib/gis/utils/ogrinspect.py
index c10364491d..52e0e655e4 100644
--- a/django/contrib/gis/utils/ogrinspect.py
+++ b/django/contrib/gis/utils/ogrinspect.py
@@ -8,7 +8,6 @@ from django.contrib.gis.gdal.field import (
     OFTDate, OFTDateTime, OFTInteger, OFTInteger64, OFTReal, OFTString,
     OFTTime,
 )
-from django.utils.six.moves import zip
 
 
 def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False):
diff --git a/django/contrib/messages/storage/cookie.py b/django/contrib/messages/storage/cookie.py
index dec609dffa..b8d7f7474c 100644
--- a/django/contrib/messages/storage/cookie.py
+++ b/django/contrib/messages/storage/cookie.py
@@ -3,7 +3,6 @@ import json
 from django.conf import settings
 from django.contrib.messages.storage.base import BaseStorage, Message
 from django.http import SimpleCookie
-from django.utils import six
 from django.utils.crypto import constant_time_compare, salted_hmac
 from django.utils.safestring import SafeData, mark_safe
 
@@ -42,7 +41,7 @@ class MessageDecoder(json.JSONDecoder):
             return [self.process_messages(item) for item in obj]
         if isinstance(obj, dict):
             return {key: self.process_messages(value)
-                    for key, value in six.iteritems(obj)}
+                    for key, value in obj.items()}
         return obj
 
     def decode(self, s, **kwargs):
diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py
index d50a17cdd8..c64d7f6a6c 100644
--- a/django/contrib/sessions/backends/cache.py
+++ b/django/contrib/sessions/backends/cache.py
@@ -3,7 +3,6 @@ from django.contrib.sessions.backends.base import (
     CreateError, SessionBase, UpdateError,
 )
 from django.core.cache import caches
-from django.utils.six.moves import range
 
 KEY_PREFIX = "django.contrib.sessions.cache"
 
diff --git a/django/contrib/sessions/serializers.py b/django/contrib/sessions/serializers.py
index b272c9c95d..1badd17f46 100644
--- a/django/contrib/sessions/serializers.py
+++ b/django/contrib/sessions/serializers.py
@@ -1,9 +1,6 @@
-from django.core.signing import JSONSerializer as BaseJSONSerializer
+import pickle
 
-try:
-    from django.utils.six.moves import cPickle as pickle
-except ImportError:
-    import pickle
+from django.core.signing import JSONSerializer as BaseJSONSerializer
 
 
 class PickleSerializer(object):
diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
index 5721c8861d..6e30a30f09 100644
--- a/django/contrib/sitemaps/__init__.py
+++ b/django/contrib/sitemaps/__init__.py
@@ -1,11 +1,12 @@
+from urllib.parse import urlencode
+from urllib.request import urlopen
+
 from django.apps import apps as django_apps
 from django.conf import settings
 from django.core import paginator
 from django.core.exceptions import ImproperlyConfigured
 from django.urls import NoReverseMatch, reverse
 from django.utils import translation
-from django.utils.six.moves.urllib.parse import urlencode
-from django.utils.six.moves.urllib.request import urlopen
 
 PING_URL = "https://www.google.com/webmasters/tools/ping"
 
diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py
index 81a919bf04..c3da95ddd3 100644
--- a/django/contrib/staticfiles/finders.py
+++ b/django/contrib/staticfiles/finders.py
@@ -8,7 +8,7 @@ from django.core.exceptions import ImproperlyConfigured
 from django.core.files.storage import (
     FileSystemStorage, Storage, default_storage,
 )
-from django.utils import lru_cache, six
+from django.utils import lru_cache
 from django.utils._os import safe_join
 from django.utils.functional import LazyObject, empty
 from django.utils.module_loading import import_string
@@ -143,7 +143,7 @@ class AppDirectoriesFinder(BaseFinder):
         """
         List all files in all app storages.
         """
-        for storage in six.itervalues(self.storages):
+        for storage in self.storages.values():
             if storage.exists(''):  # check if storage location exists
                 for path in utils.get_files(storage, ignore_patterns):
                     yield path, storage
diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
index 8dedd60ce8..ce8b7e20c1 100644
--- a/django/contrib/staticfiles/handlers.py
+++ b/django/contrib/staticfiles/handlers.py
@@ -1,9 +1,10 @@
+from urllib.parse import urlparse
+from urllib.request import url2pathname
+
 from django.conf import settings
 from django.contrib.staticfiles import utils
 from django.contrib.staticfiles.views import serve
 from django.core.handlers.wsgi import WSGIHandler, get_path_info
-from django.utils.six.moves.urllib.parse import urlparse
-from django.utils.six.moves.urllib.request import url2pathname
 
 
 class StaticFilesHandler(WSGIHandler):
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
index fac96e5bf4..3ee6d354af 100644
--- a/django/contrib/staticfiles/management/commands/collectstatic.py
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -9,7 +9,6 @@ from django.core.management.base import BaseCommand, CommandError
 from django.core.management.color import no_style
 from django.utils.encoding import force_text
 from django.utils.functional import cached_property
-from django.utils.six.moves import input
 
 
 class Command(BaseCommand):
diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
index b033243ecc..fe4a7c39f7 100644
--- a/django/contrib/staticfiles/storage.py
+++ b/django/contrib/staticfiles/storage.py
@@ -4,6 +4,7 @@ import os
 import posixpath
 import re
 from collections import OrderedDict
+from urllib.parse import unquote, urldefrag, urlsplit, urlunsplit
 
 from django.conf import settings
 from django.contrib.staticfiles.utils import check_settings, matches_patterns
@@ -15,11 +16,6 @@ from django.core.files.base import ContentFile
 from django.core.files.storage import FileSystemStorage, get_storage_class
 from django.utils.encoding import force_bytes, force_text
 from django.utils.functional import LazyObject
-from django.utils.six import iteritems
-from django.utils.six.moves import range
-from django.utils.six.moves.urllib.parse import (
-    unquote, urldefrag, urlsplit, urlunsplit,
-)
 
 
 class StaticFilesStorage(FileSystemStorage):
@@ -293,7 +289,7 @@ class HashedFilesMixin(object):
                 if name in adjustable_paths:
                     old_hashed_name = hashed_name
                     content = original_file.read().decode(settings.FILE_CHARSET)
-                    for extension, patterns in iteritems(self._patterns):
+                    for extension, patterns in self._patterns.items():
                         if matches_patterns(path, (extension,)):
                             for pattern, template in patterns:
                                 converter = self.url_converter(name, hashed_files, template)
diff --git a/django/contrib/staticfiles/views.py b/django/contrib/staticfiles/views.py
index cec1247e5e..dfdbdac9e9 100644
--- a/django/contrib/staticfiles/views.py
+++ b/django/contrib/staticfiles/views.py
@@ -5,11 +5,11 @@ development, and SHOULD NOT be used in a production setting.
 """
 import os
 import posixpath
+from urllib.parse import unquote
 
 from django.conf import settings
 from django.contrib.staticfiles import finders
 from django.http import Http404
-from django.utils.six.moves.urllib.parse import unquote
 from django.views import static
 
 
diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py
index fa9c5e7753..2cfe930338 100644
--- a/django/contrib/syndication/views.py
+++ b/django/contrib/syndication/views.py
@@ -5,7 +5,7 @@ from django.contrib.sites.shortcuts import get_current_site
 from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
 from django.http import Http404, HttpResponse
 from django.template import TemplateDoesNotExist, loader
-from django.utils import feedgenerator, six
+from django.utils import feedgenerator
 from django.utils.encoding import force_text, iri_to_uri
 from django.utils.html import escape
 from django.utils.http import http_date
@@ -83,9 +83,9 @@ class Feed(object):
             # catching the TypeError, because something inside the function
             # may raise the TypeError. This technique is more accurate.
             try:
-                code = six.get_function_code(attr)
+                code = attr.__code__
             except AttributeError:
-                code = six.get_function_code(attr.__call__)
+                code = attr.__call__.__code__
             if code.co_argcount == 2:       # one argument is 'self'
                 return attr(obj)
             else:
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index ad805324bd..97f307e24d 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -1,5 +1,6 @@
 "Database cache backend."
 import base64
+import pickle
 from datetime import datetime
 
 from django.conf import settings
@@ -8,11 +9,6 @@ from django.db import DatabaseError, connections, models, router, transaction
 from django.utils import timezone
 from django.utils.encoding import force_bytes
 
-try:
-    from django.utils.six.moves import cPickle as pickle
-except ImportError:
-    import pickle
-
 
 class Options(object):
     """A class that will quack like a Django model _meta class.
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 7c2c5c7edb..88509ed8de 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -4,6 +4,7 @@ import glob
 import hashlib
 import io
 import os
+import pickle
 import random
 import tempfile
 import time
@@ -13,11 +14,6 @@ from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
 from django.core.files.move import file_move_safe
 from django.utils.encoding import force_bytes
 
-try:
-    from django.utils.six.moves import cPickle as pickle
-except ImportError:
-    import pickle
-
 
 class FileBasedCache(BaseCache):
     cache_suffix = '.djcache'
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index 8916ad176c..cf86dcaa0c 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -1,17 +1,12 @@
 "Thread-safe in-memory cache backend."
 
+import pickle
 import time
 from contextlib import contextmanager
 
 from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
 from django.utils.synch import RWLock
 
-try:
-    from django.utils.six.moves import cPickle as pickle
-except ImportError:
-    import pickle
-
-
 # Global in-memory store of cache data. Keyed by name, to provide
 # multiple named local memory caches.
 _caches = {}
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index 9cd4196c23..cf17eea339 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -1,6 +1,7 @@
 import errno
 import os
 from datetime import datetime
+from urllib.parse import urljoin
 
 from django.conf import settings
 from django.core.exceptions import SuspiciousFileOperation
@@ -14,7 +15,6 @@ from django.utils.deconstruct import deconstructible
 from django.utils.encoding import filepath_to_uri, force_text
 from django.utils.functional import LazyObject, cached_property
 from django.utils.module_loading import import_string
-from django.utils.six.moves.urllib.parse import urljoin
 from django.utils.text import get_valid_filename
 
 __all__ = ('Storage', 'FileSystemStorage', 'DefaultStorage', 'default_storage')
diff --git a/django/core/mail/message.py b/django/core/mail/message.py
index a8ea1f0991..117f2dddfc 100644
--- a/django/core/mail/message.py
+++ b/django/core/mail/message.py
@@ -14,11 +14,10 @@ from email.mime.message import MIMEMessage
 from email.mime.multipart import MIMEMultipart
 from email.mime.text import MIMEText
 from email.utils import formatdate, getaddresses, parseaddr
-from io import BytesIO
+from io import BytesIO, StringIO
 
 from django.conf import settings
 from django.core.mail.utils import DNS_NAME
-from django.utils import six
 from django.utils.encoding import force_text
 
 # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
@@ -164,7 +163,7 @@ class MIMEMixin():
         This overrides the default as_string() implementation to not mangle
         lines that begin with 'From '. See bug #13433 for details.
         """
-        fp = six.StringIO()
+        fp = StringIO()
         g = generator.Generator(fp, mangle_from_=False)
         g.flatten(self, unixfrom=unixfrom, linesep=linesep)
         return fp.getvalue()
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index 6b89fff351..00ee58a34b 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -12,7 +12,7 @@ from django.core.management.base import (
     BaseCommand, CommandError, CommandParser, handle_default_options,
 )
 from django.core.management.color import color_style
-from django.utils import autoreload, lru_cache, six
+from django.utils import autoreload, lru_cache
 from django.utils._os import npath, upath
 from django.utils.encoding import force_text
 
@@ -154,7 +154,7 @@ class ManagementUtility(object):
                 "Available subcommands:",
             ]
             commands_dict = defaultdict(lambda: [])
-            for name, app in six.iteritems(get_commands()):
+            for name, app in get_commands().items():
                 if app == 'django.core':
                     app = 'django'
                 else:
diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py
index dbe870f322..8d1621b116 100644
--- a/django/core/management/commands/flush.py
+++ b/django/core/management/commands/flush.py
@@ -7,7 +7,6 @@ from django.core.management.color import no_style
 from django.core.management.sql import emit_post_migrate_signal, sql_flush
 from django.db import DEFAULT_DB_ALIAS, connections, transaction
 from django.utils import six
-from django.utils.six.moves import input
 
 
 class Command(BaseCommand):
diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py
index b6a31d16fd..3364d958a9 100644
--- a/django/core/management/commands/makemigrations.py
+++ b/django/core/management/commands/makemigrations.py
@@ -17,8 +17,6 @@ from django.db.migrations.questioner import (
 from django.db.migrations.state import ProjectState
 from django.db.migrations.utils import get_migration_name_timestamp
 from django.db.migrations.writer import MigrationWriter
-from django.utils.six import iteritems
-from django.utils.six.moves import zip
 
 
 class Command(BaseCommand):
@@ -102,7 +100,7 @@ class Command(BaseCommand):
         # If app_labels is specified, filter out conflicting migrations for unspecified apps
         if app_labels:
             conflicts = {
-                app_label: conflict for app_label, conflict in iteritems(conflicts)
+                app_label: conflict for app_label, conflict in conflicts.items()
                 if app_label in app_labels
             }
 
diff --git a/django/core/management/commands/squashmigrations.py b/django/core/management/commands/squashmigrations.py
index dacc8fe6ce..5556b24745 100644
--- a/django/core/management/commands/squashmigrations.py
+++ b/django/core/management/commands/squashmigrations.py
@@ -7,7 +7,6 @@ from django.db.migrations.loader import AmbiguityError, MigrationLoader
 from django.db.migrations.migration import SwappableTuple
 from django.db.migrations.optimizer import MigrationOptimizer
 from django.db.migrations.writer import MigrationWriter
-from django.utils import six
 from django.utils.version import get_docs_version
 
 
@@ -86,7 +85,7 @@ class Command(BaseCommand):
             if self.interactive:
                 answer = None
                 while not answer or answer not in "yn":
-                    answer = six.moves.input("Do you wish to proceed? [yN] ")
+                    answer = input("Do you wish to proceed? [yN] ")
                     if not answer:
                         answer = "n"
                         break
diff --git a/django/core/management/templates.py b/django/core/management/templates.py
index 775e75201b..ccc4920fe3 100644
--- a/django/core/management/templates.py
+++ b/django/core/management/templates.py
@@ -10,6 +10,7 @@ import stat
 import sys
 import tempfile
 from os import path
+from urllib.request import urlretrieve
 
 import django
 from django.conf import settings
@@ -17,7 +18,6 @@ from django.core.management.base import BaseCommand, CommandError
 from django.core.management.utils import handle_extensions
 from django.template import Context, Engine
 from django.utils import archive
-from django.utils.six.moves.urllib.request import urlretrieve
 from django.utils.version import get_docs_version
 
 _drive_re = re.compile('^([a-z]):', re.I)
diff --git a/django/core/paginator.py b/django/core/paginator.py
index ae085bf837..82aad33a15 100644
--- a/django/core/paginator.py
+++ b/django/core/paginator.py
@@ -2,7 +2,6 @@ import collections
 import warnings
 from math import ceil
 
-from django.utils import six
 from django.utils.functional import cached_property
 from django.utils.translation import ugettext_lazy as _
 
@@ -99,7 +98,7 @@ class Paginator(object):
         Returns a 1-based range of pages for iterating through within
         a template for loop.
         """
-        return six.moves.range(1, self.num_pages + 1)
+        return range(1, self.num_pages + 1)
 
     def _check_object_list_is_ordered(self):
         """
diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py
index e62dbf75da..57d149cf0c 100644
--- a/django/core/serializers/__init__.py
+++ b/django/core/serializers/__init__.py
@@ -21,7 +21,6 @@ import importlib
 from django.apps import apps
 from django.conf import settings
 from django.core.serializers.base import SerializerDoesNotExist
-from django.utils import six
 
 # Built-in serializers
 BUILTIN_SERIALIZERS = {
@@ -109,7 +108,7 @@ def get_serializer_formats():
 def get_public_serializer_formats():
     if not _serializers:
         _load_serializers()
-    return [k for k, v in six.iteritems(_serializers) if not v.Serializer.internal_use_only]
+    return [k for k, v in _serializers.items() if not v.Serializer.internal_use_only]
 
 
 def get_deserializer(format):
diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py
index 2a10fbe19e..bd35c0b797 100644
--- a/django/core/serializers/base.py
+++ b/django/core/serializers/base.py
@@ -1,8 +1,9 @@
 """
 Module for abstract serializer/unserializer base classes.
 """
+from io import StringIO
+
 from django.db import models
-from django.utils import six
 
 
 class SerializerDoesNotExist(KeyError):
@@ -59,7 +60,7 @@ class Serializer(object):
     # internal Django use.
     internal_use_only = False
     progress_class = ProgressBar
-    stream_class = six.StringIO
+    stream_class = StringIO
 
     def serialize(self, queryset, **options):
         """
@@ -158,7 +159,7 @@ class Serializer(object):
             return self.stream.getvalue()
 
 
-class Deserializer(six.Iterator):
+class Deserializer:
     """
     Abstract base deserializer class.
     """
@@ -169,7 +170,7 @@ class Deserializer(six.Iterator):
         """
         self.options = options
         if isinstance(stream_or_string, str):
-            self.stream = six.StringIO(stream_or_string)
+            self.stream = StringIO(stream_or_string)
         else:
             self.stream = stream_or_string
 
diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py
index 9db5c2e6a6..7b7510be41 100644
--- a/django/core/serializers/python.py
+++ b/django/core/serializers/python.py
@@ -9,7 +9,6 @@ from django.apps import apps
 from django.conf import settings
 from django.core.serializers import base
 from django.db import DEFAULT_DB_ALIAS, models
-from django.utils import six
 from django.utils.encoding import force_text, is_protected_type
 
 
@@ -113,7 +112,7 @@ def Deserializer(object_list, **options):
         field_names = field_names_cache[Model]
 
         # Handle each field
-        for (field_name, field_value) in six.iteritems(d["fields"]):
+        for (field_name, field_value) in d["fields"].items():
 
             if ignore and field_name not in field_names:
                 # skip fields no longer on model
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index e27378e7a5..da55eac58d 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -9,6 +9,7 @@ been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE!
 
 import logging
 import socket
+import socketserver
 import sys
 from wsgiref import simple_server
 
@@ -16,7 +17,6 @@ from django.core.exceptions import ImproperlyConfigured
 from django.core.wsgi import get_wsgi_application
 from django.utils import six
 from django.utils.module_loading import import_string
-from django.utils.six.moves import socketserver
 
 __all__ = ('WSGIServer', 'WSGIRequestHandler')
 
diff --git a/django/core/validators.py b/django/core/validators.py
index e2fcd6f72e..6e7220c826 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -1,12 +1,12 @@
 import os
 import re
+from urllib.parse import urlsplit, urlunsplit
 
 from django.core.exceptions import ValidationError
 from django.utils.deconstruct import deconstructible
 from django.utils.encoding import force_text
 from django.utils.functional import SimpleLazyObject
 from django.utils.ipv6 import is_valid_ipv6_address
-from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
 
 # These values, if given to validate(), will trigger the self.required check.
diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py
index c50cf5266a..ea976519ec 100644
--- a/django/db/backends/base/base.py
+++ b/django/db/backends/base/base.py
@@ -4,6 +4,7 @@ import warnings
 from collections import deque
 from contextlib import contextmanager
 
+import _thread
 import pytz
 
 from django.conf import settings
@@ -16,7 +17,6 @@ from django.db.transaction import TransactionManagementError
 from django.db.utils import DatabaseError, DatabaseErrorWrapper
 from django.utils import timezone
 from django.utils.functional import cached_property
-from django.utils.six.moves import _thread as thread
 
 NO_DB_ALIAS = '__no_db__'
 
@@ -82,7 +82,7 @@ class BaseDatabaseWrapper(object):
 
         # Thread-safety related attributes.
         self.allow_thread_sharing = allow_thread_sharing
-        self._thread_ident = thread.get_ident()
+        self._thread_ident = _thread.get_ident()
 
         # A list of no-argument functions to run when the transaction commits.
         # Each entry is an (sids, func) tuple, where sids is a set of the
@@ -326,7 +326,7 @@ class BaseDatabaseWrapper(object):
         if not self._savepoint_allowed():
             return
 
-        thread_ident = thread.get_ident()
+        thread_ident = _thread.get_ident()
         tid = str(thread_ident).replace('-', '')
 
         self.savepoint_state += 1
@@ -533,13 +533,13 @@ class BaseDatabaseWrapper(object):
         authorized to be shared between threads (via the `allow_thread_sharing`
         property). Raises an exception if the validation fails.
         """
-        if not (self.allow_thread_sharing or self._thread_ident == thread.get_ident()):
+        if not (self.allow_thread_sharing or self._thread_ident == _thread.get_ident()):
             raise DatabaseError(
                 "DatabaseWrapper objects created in a "
                 "thread can only be used in that same thread. The object "
                 "with alias '%s' was created in thread id %s and this is "
                 "thread id %s."
-                % (self.alias, self._thread_ident, thread.get_ident())
+                % (self.alias, self._thread_ident, _thread.get_ident())
             )
 
     # ##### Miscellaneous #####
diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py
index 0cb6d75ff3..cf3e6c3347 100644
--- a/django/db/backends/base/creation.py
+++ b/django/db/backends/base/creation.py
@@ -1,11 +1,10 @@
 import sys
+from io import StringIO
 
 from django.apps import apps
 from django.conf import settings
 from django.core import serializers
 from django.db import router
-from django.utils.six import StringIO
-from django.utils.six.moves import input
 
 # The prefix to put on the default database name when creating
 # the test database.
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index c365c673c5..257dab0b9d 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -516,7 +516,7 @@ class FormatStylePlaceholderCursor(object):
         return CursorIterator(self.cursor)
 
 
-class CursorIterator(six.Iterator):
+class CursorIterator:
     """
     Cursor iterator wrapper that invokes our custom row factory.
     """
diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
index d34f2a2d50..adbf92b6a3 100644
--- a/django/db/backends/oracle/creation.py
+++ b/django/db/backends/oracle/creation.py
@@ -5,7 +5,6 @@ from django.db.backends.base.creation import BaseDatabaseCreation
 from django.db.utils import DatabaseError
 from django.utils.crypto import get_random_string
 from django.utils.functional import cached_property
-from django.utils.six.moves import input
 
 TEST_DATABASE_PREFIX = 'test_'
 
diff --git a/django/db/backends/postgresql/client.py b/django/db/backends/postgresql/client.py
index dd3d40c064..005f43ddb8 100644
--- a/django/db/backends/postgresql/client.py
+++ b/django/db/backends/postgresql/client.py
@@ -3,7 +3,6 @@ import subprocess
 
 from django.core.files.temp import NamedTemporaryFile
 from django.db.backends.base.client import BaseDatabaseClient
-from django.utils.six import print_
 
 
 def _escape_pgpass(txt):
@@ -40,7 +39,7 @@ class DatabaseClient(BaseDatabaseClient):
                 # Create temporary .pgpass file.
                 temp_pgpass = NamedTemporaryFile(mode='w+')
                 try:
-                    print_(
+                    print(
                         _escape_pgpass(host) or '*',
                         str(port) or '*',
                         _escape_pgpass(dbname) or '*',
diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
index 33661e1357..e58a1303a7 100644
--- a/django/db/backends/sqlite3/creation.py
+++ b/django/db/backends/sqlite3/creation.py
@@ -5,7 +5,6 @@ import sys
 from django.core.exceptions import ImproperlyConfigured
 from django.db.backends.base.creation import BaseDatabaseCreation
 from django.utils.encoding import force_text
-from django.utils.six.moves import input
 
 
 class DatabaseCreation(BaseDatabaseCreation):
diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py
index 8a42d96e9a..492ad145fd 100644
--- a/django/db/migrations/loader.py
+++ b/django/db/migrations/loader.py
@@ -1,6 +1,6 @@
 import os
 import sys
-from importlib import import_module
+from importlib import import_module, reload
 
 from django.apps import apps
 from django.conf import settings
@@ -97,7 +97,7 @@ class MigrationLoader(object):
                     continue
                 # Force a reload if it's already loaded (tests need this)
                 if was_loaded:
-                    six.moves.reload_module(module)
+                    reload(module)
             self.migrated_apps.add(app_config.label)
             directory = os.path.dirname(module.__file__)
             # Scan for .py files
diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py
index 17b9da8eaf..caae498337 100644
--- a/django/db/migrations/questioner.py
+++ b/django/db/migrations/questioner.py
@@ -5,7 +5,6 @@ import sys
 from django.apps import apps
 from django.db.models.fields import NOT_PROVIDED
 from django.utils import datetime_safe, timezone
-from django.utils.six.moves import input
 
 from .loader import MigrationLoader
 
diff --git a/django/db/migrations/serializer.py b/django/db/migrations/serializer.py
index bcd2268e2a..4786e7b012 100644
--- a/django/db/migrations/serializer.py
+++ b/django/db/migrations/serializer.py
@@ -1,3 +1,4 @@
+import builtins
 import collections
 import datetime
 import decimal
@@ -10,7 +11,7 @@ from importlib import import_module
 from django.db import models
 from django.db.migrations.operations.base import Operation
 from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
-from django.utils import datetime_safe, six
+from django.utils import datetime_safe
 from django.utils.encoding import force_text
 from django.utils.functional import LazyObject, Promise
 from django.utils.timezone import utc
@@ -305,7 +306,7 @@ class TypeSerializer(BaseSerializer):
                 return string, set(imports)
         if hasattr(self.value, "__module__"):
             module = self.value.__module__
-            if module == six.moves.builtins.__name__:
+            if module == builtins.__name__:
                 return self.value.__name__, set()
             else:
                 return "%s.%s" % (module, self.value.__name__), {"import %s" % module}
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 95700edb13..b5f6c1e353 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -26,10 +26,8 @@ from django.db.models.signals import (
     class_prepared, post_init, post_save, pre_init, pre_save,
 )
 from django.db.models.utils import make_model_tuple
-from django.utils import six
 from django.utils.encoding import force_str, force_text
 from django.utils.functional import curry
-from django.utils.six.moves import zip
 from django.utils.text import capfirst, get_text_list
 from django.utils.translation import ugettext_lazy as _
 from django.utils.version import get_version
@@ -385,7 +383,7 @@ class ModelState(object):
         self.adding = True
 
 
-class Model(six.with_metaclass(ModelBase)):
+class Model(metaclass=ModelBase):
 
     def __init__(self, *args, **kwargs):
         # Alias some things as locals to avoid repeat global lookups
diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py
index 26073be3ba..0793475e65 100644
--- a/django/db/models/deletion.py
+++ b/django/db/models/deletion.py
@@ -3,7 +3,6 @@ from operator import attrgetter
 
 from django.db import IntegrityError, connections, transaction
 from django.db.models import signals, sql
-from django.utils import six
 
 
 class ProtectedError(IntegrityError):
@@ -198,7 +197,7 @@ class Collector(object):
             # Recursively collect concrete model's parent models, but not their
             # related objects. These will be found by meta.get_fields()
             concrete_model = model._meta.concrete_model
-            for ptr in six.itervalues(concrete_model._meta.parents):
+            for ptr in concrete_model._meta.parents.values():
                 if ptr:
                     parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
                     self.collect(parent_objs, source=model,
@@ -236,7 +235,7 @@ class Collector(object):
         )
 
     def instances_with_model(self):
-        for model, instances in six.iteritems(self.data):
+        for model, instances in self.data.items():
             for obj in instances:
                 yield model, obj
 
@@ -285,18 +284,18 @@ class Collector(object):
                 deleted_counter[qs.model._meta.label] += count
 
             # update fields
-            for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
+            for model, instances_for_fieldvalues in self.field_updates.items():
                 query = sql.UpdateQuery(model)
-                for (field, value), instances in six.iteritems(instances_for_fieldvalues):
+                for (field, value), instances in instances_for_fieldvalues.items():
                     query.update_batch([obj.pk for obj in instances],
                                        {field.name: value}, self.using)
 
             # reverse instance collections
-            for instances in six.itervalues(self.data):
+            for instances in self.data.values():
                 instances.reverse()
 
             # delete instances
-            for model, instances in six.iteritems(self.data):
+            for model, instances in self.data.items():
                 query = sql.DeleteQuery(model)
                 pk_list = [obj.pk for obj in instances]
                 count = query.delete_batch(pk_list, self.using)
@@ -309,11 +308,11 @@ class Collector(object):
                         )
 
         # update collected instances
-        for model, instances_for_fieldvalues in six.iteritems(self.field_updates):
-            for (field, value), instances in six.iteritems(instances_for_fieldvalues):
+        for model, instances_for_fieldvalues in self.field_updates.items():
+            for (field, value), instances in instances_for_fieldvalues.items():
                 for obj in instances:
                     setattr(obj, field.attname, value)
-        for model, instances in six.iteritems(self.data):
+        for model, instances in self.data.items():
             for instance in instances:
                 setattr(instance, model._meta.pk.attname, None)
         return sum(deleted_counter.values()), dict(deleted_counter)
diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py
index b8093ddc34..4b09143661 100644
--- a/django/db/models/lookups.py
+++ b/django/db/models/lookups.py
@@ -10,7 +10,6 @@ from django.db.models.fields import (
 )
 from django.db.models.query_utils import RegisterLookupMixin
 from django.utils.functional import cached_property
-from django.utils.six.moves import range
 
 
 class Lookup(object):
diff --git a/django/db/models/options.py b/django/db/models/options.py
index cf3e031104..7594b70fb2 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -13,7 +13,6 @@ from django.db.models.fields import AutoField
 from django.db.models.fields.proxy import OrderWrt
 from django.db.models.fields.related import OneToOneField
 from django.db.models.query_utils import PathInfo
-from django.utils import six
 from django.utils.datastructures import ImmutableList, OrderedSet
 from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.encoding import force_text
@@ -228,7 +227,7 @@ class Options(object):
             if self.parents:
                 # Promote the first parent link in lieu of adding yet another
                 # field.
-                field = next(six.itervalues(self.parents))
+                field = next(iter(self.parents.values()))
                 # Look for a local field with the same name as the
                 # first parent link. If a local field has already been
                 # created, use it instead of promoting the parent
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 69f84d72a9..1c20d9cbf6 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -480,7 +480,7 @@ class QuerySet(object):
                 obj, created = self._create_object_from_params(lookup, params)
                 if created:
                     return obj, created
-            for k, v in six.iteritems(defaults):
+            for k, v in defaults.items():
                 setattr(obj, k, v() if callable(v) else v)
             obj.save(using=self.db)
         return obj, False
@@ -1170,7 +1170,7 @@ class InstanceCheckMeta(type):
         return isinstance(instance, QuerySet) and instance.query.is_empty()
 
 
-class EmptyQuerySet(six.with_metaclass(InstanceCheckMeta)):
+class EmptyQuerySet(metaclass=InstanceCheckMeta):
     """
     Marker class usable for checking if a queryset is empty by .none():
         isinstance(qs.none(), EmptyQuerySet) -> True
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 37442c06c4..41733210c4 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -11,7 +11,6 @@ from django.db.models.sql.constants import (
 from django.db.models.sql.query import Query, get_order_dir
 from django.db.transaction import TransactionManagementError
 from django.db.utils import DatabaseError
-from django.utils.six.moves import zip
 
 FORCE = object()
 
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 57ba7dfc8d..3acef8faf5 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -30,7 +30,6 @@ from django.db.models.sql.datastructures import (
 from django.db.models.sql.where import (
     AND, OR, ExtraWhere, NothingNode, WhereNode,
 )
-from django.utils import six
 from django.utils.encoding import force_text
 from django.utils.tree import Node
 
@@ -104,7 +103,7 @@ class RawQuery(object):
         if params_type is tuple:
             params = tuple(adapter(val) for val in self.params)
         elif params_type is dict:
-            params = dict((key, adapter(val)) for key, val in six.iteritems(self.params))
+            params = {key: adapter(val) for key, val in self.params.items()}
         else:
             raise RuntimeError("Unexpected params type: %s" % params_type)
 
@@ -665,23 +664,23 @@ class Query(object):
             # slight complexity here is handling fields that exist on parent
             # models.
             workset = {}
-            for model, values in six.iteritems(seen):
+            for model, values in seen.items():
                 for field in model._meta.fields:
                     if field in values:
                         continue
                     m = field.model._meta.concrete_model
                     add_to_dict(workset, m, field)
-            for model, values in six.iteritems(must_include):
+            for model, values in must_include.items():
                 # If we haven't included a model in workset, we don't add the
                 # corresponding must_include fields for that model, since an
                 # empty set means "include all fields". That's why there's no
                 # "else" branch here.
                 if model in workset:
                     workset[model].update(values)
-            for model, values in six.iteritems(workset):
+            for model, values in workset.items():
                 callback(target, model, values)
         else:
-            for model, values in six.iteritems(must_include):
+            for model, values in must_include.items():
                 if model in seen:
                     seen[model].update(values)
                 else:
@@ -695,7 +694,7 @@ class Query(object):
             for model in orig_opts.get_parent_list():
                 if model not in seen:
                     seen[model] = set()
-            for model, values in six.iteritems(seen):
+            for model, values in seen.items():
                 callback(target, model, values)
 
     def table_alias(self, table_name, create=False):
@@ -813,7 +812,7 @@ class Query(object):
                 (key, col.relabeled_clone(change_map)) for key, col in self._annotations.items())
 
         # 2. Rename the alias in the internal table/alias datastructures.
-        for old_alias, new_alias in six.iteritems(change_map):
+        for old_alias, new_alias in change_map.items():
             if old_alias not in self.alias_map:
                 continue
             alias_data = self.alias_map[old_alias].relabeled_clone(change_map)
@@ -1698,7 +1697,7 @@ class Query(object):
             self.group_by.append(col)
 
         if self.annotation_select:
-            for alias, annotation in six.iteritems(self.annotation_select):
+            for alias, annotation in self.annotation_select.items():
                 for col in annotation.get_group_by_cols():
                     self.group_by.append(col)
 
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
index 089f04e04e..cfdadefdff 100644
--- a/django/db/models/sql/subqueries.py
+++ b/django/db/models/sql/subqueries.py
@@ -9,7 +9,6 @@ from django.db.models.sql.constants import (
     CURSOR, GET_ITERATOR_CHUNK_SIZE, NO_RESULTS,
 )
 from django.db.models.sql.query import Query
-from django.utils import six
 
 __all__ = ['DeleteQuery', 'UpdateQuery', 'InsertQuery', 'AggregateQuery']
 
@@ -120,7 +119,7 @@ class UpdateQuery(Query):
         querysets.
         """
         values_seq = []
-        for name, val in six.iteritems(values):
+        for name, val in values.items():
             field = self.get_meta().get_field(name)
             direct = not (field.auto_created and not field.concrete) or not field.concrete
             model = field.model._meta.concrete_model
@@ -164,7 +163,7 @@ class UpdateQuery(Query):
         if not self.related_updates:
             return []
         result = []
-        for model, values in six.iteritems(self.related_updates):
+        for model, values in self.related_updates.items():
             query = UpdateQuery(model)
             query.values = values
             if self.related_ids is not None:
diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py
index 8cf1680b6e..706c9eebbf 100644
--- a/django/dispatch/dispatcher.py
+++ b/django/dispatch/dispatcher.py
@@ -3,7 +3,6 @@ import threading
 import weakref
 
 from django.utils.inspect import func_accepts_kwargs
-from django.utils.six.moves import range
 
 
 def _make_id(target):
diff --git a/django/forms/fields.py b/django/forms/fields.py
index 5be10708c3..94a7bbbfc2 100644
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -11,6 +11,7 @@ import sys
 import uuid
 from decimal import Decimal, DecimalException
 from io import BytesIO
+from urllib.parse import urlsplit, urlunsplit
 
 from django.core import validators
 from django.core.exceptions import ValidationError
@@ -30,7 +31,6 @@ from django.utils.dateparse import parse_duration
 from django.utils.duration import duration_string
 from django.utils.encoding import force_str, force_text
 from django.utils.ipv6 import clean_ipv6_address
-from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
 
 __all__ = (
diff --git a/django/forms/forms.py b/django/forms/forms.py
index c8086b1361..de19edb668 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -12,7 +12,6 @@ from django.forms.fields import Field, FileField
 # pretty_name is imported for backwards compatibility in Django 1.9
 from django.forms.utils import ErrorDict, ErrorList, pretty_name  # NOQA
 from django.forms.widgets import Media, MediaDefiningClass
-from django.utils import six
 from django.utils.encoding import force_text
 from django.utils.functional import cached_property
 from django.utils.html import conditional_escape, html_safe
@@ -504,7 +503,7 @@ class BaseForm(object):
         return value
 
 
-class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
+class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):
     "A collection of Fields, plus their associated data."
     # This is a separate class from BaseForm in order to abstract the way
     # self.fields is specified. This class (Form) is the one that does the
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 60638c475c..964a83d4ca 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -6,7 +6,6 @@ from django.forms.widgets import HiddenInput
 from django.utils.functional import cached_property
 from django.utils.html import html_safe
 from django.utils.safestring import mark_safe
-from django.utils.six.moves import range
 from django.utils.translation import ugettext as _, ungettext
 
 __all__ = ('BaseFormSet', 'formset_factory', 'all_valid')
diff --git a/django/forms/models.py b/django/forms/models.py
index c3a1b81c8c..093a7a6078 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -16,7 +16,6 @@ from django.forms.utils import ErrorList
 from django.forms.widgets import (
     HiddenInput, MultipleHiddenInput, SelectMultiple,
 )
-from django.utils import six
 from django.utils.encoding import force_text
 from django.utils.text import capfirst, get_text_list
 from django.utils.translation import ugettext, ugettext_lazy as _
@@ -250,7 +249,7 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
                                       opts.field_classes)
 
             # make sure opts.fields doesn't specify an invalid field
-            none_model_fields = [k for k, v in six.iteritems(fields) if not v]
+            none_model_fields = [k for k, v in fields.items() if not v]
             missing_fields = (set(none_model_fields) -
                               set(new_class.declared_fields.keys()))
             if missing_fields:
@@ -457,7 +456,7 @@ class BaseModelForm(BaseForm):
     save.alters_data = True
 
 
-class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)):
+class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
     pass
 
 
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index e21dba0607..b04864205c 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -11,13 +11,12 @@ from itertools import chain
 from django.conf import settings
 from django.forms.utils import to_current_timezone
 from django.templatetags.static import static
-from django.utils import datetime_safe, formats, six
+from django.utils import datetime_safe, formats
 from django.utils.dates import MONTHS
 from django.utils.encoding import force_str, force_text
 from django.utils.formats import get_format
 from django.utils.html import format_html, html_safe
 from django.utils.safestring import mark_safe
-from django.utils.six.moves import range
 from django.utils.translation import ugettext_lazy
 
 from .renderers import get_default_renderer
@@ -152,7 +151,7 @@ class MediaDefiningClass(type):
         return new_class
 
 
-class Widget(six.with_metaclass(MediaDefiningClass)):
+class Widget(metaclass=MediaDefiningClass):
     needs_multipart_form = False  # Determines does this widget need multipart form
     is_localized = False
     is_required = False
diff --git a/django/http/cookie.py b/django/http/cookie.py
index 7251d04c40..f45ef11295 100644
--- a/django/http/cookie.py
+++ b/django/http/cookie.py
@@ -1,6 +1,5 @@
 import sys
-
-from django.utils.six.moves import http_cookies
+from http import cookies
 
 # Cookie pickling bug is fixed in Python 2.7.9 and Python 3.4.3+
 # http://bugs.python.org/issue22775
@@ -10,11 +9,11 @@ cookie_pickles_properly = (
 )
 
 if cookie_pickles_properly:
-    SimpleCookie = http_cookies.SimpleCookie
+    SimpleCookie = cookies.SimpleCookie
 else:
-    Morsel = http_cookies.Morsel
+    Morsel = cookies.Morsel
 
-    class SimpleCookie(http_cookies.SimpleCookie):
+    class SimpleCookie(cookies.SimpleCookie):
         if not cookie_pickles_properly:
             def __setitem__(self, key, value):
                 # Apply the fix from http://bugs.python.org/issue22775 where
@@ -41,5 +40,5 @@ def parse_cookie(cookie):
         key, val = key.strip(), val.strip()
         if key or val:
             # unquote using Python's algorithm.
-            cookiedict[key] = http_cookies._unquote(val)
+            cookiedict[key] = cookies._unquote(val)
     return cookiedict
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index b1db1f81ca..4cbb98afdd 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -8,6 +8,7 @@ import base64
 import binascii
 import cgi
 import sys
+from urllib.parse import unquote
 
 from django.conf import settings
 from django.core.exceptions import (
@@ -19,7 +20,6 @@ from django.core.files.uploadhandler import (
 from django.utils import six
 from django.utils.datastructures import MultiValueDict
 from django.utils.encoding import force_text
-from django.utils.six.moves.urllib.parse import unquote
 from django.utils.text import unescape_entities
 
 __all__ = ('MultiPartParser', 'MultiPartParserError', 'InputStreamExhausted')
@@ -312,7 +312,7 @@ class MultiPartParser(object):
                 handler.file.close()
 
 
-class LazyStream(six.Iterator):
+class LazyStream:
     """
     The LazyStream wrapper allows one to get and "unget" bytes from a stream.
 
@@ -429,7 +429,7 @@ class LazyStream(six.Iterator):
             )
 
 
-class ChunkIter(six.Iterator):
+class ChunkIter:
     """
     An iterable that will yield chunks of data. Given a file-like object as the
     constructor, this object will yield chunks of read operations from that
@@ -453,7 +453,7 @@ class ChunkIter(six.Iterator):
         return self
 
 
-class InterBoundaryIter(six.Iterator):
+class InterBoundaryIter:
     """
     A Producer that will iterate over boundaries.
     """
@@ -471,7 +471,7 @@ class InterBoundaryIter(six.Iterator):
             raise StopIteration()
 
 
-class BoundaryIter(six.Iterator):
+class BoundaryIter:
     """
     A Producer that is sensitive to boundaries.
 
diff --git a/django/http/request.py b/django/http/request.py
index fe1684ee58..a930c93b26 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -3,6 +3,7 @@ import re
 import sys
 from io import BytesIO
 from itertools import chain
+from urllib.parse import quote, urlencode, urljoin, urlsplit
 
 from django.conf import settings
 from django.core import signing
@@ -17,9 +18,6 @@ from django.utils.encoding import (
     escape_uri_path, force_bytes, force_str, iri_to_uri,
 )
 from django.utils.http import is_same_domain, limited_parse_qsl
-from django.utils.six.moves.urllib.parse import (
-    quote, urlencode, urljoin, urlsplit,
-)
 
 RAISE_ERROR = object()
 host_validation_re = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9\.:]+\])(:\d+)?$")
@@ -431,14 +429,14 @@ class QueryDict(MultiValueDict):
 
     def __copy__(self):
         result = self.__class__('', mutable=True, encoding=self.encoding)
-        for key, value in six.iterlists(self):
+        for key, value in self.lists():
             result.setlist(key, value)
         return result
 
     def __deepcopy__(self, memo):
         result = self.__class__('', mutable=True, encoding=self.encoding)
         memo[id(self)] = result
-        for key, value in six.iterlists(self):
+        for key, value in self.lists():
             result.setlist(copy.deepcopy(key, memo), copy.deepcopy(value, memo))
         return result
 
diff --git a/django/http/response.py b/django/http/response.py
index 1c2677035d..c5294acbbd 100644
--- a/django/http/response.py
+++ b/django/http/response.py
@@ -4,20 +4,19 @@ import re
 import sys
 import time
 from email.header import Header
+from http.client import responses
+from urllib.parse import urlparse
 
 from django.conf import settings
 from django.core import signals, signing
 from django.core.exceptions import DisallowedRedirect
 from django.core.serializers.json import DjangoJSONEncoder
 from django.http.cookie import SimpleCookie
-from django.utils import six, timezone
+from django.utils import timezone
 from django.utils.encoding import (
     force_bytes, force_str, force_text, iri_to_uri,
 )
 from django.utils.http import cookie_date
-from django.utils.six.moves import map
-from django.utils.six.moves.http_client import responses
-from django.utils.six.moves.urllib.parse import urlparse
 
 _charset_from_content_type_re = re.compile(r';\s*charset=(?P<charset>[^\s;]+)', re.I)
 
@@ -26,7 +25,7 @@ class BadHeaderError(ValueError):
     pass
 
 
-class HttpResponseBase(six.Iterator):
+class HttpResponseBase:
     """
     An HTTP response base class with dictionary-accessed headers.
 
diff --git a/django/middleware/common.py b/django/middleware/common.py
index d18d23fa43..304e6318c4 100644
--- a/django/middleware/common.py
+++ b/django/middleware/common.py
@@ -1,5 +1,6 @@
 import re
 import warnings
+from urllib.parse import urlparse
 
 from django import http
 from django.conf import settings
@@ -11,7 +12,6 @@ from django.utils.cache import (
 )
 from django.utils.deprecation import MiddlewareMixin, RemovedInDjango21Warning
 from django.utils.encoding import force_text
-from django.utils.six.moves.urllib.parse import urlparse
 
 
 class CommonMiddleware(MiddlewareMixin):
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index 14d3537f29..f6584cbea8 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -7,6 +7,7 @@ against request forgeries from other sites.
 import logging
 import re
 import string
+from urllib.parse import urlparse
 
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
@@ -16,8 +17,6 @@ from django.utils.crypto import constant_time_compare, get_random_string
 from django.utils.deprecation import MiddlewareMixin
 from django.utils.encoding import force_text
 from django.utils.http import is_same_domain
-from django.utils.six.moves import zip
-from django.utils.six.moves.urllib.parse import urlparse
 
 logger = logging.getLogger('django.security.csrf')
 
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 2c455aff7b..888d837130 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -7,7 +7,7 @@ from datetime import datetime
 from itertools import cycle as itertools_cycle, groupby
 
 from django.conf import settings
-from django.utils import six, timezone
+from django.utils import timezone
 from django.utils.encoding import force_text
 from django.utils.html import conditional_escape, format_html
 from django.utils.lorem_ipsum import paragraphs, words
@@ -521,8 +521,7 @@ class WithNode(Node):
         return "<WithNode>"
 
     def render(self, context):
-        values = {key: val.resolve(context) for key, val in
-                  six.iteritems(self.extra_context)}
+        values = {key: val.resolve(context) for key, val in self.extra_context.items()}
         with context.push(**values):
             return self.nodelist.render(context)
 
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index e5c44c88b0..2c27902238 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -3,7 +3,6 @@ import posixpath
 import warnings
 from collections import defaultdict
 
-from django.utils import six
 from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.safestring import mark_safe
 
@@ -25,7 +24,7 @@ class BlockContext(object):
         self.blocks = defaultdict(list)
 
     def add_blocks(self, blocks):
-        for name, block in six.iteritems(blocks):
+        for name, block in blocks.items():
             self.blocks[name].insert(0, block)
 
     def pop(self, name):
@@ -183,7 +182,7 @@ class IncludeNode(Node):
                     cache[template_name] = template
             values = {
                 name: var.resolve(context)
-                for name, var in six.iteritems(self.extra_context)
+                for name, var in self.extra_context.items()
             }
             if self.isolated_context:
                 return template.render(context.new(values))
diff --git a/django/templatetags/static.py b/django/templatetags/static.py
index e4789bda71..4b25eca6a7 100644
--- a/django/templatetags/static.py
+++ b/django/templatetags/static.py
@@ -1,8 +1,9 @@
+from urllib.parse import quote, urljoin
+
 from django import template
 from django.apps import apps
 from django.utils.encoding import iri_to_uri
 from django.utils.html import conditional_escape
-from django.utils.six.moves.urllib.parse import quote, urljoin
 
 register = template.Library()
 
diff --git a/django/test/client.py b/django/test/client.py
index 1c08ada1cf..3b31dae9bd 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -6,6 +6,7 @@ import sys
 from copy import copy
 from importlib import import_module
 from io import BytesIO
+from urllib.parse import urljoin, urlparse, urlsplit
 
 from django.conf import settings
 from django.core.handlers.base import BaseHandler
@@ -24,7 +25,6 @@ from django.utils.encoding import force_bytes, force_str, uri_to_iri
 from django.utils.functional import SimpleLazyObject, curry
 from django.utils.http import urlencode
 from django.utils.itercompat import is_iterable
-from django.utils.six.moves.urllib.parse import urljoin, urlparse, urlsplit
 
 __all__ = ('Client', 'RedirectCycleError', 'RequestFactory', 'encode_file', 'encode_multipart')
 
diff --git a/django/test/runner.py b/django/test/runner.py
index 487ec7c682..77a309d8e6 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -8,6 +8,7 @@ import textwrap
 import unittest
 import warnings
 from importlib import import_module
+from io import StringIO
 
 from django.core.management import call_command
 from django.db import connections
@@ -18,7 +19,6 @@ from django.test.utils import (
 )
 from django.utils.datastructures import OrderedSet
 from django.utils.deprecation import RemovedInDjango21Warning
-from django.utils.six import StringIO
 
 try:
     import tblib.pickling_support
diff --git a/django/test/selenium.py b/django/test/selenium.py
index de3744c91e..236f7d8053 100644
--- a/django/test/selenium.py
+++ b/django/test/selenium.py
@@ -4,7 +4,6 @@ from contextlib import contextmanager
 
 from django.test import LiveServerTestCase, tag
 from django.utils.module_loading import import_string
-from django.utils.six import with_metaclass
 from django.utils.text import capfirst
 
 
@@ -54,7 +53,7 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)):
 
 
 @tag('selenium')
-class SeleniumTestCase(with_metaclass(SeleniumTestCaseBase, LiveServerTestCase)):
+class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase):
     implicit_wait = 10
 
     @classmethod
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 9d08940f5a..dad3fb2df0 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -9,6 +9,8 @@ from contextlib import contextmanager
 from copy import copy
 from functools import wraps
 from unittest.util import safe_repr
+from urllib.parse import unquote, urljoin, urlparse, urlsplit
+from urllib.request import url2pathname
 
 from django.apps import apps
 from django.conf import settings
@@ -31,13 +33,8 @@ from django.test.utils import (
     CaptureQueriesContext, ContextList, compare_xml, modify_settings,
     override_settings,
 )
-from django.utils import six
 from django.utils.decorators import classproperty
 from django.utils.encoding import force_text
-from django.utils.six.moves.urllib.parse import (
-    unquote, urljoin, urlparse, urlsplit,
-)
-from django.utils.six.moves.urllib.request import url2pathname
 from django.views.static import serve
 
 __all__ = ('TestCase', 'TransactionTestCase',
@@ -921,7 +918,7 @@ class TransactionTestCase(SimpleTestCase):
                          inhibit_post_migrate=inhibit_post_migrate)
 
     def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True, msg=None):
-        items = six.moves.map(transform, qs)
+        items = map(transform, qs)
         if not ordered:
             return self.assertEqual(Counter(items), Counter(values), msg=msg)
         values = list(values)
diff --git a/django/test/utils.py b/django/test/utils.py
index 7395396fbb..55428ccf85 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -6,6 +6,7 @@ import time
 import warnings
 from contextlib import contextmanager
 from functools import wraps
+from io import StringIO
 from types import SimpleNamespace
 from unittest import TestCase, skipIf, skipUnless
 from xml.dom.minidom import Node, parseString
@@ -21,7 +22,6 @@ from django.db.models.options import Options
 from django.template import Template
 from django.test.signals import setting_changed, template_rendered
 from django.urls import get_script_prefix, set_script_prefix
-from django.utils import six
 from django.utils.decorators import available_attrs
 from django.utils.encoding import force_str
 from django.utils.translation import deactivate
@@ -728,7 +728,7 @@ def captured_output(stream_name):
     Note: This function and the following ``captured_std*`` are copied
           from CPython's ``test.support`` module."""
     orig_stdout = getattr(sys, stream_name)
-    setattr(sys, stream_name, six.StringIO())
+    setattr(sys, stream_name, StringIO())
     try:
         yield getattr(sys, stream_name)
     finally:
@@ -840,7 +840,7 @@ class LoggingCaptureMixin(object):
     def setUp(self):
         self.logger = logging.getLogger('django')
         self.old_stream = self.logger.handlers[0].stream
-        self.logger_output = six.StringIO()
+        self.logger_output = StringIO()
         self.logger.handlers[0].stream = self.logger_output
 
     def tearDown(self):
diff --git a/django/urls/base.py b/django/urls/base.py
index c09eeed8f6..408bc36ead 100644
--- a/django/urls/base.py
+++ b/django/urls/base.py
@@ -1,8 +1,8 @@
 from threading import local
+from urllib.parse import urlsplit, urlunsplit
 
 from django.utils.encoding import force_text, iri_to_uri
 from django.utils.functional import lazy
-from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
 from django.utils.translation import override
 
 from .exceptions import NoReverseMatch, Resolver404
diff --git a/django/utils/_os.py b/django/utils/_os.py
index ddf2132f5f..6507e58764 100644
--- a/django/utils/_os.py
+++ b/django/utils/_os.py
@@ -5,7 +5,6 @@ from os.path import abspath, dirname, join, normcase, sep
 from django.core.exceptions import SuspiciousFileOperation
 from django.utils.encoding import force_text
 
-
 abspathu = abspath
 
 
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index e7c9acbaea..d6a5b1a319 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -35,12 +35,13 @@ import sys
 import time
 import traceback
 
+import _thread
+
 from django.apps import apps
 from django.conf import settings
 from django.core.signals import request_finished
 from django.utils import six
 from django.utils._os import npath
-from django.utils.six.moves import _thread as thread
 
 # This import does nothing, but it's necessary to avoid some race conditions
 # in the threading module. See http://code.djangoproject.com/ticket/2330 .
@@ -293,7 +294,7 @@ def restart_with_reloader():
 
 def python_reloader(main_func, args, kwargs):
     if os.environ.get("RUN_MAIN") == "true":
-        thread.start_new_thread(main_func, args, kwargs)
+        _thread.start_new_thread(main_func, args, kwargs)
         try:
             reloader_thread()
         except KeyboardInterrupt:
@@ -311,7 +312,7 @@ def python_reloader(main_func, args, kwargs):
 
 def jython_reloader(main_func, args, kwargs):
     from _systemrestart import SystemRestart
-    thread.start_new_thread(main_func, args)
+    _thread.start_new_thread(main_func, args)
     while True:
         if code_changed():
             raise SystemRestart
diff --git a/django/utils/crypto.py b/django/utils/crypto.py
index 554958b6ee..74dd9fd7b0 100644
--- a/django/utils/crypto.py
+++ b/django/utils/crypto.py
@@ -10,7 +10,6 @@ import time
 
 from django.conf import settings
 from django.utils.encoding import force_bytes
-from django.utils.six.moves import range
 
 # Use the system PRNG if possible
 try:
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index 7367924600..bb8166a734 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -1,8 +1,6 @@
 import copy
 from collections import OrderedDict
 
-from django.utils import six
-
 
 class OrderedSet(object):
     """
@@ -189,7 +187,7 @@ class MultiValueDict(dict):
 
     def lists(self):
         """Yields (key, list) pairs."""
-        return six.iteritems(super(MultiValueDict, self))
+        return iter(super(MultiValueDict, self).items())
 
     def values(self):
         """Yield the last value on every key list."""
@@ -218,7 +216,7 @@ class MultiValueDict(dict):
                         self.setlistdefault(key).append(value)
                 except TypeError:
                     raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary")
-        for key, value in six.iteritems(kwargs):
+        for key, value in kwargs.items():
             self.setlistdefault(key).append(value)
 
     def dict(self):
diff --git a/django/utils/dateparse.py b/django/utils/dateparse.py
index b2020b5281..d091100a0d 100644
--- a/django/utils/dateparse.py
+++ b/django/utils/dateparse.py
@@ -8,7 +8,6 @@
 import datetime
 import re
 
-from django.utils import six
 from django.utils.timezone import get_fixed_timezone, utc
 
 date_re = re.compile(
@@ -60,7 +59,7 @@ def parse_date(value):
     """
     match = date_re.match(value)
     if match:
-        kw = {k: int(v) for k, v in six.iteritems(match.groupdict())}
+        kw = {k: int(v) for k, v in match.groupdict().items()}
         return datetime.date(**kw)
 
 
@@ -78,7 +77,7 @@ def parse_time(value):
         kw = match.groupdict()
         if kw['microsecond']:
             kw['microsecond'] = kw['microsecond'].ljust(6, '0')
-        kw = {k: int(v) for k, v in six.iteritems(kw) if v is not None}
+        kw = {k: int(v) for k, v in kw.items() if v is not None}
         return datetime.time(**kw)
 
 
@@ -105,7 +104,7 @@ def parse_datetime(value):
             if tzinfo[0] == '-':
                 offset = -offset
             tzinfo = get_fixed_timezone(offset)
-        kw = {k: int(v) for k, v in six.iteritems(kw) if v is not None}
+        kw = {k: int(v) for k, v in kw.items() if v is not None}
         kw['tzinfo'] = tzinfo
         return datetime.datetime(**kw)
 
@@ -127,5 +126,5 @@ def parse_duration(value):
             kw['microseconds'] = kw['microseconds'].ljust(6, '0')
         if kw.get('seconds') and kw.get('microseconds') and kw['seconds'].startswith('-'):
             kw['microseconds'] = '-' + kw['microseconds']
-        kw = {k: float(v) for k, v in six.iteritems(kw) if v is not None}
+        kw = {k: float(v) for k, v in kw.items() if v is not None}
         return sign * datetime.timedelta(**kw)
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index abd17526c6..71c2985e27 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -2,11 +2,10 @@ import codecs
 import datetime
 import locale
 from decimal import Decimal
-from urllib.parse import unquote_to_bytes
+from urllib.parse import quote, unquote_to_bytes
 
 from django.utils import six
 from django.utils.functional import Promise
-from django.utils.six.moves.urllib.parse import quote
 
 
 class DjangoUnicodeDecodeError(UnicodeDecodeError):
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index fbab58b905..33ef20e16b 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -22,11 +22,11 @@ For definitions of the different versions of RSS, see:
 http://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004/02/04/incompatible-rss
 """
 import datetime
+from io import StringIO
+from urllib.parse import urlparse
 
 from django.utils import datetime_safe
 from django.utils.encoding import force_text, iri_to_uri
-from django.utils.six import StringIO
-from django.utils.six.moves.urllib.parse import urlparse
 from django.utils.timezone import utc
 from django.utils.xmlutils import SimplerXMLGenerator
 
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 3e35582cbf..0efce6c635 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -2,8 +2,6 @@ import copy
 import operator
 from functools import total_ordering, wraps
 
-from django.utils import six
-
 
 # You can't trivially replace this with `functools.partial` because this binds
 # to classes and returns bound instances, whereas functools.partial (on
@@ -193,7 +191,7 @@ def keep_lazy(*resultclasses):
 
         @wraps(func)
         def wrapper(*args, **kwargs):
-            for arg in list(args) + list(six.itervalues(kwargs)):
+            for arg in list(args) + list(kwargs.values()):
                 if isinstance(arg, Promise):
                     break
             else:
diff --git a/django/utils/html.py b/django/utils/html.py
index 430350fed6..1fb39bb9b6 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -1,15 +1,14 @@
 """HTML utilities suitable for global use."""
 
 import re
+from urllib.parse import (
+    parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit,
+)
 
-from django.utils import six
 from django.utils.encoding import force_str, force_text
 from django.utils.functional import keep_lazy, keep_lazy_text
 from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS
 from django.utils.safestring import SafeData, SafeText, mark_safe
-from django.utils.six.moves.urllib.parse import (
-    parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit,
-)
 from django.utils.text import normalize_newlines
 
 from .html_parser import HTMLParseError, HTMLParser
@@ -93,7 +92,7 @@ def format_html(format_string, *args, **kwargs):
     of str.format or % interpolation to build up small HTML fragments.
     """
     args_safe = map(conditional_escape, args)
-    kwargs_safe = {k: conditional_escape(v) for (k, v) in six.iteritems(kwargs)}
+    kwargs_safe = {k: conditional_escape(v) for (k, v) in kwargs.items()}
     return mark_safe(format_string.format(*args_safe, **kwargs_safe))
 
 
diff --git a/django/utils/html_parser.py b/django/utils/html_parser.py
index d272004f77..e3e19ee9c3 100644
--- a/django/utils/html_parser.py
+++ b/django/utils/html_parser.py
@@ -1,14 +1,14 @@
-from django.utils.six.moves import html_parser as _html_parser
+import html.parser
 
 try:
-    HTMLParseError = _html_parser.HTMLParseError
+    HTMLParseError = html.parser.HTMLParseError
 except AttributeError:
     # create a dummy class for Python 3.5+ where it's been removed
     class HTMLParseError(Exception):
         pass
 
 
-class HTMLParser(_html_parser.HTMLParser):
+class HTMLParser(html.parser.HTMLParser):
     """Explicitly set convert_charrefs to be False.
 
     This silences a deprecation warning on Python 3.4, but we can't do
@@ -16,4 +16,4 @@ class HTMLParser(_html_parser.HTMLParser):
     argument.
     """
     def __init__(self, convert_charrefs=False, **kwargs):
-        _html_parser.HTMLParser.__init__(self, convert_charrefs=convert_charrefs, **kwargs)
+        html.parser.HTMLParser.__init__(self, convert_charrefs=convert_charrefs, **kwargs)
diff --git a/django/utils/http.py b/django/utils/http.py
index 0308e14676..ae23eec06a 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -7,6 +7,10 @@ import unicodedata
 import warnings
 from binascii import Error as BinasciiError
 from email.utils import formatdate
+from urllib.parse import (
+    quote, quote_plus, unquote, unquote_plus, urlencode as original_urlencode,
+    urlparse,
+)
 
 from django.core.exceptions import TooManyFieldsSent
 from django.utils import six
@@ -14,10 +18,6 @@ from django.utils.datastructures import MultiValueDict
 from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.encoding import force_bytes, force_str, force_text
 from django.utils.functional import keep_lazy_text
-from django.utils.six.moves.urllib.parse import (
-    quote, quote_plus, unquote, unquote_plus, urlencode as original_urlencode,
-    urlparse,
-)
 
 # based on RFC 7232, Appendix C
 ETAG_MATCH = re.compile(r'''
diff --git a/django/utils/ipv6.py b/django/utils/ipv6.py
index c41f1e2b46..a1e63be727 100644
--- a/django/utils/ipv6.py
+++ b/django/utils/ipv6.py
@@ -4,7 +4,6 @@
 import re
 
 from django.core.exceptions import ValidationError
-from django.utils.six.moves import range
 from django.utils.translation import ugettext_lazy as _
 
 
diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py
index bc4a09b359..f5cfc57156 100644
--- a/django/utils/regex_helper.py
+++ b/django/utils/regex_helper.py
@@ -8,7 +8,6 @@ should be good enough for a large class of URLS, however.
 import warnings
 
 from django.utils.deprecation import RemovedInDjango21Warning
-from django.utils.six.moves import zip
 
 # Mapping of an escape character to a representative of that class. So, e.g.,
 # "\w" is replaced by "x" in a reverse URL. A value of None means to ignore
diff --git a/django/utils/termcolors.py b/django/utils/termcolors.py
index 87ed8c1187..f75e068187 100644
--- a/django/utils/termcolors.py
+++ b/django/utils/termcolors.py
@@ -2,8 +2,6 @@
 termcolors.py
 """
 
-from django.utils import six
-
 color_names = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white')
 foreground = {color_names[x]: '3%s' % x for x in range(8)}
 background = {color_names[x]: '4%s' % x for x in range(8)}
@@ -44,7 +42,7 @@ def colorize(text='', opts=(), **kwargs):
     code_list = []
     if text == '' and len(opts) == 1 and opts[0] == 'reset':
         return '\x1b[%sm' % RESET
-    for k, v in six.iteritems(kwargs):
+    for k, v in kwargs.items():
         if k == 'fg':
             code_list.append(foreground[v])
         elif k == 'bg':
diff --git a/django/utils/text.py b/django/utils/text.py
index 15a9b6160a..d716a0b345 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -1,15 +1,14 @@
+import html.entities
 import re
 import unicodedata
 from gzip import GzipFile
 from io import BytesIO
 
-from django.utils import six
 from django.utils.encoding import force_text
 from django.utils.functional import (
     SimpleLazyObject, keep_lazy, keep_lazy_text, lazy,
 )
 from django.utils.safestring import SafeText, mark_safe
-from django.utils.six.moves import html_entities
 from django.utils.translation import pgettext, ugettext as _, ugettext_lazy
 
 
@@ -369,12 +368,12 @@ def _replace_entity(match):
                 c = int(text[1:], 16)
             else:
                 c = int(text)
-            return six.unichr(c)
+            return chr(c)
         except ValueError:
             return match.group(0)
     else:
         try:
-            return six.unichr(html_entities.name2codepoint[text])
+            return chr(html.entities.name2codepoint[text])
         except (ValueError, KeyError):
             return match.group(0)
 
diff --git a/django/utils/translation/template.py b/django/utils/translation/template.py
index b6d8832f9c..42ff84dc27 100644
--- a/django/utils/translation/template.py
+++ b/django/utils/translation/template.py
@@ -1,12 +1,12 @@
 import re
 import warnings
+from io import StringIO
 
 from django.template.base import (
     TOKEN_BLOCK, TOKEN_COMMENT, TOKEN_TEXT, TOKEN_VAR, TRANSLATOR_COMMENT_MARK,
     Lexer,
 )
 from django.utils.encoding import force_text
-from django.utils.six import StringIO
 
 from . import TranslatorCommentWarning, trim_whitespace
 
diff --git a/django/views/generic/base.py b/django/views/generic/base.py
index 4187e70b12..668aef7760 100644
--- a/django/views/generic/base.py
+++ b/django/views/generic/base.py
@@ -5,7 +5,6 @@ from django import http
 from django.core.exceptions import ImproperlyConfigured
 from django.template.response import TemplateResponse
 from django.urls import NoReverseMatch, reverse
-from django.utils import six
 from django.utils.decorators import classonlymethod
 
 logger = logging.getLogger('django.request')
@@ -38,7 +37,7 @@ class View(object):
         """
         # Go through keyword arguments, and either save their values to our
         # instance, or raise an error.
-        for key, value in six.iteritems(kwargs):
+        for key, value in kwargs.items():
             setattr(self, key, value)
 
     @classonlymethod
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 1fc5461b1c..ada2624795 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -7,7 +7,6 @@ from django.apps import apps
 from django.conf import settings
 from django.template import Context, Engine
 from django.urls import translate_url
-from django.utils import six
 from django.utils.encoding import force_text
 from django.utils.formats import get_format
 from django.utils.http import is_safe_url, urlunquote
@@ -265,7 +264,7 @@ class JavaScriptCatalog(View):
         catalog = {}
         trans_cat = self.translation._catalog
         trans_fallback_cat = self.translation._fallback._catalog if self.translation._fallback else {}
-        for key, value in itertools.chain(six.iteritems(trans_cat), six.iteritems(trans_fallback_cat)):
+        for key, value in itertools.chain(iter(trans_cat.items()), iter(trans_fallback_cat.items())):
             if key == '' or key in catalog:
                 continue
             if isinstance(key, str):
diff --git a/django/views/static.py b/django/views/static.py
index bfa171357d..a0f4bdab56 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -7,6 +7,7 @@ import os
 import posixpath
 import re
 import stat
+from urllib.parse import unquote
 
 from django.http import (
     FileResponse, Http404, HttpResponse, HttpResponseNotModified,
@@ -14,7 +15,6 @@ from django.http import (
 )
 from django.template import Context, Engine, TemplateDoesNotExist, loader
 from django.utils.http import http_date, parse_http_date
-from django.utils.six.moves.urllib.parse import unquote
 from django.utils.translation import ugettext as _, ugettext_lazy
 
 
diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py
index e37e8a4256..868d04196f 100644
--- a/tests/admin_scripts/tests.py
+++ b/tests/admin_scripts/tests.py
@@ -12,6 +12,7 @@ import subprocess
 import sys
 import tempfile
 import unittest
+from io import StringIO
 
 import django
 from django import conf, get_version
@@ -27,7 +28,6 @@ from django.test import (
 )
 from django.utils._os import npath, upath
 from django.utils.encoding import force_text
-from django.utils.six import StringIO
 
 custom_templates_dir = os.path.join(os.path.dirname(upath(__file__)), 'custom_templates')
 
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index b8d35b6d81..ffce2c0f34 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -1,5 +1,6 @@
 import os
 import tempfile
+from io import StringIO
 from wsgiref.util import FileWrapper
 
 from django import forms
@@ -17,7 +18,6 @@ from django.forms.models import BaseModelFormSet
 from django.http import HttpResponse, StreamingHttpResponse
 from django.utils.html import format_html
 from django.utils.safestring import mark_safe
-from django.utils.six import StringIO
 
 from .forms import MediaActionForm
 from .models import (
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index fb404e5fd2..81b7e1f6a4 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -3,6 +3,7 @@ import json
 import os
 import re
 import unittest
+from urllib.parse import parse_qsl, urljoin, urlparse
 
 from django.contrib.admin import AdminSite, ModelAdmin
 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
@@ -36,7 +37,6 @@ from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.encoding import force_bytes, force_text, iri_to_uri
 from django.utils.html import escape
 from django.utils.http import urlencode
-from django.utils.six.moves.urllib.parse import parse_qsl, urljoin, urlparse
 
 from . import customadmin
 from .admin import CityAdmin, site, site2
diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py
index afbc7c20b2..bc501fc94e 100644
--- a/tests/aggregation_regress/tests.py
+++ b/tests/aggregation_regress/tests.py
@@ -12,7 +12,6 @@ from django.db.models import (
 )
 from django.test import TestCase, skipUnlessAnyDBFeature, skipUnlessDBFeature
 from django.test.utils import Approximate
-from django.utils import six
 
 from .models import (
     Alfa, Author, Book, Bravo, Charlie, Clues, Entries, HardbackBook, ItemTag,
@@ -103,7 +102,7 @@ class AggregationTests(TestCase):
         s3.books.add(cls.b3, cls.b4, cls.b6)
 
     def assertObjectAttrs(self, obj, **kwargs):
-        for attr, value in six.iteritems(kwargs):
+        for attr, value in kwargs.items():
             self.assertEqual(getattr(obj, attr), value)
 
     def test_annotation_with_value(self):
diff --git a/tests/auth_tests/test_deprecated_views.py b/tests/auth_tests/test_deprecated_views.py
index d084f4dcef..d5088b9d8a 100644
--- a/tests/auth_tests/test_deprecated_views.py
+++ b/tests/auth_tests/test_deprecated_views.py
@@ -1,6 +1,7 @@
 import datetime
 import itertools
 import re
+from urllib.parse import ParseResult, urlparse
 
 from django.conf import settings
 from django.contrib.auth import SESSION_KEY
@@ -14,7 +15,6 @@ from django.test import TestCase, override_settings
 from django.test.utils import ignore_warnings, patch_logger
 from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.encoding import force_text
-from django.utils.six.moves.urllib.parse import ParseResult, urlparse
 
 from .models import CustomUser, UUIDUser
 from .settings import AUTH_TEMPLATES
diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py
index 9335b75dba..4fd1563f84 100644
--- a/tests/auth_tests/test_management.py
+++ b/tests/auth_tests/test_management.py
@@ -1,5 +1,7 @@
+import builtins
 import sys
 from datetime import date
+from io import StringIO
 
 from django.apps import apps
 from django.contrib.auth import management
@@ -13,7 +15,6 @@ from django.core.management import call_command
 from django.core.management.base import CommandError
 from django.db import migrations
 from django.test import TestCase, mock, override_settings
-from django.utils import six
 from django.utils.encoding import force_str
 from django.utils.translation import ugettext_lazy as _
 
@@ -49,14 +50,14 @@ def mock_inputs(inputs):
                 return response
 
             old_getpass = createsuperuser.getpass
-            old_input = createsuperuser.input
+            old_input = builtins.input
             createsuperuser.getpass = mock_getpass
-            createsuperuser.input = mock_input
+            builtins.input = mock_input
             try:
                 test_func(*args)
             finally:
                 createsuperuser.getpass = old_getpass
-                createsuperuser.input = old_input
+                builtins.input = old_input
         return wrapped
     return inner
 
@@ -105,8 +106,8 @@ class ChangepasswordManagementCommandTestCase(TestCase):
 
     def setUp(self):
         self.user = User.objects.create_user(username='joe', password='qwerty')
-        self.stdout = six.StringIO()
-        self.stderr = six.StringIO()
+        self.stdout = StringIO()
+        self.stderr = StringIO()
 
     def tearDown(self):
         self.stdout.close()
@@ -168,7 +169,7 @@ class MultiDBChangepasswordManagementCommandTestCase(TestCase):
         user = User.objects.db_manager('other').create_user(username='joe', password='qwerty')
         self.assertTrue(user.check_password('qwerty'))
 
-        out = six.StringIO()
+        out = StringIO()
         call_command('changepassword', username='joe', database='other', stdout=out)
         command_output = out.getvalue().strip()
 
@@ -188,7 +189,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
     def test_basic_usage(self):
         "Check the operation of the createsuperuser management command"
         # We can use the management command to create a superuser
-        new_io = six.StringIO()
+        new_io = StringIO()
         call_command(
             "createsuperuser",
             interactive=False,
@@ -212,7 +213,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         username_field = User._meta.get_field('username')
         old_verbose_name = username_field.verbose_name
         username_field.verbose_name = _('u\u017eivatel')
-        new_io = six.StringIO()
+        new_io = StringIO()
         try:
             call_command(
                 "createsuperuser",
@@ -228,7 +229,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
 
     def test_verbosity_zero(self):
         # We can suppress output on the management command
-        new_io = six.StringIO()
+        new_io = StringIO()
         call_command(
             "createsuperuser",
             interactive=False,
@@ -244,7 +245,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         self.assertFalse(u.has_usable_password())
 
     def test_email_in_username(self):
-        new_io = six.StringIO()
+        new_io = StringIO()
         call_command(
             "createsuperuser",
             interactive=False,
@@ -262,7 +263,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         # We can use the management command to create a superuser
         # We skip validation because the temporary substitution of the
         # swappable User model messes with validation.
-        new_io = six.StringIO()
+        new_io = StringIO()
         call_command(
             "createsuperuser",
             interactive=False,
@@ -284,7 +285,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         # We can use the management command to create a superuser
         # We skip validation because the temporary substitution of the
         # swappable User model messes with validation.
-        new_io = six.StringIO()
+        new_io = StringIO()
         with self.assertRaises(CommandError):
             call_command(
                 "createsuperuser",
@@ -306,7 +307,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
             'password': 'nopasswd',
         })
         def createsuperuser():
-            new_io = six.StringIO()
+            new_io = StringIO()
             call_command(
                 "createsuperuser",
                 interactive=True,
@@ -333,7 +334,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
             def isatty(self):
                 return False
 
-        out = six.StringIO()
+        out = StringIO()
         call_command(
             "createsuperuser",
             stdin=FakeStdin(),
@@ -355,8 +356,8 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         call_command(
             command,
             stdin=sentinel,
-            stdout=six.StringIO(),
-            stderr=six.StringIO(),
+            stdout=StringIO(),
+            stderr=StringIO(),
             interactive=False,
             verbosity=0,
             username='janet',
@@ -367,8 +368,8 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         command = createsuperuser.Command()
         call_command(
             command,
-            stdout=six.StringIO(),
-            stderr=six.StringIO(),
+            stdout=StringIO(),
+            stderr=StringIO(),
             interactive=False,
             verbosity=0,
             username='joe',
@@ -378,7 +379,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
 
     @override_settings(AUTH_USER_MODEL='auth_tests.CustomUserWithFK')
     def test_fields_with_fk(self):
-        new_io = six.StringIO()
+        new_io = StringIO()
         group = Group.objects.create(name='mygroup')
         email = Email.objects.create(email='mymail@gmail.com')
         call_command(
@@ -408,7 +409,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
 
     @override_settings(AUTH_USER_MODEL='auth_tests.CustomUserWithFK')
     def test_fields_with_fk_interactive(self):
-        new_io = six.StringIO()
+        new_io = StringIO()
         group = Group.objects.create(name='mygroup')
         email = Email.objects.create(email='mymail@gmail.com')
 
@@ -438,7 +439,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         """
         Creation should fail if the password fails validation.
         """
-        new_io = six.StringIO()
+        new_io = StringIO()
 
         # Returns '1234567890' the first two times it is called, then
         # 'password' subsequently.
@@ -472,7 +473,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         """
         Creation should fail if the user enters mismatched passwords.
         """
-        new_io = six.StringIO()
+        new_io = StringIO()
 
         # The first two passwords do not match, but the second two do match and
         # are valid.
@@ -505,7 +506,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         """
         Creation should fail if the user enters blank passwords.
         """
-        new_io = six.StringIO()
+        new_io = StringIO()
 
         # The first two passwords are empty strings, but the second two are
         # valid.
@@ -542,7 +543,7 @@ class MultiDBCreatesuperuserTestCase(TestCase):
         """
         changepassword --database should operate on the specified DB.
         """
-        new_io = six.StringIO()
+        new_io = StringIO()
         call_command(
             'createsuperuser',
             interactive=False,
diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py
index f250800921..dd87a5bf00 100644
--- a/tests/auth_tests/test_views.py
+++ b/tests/auth_tests/test_views.py
@@ -3,6 +3,7 @@ import itertools
 import os
 import re
 from importlib import import_module
+from urllib.parse import ParseResult, urlparse
 
 from django.apps import apps
 from django.conf import settings
@@ -28,7 +29,6 @@ from django.urls import NoReverseMatch, reverse, reverse_lazy
 from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.encoding import force_text
 from django.utils.http import urlquote
-from django.utils.six.moves.urllib.parse import ParseResult, urlparse
 from django.utils.translation import LANGUAGE_SESSION_KEY
 
 from .client import PasswordResetConfirmClient
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index a7939ed8be..89afb20760 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -23,7 +23,6 @@ from django.test import (
     SimpleTestCase, TestCase, TransactionTestCase, mock, override_settings,
     skipIfDBFeature, skipUnlessDBFeature,
 )
-from django.utils.six.moves import range
 
 from .models import (
     Article, Item, Object, ObjectReference, Person, Post, RawData, Reporter,
diff --git a/tests/base/models.py b/tests/base/models.py
index fb91522717..b179878704 100644
--- a/tests/base/models.py
+++ b/tests/base/models.py
@@ -1,5 +1,4 @@
 from django.db import models
-from django.utils import six
 
 
 # The models definitions below used to crash. Generating models dynamically
@@ -11,5 +10,5 @@ class CustomBaseModel(models.base.ModelBase):
     pass
 
 
-class MyModel(six.with_metaclass(CustomBaseModel, models.Model)):
-    """Model subclass with a custom base using six.with_metaclass."""
+class MyModel(models.Model, metaclass=CustomBaseModel):
+    """Model subclass with a custom base using metaclass."""
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index caeb8a47df..3bf39e2476 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -3,6 +3,7 @@
 import copy
 import io
 import os
+import pickle
 import re
 import shutil
 import tempfile
@@ -33,7 +34,7 @@ from django.test import (
     ignore_warnings, mock, override_settings,
 )
 from django.test.signals import setting_changed
-from django.utils import six, timezone, translation
+from django.utils import timezone, translation
 from django.utils.cache import (
     get_cache_key, learn_cache_key, patch_cache_control,
     patch_response_headers, patch_vary_headers,
@@ -44,11 +45,6 @@ from django.views.decorators.cache import cache_page
 
 from .models import Poll, expensive_calculation
 
-try:    # Use the same idiom as in cache backends
-    from django.utils.six.moves import cPickle as pickle
-except ImportError:
-    import pickle
-
 
 # functions/classes for complex data type tests
 def f():
@@ -978,7 +974,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
         self._perform_cull_test(caches['zero_cull'], 50, 18)
 
     def test_second_call_doesnt_crash(self):
-        out = six.StringIO()
+        out = io.StringIO()
         management.call_command('createcachetable', stdout=out)
         self.assertEqual(out.getvalue(), "Cache table 'test cache table' already exists.\n" * len(settings.CACHES))
 
@@ -988,7 +984,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
         LOCATION='createcachetable_dry_run_mode'
     ))
     def test_createcachetable_dry_run_mode(self):
-        out = six.StringIO()
+        out = io.StringIO()
         management.call_command('createcachetable', dry_run=True, stdout=out)
         output = out.getvalue()
         self.assertTrue(output.startswith("CREATE TABLE"))
@@ -999,7 +995,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
         specifying the table name).
         """
         self.drop_table()
-        out = six.StringIO()
+        out = io.StringIO()
         management.call_command(
             'createcachetable',
             'test cache table',
diff --git a/tests/check_framework/tests.py b/tests/check_framework/tests.py
index 0e82c10b45..30e1353dc0 100644
--- a/tests/check_framework/tests.py
+++ b/tests/check_framework/tests.py
@@ -1,4 +1,5 @@
 import sys
+from io import StringIO
 
 from django.apps import apps
 from django.core import checks
@@ -12,7 +13,6 @@ from django.test.utils import (
     isolate_apps, override_settings, override_system_checks,
 )
 from django.utils.encoding import force_text
-from django.utils.six import StringIO
 
 from .models import SimpleModel, my_check
 
diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py
index a46c9f3365..c91c265eaf 100644
--- a/tests/contenttypes_tests/tests.py
+++ b/tests/contenttypes_tests/tests.py
@@ -390,10 +390,7 @@ class UpdateContentTypesTests(TestCase):
         # A related object is needed to show that a custom collector with
         # can_fast_delete=False is needed.
         ModelWithNullFKToSite.objects.create(post=post)
-        with mock.patch(
-            'django.contrib.contenttypes.management.commands.remove_stale_contenttypes.input',
-            return_value='yes'
-        ):
+        with mock.patch('builtins.input', return_value='yes'):
             with captured_stdout() as stdout:
                 call_command('remove_stale_contenttypes', verbosity=2, stdout=stdout)
         self.assertEqual(Post.objects.count(), 0)
@@ -409,10 +406,7 @@ class UpdateContentTypesTests(TestCase):
         interactive mode of remove_stale_contenttypes (the default) should
         delete stale contenttypes even if there aren't any dependent objects.
         """
-        with mock.patch(
-            'django.contrib.contenttypes.management.commands.remove_stale_contenttypes.input',
-            return_value='yes'
-        ):
+        with mock.patch('builtins.input', return_value='yes'):
             with captured_stdout() as stdout:
                 call_command('remove_stale_contenttypes', verbosity=2)
         self.assertIn("Deleting stale content type", stdout.getvalue())
diff --git a/tests/db_typecasts/tests.py b/tests/db_typecasts/tests.py
index fa9eab164d..56d37c112d 100644
--- a/tests/db_typecasts/tests.py
+++ b/tests/db_typecasts/tests.py
@@ -4,7 +4,6 @@ import datetime
 import unittest
 
 from django.db.backends import utils as typecasts
-from django.utils import six
 
 TEST_CASES = {
     'typecast_date': (
@@ -53,7 +52,7 @@ TEST_CASES = {
 
 class DBTypeCasts(unittest.TestCase):
     def test_typeCasts(self):
-        for k, v in six.iteritems(TEST_CASES):
+        for k, v in TEST_CASES.items():
             for inpt, expected in v:
                 got = getattr(typecasts, k)(inpt)
                 self.assertEqual(
diff --git a/tests/delete/tests.py b/tests/delete/tests.py
index 640dfabf4b..5abae7622e 100644
--- a/tests/delete/tests.py
+++ b/tests/delete/tests.py
@@ -3,7 +3,6 @@ from math import ceil
 from django.db import IntegrityError, connection, models
 from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE
 from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
-from django.utils.six.moves import range
 
 from .models import (
     MR, A, Avatar, Base, Child, HiddenUser, HiddenUserProfile, M, M2MFrom,
diff --git a/tests/deprecation/tests.py b/tests/deprecation/tests.py
index 34056b663b..5c2361f7a0 100644
--- a/tests/deprecation/tests.py
+++ b/tests/deprecation/tests.py
@@ -2,7 +2,6 @@ import warnings
 
 from django.test import SimpleTestCase
 from django.test.utils import reset_warning_registry
-from django.utils import six
 from django.utils.deprecation import (
     DeprecationInstanceCheck, RemovedInNextVersionWarning, RenameMethodsBase,
 )
@@ -29,7 +28,7 @@ class RenameMethodsTests(SimpleTestCase):
         with warnings.catch_warnings(record=True) as recorded:
             warnings.simplefilter('always')
 
-            class Manager(six.with_metaclass(RenameManagerMethods)):
+            class Manager(metaclass=RenameManagerMethods):
                 def old(self):
                     pass
             self.assertEqual(len(recorded), 1)
@@ -43,7 +42,7 @@ class RenameMethodsTests(SimpleTestCase):
         with warnings.catch_warnings(record=True) as recorded:
             warnings.simplefilter('ignore')
 
-            class Manager(six.with_metaclass(RenameManagerMethods)):
+            class Manager(metaclass=RenameManagerMethods):
                 def new(self):
                     pass
             warnings.simplefilter('always')
@@ -62,7 +61,7 @@ class RenameMethodsTests(SimpleTestCase):
         with warnings.catch_warnings(record=True) as recorded:
             warnings.simplefilter('ignore')
 
-            class Manager(six.with_metaclass(RenameManagerMethods)):
+            class Manager(metaclass=RenameManagerMethods):
                 def old(self):
                     pass
             warnings.simplefilter('always')
@@ -82,7 +81,7 @@ class RenameMethodsTests(SimpleTestCase):
         with warnings.catch_warnings(record=True) as recorded:
             warnings.simplefilter('ignore')
 
-            class Renamed(six.with_metaclass(RenameManagerMethods)):
+            class Renamed(metaclass=RenameManagerMethods):
                 def new(self):
                     pass
 
@@ -112,7 +111,7 @@ class RenameMethodsTests(SimpleTestCase):
         with warnings.catch_warnings(record=True) as recorded:
             warnings.simplefilter('ignore')
 
-            class Deprecated(six.with_metaclass(RenameManagerMethods)):
+            class Deprecated(metaclass=RenameManagerMethods):
                 def old(self):
                     pass
 
@@ -137,7 +136,7 @@ class RenameMethodsTests(SimpleTestCase):
         with warnings.catch_warnings(record=True) as recorded:
             warnings.simplefilter('ignore')
 
-            class Renamed(six.with_metaclass(RenameManagerMethods)):
+            class Renamed(metaclass=RenameManagerMethods):
                 def new(self):
                     pass
 
@@ -168,7 +167,7 @@ class RenameMethodsTests(SimpleTestCase):
 
 class DeprecationInstanceCheckTest(SimpleTestCase):
     def test_warning(self):
-        class Manager(six.with_metaclass(DeprecationInstanceCheck)):
+        class Manager(metaclass=DeprecationInstanceCheck):
             alternative = 'fake.path.Foo'
             deprecation_warning = RemovedInNextVersionWarning
 
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index d02b6e2dbc..5aee127c8c 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -7,6 +7,8 @@ import threading
 import time
 import unittest
 from datetime import datetime, timedelta
+from io import StringIO
+from urllib.request import urlopen
 
 from django.core.cache import cache
 from django.core.exceptions import SuspiciousFileOperation, SuspiciousOperation
@@ -21,9 +23,8 @@ from django.test import (
 )
 from django.test.utils import requires_tz_support
 from django.urls import NoReverseMatch, reverse_lazy
-from django.utils import six, timezone
+from django.utils import timezone
 from django.utils._os import upath
-from django.utils.six.moves.urllib.request import urlopen
 
 from .models import Storage, temp_storage, temp_storage_location
 
@@ -301,7 +302,7 @@ class FileStorageTests(SimpleTestCase):
             self.assertFalse(file.closed)
             self.assertFalse(file.file.closed)
 
-        file = InMemoryUploadedFile(six.StringIO('1'), '', 'test', 'text/plain', 1, 'utf8')
+        file = InMemoryUploadedFile(StringIO('1'), '', 'test', 'text/plain', 1, 'utf8')
         with file:
             self.assertFalse(file.closed)
             self.storage.save('path/to/test.file', file)
@@ -578,7 +579,7 @@ class DiscardingFalseContentStorageTests(FileStorageTests):
         When Storage.save() wraps a file-like object in File, it should include
         the name argument so that bool(file) evaluates to True (#26495).
         """
-        output = six.StringIO('content')
+        output = StringIO('content')
         self.storage.save('tests/stringio', output)
         self.assertTrue(self.storage.exists('tests/stringio'))
 
@@ -788,7 +789,7 @@ class FileFieldStorageTests(TestCase):
 
     def test_stringio(self):
         # Test passing StringIO instance as content argument to save
-        output = six.StringIO()
+        output = StringIO()
         output.write('content')
         output.seek(0)
 
diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py
index f9cecf96a2..c7315ae142 100644
--- a/tests/file_uploads/tests.py
+++ b/tests/file_uploads/tests.py
@@ -7,7 +7,7 @@ import shutil
 import sys
 import tempfile as sys_tempfile
 import unittest
-from io import BytesIO
+from io import BytesIO, StringIO
 
 from django.core.files import temp as tempfile
 from django.core.files.uploadedfile import SimpleUploadedFile
@@ -15,7 +15,6 @@ from django.http.multipartparser import MultiPartParser, parse_header
 from django.test import SimpleTestCase, TestCase, client, override_settings
 from django.utils.encoding import force_bytes
 from django.utils.http import urlquote
-from django.utils.six import StringIO
 
 from . import uploadhandler
 from .models import FileModel
diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py
index df5067e223..51d8eef51b 100644
--- a/tests/fixtures/tests.py
+++ b/tests/fixtures/tests.py
@@ -3,6 +3,7 @@ import sys
 import tempfile
 import unittest
 import warnings
+from io import StringIO
 
 from django.apps import apps
 from django.contrib.sites.models import Site
@@ -15,7 +16,6 @@ from django.db import IntegrityError, connection
 from django.test import (
     TestCase, TransactionTestCase, mock, skipUnlessDBFeature,
 )
-from django.utils import six
 from django.utils.encoding import force_text
 
 from .models import (
@@ -52,7 +52,7 @@ class DumpDataAssertMixin(object):
     def _dumpdata_assert(self, args, output, format='json', filename=None,
                          natural_foreign_keys=False, natural_primary_keys=False,
                          use_base_manager=False, exclude_list=[], primary_keys=''):
-        new_io = six.StringIO()
+        new_io = StringIO()
         if filename:
             filename = os.path.join(tempfile.gettempdir(), filename)
         management.call_command('dumpdata', *args, **{'format': format,
@@ -445,7 +445,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
     def test_dumpdata_with_uuid_pks(self):
         m1 = PrimaryKeyUUIDModel.objects.create()
         m2 = PrimaryKeyUUIDModel.objects.create()
-        output = six.StringIO()
+        output = StringIO()
         management.call_command(
             'dumpdata', 'fixtures.PrimaryKeyUUIDModel', '--pks', ', '.join([str(m1.id), str(m2.id)]),
             stdout=output,
@@ -471,7 +471,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
         stdout is a tty, and verbosity > 0.
         """
         management.call_command('loaddata', 'fixture1.json', verbosity=0)
-        new_io = six.StringIO()
+        new_io = StringIO()
         new_io.isatty = lambda: True
         with NamedTemporaryFile() as file:
             options = {
@@ -485,7 +485,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
 
             # Test no progress bar when verbosity = 0
             options['verbosity'] = 0
-            new_io = six.StringIO()
+            new_io = StringIO()
             new_io.isatty = lambda: True
             options.update({'stdout': new_io, 'stderr': new_io})
             management.call_command('dumpdata', 'fixtures', **options)
@@ -583,7 +583,7 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
         ])
 
     def test_loaddata_verbosity_three(self):
-        output = six.StringIO()
+        output = StringIO()
         management.call_command('loaddata', 'fixture1.json', verbosity=3, stdout=output, stderr=output)
         command_output = force_text(output.getvalue())
         self.assertIn(
@@ -689,7 +689,7 @@ class NonExistentFixtureTests(TestCase):
     """
 
     def test_loaddata_not_existent_fixture_file(self):
-        stdout_output = six.StringIO()
+        stdout_output = StringIO()
         with self.assertRaisesMessage(CommandError, "No fixture named 'this_fixture_doesnt_exist' found."):
             management.call_command('loaddata', 'this_fixture_doesnt_exist', stdout=stdout_output)
 
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index 7ea9e6cc3e..a3ac7f5977 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -3,6 +3,7 @@ import json
 import os
 import re
 import warnings
+from io import StringIO
 
 from django.core import management, serializers
 from django.core.exceptions import ImproperlyConfigured
@@ -14,7 +15,6 @@ from django.test import (
     skipUnlessDBFeature,
 )
 from django.utils._os import upath
-from django.utils.six import StringIO
 
 from .models import (
     Absolute, Animal, Article, Book, Child, Circle1, Circle2, Circle3,
diff --git a/tests/gis_tests/gdal_tests/test_geom.py b/tests/gis_tests/gdal_tests/test_geom.py
index bc8219772c..4498ce7111 100644
--- a/tests/gis_tests/gdal_tests/test_geom.py
+++ b/tests/gis_tests/gdal_tests/test_geom.py
@@ -1,19 +1,13 @@
 import json
+import pickle
 import unittest
 from binascii import b2a_hex
 from unittest import skipUnless
 
 from django.contrib.gis.gdal import HAS_GDAL
-from django.utils.six.moves import range
 
 from ..test_data import TestDataMixin
 
-try:
-    from django.utils.six.moves import cPickle as pickle
-except ImportError:
-    import pickle
-
-
 if HAS_GDAL:
     from django.contrib.gis.gdal import (
         CoordTransform, GDALException, OGRGeometry, OGRGeomType, OGRIndexError,
diff --git a/tests/gis_tests/gdal_tests/test_raster.py b/tests/gis_tests/gdal_tests/test_raster.py
index 8649e9f231..1a2753c717 100644
--- a/tests/gis_tests/gdal_tests/test_raster.py
+++ b/tests/gis_tests/gdal_tests/test_raster.py
@@ -49,7 +49,6 @@ from django.contrib.gis.gdal import HAS_GDAL
 from django.contrib.gis.gdal.error import GDALException
 from django.contrib.gis.shortcuts import numpy
 from django.test import SimpleTestCase
-from django.utils import six
 from django.utils._os import upath
 
 from ..data.rasters.textrasters import JSON_RASTER
@@ -505,7 +504,7 @@ class GDALBandTests(SimpleTestCase):
             self.assertEqual(result, block)
 
         # Set data from memoryview
-        bandmem.data(six.memoryview(packed_block), (1, 1), (2, 2))
+        bandmem.data(memoryview(packed_block), (1, 1), (2, 2))
         result = bandmem.data(offset=(1, 1), size=(2, 2))
         if numpy:
             numpy.testing.assert_equal(result, numpy.array(block).reshape(2, 2))
diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py
index 4324364c0d..bfb84c6800 100644
--- a/tests/gis_tests/geoapp/tests.py
+++ b/tests/gis_tests/geoapp/tests.py
@@ -1,4 +1,5 @@
 import tempfile
+from io import StringIO
 
 from django.contrib.gis import gdal
 from django.contrib.gis.db.models import Extent, MakeLine, Union, functions
@@ -9,7 +10,6 @@ from django.contrib.gis.geos import (
 from django.core.management import call_command
 from django.db import connection
 from django.test import TestCase, skipUnlessDBFeature
-from django.utils import six
 
 from ..utils import no_oracle, oracle, postgis, skipUnlessGISLookup, spatialite
 from .models import (
@@ -184,7 +184,7 @@ class GeoModelTest(TestCase):
         """
         Test a dumpdata/loaddata cycle with geographic data.
         """
-        out = six.StringIO()
+        out = StringIO()
         original_data = list(City.objects.all().order_by('name'))
         call_command('dumpdata', 'geoapp.City', stdout=out)
         result = out.getvalue()
diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py
index 7877e1f2d6..f7024e790e 100644
--- a/tests/gis_tests/geos_tests/test_geos.py
+++ b/tests/gis_tests/geos_tests/test_geos.py
@@ -1,5 +1,6 @@
 import ctypes
 import json
+import pickle
 import random
 from binascii import a2b_hex, b2a_hex
 from io import BytesIO
@@ -18,7 +19,6 @@ from django.template import Context
 from django.template.engine import Engine
 from django.test import SimpleTestCase, mock
 from django.utils.encoding import force_bytes
-from django.utils.six.moves import range
 
 from ..test_data import TestDataMixin
 
@@ -1100,10 +1100,6 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
 
     def test_pickle(self):
         "Testing pickling and unpickling support."
-        # Using both pickle and cPickle -- just 'cause.
-        from django.utils.six.moves import cPickle
-        import pickle
-
         # Creating a list of test geometries for pickling,
         # and setting the SRID on some of them.
         def get_geoms(lst, srid=None):
@@ -1114,11 +1110,10 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
         tgeoms.extend(get_geoms(self.geometries.multipolygons, 3857))
 
         for geom in tgeoms:
-            s1, s2 = cPickle.dumps(geom), pickle.dumps(geom)
-            g1, g2 = cPickle.loads(s1), pickle.loads(s2)
-            for tmpg in (g1, g2):
-                self.assertEqual(geom, tmpg)
-                self.assertEqual(geom.srid, tmpg.srid)
+            s1 = pickle.dumps(geom)
+            g1 = pickle.loads(s1)
+            self.assertEqual(geom, g1)
+            self.assertEqual(geom.srid, g1.srid)
 
     def test_prepared(self):
         "Testing PreparedGeometry support."
diff --git a/tests/gis_tests/inspectapp/tests.py b/tests/gis_tests/inspectapp/tests.py
index b451603e7c..43cf2c2d66 100644
--- a/tests/gis_tests/inspectapp/tests.py
+++ b/tests/gis_tests/inspectapp/tests.py
@@ -1,12 +1,12 @@
 import os
 import re
+from io import StringIO
 
 from django.contrib.gis.gdal import HAS_GDAL
 from django.core.management import call_command
 from django.db import connection, connections
 from django.test import TestCase, skipUnlessDBFeature
 from django.test.utils import modify_settings
-from django.utils.six import StringIO
 
 from ..test_data import TEST_DATA
 from ..utils import postgis
diff --git a/tests/gis_tests/test_data.py b/tests/gis_tests/test_data.py
index 7092bd27b1..9e31b5a599 100644
--- a/tests/gis_tests/test_data.py
+++ b/tests/gis_tests/test_data.py
@@ -5,7 +5,6 @@ for the GEOS and GDAL tests.
 import json
 import os
 
-from django.utils import six
 from django.utils._os import upath
 from django.utils.functional import cached_property
 
@@ -22,7 +21,7 @@ def tuplize(seq):
 
 def strconvert(d):
     "Converts all keys in dictionary to str type."
-    return {str(k): v for k, v in six.iteritems(d)}
+    return {str(k): v for k, v in d.items()}
 
 
 def get_ds_file(name, ext):
diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py
index b8d359de41..a4ea614175 100644
--- a/tests/httpwrappers/tests.py
+++ b/tests/httpwrappers/tests.py
@@ -16,7 +16,6 @@ from django.http import (
     StreamingHttpResponse, parse_cookie,
 )
 from django.test import SimpleTestCase
-from django.utils import six
 from django.utils._os import upath
 from django.utils.encoding import force_str
 from django.utils.functional import lazystr
@@ -56,10 +55,10 @@ class QueryDictTests(SimpleTestCase):
         q = QueryDict()
         self.assertEqual(q.getlist('foo'), [])
         self.assertNotIn('foo', q)
-        self.assertEqual(list(six.iteritems(q)), [])
-        self.assertEqual(list(six.iterlists(q)), [])
-        self.assertEqual(list(six.iterkeys(q)), [])
-        self.assertEqual(list(six.itervalues(q)), [])
+        self.assertEqual(list(q.items()), [])
+        self.assertEqual(list(q.lists()), [])
+        self.assertEqual(list(q.keys()), [])
+        self.assertEqual(list(q.values()), [])
         self.assertEqual(len(q), 0)
         self.assertEqual(q.urlencode(), '')
 
@@ -86,10 +85,10 @@ class QueryDictTests(SimpleTestCase):
         self.assertIn('foo', q)
         self.assertNotIn('bar', q)
 
-        self.assertEqual(list(six.iteritems(q)), [('foo', 'bar')])
-        self.assertEqual(list(six.iterlists(q)), [('foo', ['bar'])])
-        self.assertEqual(list(six.iterkeys(q)), ['foo'])
-        self.assertEqual(list(six.itervalues(q)), ['bar'])
+        self.assertEqual(list(q.items()), [('foo', 'bar')])
+        self.assertEqual(list(q.lists()), [('foo', ['bar'])])
+        self.assertEqual(list(q.keys()), ['foo'])
+        self.assertEqual(list(q.values()), ['bar'])
         self.assertEqual(len(q), 1)
 
         with self.assertRaises(AttributeError):
@@ -146,14 +145,10 @@ class QueryDictTests(SimpleTestCase):
         self.assertEqual(q['foo'], 'another')
         self.assertIn('foo', q)
 
-        self.assertListEqual(sorted(six.iteritems(q)),
-                             [('foo', 'another'), ('name', 'john')])
-        self.assertListEqual(sorted(six.iterlists(q)),
-                             [('foo', ['bar', 'baz', 'another']), ('name', ['john'])])
-        self.assertListEqual(sorted(six.iterkeys(q)),
-                             ['foo', 'name'])
-        self.assertListEqual(sorted(six.itervalues(q)),
-                             ['another', 'john'])
+        self.assertListEqual(sorted(q.items()), [('foo', 'another'), ('name', 'john')])
+        self.assertListEqual(sorted(q.lists()), [('foo', ['bar', 'baz', 'another']), ('name', ['john'])])
+        self.assertListEqual(sorted(q.keys()), ['foo', 'name'])
+        self.assertListEqual(sorted(q.values()), ['another', 'john'])
 
         q.update({'foo': 'hello'})
         self.assertEqual(q['foo'], 'hello')
@@ -193,10 +188,10 @@ class QueryDictTests(SimpleTestCase):
 
         self.assertIn('vote', q)
         self.assertNotIn('foo', q)
-        self.assertEqual(list(six.iteritems(q)), [('vote', 'no')])
-        self.assertEqual(list(six.iterlists(q)), [('vote', ['yes', 'no'])])
-        self.assertEqual(list(six.iterkeys(q)), ['vote'])
-        self.assertEqual(list(six.itervalues(q)), ['no'])
+        self.assertEqual(list(q.items()), [('vote', 'no')])
+        self.assertEqual(list(q.lists()), [('vote', ['yes', 'no'])])
+        self.assertEqual(list(q.keys()), ['vote'])
+        self.assertEqual(list(q.values()), ['no'])
         self.assertEqual(len(q), 1)
 
         with self.assertRaises(AttributeError):
@@ -234,11 +229,11 @@ class QueryDictTests(SimpleTestCase):
         """#13572 - QueryDict with a non-default encoding"""
         q = QueryDict(str('cur=%A4'), encoding='iso-8859-15')
         self.assertEqual(q.encoding, 'iso-8859-15')
-        self.assertEqual(list(six.iteritems(q)), [('cur', '€')])
+        self.assertEqual(list(q.items()), [('cur', '€')])
         self.assertEqual(q.urlencode(), 'cur=%A4')
         q = q.copy()
         self.assertEqual(q.encoding, 'iso-8859-15')
-        self.assertEqual(list(six.iteritems(q)), [('cur', '€')])
+        self.assertEqual(list(q.items()), [('cur', '€')])
         self.assertEqual(q.urlencode(), 'cur=%A4')
         self.assertEqual(copy.copy(q).encoding, 'iso-8859-15')
         self.assertEqual(copy.deepcopy(q).encoding, 'iso-8859-15')
diff --git a/tests/i18n/test_compilation.py b/tests/i18n/test_compilation.py
index 3f3bf773bf..159b6d91ec 100644
--- a/tests/i18n/test_compilation.py
+++ b/tests/i18n/test_compilation.py
@@ -2,6 +2,7 @@ import gettext as gettext_module
 import os
 import stat
 import unittest
+from io import StringIO
 from subprocess import Popen
 
 from django.core.management import (
@@ -14,7 +15,6 @@ from django.test import SimpleTestCase, mock, override_settings
 from django.test.utils import captured_stderr, captured_stdout
 from django.utils import translation
 from django.utils.encoding import force_text
-from django.utils.six import StringIO
 from django.utils.translation import ugettext
 
 from .utils import RunInTmpDirMixin, copytree
diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py
index b8342865a9..aaa2a4e8b5 100644
--- a/tests/i18n/test_extraction.py
+++ b/tests/i18n/test_extraction.py
@@ -4,6 +4,7 @@ import re
 import shutil
 import time
 import warnings
+from io import StringIO
 from unittest import skipUnless
 
 from admin_scripts.tests import AdminScriptTestCase
@@ -17,7 +18,6 @@ from django.core.management.utils import find_command
 from django.test import SimpleTestCase, mock, override_settings
 from django.test.utils import captured_stderr, captured_stdout
 from django.utils.encoding import force_text
-from django.utils.six import StringIO
 from django.utils.translation import TranslatorCommentWarning
 
 from .utils import POFileAssertionMixin, RunInTmpDirMixin, copytree
diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py
index 8f17735929..ef26233342 100644
--- a/tests/inspectdb/tests.py
+++ b/tests/inspectdb/tests.py
@@ -1,11 +1,11 @@
 import re
+from io import StringIO
 from unittest import skipUnless
 
 from django.core.management import call_command
 from django.db import connection
 from django.test import TestCase, mock, skipUnlessDBFeature
 from django.utils.encoding import force_text
-from django.utils.six import StringIO
 
 from .models import ColumnTypes
 
diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
index 134cc71808..a91bcd66bf 100644
--- a/tests/logging_tests/tests.py
+++ b/tests/logging_tests/tests.py
@@ -1,5 +1,6 @@
 import logging
 from contextlib import contextmanager
+from io import StringIO
 
 from admin_scripts.tests import AdminScriptTestCase
 
@@ -10,7 +11,6 @@ from django.core.management import color
 from django.db import connection
 from django.test import RequestFactory, SimpleTestCase, override_settings
 from django.test.utils import LoggingCaptureMixin, patch_logger
-from django.utils import six
 from django.utils.log import (
     DEFAULT_LOGGING, AdminEmailHandler, CallbackFilter, RequireDebugFalse,
     RequireDebugTrue, ServerFormatter,
@@ -513,7 +513,7 @@ class LogFormattersTests(SimpleTestCase):
         @contextmanager
         def patch_django_server_logger():
             old_stream = logger.handlers[0].stream
-            new_stream = six.StringIO()
+            new_stream = StringIO()
             logger.handlers[0].stream = new_stream
             yield new_stream
             logger.handlers[0].stream = old_stream
diff --git a/tests/m2m_through_regress/tests.py b/tests/m2m_through_regress/tests.py
index a1739f2960..64be4252bd 100644
--- a/tests/m2m_through_regress/tests.py
+++ b/tests/m2m_through_regress/tests.py
@@ -1,7 +1,8 @@
+from io import StringIO
+
 from django.contrib.auth.models import User
 from django.core import management
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from .models import (
     Car, CarDriver, Driver, Group, Membership, Person, UserMembership,
diff --git a/tests/mail/tests.py b/tests/mail/tests.py
index 7c2cd8342a..77ea87fe7d 100644
--- a/tests/mail/tests.py
+++ b/tests/mail/tests.py
@@ -12,6 +12,7 @@ from email import message_from_binary_file, message_from_bytes
 from email.header import Header
 from email.mime.text import MIMEText
 from email.utils import parseaddr
+from io import StringIO
 from smtplib import SMTP, SMTPAuthenticationError, SMTPException
 from ssl import SSLError
 
@@ -26,7 +27,6 @@ from django.test import SimpleTestCase, override_settings
 from django.test.utils import requires_tz_support
 from django.utils._os import upath
 from django.utils.encoding import force_bytes, force_text
-from django.utils.six import StringIO
 from django.utils.translation import ugettext_lazy
 
 
diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py
index 3c0d3d81ea..8749a9ee50 100644
--- a/tests/middleware/tests.py
+++ b/tests/middleware/tests.py
@@ -1,7 +1,9 @@
 import gzip
 import random
 import re
+import struct
 from io import BytesIO
+from urllib.parse import quote
 
 from django.conf import settings
 from django.core import mail
@@ -19,11 +21,10 @@ from django.middleware.http import ConditionalGetMiddleware
 from django.test import (
     RequestFactory, SimpleTestCase, ignore_warnings, override_settings,
 )
-from django.utils import six
 from django.utils.deprecation import RemovedInDjango21Warning
 from django.utils.encoding import force_str
-from django.utils.six.moves import range
-from django.utils.six.moves.urllib.parse import quote
+
+int2byte = struct.Struct(">B").pack
 
 
 @override_settings(ROOT_URLCONF='middleware.urls')
@@ -732,7 +733,7 @@ class GZipMiddlewareTest(SimpleTestCase):
     """
     short_string = b"This string is too short to be worth compressing."
     compressible_string = b'a' * 500
-    incompressible_string = b''.join(six.int2byte(random.randint(0, 255)) for _ in range(500))
+    incompressible_string = b''.join(int2byte(random.randint(0, 255)) for _ in range(500))
     sequence = [b'a' * 500, b'b' * 200, b'a' * 300]
     sequence_unicode = ['a' * 500, 'é' * 200, 'a' * 300]
 
diff --git a/tests/migrate_signals/tests.py b/tests/migrate_signals/tests.py
index 84b6608f46..97f449e805 100644
--- a/tests/migrate_signals/tests.py
+++ b/tests/migrate_signals/tests.py
@@ -1,9 +1,10 @@
+from io import StringIO
+
 from django.apps import apps
 from django.core import management
 from django.db import migrations
 from django.db.models import signals
 from django.test import TransactionTestCase, override_settings
-from django.utils import six
 
 APP_CONFIG = apps.get_app_config('migrate_signals')
 SIGNAL_ARGS = ['app_config', 'verbosity', 'interactive', 'using', 'plan', 'apps']
@@ -70,7 +71,7 @@ class MigrateSignalTests(TransactionTestCase):
         post_migrate_receiver = Receiver(signals.post_migrate)
         management.call_command(
             'migrate', database=MIGRATE_DATABASE, verbosity=MIGRATE_VERBOSITY,
-            interactive=MIGRATE_INTERACTIVE, stdout=six.StringIO(),
+            interactive=MIGRATE_INTERACTIVE, stdout=StringIO(),
         )
 
         for receiver in [pre_migrate_receiver, post_migrate_receiver]:
@@ -91,7 +92,7 @@ class MigrateSignalTests(TransactionTestCase):
         """
         pre_migrate_receiver = Receiver(signals.pre_migrate)
         post_migrate_receiver = Receiver(signals.post_migrate)
-        stdout = six.StringIO()
+        stdout = StringIO()
         management.call_command(
             'migrate', database=MIGRATE_DATABASE, verbosity=MIGRATE_VERBOSITY,
             interactive=MIGRATE_INTERACTIVE, stdout=stdout,
diff --git a/tests/migrations/models.py b/tests/migrations/models.py
index dea320c033..9a34edf349 100644
--- a/tests/migrations/models.py
+++ b/tests/migrations/models.py
@@ -1,13 +1,12 @@
 from django.apps.registry import Apps
 from django.db import models
-from django.utils import six
 
 
 class CustomModelBase(models.base.ModelBase):
     pass
 
 
-class ModelWithCustomBase(six.with_metaclass(CustomModelBase, models.Model)):
+class ModelWithCustomBase(models.Model, metaclass=CustomModelBase):
     pass
 
 
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index ca6b42044f..e6de83212e 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -15,7 +15,6 @@ from django.db.migrations.exceptions import (
 )
 from django.db.migrations.recorder import MigrationRecorder
 from django.test import mock, override_settings
-from django.utils import six
 from django.utils.encoding import force_text
 
 from .models import UnicodeModel, UnserializableModel
@@ -63,7 +62,7 @@ class MigrateTests(MigrationTestBase):
         'migrations.migrations_test_apps.migrated_app',
     ])
     def test_migrate_with_system_checks(self):
-        out = six.StringIO()
+        out = io.StringIO()
         call_command('migrate', skip_checks=False, no_color=True, stdout=out)
         self.assertIn('Apply all migrations: migrated_app', out.getvalue())
 
@@ -125,7 +124,7 @@ class MigrateTests(MigrationTestBase):
         with self.assertRaises(DatabaseError):
             call_command("migrate", "migrations", "0001", verbosity=0)
         # Run initial migration with an explicit --fake-initial
-        out = six.StringIO()
+        out = io.StringIO()
         with mock.patch('django.core.management.color.supports_color', lambda *args: False):
             call_command("migrate", "migrations", "0001", fake_initial=True, stdout=out, verbosity=1)
             call_command("migrate", "migrations", "0001", fake_initial=True, verbosity=0, database="other")
@@ -177,7 +176,7 @@ class MigrateTests(MigrationTestBase):
         """
         call_command("migrate", "migrations", "0002", verbosity=0)
         call_command("migrate", "migrations", "zero", fake=True, verbosity=0)
-        out = six.StringIO()
+        out = io.StringIO()
         with mock.patch('django.core.management.color.supports_color', lambda *args: False):
             call_command("migrate", "migrations", "0002", fake_initial=True, stdout=out, verbosity=1)
         value = out.getvalue().lower()
@@ -202,7 +201,7 @@ class MigrateTests(MigrationTestBase):
         showmigrations --list  displays migrations and whether or not they're
         applied.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with mock.patch('django.core.management.color.supports_color', lambda *args: True):
             call_command("showmigrations", format='list', stdout=out, verbosity=0, no_color=False)
         self.assertEqual(
@@ -214,7 +213,7 @@ class MigrateTests(MigrationTestBase):
 
         call_command("migrate", "migrations", "0001", verbosity=0)
 
-        out = six.StringIO()
+        out = io.StringIO()
         # Giving the explicit app_label tests for selective `show_list` in the command
         call_command("showmigrations", "migrations", format='list', stdout=out, verbosity=0, no_color=True)
         self.assertEqual(
@@ -231,7 +230,7 @@ class MigrateTests(MigrationTestBase):
         """
         Tests --plan output of showmigrations command
         """
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out)
         self.assertEqual(
             "[ ]  migrations.0001_initial\n"
@@ -240,7 +239,7 @@ class MigrateTests(MigrationTestBase):
             out.getvalue().lower()
         )
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out, verbosity=2)
         self.assertEqual(
             "[ ]  migrations.0001_initial\n"
@@ -250,7 +249,7 @@ class MigrateTests(MigrationTestBase):
         )
         call_command("migrate", "migrations", "0003", verbosity=0)
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out)
         self.assertEqual(
             "[x]  migrations.0001_initial\n"
@@ -259,7 +258,7 @@ class MigrateTests(MigrationTestBase):
             out.getvalue().lower()
         )
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out, verbosity=2)
         self.assertEqual(
             "[x]  migrations.0001_initial\n"
@@ -276,11 +275,11 @@ class MigrateTests(MigrationTestBase):
         """
         Tests --plan output of showmigrations command without migrations
         """
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out)
         self.assertEqual("", out.getvalue().lower())
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out, verbosity=2)
         self.assertEqual("", out.getvalue().lower())
 
@@ -289,7 +288,7 @@ class MigrateTests(MigrationTestBase):
         """
         Tests --plan output of showmigrations command with squashed migrations.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out)
         self.assertEqual(
             "[ ]  migrations.1_auto\n"
@@ -300,7 +299,7 @@ class MigrateTests(MigrationTestBase):
             out.getvalue().lower()
         )
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out, verbosity=2)
         self.assertEqual(
             "[ ]  migrations.1_auto\n"
@@ -313,7 +312,7 @@ class MigrateTests(MigrationTestBase):
 
         call_command("migrate", "migrations", "3_squashed_5", verbosity=0)
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out)
         self.assertEqual(
             "[x]  migrations.1_auto\n"
@@ -324,7 +323,7 @@ class MigrateTests(MigrationTestBase):
             out.getvalue().lower()
         )
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("showmigrations", format='plan', stdout=out, verbosity=2)
         self.assertEqual(
             "[x]  migrations.1_auto\n"
@@ -345,7 +344,7 @@ class MigrateTests(MigrationTestBase):
         `showmigrations --plan app_label` output with a single app_label.
         """
         # Single app with no dependencies on other apps.
-        out = six.StringIO()
+        out = io.StringIO()
         call_command('showmigrations', 'mutate_state_b', format='plan', stdout=out)
         self.assertEqual(
             '[ ]  mutate_state_b.0001_initial\n'
@@ -353,7 +352,7 @@ class MigrateTests(MigrationTestBase):
             out.getvalue()
         )
         # Single app with dependencies.
-        out = six.StringIO()
+        out = io.StringIO()
         call_command('showmigrations', 'author_app', format='plan', stdout=out)
         self.assertEqual(
             '[ ]  author_app.0001_initial\n'
@@ -363,7 +362,7 @@ class MigrateTests(MigrationTestBase):
         )
         # Some migrations already applied.
         call_command('migrate', 'author_app', '0001', verbosity=0)
-        out = six.StringIO()
+        out = io.StringIO()
         call_command('showmigrations', 'author_app', format='plan', stdout=out)
         self.assertEqual(
             '[X]  author_app.0001_initial\n'
@@ -385,7 +384,7 @@ class MigrateTests(MigrationTestBase):
         """
         # Multiple apps: author_app depends on book_app; mutate_state_b doesn't
         # depend on other apps.
-        out = six.StringIO()
+        out = io.StringIO()
         call_command('showmigrations', 'mutate_state_b', 'author_app', format='plan', stdout=out)
         self.assertEqual(
             '[ ]  author_app.0001_initial\n'
@@ -397,7 +396,7 @@ class MigrateTests(MigrationTestBase):
         )
         # Multiple apps: args order shouldn't matter (the same result is
         # expected as above).
-        out = six.StringIO()
+        out = io.StringIO()
         call_command('showmigrations', 'author_app', 'mutate_state_b', format='plan', stdout=out)
         self.assertEqual(
             '[ ]  author_app.0001_initial\n'
@@ -431,7 +430,7 @@ class MigrateTests(MigrationTestBase):
         """
         sqlmigrate outputs forward looking SQL.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("sqlmigrate", "migrations", "0001", stdout=out)
         output = out.getvalue().lower()
 
@@ -472,7 +471,7 @@ class MigrateTests(MigrationTestBase):
         # Cannot generate the reverse SQL unless we've applied the migration.
         call_command("migrate", "migrations", verbosity=0)
 
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("sqlmigrate", "migrations", "0001", stdout=out, backwards=True)
         output = out.getvalue().lower()
 
@@ -514,7 +513,7 @@ class MigrateTests(MigrationTestBase):
         """
         Transaction wrappers aren't shown for non-atomic migrations.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("sqlmigrate", "migrations", "0001", stdout=out)
         output = out.getvalue().lower()
         queries = [q.strip() for q in output.splitlines()]
@@ -541,7 +540,7 @@ class MigrateTests(MigrationTestBase):
         "B" was not included in the ProjectState that is used to detect
         soft-applied migrations (#22823).
         """
-        call_command("migrate", "migrated_unapplied_app", stdout=six.StringIO())
+        call_command("migrate", "migrated_unapplied_app", stdout=io.StringIO())
 
         # unmigrated_app.SillyModel has a foreign key to 'migrations.Tribble',
         # but that model is only defined in a migration, so the global app
@@ -570,7 +569,7 @@ class MigrateTests(MigrationTestBase):
         replaced migrations as run.
         """
         recorder = MigrationRecorder(connection)
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("migrate", "migrations", verbosity=0)
         call_command("showmigrations", "migrations", stdout=out, no_color=True)
         self.assertEqual(
@@ -594,7 +593,7 @@ class MigrateTests(MigrationTestBase):
         recorder = MigrationRecorder(connection)
         recorder.record_applied("migrations", "0001_initial")
         recorder.record_applied("migrations", "0002_second")
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("migrate", "migrations", verbosity=0)
         call_command("showmigrations", "migrations", stdout=out, no_color=True)
         self.assertEqual(
@@ -683,7 +682,7 @@ class MakeMigrationsTests(MigrationTestBase):
         empty_connections = ConnectionHandler({'default': {}})
         with mock.patch('django.core.management.commands.makemigrations.connections', new=empty_connections):
             # with no apps
-            out = six.StringIO()
+            out = io.StringIO()
             call_command('makemigrations', stdout=out)
             self.assertIn('No changes detected', out.getvalue())
             # with an app
@@ -767,7 +766,7 @@ class MakeMigrationsTests(MigrationTestBase):
         """
         makemigrations exits if in merge mode with no conflicts.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations"):
             call_command("makemigrations", merge=True, stdout=out)
         self.assertIn("No conflicts detected to merge.", out.getvalue())
@@ -776,7 +775,7 @@ class MakeMigrationsTests(MigrationTestBase):
         """
         makemigrations exits if a non-existent app is specified.
         """
-        err = six.StringIO()
+        err = io.StringIO()
         with self.assertRaises(SystemExit):
             call_command("makemigrations", "this_app_does_not_exist", stderr=err)
         self.assertIn("'this_app_does_not_exist' could not be found.", err.getvalue())
@@ -824,7 +823,7 @@ class MakeMigrationsTests(MigrationTestBase):
         """
         makemigrations exits when there are no changes and no apps are specified.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         call_command("makemigrations", stdout=out)
         self.assertIn("No changes detected", out.getvalue())
 
@@ -832,7 +831,7 @@ class MakeMigrationsTests(MigrationTestBase):
         """
         makemigrations exits when there are no changes to an app.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_no_changes"):
             call_command("makemigrations", "migrations", stdout=out)
         self.assertIn("No changes detected in app 'migrations'", out.getvalue())
@@ -842,7 +841,7 @@ class MakeMigrationsTests(MigrationTestBase):
         makemigrations should detect initial is needed on empty migration
         modules if no app provided.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_empty"):
             call_command("makemigrations", stdout=out)
         self.assertIn("0001_initial.py", out.getvalue())
@@ -851,7 +850,7 @@ class MakeMigrationsTests(MigrationTestBase):
         """
         makemigrations announces the migration at the default verbosity level.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module():
             call_command("makemigrations", "migrations", stdout=out)
         self.assertIn("Migrations for 'migrations'", out.getvalue())
@@ -873,7 +872,7 @@ class MakeMigrationsTests(MigrationTestBase):
         makemigrations enters and exits interactive mode properly.
         """
         # Monkeypatch interactive questioner to auto reject
-        with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')):
+        with mock.patch('builtins.input', mock.Mock(return_value='N')):
             with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
                 call_command("makemigrations", "migrations", name="merge", merge=True, interactive=True, verbosity=0)
                 merge_file = os.path.join(migration_dir, '0003_merge.py')
@@ -884,8 +883,8 @@ class MakeMigrationsTests(MigrationTestBase):
         makemigrations enters interactive mode and merges properly.
         """
         # Monkeypatch interactive questioner to auto accept
-        with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
-            out = six.StringIO()
+        with mock.patch('builtins.input', mock.Mock(return_value='y')):
+            out = io.StringIO()
             with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
                 call_command("makemigrations", "migrations", name="merge", merge=True, interactive=True, stdout=out)
                 merge_file = os.path.join(migration_dir, '0003_merge.py')
@@ -895,8 +894,8 @@ class MakeMigrationsTests(MigrationTestBase):
     @mock.patch('django.db.migrations.utils.datetime')
     def test_makemigrations_default_merge_name(self, mock_datetime):
         mock_datetime.datetime.now.return_value = datetime.datetime(2016, 1, 2, 3, 4)
-        with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
-            out = six.StringIO()
+        with mock.patch('builtins.input', mock.Mock(return_value='y')):
+            out = io.StringIO()
             with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
                 call_command("makemigrations", "migrations", merge=True, interactive=True, stdout=out)
                 merge_file = os.path.join(migration_dir, '0003_merge_20160102_0304.py')
@@ -915,7 +914,7 @@ class MakeMigrationsTests(MigrationTestBase):
             class Meta:
                 app_label = "migrations"
 
-        out = six.StringIO()
+        out = io.StringIO()
         with self.assertRaises(SystemExit):
             with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
                 call_command("makemigrations", "migrations", interactive=False, stdout=out)
@@ -933,7 +932,7 @@ class MakeMigrationsTests(MigrationTestBase):
             class Meta:
                 app_label = "migrations"
 
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations"):
             call_command("makemigrations", "migrations", interactive=False, stdout=out)
         self.assertIn("Alter field slug on author", out.getvalue())
@@ -949,7 +948,7 @@ class MakeMigrationsTests(MigrationTestBase):
             class Meta:
                 app_label = "migrations"
 
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
             call_command("makemigrations", "migrations", interactive=False, stdout=out)
         self.assertIn("Delete model SillyModel", out.getvalue())
@@ -966,7 +965,7 @@ class MakeMigrationsTests(MigrationTestBase):
             class Meta:
                 app_label = "migrations"
 
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
             call_command("makemigrations", "migrations", interactive=False, stdout=out)
         self.assertIn("Remove field silly_field from sillymodel", out.getvalue())
@@ -976,7 +975,7 @@ class MakeMigrationsTests(MigrationTestBase):
         """
         makemigrations properly merges the conflicting migrations with --noinput.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
             call_command("makemigrations", "migrations", name="merge", merge=True, interactive=False, stdout=out)
             merge_file = os.path.join(migration_dir, '0003_merge.py')
@@ -992,7 +991,7 @@ class MakeMigrationsTests(MigrationTestBase):
         makemigrations respects --dry-run option when fixing migration
         conflicts (#24427).
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
             call_command(
                 "makemigrations", "migrations", name="merge", dry_run=True,
@@ -1011,7 +1010,7 @@ class MakeMigrationsTests(MigrationTestBase):
         `makemigrations --merge --dry-run` writes the merge migration file to
         stdout with `verbosity == 3` (#24427).
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
             call_command(
                 "makemigrations", "migrations", name="merge", dry_run=True,
@@ -1045,7 +1044,7 @@ class MakeMigrationsTests(MigrationTestBase):
             class Meta:
                 app_label = "migrations"
 
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
             call_command("makemigrations", "migrations", dry_run=True, stdout=out)
         # Output the expected changes directly, without asking for defaults
@@ -1063,7 +1062,7 @@ class MakeMigrationsTests(MigrationTestBase):
             class Meta:
                 app_label = "migrations"
 
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_no_default"):
             call_command("makemigrations", "migrations", dry_run=True, stdout=out, verbosity=3)
 
@@ -1091,7 +1090,7 @@ class MakeMigrationsTests(MigrationTestBase):
             class Meta:
                 app_label = "migrations"
 
-        out = six.StringIO()
+        out = io.StringIO()
         migration_module = "migrations.test_migrations_path_doesnt_exist.foo.bar"
         with self.temporary_migration_module(module=migration_module) as migration_dir:
             call_command("makemigrations", "migrations", stdout=out)
@@ -1110,8 +1109,8 @@ class MakeMigrationsTests(MigrationTestBase):
         --noinput is specified.
         """
         # Monkeypatch interactive questioner to auto reject
-        out = six.StringIO()
-        with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')):
+        out = io.StringIO()
+        with mock.patch('builtins.input', mock.Mock(return_value='N')):
             with self.temporary_migration_module(module="migrations.test_migrations_conflict") as migration_dir:
                 call_command("makemigrations", "migrations", name="merge", merge=True, stdout=out)
                 merge_file = os.path.join(migration_dir, '0003_merge.py')
@@ -1141,8 +1140,8 @@ class MakeMigrationsTests(MigrationTestBase):
         it has conflicting migrations.
         """
         # Monkeypatch interactive questioner to auto accept
-        with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='y')):
-            out = six.StringIO()
+        with mock.patch('builtins.input', mock.Mock(return_value='y')):
+            out = io.StringIO()
             with self.temporary_migration_module(app_label="migrated_app") as migration_dir:
                 call_command("makemigrations", "migrated_app", name="merge", merge=True, interactive=True, stdout=out)
                 merge_file = os.path.join(migration_dir, '0003_merge.py')
@@ -1159,8 +1158,8 @@ class MakeMigrationsTests(MigrationTestBase):
         don't belong to a given app.
         """
         # Monkeypatch interactive questioner to auto accept
-        with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')):
-            out = six.StringIO()
+        with mock.patch('builtins.input', mock.Mock(return_value='N')):
+            out = io.StringIO()
             with mock.patch('django.core.management.color.supports_color', lambda *args: False):
                 call_command(
                     "makemigrations", "conflicting_app_with_dependencies",
@@ -1232,7 +1231,7 @@ class MakeMigrationsTests(MigrationTestBase):
         they are outside of the current tree, in which case the absolute path
         should be shown.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         apps.register_model('migrations', UnicodeModel)
         with self.temporary_migration_module() as migration_dir:
             call_command("makemigrations", "migrations", stdout=out)
@@ -1245,7 +1244,7 @@ class MakeMigrationsTests(MigrationTestBase):
         Windows if Django is installed on a different drive than where the
         migration files are created.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module() as migration_dir:
             with mock.patch('os.path.relpath', side_effect=ValueError):
                 call_command('makemigrations', 'migrations', stdout=out)
@@ -1263,7 +1262,7 @@ class MakeMigrationsTests(MigrationTestBase):
             with self.assertRaisesMessage(InconsistentMigrationHistory, msg):
                 call_command("makemigrations")
 
-    @mock.patch('django.db.migrations.questioner.input', return_value='1')
+    @mock.patch('builtins.input', return_value='1')
     @mock.patch('django.db.migrations.questioner.sys.stdin', mock.MagicMock(encoding=sys.getdefaultencoding()))
     def test_makemigrations_auto_now_add_interactive(self, *args):
         """
@@ -1278,8 +1277,8 @@ class MakeMigrationsTests(MigrationTestBase):
                 app_label = 'migrations'
 
         # Monkeypatch interactive questioner to auto accept
-        with mock.patch('django.db.migrations.questioner.sys.stdout', new_callable=six.StringIO) as prompt_stdout:
-            out = six.StringIO()
+        with mock.patch('django.db.migrations.questioner.sys.stdout', new_callable=io.StringIO) as prompt_stdout:
+            out = io.StringIO()
             with self.temporary_migration_module(module='migrations.test_auto_now_add'):
                 call_command('makemigrations', 'migrations', interactive=True, stdout=out)
             output = out.getvalue()
@@ -1316,7 +1315,7 @@ class SquashMigrationsTests(MigrationTestBase):
         """
         squashmigrations optimizes operations.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations"):
             call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=1, stdout=out)
         self.assertIn("Optimized from 8 operations to 3 operations.", out.getvalue())
@@ -1325,7 +1324,7 @@ class SquashMigrationsTests(MigrationTestBase):
         """
         squashmigrations --no-optimize doesn't optimize operations.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations"):
             call_command("squashmigrations", "migrations", "0002",
                          interactive=False, verbosity=1, no_optimize=True, stdout=out)
@@ -1335,7 +1334,7 @@ class SquashMigrationsTests(MigrationTestBase):
         """
         squashmigrations accepts a starting migration.
         """
-        out = six.StringIO()
+        out = io.StringIO()
         with self.temporary_migration_module(module="migrations.test_migrations_no_changes") as migration_dir:
             call_command("squashmigrations", "migrations", "0002", "0003",
                          interactive=False, verbosity=1, stdout=out)
diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py
index 59fd8eece5..fb16b441d1 100644
--- a/tests/migrations/test_writer.py
+++ b/tests/migrations/test_writer.py
@@ -8,6 +8,7 @@ import sys
 import tokenize
 import unittest
 import uuid
+from io import StringIO
 
 import custom_migration_operations.more_operations
 import custom_migration_operations.operations
@@ -20,7 +21,7 @@ from django.db.migrations.writer import (
     MigrationWriter, OperationWriter, SettingsReference,
 )
 from django.test import SimpleTestCase, ignore_warnings, mock
-from django.utils import datetime_safe, six
+from django.utils import datetime_safe
 from django.utils._os import upath
 from django.utils.deconstruct import deconstructible
 from django.utils.encoding import force_str
@@ -561,7 +562,7 @@ class WriterTests(SimpleTestCase):
         self.assertIn("Migration", result)
         # In order to preserve compatibility with Python 3.2 unicode literals
         # prefix shouldn't be added to strings.
-        tokens = tokenize.generate_tokens(six.StringIO(str(output)).readline)
+        tokens = tokenize.generate_tokens(StringIO(str(output)).readline)
         for token_type, token_source, (srow, scol), __, line in tokens:
             if token_type == tokenize.STRING:
                 self.assertFalse(
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
index 019301d5e2..4830fb9ed2 100644
--- a/tests/model_fields/models.py
+++ b/tests/model_fields/models.py
@@ -12,7 +12,6 @@ from django.db.models.fields.files import ImageField, ImageFieldFile
 from django.db.models.fields.related import (
     ForeignKey, ForeignObject, ManyToManyField, OneToOneField,
 )
-from django.utils import six
 
 try:
     from PIL import Image
@@ -51,7 +50,7 @@ class Whiz(models.Model):
     c = models.IntegerField(choices=CHOICES, null=True)
 
 
-class Counter(six.Iterator):
+class Counter:
     def __init__(self):
         self.n = 1
 
diff --git a/tests/model_fields/test_binaryfield.py b/tests/model_fields/test_binaryfield.py
index 641dc17287..9d97d1118f 100644
--- a/tests/model_fields/test_binaryfield.py
+++ b/tests/model_fields/test_binaryfield.py
@@ -1,6 +1,5 @@
 from django.core.exceptions import ValidationError
 from django.test import TestCase
-from django.utils import six
 
 from .models import DataModel
 
@@ -9,7 +8,7 @@ class BinaryFieldTests(TestCase):
     binary_data = b'\x00\x46\xFE'
 
     def test_set_and_retrieve(self):
-        data_set = (self.binary_data, six.memoryview(self.binary_data))
+        data_set = (self.binary_data, memoryview(self.binary_data))
         for bdata in data_set:
             dm = DataModel(data=bdata)
             dm.save()
diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py
index 0914fc2587..f85ae8e1fd 100644
--- a/tests/model_forms/models.py
+++ b/tests/model_forms/models.py
@@ -16,7 +16,6 @@ from django.core.exceptions import ValidationError
 from django.core.files.storage import FileSystemStorage
 from django.db import models
 from django.utils._os import upath
-from django.utils.six.moves import range
 
 temp_storage_dir = tempfile.mkdtemp()
 temp_storage = FileSystemStorage(temp_storage_dir)
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index 27143e97f4..adb90d6d14 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -17,7 +17,6 @@ from django.forms.models import (
 )
 from django.template import Context, Template
 from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
-from django.utils import six
 from django.utils._os import upath
 
 from .models import (
@@ -2954,7 +2953,7 @@ class CustomMetaclass(ModelFormMetaclass):
         return new
 
 
-class CustomMetaclassForm(six.with_metaclass(CustomMetaclass, forms.ModelForm)):
+class CustomMetaclassForm(forms.ModelForm, metaclass=CustomMetaclass):
     pass
 
 
diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py
index 7e936216b7..3487894fd9 100644
--- a/tests/multiple_database/tests.py
+++ b/tests/multiple_database/tests.py
@@ -1,5 +1,6 @@
 import datetime
 import pickle
+from io import StringIO
 from operator import attrgetter
 
 from django.contrib.auth.models import User
@@ -9,7 +10,6 @@ from django.db import DEFAULT_DB_ALIAS, connections, router, transaction
 from django.db.models import signals
 from django.db.utils import ConnectionRouter
 from django.test import SimpleTestCase, TestCase, override_settings
-from django.utils.six import StringIO
 
 from .models import Book, Person, Pet, Review, UserProfile
 from .routers import AuthRouter, TestRouter, WriteRouter
diff --git a/tests/pagination/tests.py b/tests/pagination/tests.py
index cc3a0efbc2..2e2e2d37db 100644
--- a/tests/pagination/tests.py
+++ b/tests/pagination/tests.py
@@ -6,7 +6,6 @@ from django.core.paginator import (
     UnorderedObjectListWarning,
 )
 from django.test import TestCase
-from django.utils import six
 
 from .custom import ValidAdjacentNumsPaginator
 from .models import Article
@@ -240,7 +239,7 @@ class PaginationTests(unittest.TestCase):
         """
         Paginator.page_range should be an iterator.
         """
-        self.assertIsInstance(Paginator([1, 2, 3], 2).page_range, type(six.moves.range(0)))
+        self.assertIsInstance(Paginator([1, 2, 3], 2).page_range, type(range(0)))
 
 
 class ModelPaginationTests(TestCase):
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index ed323be154..1f9094e49f 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -11,7 +11,6 @@ from django.db.models.sql.constants import LOUTER
 from django.db.models.sql.where import NothingNode, WhereNode
 from django.test import TestCase, skipUnlessDBFeature
 from django.test.utils import CaptureQueriesContext
-from django.utils.six.moves import range
 
 from .models import (
     FK1, Annotation, Article, Author, BaseA, Book, CategoryItem,
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index 09b08540f4..54fe797728 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -1,7 +1,9 @@
 import time
 from datetime import datetime, timedelta
+from http import cookies
 from io import BytesIO
 from itertools import chain
+from urllib.parse import urlencode as original_urlencode
 
 from django.core.exceptions import SuspiciousOperation
 from django.core.handlers.wsgi import LimitedStream, WSGIRequest
@@ -14,8 +16,6 @@ from django.test.client import FakePayload
 from django.test.utils import freeze_time, str_prefix
 from django.utils.encoding import force_str
 from django.utils.http import cookie_date, urlencode
-from django.utils.six.moves import http_cookies
-from django.utils.six.moves.urllib.parse import urlencode as original_urlencode
 from django.utils.timezone import utc
 
 
@@ -262,7 +262,7 @@ class RequestsTests(SimpleTestCase):
         example_cookie = response.cookies['example']
         # A compat cookie may be in use -- check that it has worked
         # both as an output string, and using the cookie attributes
-        self.assertIn('; %s' % http_cookies.Morsel._reserved['httponly'], str(example_cookie))
+        self.assertIn('; %s' % cookies.Morsel._reserved['httponly'], str(example_cookie))
         self.assertTrue(example_cookie['httponly'])
 
     def test_unicode_cookie(self):
diff --git a/tests/serializers/test_data.py b/tests/serializers/test_data.py
index 2e9527f982..966df682b2 100644
--- a/tests/serializers/test_data.py
+++ b/tests/serializers/test_data.py
@@ -13,7 +13,6 @@ import uuid
 from django.core import serializers
 from django.db import connection, models
 from django.test import TestCase
-from django.utils import six
 
 from .models import (
     Anchor, AutoNowDateTimeData, BigIntegerData, BinaryData, BooleanData,
@@ -197,7 +196,7 @@ uuid_obj = uuid.uuid4()
 
 test_data = [
     # Format: (data type, PK value, Model Class, data)
-    (data_obj, 1, BinaryData, six.memoryview(b"\x05\xFD\x00")),
+    (data_obj, 1, BinaryData, memoryview(b"\x05\xFD\x00")),
     (data_obj, 2, BinaryData, None),
     (data_obj, 5, BooleanData, True),
     (data_obj, 6, BooleanData, False),
diff --git a/tests/serializers/test_yaml.py b/tests/serializers/test_yaml.py
index f3988d7ff1..542c28fb2e 100644
--- a/tests/serializers/test_yaml.py
+++ b/tests/serializers/test_yaml.py
@@ -1,10 +1,10 @@
 import importlib
 import unittest
+from io import StringIO
 
 from django.core import management, serializers
 from django.core.serializers.base import DeserializationError
 from django.test import SimpleTestCase, TestCase, TransactionTestCase
-from django.utils.six import StringIO
 
 from .models import Author
 from .tests import SerializersTestBase, SerializersTransactionTestBase
diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py
index 656bc4b4db..5c30d66082 100644
--- a/tests/serializers/tests.py
+++ b/tests/serializers/tests.py
@@ -1,4 +1,5 @@
 from datetime import datetime
+from io import StringIO
 
 from django.core import serializers
 from django.core.serializers import SerializerDoesNotExist
@@ -10,7 +11,6 @@ from django.test import (
 )
 from django.test.utils import Approximate
 from django.utils.functional import curry
-from django.utils.six import StringIO
 
 from .models import (
     Actor, Article, Author, AuthorProfile, BaseModel, Category, ComplexModel,
diff --git a/tests/servers/tests.py b/tests/servers/tests.py
index ee0d8629cf..8f7e3253e7 100644
--- a/tests/servers/tests.py
+++ b/tests/servers/tests.py
@@ -5,12 +5,12 @@ import contextlib
 import errno
 import os
 import socket
+from urllib.error import HTTPError
+from urllib.request import urlopen
 
 from django.test import LiveServerTestCase, override_settings
 from django.utils._os import upath
 from django.utils.http import urlencode
-from django.utils.six.moves.urllib.error import HTTPError
-from django.utils.six.moves.urllib.request import urlopen
 
 from .models import Person
 
diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py
index ae57f1d919..29de5782d9 100644
--- a/tests/sessions_tests/tests.py
+++ b/tests/sessions_tests/tests.py
@@ -6,6 +6,7 @@ import sys
 import tempfile
 import unittest
 from datetime import timedelta
+from http import cookies
 
 from django.conf import settings
 from django.contrib.sessions.backends.base import UpdateError
@@ -31,9 +32,8 @@ from django.test import (
     RequestFactory, TestCase, ignore_warnings, override_settings,
 )
 from django.test.utils import patch_logger
-from django.utils import six, timezone
+from django.utils import timezone
 from django.utils.encoding import force_text
-from django.utils.six.moves import http_cookies
 
 from .models import SessionStore as CustomDatabaseSession
 
@@ -122,7 +122,7 @@ class SessionTestsMixin(object):
         self.session['x'] = 1
         self.session.modified = False
         self.session.accessed = False
-        i = six.iterkeys(self.session)
+        i = iter(self.session.keys())
         self.assertTrue(hasattr(i, '__iter__'))
         self.assertTrue(self.session.accessed)
         self.assertFalse(self.session.modified)
@@ -132,7 +132,7 @@ class SessionTestsMixin(object):
         self.session['x'] = 1
         self.session.modified = False
         self.session.accessed = False
-        i = six.itervalues(self.session)
+        i = iter(self.session.values())
         self.assertTrue(hasattr(i, '__iter__'))
         self.assertTrue(self.session.accessed)
         self.assertFalse(self.session.modified)
@@ -142,7 +142,7 @@ class SessionTestsMixin(object):
         self.session['x'] = 1
         self.session.modified = False
         self.session.accessed = False
-        i = six.iteritems(self.session)
+        i = iter(self.session.items())
         self.assertTrue(hasattr(i, '__iter__'))
         self.assertTrue(self.session.accessed)
         self.assertFalse(self.session.modified)
@@ -661,7 +661,7 @@ class SessionMiddlewareTests(TestCase):
         self.assertTrue(
             response.cookies[settings.SESSION_COOKIE_NAME]['httponly'])
         self.assertIn(
-            http_cookies.Morsel._reserved['httponly'],
+            cookies.Morsel._reserved['httponly'],
             str(response.cookies[settings.SESSION_COOKIE_NAME])
         )
 
@@ -679,7 +679,7 @@ class SessionMiddlewareTests(TestCase):
         response = middleware.process_response(request, response)
         self.assertFalse(response.cookies[settings.SESSION_COOKIE_NAME]['httponly'])
 
-        self.assertNotIn(http_cookies.Morsel._reserved['httponly'],
+        self.assertNotIn(cookies.Morsel._reserved['httponly'],
                          str(response.cookies[settings.SESSION_COOKIE_NAME]))
 
     def test_session_save_on_500(self):
diff --git a/tests/sitemaps_tests/test_utils.py b/tests/sitemaps_tests/test_utils.py
index ba1eadd5d1..dc4d6511f3 100644
--- a/tests/sitemaps_tests/test_utils.py
+++ b/tests/sitemaps_tests/test_utils.py
@@ -1,9 +1,10 @@
+from urllib.parse import urlencode
+
 from django.contrib.sitemaps import (
     SitemapNotFound, _get_sitemap_full_url, ping_google,
 )
 from django.core.exceptions import ImproperlyConfigured
 from django.test import mock, modify_settings, override_settings
-from django.utils.six.moves.urllib.parse import urlencode
 
 from .base import SitemapTestsBase
 
diff --git a/tests/staticfiles_tests/test_forms.py b/tests/staticfiles_tests/test_forms.py
index e3d4772662..4666520bc1 100644
--- a/tests/staticfiles_tests/test_forms.py
+++ b/tests/staticfiles_tests/test_forms.py
@@ -1,8 +1,9 @@
+from urllib.parse import urljoin
+
 from django.contrib.staticfiles import storage
 from django.contrib.staticfiles.templatetags.staticfiles import static
 from django.forms import Media
 from django.test import SimpleTestCase, override_settings
-from django.utils.six.moves.urllib.parse import urljoin
 
 
 class StaticTestStorage(storage.StaticFilesStorage):
diff --git a/tests/staticfiles_tests/test_liveserver.py b/tests/staticfiles_tests/test_liveserver.py
index 1714ae8b1b..b3727bea12 100644
--- a/tests/staticfiles_tests/test_liveserver.py
+++ b/tests/staticfiles_tests/test_liveserver.py
@@ -6,12 +6,12 @@ django.test.LiveServerTestCase.
 
 import contextlib
 import os
+from urllib.request import urlopen
 
 from django.contrib.staticfiles.testing import StaticLiveServerTestCase
 from django.core.exceptions import ImproperlyConfigured
 from django.test import modify_settings, override_settings
 from django.utils._os import upath
-from django.utils.six.moves.urllib.request import urlopen
 
 TEST_ROOT = os.path.dirname(upath(__file__))
 TEST_SETTINGS = {
diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py
index 7d17f2abe1..310c152d06 100644
--- a/tests/staticfiles_tests/test_management.py
+++ b/tests/staticfiles_tests/test_management.py
@@ -4,6 +4,7 @@ import os
 import shutil
 import tempfile
 import unittest
+from io import StringIO
 
 from admin_scripts.tests import AdminScriptTestCase
 
@@ -14,7 +15,7 @@ from django.core.exceptions import ImproperlyConfigured
 from django.core.management import call_command
 from django.test import mock, override_settings
 from django.test.utils import extend_sys_path
-from django.utils import six, timezone
+from django.utils import timezone
 from django.utils._os import symlinks_supported
 from django.utils.encoding import force_text
 from django.utils.functional import empty
@@ -38,7 +39,7 @@ class TestFindStatic(TestDefaults, CollectionTestCase):
     Test ``findstatic`` management command.
     """
     def _get_file(self, filepath):
-        path = call_command('findstatic', filepath, all=False, verbosity=0, stdout=six.StringIO())
+        path = call_command('findstatic', filepath, all=False, verbosity=0, stdout=StringIO())
         with codecs.open(force_text(path), "r", "utf-8") as f:
             return f.read()
 
@@ -46,7 +47,7 @@ class TestFindStatic(TestDefaults, CollectionTestCase):
         """
         findstatic returns all candidate files if run without --first and -v1.
         """
-        result = call_command('findstatic', 'test/file.txt', verbosity=1, stdout=six.StringIO())
+        result = call_command('findstatic', 'test/file.txt', verbosity=1, stdout=StringIO())
         lines = [l.strip() for l in result.split('\n')]
         self.assertEqual(len(lines), 3)  # three because there is also the "Found <file> here" line
         self.assertIn('project', force_text(lines[1]))
@@ -56,7 +57,7 @@ class TestFindStatic(TestDefaults, CollectionTestCase):
         """
         findstatic returns all candidate files if run without --first and -v0.
         """
-        result = call_command('findstatic', 'test/file.txt', verbosity=0, stdout=six.StringIO())
+        result = call_command('findstatic', 'test/file.txt', verbosity=0, stdout=StringIO())
         lines = [l.strip() for l in result.split('\n')]
         self.assertEqual(len(lines), 2)
         self.assertIn('project', force_text(lines[0]))
@@ -67,7 +68,7 @@ class TestFindStatic(TestDefaults, CollectionTestCase):
         findstatic returns all candidate files if run without --first and -v2.
         Also, test that findstatic returns the searched locations with -v2.
         """
-        result = call_command('findstatic', 'test/file.txt', verbosity=2, stdout=six.StringIO())
+        result = call_command('findstatic', 'test/file.txt', verbosity=2, stdout=StringIO())
         lines = [l.strip() for l in result.split('\n')]
         self.assertIn('project', force_text(lines[1]))
         self.assertIn('apps', force_text(lines[2]))
@@ -89,7 +90,7 @@ class TestFindStatic(TestDefaults, CollectionTestCase):
 class TestConfiguration(StaticFilesTestCase):
     def test_location_empty(self):
         msg = 'without having set the STATIC_ROOT setting to a filesystem path'
-        err = six.StringIO()
+        err = StringIO()
         for root in ['', None]:
             with override_settings(STATIC_ROOT=root):
                 with self.assertRaisesMessage(ImproperlyConfigured, msg):
@@ -188,10 +189,9 @@ class TestInteractiveMessages(CollectionTestCase):
         return _input
 
     def test_warning_when_clearing_staticdir(self):
-        stdout = six.StringIO()
+        stdout = StringIO()
         self.run_collectstatic()
-        with mock.patch('django.contrib.staticfiles.management.commands.collectstatic.input',
-                        side_effect=self.mock_input(stdout)):
+        with mock.patch('builtins.input', side_effect=self.mock_input(stdout)):
             call_command('collectstatic', interactive=True, clear=True, stdout=stdout)
 
         output = force_text(stdout.getvalue())
@@ -199,17 +199,16 @@ class TestInteractiveMessages(CollectionTestCase):
         self.assertIn(self.delete_warning_msg, output)
 
     def test_warning_when_overwriting_files_in_staticdir(self):
-        stdout = six.StringIO()
+        stdout = StringIO()
         self.run_collectstatic()
-        with mock.patch('django.contrib.staticfiles.management.commands.collectstatic.input',
-                        side_effect=self.mock_input(stdout)):
+        with mock.patch('builtins.input', side_effect=self.mock_input(stdout)):
             call_command('collectstatic', interactive=True, stdout=stdout)
         output = force_text(stdout.getvalue())
         self.assertIn(self.overwrite_warning_msg, output)
         self.assertNotIn(self.delete_warning_msg, output)
 
     def test_no_warning_when_staticdir_does_not_exist(self):
-        stdout = six.StringIO()
+        stdout = StringIO()
         shutil.rmtree(settings.STATIC_ROOT)
         call_command('collectstatic', interactive=True, stdout=stdout)
         output = force_text(stdout.getvalue())
@@ -218,7 +217,7 @@ class TestInteractiveMessages(CollectionTestCase):
         self.assertIn(self.files_copied_msg, output)
 
     def test_no_warning_for_empty_staticdir(self):
-        stdout = six.StringIO()
+        stdout = StringIO()
         static_dir = tempfile.mkdtemp(prefix='collectstatic_empty_staticdir_test')
         with override_settings(STATIC_ROOT=static_dir):
             call_command('collectstatic', interactive=True, stdout=stdout)
@@ -347,7 +346,7 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
         the command at highest verbosity, which is why we can't
         just call e.g. BaseCollectionTestCase.run_collectstatic()
         """
-        out = six.StringIO()
+        out = StringIO()
         call_command('collectstatic', interactive=False, verbosity=3, stdout=out, **kwargs)
         return force_text(out.getvalue())
 
@@ -407,7 +406,7 @@ class TestCollectionNeverCopyStorage(CollectionTestCase):
         NeverCopyRemoteStorage.get_modified_time() returns a datetime in the
         future to simulate an unmodified file.
         """
-        stdout = six.StringIO()
+        stdout = StringIO()
         self.run_collectstatic(stdout=stdout, verbosity=2)
         output = force_text(stdout.getvalue())
         self.assertIn("Skipping 'test.txt' (not modified)", output)
diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py
index 6333be7549..e299feea78 100644
--- a/tests/staticfiles_tests/test_storage.py
+++ b/tests/staticfiles_tests/test_storage.py
@@ -3,6 +3,7 @@ import shutil
 import sys
 import tempfile
 import unittest
+from io import StringIO
 
 from django.conf import settings
 from django.contrib.staticfiles import finders, storage
@@ -11,7 +12,6 @@ from django.contrib.staticfiles.management.commands.collectstatic import \
 from django.core.cache.backends.base import BaseCache
 from django.core.management import call_command
 from django.test import override_settings
-from django.utils import six
 from django.utils.encoding import force_text
 
 from .cases import CollectionTestCase
@@ -172,7 +172,7 @@ class TestHashedFiles(object):
     )
     def test_import_loop(self):
         finders.get_finder.cache_clear()
-        err = six.StringIO()
+        err = StringIO()
         with self.assertRaisesMessage(RuntimeError, 'Max post-process passes exceeded'):
             call_command('collectstatic', interactive=False, verbosity=0, stderr=err)
         self.assertEqual("Post-processing 'All' failed!\n\n", err.getvalue())
@@ -225,7 +225,7 @@ class TestHashedFiles(object):
         post_processing indicates the origin of the error when it fails.
         """
         finders.get_finder.cache_clear()
-        err = six.StringIO()
+        err = StringIO()
         with self.assertRaises(Exception):
             call_command('collectstatic', interactive=False, verbosity=0, stderr=err)
         self.assertEqual("Post-processing 'faulty.css' failed!\n\n", err.getvalue())
@@ -429,7 +429,7 @@ class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
         with self.assertRaisesMessage(ValueError, err_msg):
             self.hashed_file_path(missing_file_name)
 
-        content = six.StringIO()
+        content = StringIO()
         content.write('Found')
         configured_storage.save(missing_file_name, content)
         # File exists on disk
@@ -566,7 +566,7 @@ class TestCollectionHashedFilesCache(CollectionTestCase):
 
     def test_file_change_after_collectstatic(self):
         finders.get_finder.cache_clear()
-        err = six.StringIO()
+        err = StringIO()
         call_command('collectstatic', interactive=False, verbosity=0, stderr=err)
         with open(self.testimage_path, 'w+b') as f:
             f.write(b"new content of png file to change it's hash")
diff --git a/tests/swappable_models/tests.py b/tests/swappable_models/tests.py
index b67aece8c0..e9d15db015 100644
--- a/tests/swappable_models/tests.py
+++ b/tests/swappable_models/tests.py
@@ -1,10 +1,11 @@
-from swappable_models.models import Article
+from io import StringIO
 
 from django.contrib.auth.models import Permission
 from django.contrib.contenttypes.models import ContentType
 from django.core import management
 from django.test import TestCase, override_settings
-from django.utils.six import StringIO
+
+from .models import Article
 
 
 class SwappableModelTests(TestCase):
diff --git a/tests/template_tests/syntax_tests/test_static.py b/tests/template_tests/syntax_tests/test_static.py
index deb0ce6c78..345f943caf 100644
--- a/tests/template_tests/syntax_tests/test_static.py
+++ b/tests/template_tests/syntax_tests/test_static.py
@@ -1,6 +1,7 @@
+from urllib.parse import urljoin
+
 from django.conf import settings
 from django.test import SimpleTestCase, override_settings
-from django.utils.six.moves.urllib.parse import urljoin
 
 from ..utils import setup
 
diff --git a/tests/template_tests/templatetags/custom.py b/tests/template_tests/templatetags/custom.py
index 363e7094ce..485f49a82d 100644
--- a/tests/template_tests/templatetags/custom.py
+++ b/tests/template_tests/templatetags/custom.py
@@ -2,7 +2,6 @@ import operator
 
 from django import template
 from django.template.defaultfilters import stringfilter
-from django.utils import six
 from django.utils.html import escape, format_html
 
 register = template.Library()
@@ -114,7 +113,7 @@ simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dic
 def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     """Expected simple_unlimited_args_kwargs __doc__"""
     # Sort the dictionary by key to guarantee the order for testing.
-    sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0))
+    sorted_kwarg = sorted(kwargs.items(), key=operator.itemgetter(0))
     return "simple_unlimited_args_kwargs - Expected result: %s / %s" % (
         ', '.join(str(arg) for arg in [one, two] + list(args)),
         ', '.join('%s=%s' % (k, v) for (k, v) in sorted_kwarg)
diff --git a/tests/template_tests/templatetags/inclusion.py b/tests/template_tests/templatetags/inclusion.py
index 745dd7ffae..60f654ec00 100644
--- a/tests/template_tests/templatetags/inclusion.py
+++ b/tests/template_tests/templatetags/inclusion.py
@@ -1,7 +1,6 @@
 import operator
 
 from django.template import Engine, Library
-from django.utils import six
 
 engine = Engine(app_dirs=True)
 register = Library()
@@ -215,7 +214,7 @@ inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__"
 def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
     """Expected inclusion_unlimited_args_kwargs __doc__"""
     # Sort the dictionary by key to guarantee the order for testing.
-    sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0))
+    sorted_kwarg = sorted(kwargs.items(), key=operator.itemgetter(0))
     return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % (
         ', '.join(str(arg) for arg in [one, two] + list(args)),
         ', '.join('%s=%s' % (k, v) for (k, v) in sorted_kwarg)
diff --git a/tests/test_client/views.py b/tests/test_client/views.py
index af30c4283d..e9a28449ec 100644
--- a/tests/test_client/views.py
+++ b/tests/test_client/views.py
@@ -1,3 +1,4 @@
+from urllib.parse import urlencode
 from xml.dom.minidom import parseString
 
 from django.contrib.auth.decorators import login_required, permission_required
@@ -13,7 +14,6 @@ from django.shortcuts import render
 from django.template import Context, Template
 from django.test import Client
 from django.utils.decorators import method_decorator
-from django.utils.six.moves.urllib.parse import urlencode
 
 
 def get_view(request):
diff --git a/tests/test_client_regress/views.py b/tests/test_client_regress/views.py
index ce60c0398c..854cdc3bc0 100644
--- a/tests/test_client_regress/views.py
+++ b/tests/test_client_regress/views.py
@@ -1,4 +1,5 @@
 import json
+from urllib.parse import urlencode
 
 from django.conf import settings
 from django.contrib.auth.decorators import login_required
@@ -8,7 +9,6 @@ from django.shortcuts import render
 from django.template.loader import render_to_string
 from django.test import Client
 from django.test.client import CONTENT_TYPE_RE
-from django.utils.six.moves.urllib.parse import urlencode
 
 
 class CustomTestException(Exception):
diff --git a/tests/test_runner/test_debug_sql.py b/tests/test_runner/test_debug_sql.py
index c6b9b01dbc..1b36fbc876 100644
--- a/tests/test_runner/test_debug_sql.py
+++ b/tests/test_runner/test_debug_sql.py
@@ -1,10 +1,10 @@
 import sys
 import unittest
+from io import StringIO
 
 from django.db import connection
 from django.test import TestCase
 from django.test.runner import DiscoverRunner
-from django.utils import six
 
 from .models import Person
 
@@ -33,7 +33,7 @@ class TestDebugSQL(unittest.TestCase):
         suite.addTest(self.ErrorTest())
         suite.addTest(self.PassingTest())
         old_config = runner.setup_databases()
-        stream = six.StringIO()
+        stream = StringIO()
         resultclass = runner.get_resultclass()
         runner.test_runner(
             verbosity=verbosity,
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index 4d29f1a491..f8eda7ed39 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -1,5 +1,6 @@
 import sys
 import unittest
+from io import StringIO
 
 from django.conf.urls import url
 from django.contrib.staticfiles.finders import get_finder, get_finders
@@ -18,7 +19,6 @@ from django.test.utils import (
     setup_test_environment,
 )
 from django.urls import NoReverseMatch, reverse
-from django.utils import six
 from django.utils._os import abspathu
 
 from .models import Car, Person, PossessedCar
@@ -117,7 +117,7 @@ class SkippingClassTestCase(SimpleTestCase):
             test_suite.addTest(SkippedTestsSubclass('test_will_be_skipped'))
         except unittest.SkipTest:
             self.fail("SkipTest should not be raised at this stage")
-        result = unittest.TextTestRunner(stream=six.StringIO()).run(test_suite)
+        result = unittest.TextTestRunner(stream=StringIO()).run(test_suite)
         self.assertEqual(result.testsRun, 3)
         self.assertEqual(len(result.skipped), 2)
         self.assertEqual(result.skipped[0][1], 'Database has feature(s) __class__')
diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py
index 7f7c8228e8..e5a67f745e 100644
--- a/tests/timezones/tests.py
+++ b/tests/timezones/tests.py
@@ -23,7 +23,7 @@ from django.test import (
 )
 from django.test.utils import requires_tz_support
 from django.urls import reverse
-from django.utils import six, timezone
+from django.utils import timezone
 from django.utils.timezone import timedelta
 
 from .forms import (
@@ -888,8 +888,8 @@ class TemplateTests(SimpleTestCase):
             }
         }
 
-        for k1, dt in six.iteritems(datetimes):
-            for k2, tpl in six.iteritems(templates):
+        for k1, dt in datetimes.items():
+            for k2, tpl in templates.items():
                 ctx = Context({'dt': dt, 'ICT': ICT})
                 actual = tpl.render(ctx)
                 expected = results[k1][k2]
@@ -901,8 +901,8 @@ class TemplateTests(SimpleTestCase):
         results['ict']['notag'] = t('ict', 'eat', 'utc', 'ict')
 
         with self.settings(USE_TZ=False):
-            for k1, dt in six.iteritems(datetimes):
-                for k2, tpl in six.iteritems(templates):
+            for k1, dt in datetimes.items():
+                for k2, tpl in templates.items():
                     ctx = Context({'dt': dt, 'ICT': ICT})
                     actual = tpl.render(ctx)
                     expected = results[k1][k2]
diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py
index 4e4f5dc911..cfc4f5fb2e 100644
--- a/tests/user_commands/tests.py
+++ b/tests/user_commands/tests.py
@@ -1,4 +1,5 @@
 import os
+from io import StringIO
 
 from admin_scripts.tests import AdminScriptTestCase
 
@@ -11,7 +12,6 @@ from django.test import SimpleTestCase, mock, override_settings
 from django.test.utils import captured_stderr, extend_sys_path
 from django.utils import translation
 from django.utils._os import upath
-from django.utils.six import StringIO
 
 from .management.commands import dance
 
diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py
index 5d42af62c8..4036df8dd8 100644
--- a/tests/utils_tests/test_autoreload.py
+++ b/tests/utils_tests/test_autoreload.py
@@ -4,13 +4,14 @@ import shutil
 import tempfile
 from importlib import import_module
 
+import _thread
+
 from django import conf
 from django.contrib import admin
 from django.test import SimpleTestCase, mock, override_settings
 from django.test.utils import extend_sys_path
 from django.utils import autoreload
 from django.utils._os import npath
-from django.utils.six.moves import _thread
 from django.utils.translation import trans_real
 
 LOCALE_PATH = os.path.join(os.path.dirname(__file__), 'locale')
diff --git a/tests/utils_tests/test_baseconv.py b/tests/utils_tests/test_baseconv.py
index 538189bbe6..948b991ad3 100644
--- a/tests/utils_tests/test_baseconv.py
+++ b/tests/utils_tests/test_baseconv.py
@@ -3,7 +3,6 @@ from unittest import TestCase
 from django.utils.baseconv import (
     BaseConverter, base2, base16, base36, base56, base62, base64,
 )
-from django.utils.six.moves import range
 
 
 class TestBaseConv(TestCase):
diff --git a/tests/utils_tests/test_datastructures.py b/tests/utils_tests/test_datastructures.py
index f917a2ae4e..df7020eac5 100644
--- a/tests/utils_tests/test_datastructures.py
+++ b/tests/utils_tests/test_datastructures.py
@@ -5,7 +5,6 @@ Tests for stuff in django.utils.datastructures.
 import copy
 
 from django.test import SimpleTestCase
-from django.utils import six
 from django.utils.datastructures import (
     DictWrapper, ImmutableList, MultiValueDict, MultiValueDictKeyError,
     OrderedSet,
@@ -40,12 +39,12 @@ class MultiValueDictTests(SimpleTestCase):
         self.assertEqual(d.get('name'), 'Simon')
         self.assertEqual(d.getlist('name'), ['Adrian', 'Simon'])
         self.assertEqual(
-            sorted(six.iteritems(d)),
+            sorted(d.items()),
             [('name', 'Simon'), ('position', 'Developer')]
         )
 
         self.assertEqual(
-            sorted(six.iterlists(d)),
+            sorted(d.lists()),
             [('name', ['Adrian', 'Simon']), ('position', ['Developer'])]
         )
 
@@ -60,8 +59,7 @@ class MultiValueDictTests(SimpleTestCase):
 
         d.setlist('lastname', ['Holovaty', 'Willison'])
         self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison'])
-        self.assertEqual(sorted(six.itervalues(d)),
-                         ['Developer', 'Simon', 'Willison'])
+        self.assertEqual(sorted(d.values()), ['Developer', 'Simon', 'Willison'])
 
     def test_appendlist(self):
         d = MultiValueDict()
@@ -95,8 +93,8 @@ class MultiValueDictTests(SimpleTestCase):
             'pm': ['Rory'],
         })
         d = mvd.dict()
-        self.assertEqual(sorted(six.iterkeys(d)), sorted(six.iterkeys(mvd)))
-        for key in six.iterkeys(mvd):
+        self.assertEqual(sorted(d.keys()), sorted(mvd.keys()))
+        for key in mvd.keys():
             self.assertEqual(d[key], mvd[key])
 
         self.assertEqual({}, MultiValueDict().dict())
diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py
index 8c9b997b3c..513123ea00 100644
--- a/tests/utils_tests/test_lazyobject.py
+++ b/tests/utils_tests/test_lazyobject.py
@@ -4,7 +4,6 @@ import sys
 import warnings
 from unittest import TestCase
 
-from django.utils import six
 from django.utils.functional import LazyObject, SimpleLazyObject, empty
 
 from .models import Category, CategoryInfo
@@ -300,7 +299,7 @@ class SimpleLazyObjectTestCase(LazyObjectTestCase):
         obj = self.lazy_wrap(42)
         # __repr__ contains __repr__ of setup function and does not evaluate
         # the SimpleLazyObject
-        six.assertRegex(self, repr(obj), '^<SimpleLazyObject:')
+        self.assertRegex(repr(obj), '^<SimpleLazyObject:')
         self.assertIs(obj._wrapped, empty)  # make sure evaluation hasn't been triggered
 
         self.assertEqual(obj, 42)  # evaluate the lazy object
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index 1d8397c5ef..7a9cea826b 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -4,6 +4,7 @@ import os
 import re
 import sys
 import tempfile
+from io import StringIO
 
 from django.conf.urls import url
 from django.core import mail
@@ -13,7 +14,6 @@ from django.template import TemplateDoesNotExist
 from django.test import RequestFactory, SimpleTestCase, override_settings
 from django.test.utils import LoggingCaptureMixin, patch_logger
 from django.urls import reverse
-from django.utils import six
 from django.utils.encoding import force_bytes, force_text
 from django.utils.functional import SimpleLazyObject
 from django.views.debug import (
@@ -513,7 +513,7 @@ class ExceptionReporterTests(SimpleTestCase):
         html = reporter.get_traceback_html()
         self.assertInHTML(value, html)
         # FILES
-        fp = six.StringIO('filecontent')
+        fp = StringIO('filecontent')
         request = self.rf.post('/test_view/', data={'name': 'filename', 'items': fp})
         reporter = ExceptionReporter(request, None, None, None)
         html = reporter.get_traceback_html()
@@ -628,7 +628,7 @@ class PlainTextReportTests(SimpleTestCase):
         text = reporter.get_traceback_text()
         self.assertIn("items = 'Oops'", text)
         # FILES
-        fp = six.StringIO('filecontent')
+        fp = StringIO('filecontent')
         request = self.rf.post('/test_view/', data={'name': 'filename', 'items': fp})
         reporter = ExceptionReporter(request, None, None, None)
         text = reporter.get_traceback_text()