Browse Source

Refs #27795 -- Replaced many force_text() with str()

Thanks Tim Graham for the review.
Claude Paroz 8 years ago
parent
commit
301de774c2
47 changed files with 135 additions and 181 deletions
  1. 2 3
      django/contrib/admin/actions.py
  2. 4 5
      django/contrib/admin/filters.py
  3. 2 3
      django/contrib/admin/models.py
  4. 8 9
      django/contrib/admin/options.py
  5. 2 3
      django/contrib/admin/templatetags/admin_list.py
  6. 9 10
      django/contrib/admin/utils.py
  7. 1 2
      django/contrib/admin/widgets.py
  8. 1 2
      django/contrib/auth/mixins.py
  9. 1 2
      django/contrib/auth/password_validation.py
  10. 1 2
      django/contrib/contenttypes/fields.py
  11. 1 2
      django/contrib/contenttypes/models.py
  12. 3 4
      django/contrib/postgres/fields/hstore.py
  13. 1 2
      django/contrib/postgres/lookups.py
  14. 2 2
      django/contrib/sessions/backends/base.py
  15. 1 2
      django/contrib/sessions/backends/cached_db.py
  16. 1 2
      django/contrib/sessions/backends/db.py
  17. 1 2
      django/contrib/sessions/backends/file.py
  18. 6 6
      django/contrib/syndication/views.py
  19. 1 3
      django/core/checks/messages.py
  20. 1 2
      django/core/exceptions.py
  21. 1 2
      django/core/handlers/exception.py
  22. 5 5
      django/core/mail/message.py
  23. 7 8
      django/core/serializers/xml_serializer.py
  24. 1 3
      django/core/validators.py
  25. 1 1
      django/db/backends/base/operations.py
  26. 1 2
      django/db/backends/sqlite3/base.py
  27. 1 2
      django/db/migrations/serializer.py
  28. 5 5
      django/db/models/fields/__init__.py
  29. 1 2
      django/db/models/fields/reverse_related.py
  30. 1 2
      django/db/models/options.py
  31. 3 4
      django/forms/boundfield.py
  32. 11 12
      django/forms/fields.py
  33. 3 4
      django/forms/forms.py
  34. 7 8
      django/forms/models.py
  35. 3 4
      django/forms/widgets.py
  36. 2 2
      django/http/response.py
  37. 1 2
      django/shortcuts.py
  38. 4 5
      django/urls/resolvers.py
  39. 2 3
      django/utils/dateformat.py
  40. 4 4
      django/utils/html.py
  41. 10 13
      django/utils/text.py
  42. 1 2
      django/utils/translation/__init__.py
  43. 2 2
      django/views/debug.py
  44. 1 2
      django/views/defaults.py
  45. 3 8
      django/views/generic/edit.py
  46. 4 4
      docs/howto/custom-model-fields.txt
  47. 1 2
      docs/topics/serialization.txt

+ 2 - 3
django/contrib/admin/actions.py

@@ -8,7 +8,6 @@ from django.contrib.admin.utils import get_deleted_objects, model_ngettext
 from django.core.exceptions import PermissionDenied
 from django.db import router
 from django.template.response import TemplateResponse
-from django.utils.encoding import force_text
 from django.utils.translation import gettext as _, gettext_lazy
 
 
@@ -44,7 +43,7 @@ def delete_selected(modeladmin, request, queryset):
         n = queryset.count()
         if n:
             for obj in queryset:
-                obj_display = force_text(obj)
+                obj_display = str(obj)
                 modeladmin.log_deletion(request, obj, obj_display)
             queryset.delete()
             modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % {
@@ -63,7 +62,7 @@ def delete_selected(modeladmin, request, queryset):
     context = dict(
         modeladmin.admin_site.each_context(request),
         title=title,
-        objects_name=objects_name,
+        objects_name=str(objects_name),
         deletable_objects=[deletable_objects],
         model_count=dict(model_count).items(),
         queryset=queryset,

+ 4 - 5
django/contrib/admin/filters.py

@@ -14,7 +14,6 @@ from django.contrib.admin.utils import (
 from django.core.exceptions import ImproperlyConfigured, ValidationError
 from django.db import models
 from django.utils import timezone
-from django.utils.encoding import force_text
 from django.utils.translation import gettext_lazy as _
 
 
@@ -107,7 +106,7 @@ class SimpleListFilter(ListFilter):
         }
         for lookup, title in self.lookup_choices:
             yield {
-                'selected': self.value() == force_text(lookup),
+                'selected': self.value() == str(lookup),
                 'query_string': changelist.get_query_string({self.parameter_name: lookup}, []),
                 'display': title,
             }
@@ -204,7 +203,7 @@ class RelatedFieldListFilter(FieldListFilter):
         }
         for pk_val, val in self.lookup_choices:
             yield {
-                'selected': self.lookup_val == force_text(pk_val),
+                'selected': self.lookup_val == str(pk_val),
                 'query_string': changelist.get_query_string({
                     self.lookup_kwarg: pk_val,
                 }, [self.lookup_kwarg_isnull]),
@@ -290,7 +289,7 @@ class ChoicesFieldListFilter(FieldListFilter):
                 none_title = title
                 continue
             yield {
-                'selected': force_text(lookup) == self.lookup_val,
+                'selected': str(lookup) == self.lookup_val,
                 'query_string': changelist.get_query_string(
                     {self.lookup_kwarg: lookup}, [self.lookup_kwarg_isnull]
                 ),
@@ -415,7 +414,7 @@ class AllValuesFieldListFilter(FieldListFilter):
             if val is None:
                 include_none = True
                 continue
-            val = force_text(val)
+            val = str(val)
             yield {
                 'selected': self.lookup_val == val,
                 'query_string': changelist.get_query_string({

+ 2 - 3
django/contrib/admin/models.py

@@ -6,7 +6,6 @@ from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.urls import NoReverseMatch, reverse
 from django.utils import timezone
-from django.utils.encoding import force_text
 from django.utils.text import get_text_list
 from django.utils.translation import gettext, gettext_lazy as _
 
@@ -24,7 +23,7 @@ class LogEntryManager(models.Manager):
         return self.model.objects.create(
             user_id=user_id,
             content_type_id=content_type_id,
-            object_id=force_text(object_id),
+            object_id=str(object_id),
             object_repr=object_repr[:200],
             action_flag=action_flag,
             change_message=change_message,
@@ -64,7 +63,7 @@ class LogEntry(models.Model):
         ordering = ('-action_time',)
 
     def __repr__(self):
-        return force_text(self.action_time)
+        return str(self.action_time)
 
     def __str__(self):
         if self.is_addition():

+ 8 - 9
django/contrib/admin/options.py

@@ -38,7 +38,6 @@ from django.http.response import HttpResponseBase
 from django.template.response import SimpleTemplateResponse, TemplateResponse
 from django.urls import reverse
 from django.utils.decorators import method_decorator
-from django.utils.encoding import force_text
 from django.utils.html import format_html
 from django.utils.http import urlencode
 from django.utils.safestring import mark_safe
@@ -722,7 +721,7 @@ class ModelAdmin(BaseModelAdmin):
             user_id=request.user.pk,
             content_type_id=get_content_type_for_model(object).pk,
             object_id=object.pk,
-            object_repr=force_text(object),
+            object_repr=str(object),
             action_flag=ADDITION,
             change_message=message,
         )
@@ -738,7 +737,7 @@ class ModelAdmin(BaseModelAdmin):
             user_id=request.user.pk,
             content_type_id=get_content_type_for_model(object).pk,
             object_id=object.pk,
-            object_repr=force_text(object),
+            object_repr=str(object),
             action_flag=CHANGE,
             change_message=message,
         )
@@ -763,7 +762,7 @@ class ModelAdmin(BaseModelAdmin):
         """
         A list_display column containing a checkbox widget.
         """
-        return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_text(obj.pk))
+        return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk))
     action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />')
 
     def get_actions(self, request):
@@ -1056,7 +1055,7 @@ class ModelAdmin(BaseModelAdmin):
         if self.has_change_permission(request, obj):
             obj_repr = format_html('<a href="{}">{}</a>', urlquote(obj_url), obj)
         else:
-            obj_repr = force_text(obj)
+            obj_repr = str(obj)
         msg_dict = {
             'name': opts.verbose_name,
             'obj': obj_repr,
@@ -1652,7 +1651,7 @@ class ModelAdmin(BaseModelAdmin):
 
         context = dict(
             self.admin_site.each_context(request),
-            module_name=force_text(opts.verbose_name_plural),
+            module_name=str(opts.verbose_name_plural),
             selection_note=_('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
             selection_note_all=selection_note_all % {'total_count': cl.result_count},
             title=cl.title,
@@ -1710,7 +1709,7 @@ class ModelAdmin(BaseModelAdmin):
         if request.POST and not protected:  # The user has confirmed the deletion.
             if perms_needed:
                 raise PermissionDenied
-            obj_display = force_text(obj)
+            obj_display = str(obj)
             attr = str(to_field) if to_field else opts.pk.attname
             obj_id = obj.serializable_value(attr)
             self.log_deletion(request, obj, obj_display)
@@ -1718,7 +1717,7 @@ class ModelAdmin(BaseModelAdmin):
 
             return self.response_delete(request, obj_display, obj_id)
 
-        object_name = force_text(opts.verbose_name)
+        object_name = str(opts.verbose_name)
 
         if perms_needed or protected:
             title = _("Cannot delete %(name)s") % {"name": object_name}
@@ -1769,7 +1768,7 @@ class ModelAdmin(BaseModelAdmin):
             self.admin_site.each_context(request),
             title=_('Change history: %s') % obj,
             action_list=action_list,
-            module_name=capfirst(force_text(opts.verbose_name_plural)),
+            module_name=str(capfirst(opts.verbose_name_plural)),
             object=obj,
             opts=opts,
             preserved_filters=self.get_preserved_filters(request),

+ 2 - 3
django/contrib/admin/templatetags/admin_list.py

@@ -14,7 +14,6 @@ from django.template.loader import get_template
 from django.templatetags.static import static
 from django.urls import NoReverseMatch
 from django.utils import formats
-from django.utils.encoding import force_text
 from django.utils.html import format_html
 from django.utils.safestring import mark_safe
 from django.utils.text import capfirst
@@ -233,7 +232,7 @@ def items_for_result(cl, result, form):
                     result_repr = display_for_field(value, f, empty_value_display)
                 if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
                     row_classes.append('nowrap')
-        if force_text(result_repr) == '':
+        if str(result_repr) == '':
             result_repr = mark_safe('&nbsp;')
         row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
         # If list_display_links not defined, add the link tag to the first field
@@ -277,7 +276,7 @@ def items_for_result(cl, result, form):
                     field_name == cl.model._meta.pk.name and
                     form[cl.model._meta.pk.name].is_hidden)):
                 bf = form[field_name]
-                result_repr = mark_safe(force_text(bf.errors) + force_text(bf))
+                result_repr = mark_safe(str(bf.errors) + str(bf))
             yield format_html('<td{}>{}</td>', row_class, result_repr)
     if form and not form[cl.model._meta.pk.name].is_hidden:
         yield format_html('<td>{}</td>', form[cl.model._meta.pk.name])

+ 9 - 10
django/contrib/admin/utils.py

@@ -11,7 +11,6 @@ from django.db.models.sql.constants import QUERY_TERMS
 from django.forms.utils import pretty_name
 from django.urls import NoReverseMatch, reverse
 from django.utils import formats, timezone
-from django.utils.encoding import force_text
 from django.utils.html import format_html
 from django.utils.text import capfirst
 from django.utils.translation import ngettext, override as translation_override
@@ -338,7 +337,7 @@ def label_for_field(name, model, model_admin=None, return_attr=False):
             label = field.related_model._meta.verbose_name
     except FieldDoesNotExist:
         if name == "__str__":
-            label = force_text(model._meta.verbose_name)
+            label = str(model._meta.verbose_name)
             attr = str
         else:
             if callable(name):
@@ -427,9 +426,9 @@ def display_for_value(value, empty_value_display, boolean=False):
     elif isinstance(value, (int, decimal.Decimal, float)):
         return formats.number_format(value)
     elif isinstance(value, (list, tuple)):
-        return ', '.join(force_text(v) for v in value)
+        return ', '.join(str(v) for v in value)
     else:
-        return force_text(value)
+        return str(value)
 
 
 class NotRelationField(Exception):
@@ -512,23 +511,23 @@ def construct_change_message(form, formsets, add):
                 for added_object in formset.new_objects:
                     change_message.append({
                         'added': {
-                            'name': force_text(added_object._meta.verbose_name),
-                            'object': force_text(added_object),
+                            'name': str(added_object._meta.verbose_name),
+                            'object': str(added_object),
                         }
                     })
                 for changed_object, changed_fields in formset.changed_objects:
                     change_message.append({
                         'changed': {
-                            'name': force_text(changed_object._meta.verbose_name),
-                            'object': force_text(changed_object),
+                            'name': str(changed_object._meta.verbose_name),
+                            'object': str(changed_object),
                             'fields': changed_fields,
                         }
                     })
                 for deleted_object in formset.deleted_objects:
                     change_message.append({
                         'deleted': {
-                            'name': force_text(deleted_object._meta.verbose_name),
-                            'object': force_text(deleted_object),
+                            'name': str(deleted_object._meta.verbose_name),
+                            'object': str(deleted_object),
                         }
                     })
     return change_message

+ 1 - 2
django/contrib/admin/widgets.py

@@ -7,7 +7,6 @@ from django import forms
 from django.db.models.deletion import CASCADE
 from django.urls import reverse
 from django.urls.exceptions import NoReverseMatch
-from django.utils.encoding import force_text
 from django.utils.html import smart_urlquote
 from django.utils.safestring import mark_safe
 from django.utils.text import Truncator
@@ -215,7 +214,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
             return value.split(',')
 
     def format_value(self, value):
-        return ','.join(force_text(v) for v in value) if value else ''
+        return ','.join(str(v) for v in value) if value else ''
 
 
 class RelatedFieldWidgetWrapper(forms.Widget):

+ 1 - 2
django/contrib/auth/mixins.py

@@ -2,7 +2,6 @@ from django.conf import settings
 from django.contrib.auth import REDIRECT_FIELD_NAME
 from django.contrib.auth.views import redirect_to_login
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
-from django.utils.encoding import force_text
 
 
 class AccessMixin:
@@ -25,7 +24,7 @@ class AccessMixin:
                 '{0} is missing the login_url attribute. Define {0}.login_url, settings.LOGIN_URL, or override '
                 '{0}.get_login_url().'.format(self.__class__.__name__)
             )
-        return force_text(login_url)
+        return str(login_url)
 
     def get_permission_denied_message(self):
         """

+ 1 - 2
django/contrib/auth/password_validation.py

@@ -8,7 +8,6 @@ from django.conf import settings
 from django.core.exceptions import (
     FieldDoesNotExist, ImproperlyConfigured, ValidationError,
 )
-from django.utils.encoding import force_text
 from django.utils.functional import lazy
 from django.utils.html import format_html
 from django.utils.module_loading import import_string
@@ -145,7 +144,7 @@ class UserAttributeSimilarityValidator:
             for value_part in value_parts:
                 if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity:
                     try:
-                        verbose_name = force_text(user._meta.get_field(attribute_name).verbose_name)
+                        verbose_name = str(user._meta.get_field(attribute_name).verbose_name)
                     except FieldDoesNotExist:
                         verbose_name = attribute_name
                     raise ValidationError(

+ 1 - 2
django/contrib/contenttypes/fields.py

@@ -11,7 +11,6 @@ from django.db.models.fields.related import (
     lazy_related_operation,
 )
 from django.db.models.query_utils import PathInfo
-from django.utils.encoding import force_text
 from django.utils.functional import cached_property
 
 
@@ -398,7 +397,7 @@ class GenericRelation(ForeignObject):
 
     def value_to_string(self, obj):
         qs = getattr(obj, self.name).all()
-        return force_text([instance._get_pk_val() for instance in qs])
+        return str([instance._get_pk_val() for instance in qs])
 
     def contribute_to_class(self, cls, name, **kwargs):
         kwargs['private_only'] = True

+ 1 - 2
django/contrib/contenttypes/models.py

@@ -2,7 +2,6 @@ from collections import defaultdict
 
 from django.apps import apps
 from django.db import models
-from django.utils.encoding import force_text
 from django.utils.translation import gettext_lazy as _
 
 
@@ -151,7 +150,7 @@ class ContentType(models.Model):
         model = self.model_class()
         if not model:
             return self.model
-        return force_text(model._meta.verbose_name)
+        return str(model._meta.verbose_name)
 
     def model_class(self):
         """Return the model class for this type of content."""

+ 3 - 4
django/contrib/postgres/fields/hstore.py

@@ -4,7 +4,6 @@ from django.contrib.postgres import forms, lookups
 from django.contrib.postgres.fields.array import ArrayField
 from django.core import exceptions
 from django.db.models import Field, TextField, Transform
-from django.utils.encoding import force_text
 from django.utils.translation import gettext_lazy as _
 
 __all__ = ['HStoreField']
@@ -57,14 +56,14 @@ class HStoreField(Field):
         if isinstance(value, dict):
             prep_value = {}
             for key, val in value.items():
-                key = force_text(key)
+                key = str(key)
                 if val is not None:
-                    val = force_text(val)
+                    val = str(val)
                 prep_value[key] = val
             value = prep_value
 
         if isinstance(value, list):
-            value = [force_text(item) for item in value]
+            value = [str(item) for item in value]
 
         return value
 

+ 1 - 2
django/contrib/postgres/lookups.py

@@ -1,5 +1,4 @@
 from django.db.models import Lookup, Transform
-from django.utils.encoding import force_text
 
 from .search import SearchVector, SearchVectorExact, SearchVectorField
 
@@ -38,7 +37,7 @@ class HasKeys(PostgresSimpleLookup):
     operator = '?&'
 
     def get_prep_lookup(self):
-        return [force_text(item) for item in self.rhs]
+        return [str(item) for item in self.rhs]
 
 
 class HasAnyKeys(HasKeys):

+ 2 - 2
django/contrib/sessions/backends/base.py

@@ -10,7 +10,7 @@ from django.utils import timezone
 from django.utils.crypto import (
     constant_time_compare, get_random_string, salted_hmac,
 )
-from django.utils.encoding import force_bytes, force_text
+from django.utils.encoding import force_bytes
 from django.utils.module_loading import import_string
 
 # session_key should not be case sensitive because some backends can store it
@@ -112,7 +112,7 @@ class SessionBase:
             # these happen, just return an empty dictionary (an empty session).
             if isinstance(e, SuspiciousOperation):
                 logger = logging.getLogger('django.security.%s' % e.__class__.__name__)
-                logger.warning(force_text(e))
+                logger.warning(str(e))
             return {}
 
     def update(self, dict_):

+ 1 - 2
django/contrib/sessions/backends/cached_db.py

@@ -9,7 +9,6 @@ from django.contrib.sessions.backends.db import SessionStore as DBStore
 from django.core.cache import caches
 from django.core.exceptions import SuspiciousOperation
 from django.utils import timezone
-from django.utils.encoding import force_text
 
 KEY_PREFIX = "django.contrib.sessions.cached_db"
 
@@ -49,7 +48,7 @@ class SessionStore(DBStore):
             except (self.model.DoesNotExist, SuspiciousOperation) as e:
                 if isinstance(e, SuspiciousOperation):
                     logger = logging.getLogger('django.security.%s' % e.__class__.__name__)
-                    logger.warning(force_text(e))
+                    logger.warning(str(e))
                 self._session_key = None
                 data = {}
         return data

+ 1 - 2
django/contrib/sessions/backends/db.py

@@ -6,7 +6,6 @@ from django.contrib.sessions.backends.base import (
 from django.core.exceptions import SuspiciousOperation
 from django.db import DatabaseError, IntegrityError, router, transaction
 from django.utils import timezone
-from django.utils.encoding import force_text
 from django.utils.functional import cached_property
 
 
@@ -38,7 +37,7 @@ class SessionStore(SessionBase):
         except (self.model.DoesNotExist, SuspiciousOperation) as e:
             if isinstance(e, SuspiciousOperation):
                 logger = logging.getLogger('django.security.%s' % e.__class__.__name__)
-                logger.warning(force_text(e))
+                logger.warning(str(e))
             self._session_key = None
             return {}
 

+ 1 - 2
django/contrib/sessions/backends/file.py

@@ -11,7 +11,6 @@ from django.contrib.sessions.backends.base import (
 from django.contrib.sessions.exceptions import InvalidSessionKey
 from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
 from django.utils import timezone
-from django.utils.encoding import force_text
 
 
 class SessionStore(SessionBase):
@@ -92,7 +91,7 @@ class SessionStore(SessionBase):
                 except (EOFError, SuspiciousOperation) as e:
                     if isinstance(e, SuspiciousOperation):
                         logger = logging.getLogger('django.security.%s' % e.__class__.__name__)
-                        logger.warning(force_text(e))
+                        logger.warning(str(e))
                     self.create()
 
                 # Remove expired sessions.

+ 6 - 6
django/contrib/syndication/views.py

@@ -6,7 +6,7 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
 from django.http import Http404, HttpResponse
 from django.template import TemplateDoesNotExist, loader
 from django.utils import feedgenerator
-from django.utils.encoding import force_text, iri_to_uri
+from django.utils.encoding import iri_to_uri
 from django.utils.html import escape
 from django.utils.http import http_date
 from django.utils.timezone import get_default_timezone, is_naive, make_aware
@@ -48,10 +48,10 @@ class Feed:
 
     def item_title(self, item):
         # Titles should be double escaped by default (see #6533)
-        return escape(force_text(item))
+        return escape(str(item))
 
     def item_description(self, item):
-        return force_text(item)
+        return str(item)
 
     def item_link(self, item):
         try:
@@ -66,9 +66,9 @@ class Feed:
         enc_url = self._get_dynamic_attr('item_enclosure_url', item)
         if enc_url:
             enc = feedgenerator.Enclosure(
-                url=force_text(enc_url),
-                length=force_text(self._get_dynamic_attr('item_enclosure_length', item)),
-                mime_type=force_text(self._get_dynamic_attr('item_enclosure_mime_type', item)),
+                url=str(enc_url),
+                length=str(self._get_dynamic_attr('item_enclosure_length', item)),
+                mime_type=str(self._get_dynamic_attr('item_enclosure_mime_type', item)),
             )
             return [enc]
         return []

+ 1 - 3
django/core/checks/messages.py

@@ -1,5 +1,3 @@
-from django.utils.encoding import force_text
-
 # Levels
 DEBUG = 10
 INFO = 20
@@ -35,7 +33,7 @@ class CheckMessage:
             # method doesn't return "applabel.modellabel" and cannot be changed.
             obj = self.obj._meta.label
         else:
-            obj = force_text(self.obj)
+            obj = str(self.obj)
         id = "(%s) " % self.id if self.id else ""
         hint = "\n\tHINT: %s" % self.hint if self.hint else ''
         return "%s: %s%s%s" % (obj, id, self.msg, hint)

+ 1 - 2
django/core/exceptions.py

@@ -1,7 +1,6 @@
 """
 Global Django exception and warning classes.
 """
-from django.utils.encoding import force_text
 
 
 class FieldDoesNotExist(Exception):
@@ -174,7 +173,7 @@ class ValidationError(Exception):
                 message = error.message
                 if error.params:
                     message %= error.params
-                yield force_text(message)
+                yield str(message)
 
     def __str__(self):
         if hasattr(self, 'error_dict'):

+ 1 - 2
django/core/handlers/exception.py

@@ -11,7 +11,6 @@ from django.core.exceptions import (
 from django.http import Http404
 from django.http.multipartparser import MultiPartParserError
 from django.urls import get_resolver, get_urlconf
-from django.utils.encoding import force_text
 from django.views import debug
 
 logger = logging.getLogger('django.request')
@@ -71,7 +70,7 @@ def response_for_exception(request, exc):
         # The security logger receives events for all SuspiciousOperations
         security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
         security_logger.error(
-            force_text(exc),
+            str(exc),
             extra={'status_code': 400, 'request': request},
         )
         if settings.DEBUG:

+ 5 - 5
django/core/mail/message.py

@@ -55,7 +55,7 @@ ADDRESS_HEADERS = {
 def forbid_multi_line_headers(name, val, encoding):
     """Forbid multi-line headers to prevent header injection."""
     encoding = encoding or settings.DEFAULT_CHARSET
-    val = force_text(val)
+    val = str(val)  # val may be lazy
     if '\n' in val or '\r' in val:
         raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
     try:
@@ -100,7 +100,7 @@ def sanitize_address(addr, encoding):
     Format a pair of (name, address) or an email address string.
     """
     if not isinstance(addr, tuple):
-        addr = parseaddr(force_text(addr))
+        addr = parseaddr(addr)
     nm, addr = addr
     localpart, domain = None, None
     nm = Header(nm, encoding).encode()
@@ -258,11 +258,11 @@ class EmailMessage:
         msg = self._create_message(msg)
         msg['Subject'] = self.subject
         msg['From'] = self.extra_headers.get('From', self.from_email)
-        msg['To'] = self.extra_headers.get('To', ', '.join(map(force_text, self.to)))
+        msg['To'] = self.extra_headers.get('To', ', '.join(map(str, self.to)))
         if self.cc:
-            msg['Cc'] = ', '.join(map(force_text, self.cc))
+            msg['Cc'] = ', '.join(map(str, self.cc))
         if self.reply_to:
-            msg['Reply-To'] = self.extra_headers.get('Reply-To', ', '.join(map(force_text, self.reply_to)))
+            msg['Reply-To'] = self.extra_headers.get('Reply-To', ', '.join(map(str, self.reply_to)))
 
         # Email header names are case-insensitive (RFC 2045), so we have to
         # accommodate that when doing comparisons.

+ 7 - 8
django/core/serializers/xml_serializer.py

@@ -11,7 +11,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.encoding import force_text
 from django.utils.xmlutils import (
     SimplerXMLGenerator, UnserializableContentError,
 )
@@ -48,11 +47,11 @@ class Serializer(base.Serializer):
             raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
 
         self.indent(1)
-        attrs = OrderedDict([("model", force_text(obj._meta))])
+        attrs = OrderedDict([("model", str(obj._meta))])
         if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
             obj_pk = obj._get_pk_val()
             if obj_pk is not None:
-                attrs['pk'] = force_text(obj_pk)
+                attrs['pk'] = str(obj_pk)
 
         self.xml.startElement("object", attrs)
 
@@ -101,10 +100,10 @@ class Serializer(base.Serializer):
                 # Iterable natural keys are rolled out as subelements
                 for key_value in related:
                     self.xml.startElement("natural", {})
-                    self.xml.characters(force_text(key_value))
+                    self.xml.characters(str(key_value))
                     self.xml.endElement("natural")
             else:
-                self.xml.characters(force_text(related_att))
+                self.xml.characters(str(related_att))
         else:
             self.xml.addQuickElement("None")
         self.xml.endElement("field")
@@ -125,13 +124,13 @@ class Serializer(base.Serializer):
                     self.xml.startElement("object", {})
                     for key_value in natural:
                         self.xml.startElement("natural", {})
-                        self.xml.characters(force_text(key_value))
+                        self.xml.characters(str(key_value))
                         self.xml.endElement("natural")
                     self.xml.endElement("object")
             else:
                 def handle_m2m(value):
                     self.xml.addQuickElement("object", attrs={
-                        'pk': force_text(value._get_pk_val())
+                        'pk': str(value._get_pk_val())
                     })
             for relobj in getattr(obj, field.name).iterator():
                 handle_m2m(relobj)
@@ -144,7 +143,7 @@ class Serializer(base.Serializer):
         self.xml.startElement("field", OrderedDict([
             ("name", field.name),
             ("rel", field.remote_field.__class__.__name__),
-            ("to", force_text(field.remote_field.model._meta)),
+            ("to", str(field.remote_field.model._meta)),
         ]))
 
 

+ 1 - 3
django/core/validators.py

@@ -5,7 +5,6 @@ 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.translation import gettext_lazy as _, ngettext_lazy
@@ -55,8 +54,7 @@ class RegexValidator:
         Validate that the input contains a match for the regular expression
         if inverse_match is False, otherwise raise ValidationError.
         """
-        if not (self.inverse_match is not bool(self.regex.search(
-                force_text(value)))):
+        if not (self.inverse_match is not bool(self.regex.search(str(value)))):
             raise ValidationError(self.message, code=self.code)
 
     def __eq__(self, other):

+ 1 - 1
django/db/backends/base/operations.py

@@ -410,7 +410,7 @@ class BaseDatabaseOperations:
 
     def prep_for_like_query(self, x):
         """Prepare a value for use in a LIKE query."""
-        return force_text(x).replace("\\", "\\\\").replace("%", r"\%").replace("_", r"\_")
+        return str(x).replace("\\", "\\\\").replace("%", r"\%").replace("_", r"\_")
 
     # Same as prep_for_like_query(), but called for "iexact" matches, which
     # need not necessarily be implemented using "LIKE" in the backend.

+ 1 - 2
django/db/backends/sqlite3/base.py

@@ -16,7 +16,6 @@ from django.utils import timezone
 from django.utils.dateparse import (
     parse_date, parse_datetime, parse_duration, parse_time,
 )
-from django.utils.encoding import force_text
 
 from .client import DatabaseClient                          # isort:skip
 from .creation import DatabaseCreation                      # isort:skip
@@ -456,7 +455,7 @@ def _sqlite_timestamp_diff(lhs, rhs):
 
 
 def _sqlite_regexp(re_pattern, re_string):
-    return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False
+    return bool(re.search(re_pattern, str(re_string))) if re_string is not None else False
 
 
 def _sqlite_power(x, y):

+ 1 - 2
django/db/migrations/serializer.py

@@ -13,7 +13,6 @@ 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
-from django.utils.encoding import force_text
 from django.utils.functional import LazyObject, Promise
 from django.utils.timezone import utc
 from django.utils.version import get_docs_version
@@ -303,7 +302,7 @@ class UUIDSerializer(BaseSerializer):
 def serializer_factory(value):
     from django.db.migrations.writer import SettingsReference
     if isinstance(value, Promise):
-        value = force_text(value)
+        value = str(value)
     elif isinstance(value, LazyObject):
         # The unwrapped value is returned as the first item of the arguments
         # tuple.

+ 5 - 5
django/db/models/fields/__init__.py

@@ -25,7 +25,7 @@ from django.utils.dateparse import (
     parse_date, parse_datetime, parse_duration, parse_time,
 )
 from django.utils.duration import duration_string
-from django.utils.encoding import force_bytes, force_text, smart_text
+from django.utils.encoding import force_bytes, smart_text
 from django.utils.functional import Promise, cached_property, curry
 from django.utils.ipv6 import clean_ipv6_address
 from django.utils.itercompat import is_iterable
@@ -805,7 +805,7 @@ class Field(RegisterLookupMixin):
         Return a string value of this field from the passed obj.
         This is used by the serialization framework.
         """
-        return force_text(self.value_from_object(obj))
+        return str(self.value_from_object(obj))
 
     def _get_flatchoices(self):
         """Flattened version of choices tuple."""
@@ -1058,7 +1058,7 @@ class CharField(Field):
     def to_python(self, value):
         if isinstance(value, str) or value is None:
             return value
-        return force_text(value)
+        return str(value)
 
     def get_prep_value(self, value):
         value = super().get_prep_value(value)
@@ -1920,7 +1920,7 @@ class GenericIPAddressField(Field):
         if value is None:
             return None
         if not isinstance(value, str):
-            value = force_text(value)
+            value = str(value)
         value = value.strip()
         if ':' in value:
             return clean_ipv6_address(value, self.unpack_ipv4, self.error_messages['invalid'])
@@ -2089,7 +2089,7 @@ class TextField(Field):
     def to_python(self, value):
         if isinstance(value, str) or value is None:
             return value
-        return force_text(value)
+        return str(value)
 
     def get_prep_value(self, value):
         value = super().get_prep_value(value)

+ 1 - 2
django/db/models/fields/reverse_related.py

@@ -10,7 +10,6 @@ they're the closest concept currently available.
 """
 
 from django.core import exceptions
-from django.utils.encoding import force_text
 from django.utils.functional import cached_property
 
 from . import BLANK_CHOICE_DASH
@@ -123,7 +122,7 @@ class ForeignObjectRel:
         initially for utilization by RelatedFieldListFilter.
         """
         return (blank_choice if include_blank else []) + [
-            (x._get_pk_val(), force_text(x)) for x in self.related_model._default_manager.all()
+            (x._get_pk_val(), str(x)) for x in self.related_model._default_manager.all()
         ]
 
     def is_hidden(self):

+ 1 - 2
django/db/models/options.py

@@ -14,7 +14,6 @@ from django.db.models.fields.proxy import OrderWrt
 from django.db.models.query_utils import PathInfo
 from django.utils.datastructures import ImmutableList, OrderedSet
 from django.utils.deprecation import RemovedInDjango21Warning
-from django.utils.encoding import force_text
 from django.utils.functional import cached_property
 from django.utils.text import camel_case_to_spaces, format_lazy
 from django.utils.translation import override
@@ -317,7 +316,7 @@ class Options:
     def verbose_name_raw(self):
         """Return the untranslated verbose name."""
         with override(None):
-            return force_text(self.verbose_name)
+            return str(self.verbose_name)
 
     @property
     def swapped(self):

+ 3 - 4
django/forms/boundfield.py

@@ -4,7 +4,6 @@ import warnings
 from django.forms.utils import flatatt, pretty_name
 from django.forms.widgets import Textarea, TextInput
 from django.utils.deprecation import RemovedInDjango21Warning
-from django.utils.encoding import force_text
 from django.utils.functional import cached_property
 from django.utils.html import conditional_escape, format_html, html_safe
 from django.utils.inspect import func_supports_parameter
@@ -213,9 +212,9 @@ class BoundField:
         Calculate and return the ID attribute for this BoundField, if the
         associated Form has specified auto_id. Return an empty string otherwise.
         """
-        auto_id = self.form.auto_id
-        if auto_id and '%s' in force_text(auto_id):
-            return force_text(auto_id) % self.html_name
+        auto_id = self.form.auto_id  # Boolean or string
+        if auto_id and '%s' in str(auto_id):
+            return auto_id % self.html_name
         elif auto_id:
             return self.html_name
         return ''

+ 11 - 12
django/forms/fields.py

@@ -28,7 +28,6 @@ from django.forms.widgets import (
 from django.utils import formats
 from django.utils.dateparse import parse_duration
 from django.utils.duration import duration_string
-from django.utils.encoding import force_text
 from django.utils.ipv6 import clean_ipv6_address
 from django.utils.translation import gettext_lazy as _, ngettext_lazy
 
@@ -221,7 +220,7 @@ class CharField(Field):
         """Return a string."""
         if value in self.empty_values:
             return self.empty_value
-        value = force_text(value)
+        value = str(value)
         if self.strip:
             value = value.strip()
         return value
@@ -268,7 +267,7 @@ class IntegerField(Field):
             value = formats.sanitize_separators(value)
         # Strip trailing decimal and zeros.
         try:
-            value = int(self.re_decimal.sub('', force_text(value)))
+            value = int(self.re_decimal.sub('', str(value)))
         except (ValueError, TypeError):
             raise ValidationError(self.error_messages['invalid'], code='invalid')
         return value
@@ -341,7 +340,7 @@ class DecimalField(IntegerField):
             return None
         if self.localize:
             value = formats.sanitize_separators(value)
-        value = force_text(value).strip()
+        value = str(value).strip()
         try:
             value = Decimal(value)
         except DecimalException:
@@ -484,7 +483,7 @@ class DurationField(Field):
             return None
         if isinstance(value, datetime.timedelta):
             return value
-        value = parse_duration(force_text(value))
+        value = parse_duration(str(value))
         if value is None:
             raise ValidationError(self.error_messages['invalid'], code='invalid')
         return value
@@ -784,7 +783,7 @@ class ChoiceField(Field):
         """Return a string."""
         if value in self.empty_values:
             return ''
-        return force_text(value)
+        return str(value)
 
     def validate(self, value):
         """Validate that the input is in self.choices."""
@@ -798,15 +797,15 @@ class ChoiceField(Field):
 
     def valid_value(self, value):
         """Check to see if the provided value is a valid choice."""
-        text_value = force_text(value)
+        text_value = str(value)
         for k, v in self.choices:
             if isinstance(v, (list, tuple)):
                 # This is an optgroup, so look inside the group for options
                 for k2, v2 in v:
-                    if value == k2 or text_value == force_text(k2):
+                    if value == k2 or text_value == str(k2):
                         return True
             else:
-                if value == k or text_value == force_text(k):
+                if value == k or text_value == str(k):
                     return True
         return False
 
@@ -851,7 +850,7 @@ class MultipleChoiceField(ChoiceField):
             return []
         elif not isinstance(value, (list, tuple)):
             raise ValidationError(self.error_messages['invalid_list'], code='invalid_list')
-        return [force_text(val) for val in value]
+        return [str(val) for val in value]
 
     def validate(self, value):
         """Validate that the input is a list or tuple."""
@@ -873,8 +872,8 @@ class MultipleChoiceField(ChoiceField):
             data = []
         if len(initial) != len(data):
             return True
-        initial_set = set(force_text(value) for value in initial)
-        data_set = set(force_text(value) for value in data)
+        initial_set = set(str(value) for value in initial)
+        data_set = set(str(value) for value in data)
         return data_set != initial_set
 
 

+ 3 - 4
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.encoding import force_text
 from django.utils.functional import cached_property
 from django.utils.html import conditional_escape, html_safe
 from django.utils.safestring import mark_safe
@@ -205,7 +204,7 @@ class BaseForm:
             if bf.is_hidden:
                 if bf_errors:
                     top_errors.extend(
-                        [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': force_text(e)}
+                        [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)}
                          for e in bf_errors])
                 hidden_fields.append(str(bf))
             else:
@@ -216,10 +215,10 @@ class BaseForm:
                     html_class_attr = ' class="%s"' % css_classes
 
                 if errors_on_separate_row and bf_errors:
-                    output.append(error_row % force_text(bf_errors))
+                    output.append(error_row % str(bf_errors))
 
                 if bf.label:
-                    label = conditional_escape(force_text(bf.label))
+                    label = conditional_escape(bf.label)
                     label = bf.label_tag(label) or ''
                 else:
                     label = ''

+ 7 - 8
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.encoding import force_text
 from django.utils.text import capfirst, get_text_list
 from django.utils.translation import gettext, gettext_lazy as _
 
@@ -1085,7 +1084,7 @@ class InlineForeignKeyField(Field):
             orig = getattr(self.parent_instance, self.to_field)
         else:
             orig = self.parent_instance.pk
-        if force_text(value) != force_text(orig):
+        if str(value) != str(orig):
             raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
         return self.parent_instance
 
@@ -1176,7 +1175,7 @@ class ModelChoiceField(ChoiceField):
         presented by this object. Subclasses can override this method to
         customize the display of the choices.
         """
-        return force_text(obj)
+        return str(obj)
 
     def _get_choices(self):
         # If self._choices is set, then somebody must have manually set
@@ -1219,7 +1218,7 @@ class ModelChoiceField(ChoiceField):
     def has_changed(self, initial, data):
         initial_value = initial if initial is not None else ''
         data_value = data if data is not None else ''
-        return force_text(self.prepare_value(initial_value)) != force_text(data_value)
+        return str(self.prepare_value(initial_value)) != str(data_value)
 
 
 class ModelMultipleChoiceField(ModelChoiceField):
@@ -1286,9 +1285,9 @@ class ModelMultipleChoiceField(ModelChoiceField):
                     params={'pk': pk},
                 )
         qs = self.queryset.filter(**{'%s__in' % key: value})
-        pks = set(force_text(getattr(o, key)) for o in qs)
+        pks = set(str(getattr(o, key)) for o in qs)
         for val in value:
-            if force_text(val) not in pks:
+            if str(val) not in pks:
                 raise ValidationError(
                     self.error_messages['invalid_choice'],
                     code='invalid_choice',
@@ -1310,8 +1309,8 @@ class ModelMultipleChoiceField(ModelChoiceField):
             data = []
         if len(initial) != len(data):
             return True
-        initial_set = set(force_text(value) for value in self.prepare_value(initial))
-        data_set = set(force_text(value) for value in data)
+        initial_set = set(str(value) for value in self.prepare_value(initial))
+        data_set = set(str(value) for value in data)
         return data_set != initial_set
 
 

+ 3 - 4
django/forms/widgets.py

@@ -12,7 +12,6 @@ from django.forms.utils import to_current_timezone
 from django.templatetags.static import static
 from django.utils import datetime_safe, formats
 from django.utils.dates import MONTHS
-from django.utils.encoding import 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
@@ -184,7 +183,7 @@ class Widget(metaclass=MediaDefiningClass):
             return None
         if self.is_localized:
             return formats.localize_input(value)
-        return force_text(value)
+        return str(value)
 
     def get_context(self, name, value, attrs):
         context = {}
@@ -483,7 +482,7 @@ class CheckboxInput(Input):
         """Only return the 'value' attribute if value isn't empty."""
         if value is True or value is False or value is None or value == '':
             return
-        return force_text(value)
+        return str(value)
 
     def get_context(self, name, value, attrs):
         if self.check_test(value):
@@ -570,7 +569,7 @@ class ChoiceWidget(Widget):
 
             for subvalue, sublabel in choices:
                 selected = (
-                    force_text(subvalue) in value and
+                    str(subvalue) in value and
                     (not has_selected or self.allow_multiple_selected)
                 )
                 if selected and not has_selected:

+ 2 - 2
django/http/response.py

@@ -13,7 +13,7 @@ from django.core.exceptions import DisallowedRedirect
 from django.core.serializers.json import DjangoJSONEncoder
 from django.http.cookie import SimpleCookie
 from django.utils import timezone
-from django.utils.encoding import force_bytes, force_text, iri_to_uri
+from django.utils.encoding import force_bytes, iri_to_uri
 from django.utils.http import cookie_date
 
 _charset_from_content_type_re = re.compile(r';\s*charset=(?P<charset>[^\s;]+)', re.I)
@@ -405,7 +405,7 @@ class HttpResponseRedirectBase(HttpResponse):
     def __init__(self, redirect_to, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self['Location'] = iri_to_uri(redirect_to)
-        parsed = urlparse(force_text(redirect_to))
+        parsed = urlparse(str(redirect_to))
         if parsed.scheme and parsed.scheme not in self.allowed_schemes:
             raise DisallowedRedirect("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
 

+ 1 - 2
django/shortcuts.py

@@ -11,7 +11,6 @@ from django.http import (
 from django.template import loader
 from django.urls import NoReverseMatch, reverse
 from django.utils.deprecation import RemovedInDjango30Warning
-from django.utils.encoding import force_text
 from django.utils.functional import Promise
 
 
@@ -138,7 +137,7 @@ def resolve_url(to, *args, **kwargs):
     if isinstance(to, Promise):
         # Expand the lazy instance, as it can cause issues when it is passed
         # further to some Python functions like urlparse.
-        to = force_text(to)
+        to = str(to)
 
     if isinstance(to, str):
         # Handle relative URLs

+ 4 - 5
django/urls/resolvers.py

@@ -16,7 +16,6 @@ from django.core.checks import Warning
 from django.core.checks.urls import check_resolver
 from django.core.exceptions import ImproperlyConfigured
 from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import force_text
 from django.utils.functional import cached_property
 from django.utils.http import RFC3986_SUBDELIMS
 from django.utils.regex_helper import normalize
@@ -92,7 +91,7 @@ class LocaleRegexDescriptor:
             return instance.__dict__['regex']
         language_code = get_language()
         if language_code not in instance._regex_dict:
-            instance._regex_dict[language_code] = self._compile(force_text(instance._regex))
+            instance._regex_dict[language_code] = self._compile(str(instance._regex))
         return instance._regex_dict[language_code]
 
     def _compile(self, regex):
@@ -347,7 +346,7 @@ class RegexURLResolver(LocaleRegexProvider):
         return name in self._callback_strs
 
     def resolve(self, path):
-        path = force_text(path)  # path may be a reverse_lazy object
+        path = str(path)  # path may be a reverse_lazy object
         tried = []
         match = self.regex.search(path)
         if match:
@@ -422,8 +421,8 @@ class RegexURLResolver(LocaleRegexProvider):
     def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
         if args and kwargs:
             raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
-        text_args = [force_text(v) for v in args]
-        text_kwargs = {k: force_text(v) for (k, v) in kwargs.items()}
+        text_args = [str(v) for v in args]
+        text_kwargs = {k: str(v) for (k, v) in kwargs.items()}
 
         if not self._populated:
             self._populate()

+ 2 - 3
django/utils/dateformat.py

@@ -18,7 +18,6 @@ import time
 from django.utils.dates import (
     MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, WEEKDAYS, WEEKDAYS_ABBR,
 )
-from django.utils.encoding import force_text
 from django.utils.timezone import get_default_timezone, is_aware, is_naive
 from django.utils.translation import gettext as _
 
@@ -29,14 +28,14 @@ re_escaped = re.compile(r'\\(.)')
 class Formatter:
     def format(self, formatstr):
         pieces = []
-        for i, piece in enumerate(re_formatchars.split(force_text(formatstr))):
+        for i, piece in enumerate(re_formatchars.split(str(formatstr))):
             if i % 2:
                 if type(self.data) is datetime.date and hasattr(TimeFormat, piece):
                     raise TypeError(
                         "The format for date objects may not contain "
                         "time-related format specifiers (found '%s')." % piece
                     )
-                pieces.append(force_text(getattr(self, piece)()))
+                pieces.append(str(getattr(self, piece)()))
             elif piece:
                 pieces.append(re_escaped.sub(r'\1', piece))
         return ''.join(pieces)

+ 4 - 4
django/utils/html.py

@@ -43,7 +43,7 @@ def escape(text):
     conditional_escape() instead.
     """
     return mark_safe(
-        force_text(text).replace('&', '&amp;').replace('<', '&lt;')
+        str(text).replace('&', '&amp;').replace('<', '&lt;')
         .replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
     )
 
@@ -70,7 +70,7 @@ _js_escapes.update((ord('%c' % z), '\\u%04X' % z) for z in range(32))
 @keep_lazy(str, SafeText)
 def escapejs(value):
     """Hex encode characters for use in JavaScript strings."""
-    return mark_safe(force_text(value).translate(_js_escapes))
+    return mark_safe(str(value).translate(_js_escapes))
 
 
 def conditional_escape(text):
@@ -121,8 +121,8 @@ def format_html_join(sep, format_string, args_generator):
 @keep_lazy_text
 def linebreaks(value, autoescape=False):
     """Convert newlines into <p> and <br />s."""
-    value = normalize_newlines(force_text(value))
-    paras = re.split('\n{2,}', value)
+    value = normalize_newlines(value)
+    paras = re.split('\n{2,}', str(value))
     if autoescape:
         paras = ['<p>%s</p>' % escape(p).replace('\n', '<br />') for p in paras]
     else:

+ 10 - 13
django/utils/text.py

@@ -4,7 +4,6 @@ import unicodedata
 from gzip import GzipFile
 from io import BytesIO
 
-from django.utils.encoding import force_text
 from django.utils.functional import (
     SimpleLazyObject, keep_lazy, keep_lazy_text, lazy,
 )
@@ -15,7 +14,7 @@ from django.utils.translation import gettext as _, gettext_lazy, pgettext
 @keep_lazy_text
 def capfirst(x):
     """Capitalize the first letter of a string."""
-    return x and force_text(x)[0].upper() + force_text(x)[1:]
+    return x and str(x)[0].upper() + str(x)[1:]
 
 
 # Set up regular expressions
@@ -62,7 +61,7 @@ class Truncator(SimpleLazyObject):
     An object used to truncate text, either by characters or words.
     """
     def __init__(self, text):
-        super().__init__(lambda: force_text(text))
+        super().__init__(lambda: str(text))
 
     def add_truncation_text(self, text, truncate=None):
         if truncate is None:
@@ -230,7 +229,7 @@ def get_valid_filename(s):
     >>> get_valid_filename("john's portrait in 2004.jpg")
     'johns_portrait_in_2004.jpg'
     """
-    s = force_text(s).strip().replace(' ', '_')
+    s = str(s).strip().replace(' ', '_')
     return re.sub(r'(?u)[^-\w.]', '', s)
 
 
@@ -251,18 +250,17 @@ def get_text_list(list_, last_word=gettext_lazy('or')):
     if len(list_) == 0:
         return ''
     if len(list_) == 1:
-        return force_text(list_[0])
+        return str(list_[0])
     return '%s %s %s' % (
         # Translators: This string is used as a separator between list elements
-        _(', ').join(force_text(i) for i in list_[:-1]),
-        force_text(last_word), force_text(list_[-1]))
+        _(', ').join(str(i) for i in list_[:-1]), str(last_word), str(list_[-1])
+    )
 
 
 @keep_lazy_text
 def normalize_newlines(text):
     """Normalize CRLF and CR newlines to just LF."""
-    text = force_text(text)
-    return re_newlines.sub('\n', text)
+    return re_newlines.sub('\n', str(text))
 
 
 @keep_lazy_text
@@ -349,8 +347,7 @@ def smart_split(text):
     >>> list(smart_split(r'A "\"funky\" style" test.'))
     ['A', '"\\"funky\\" style"', 'test.']
     """
-    text = force_text(text)
-    for bit in smart_split_re.finditer(text):
+    for bit in smart_split_re.finditer(str(text)):
         yield bit.group(0)
 
 
@@ -378,7 +375,7 @@ _entity_re = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
 
 @keep_lazy_text
 def unescape_entities(text):
-    return _entity_re.sub(_replace_entity, force_text(text))
+    return _entity_re.sub(_replace_entity, str(text))
 
 
 @keep_lazy_text
@@ -409,7 +406,7 @@ def slugify(value, allow_unicode=False):
     Remove characters that aren't alphanumerics, underscores, or hyphens.
     Convert to lowercase. Also strip leading and trailing whitespace.
     """
-    value = force_text(value)
+    value = str(value)
     if allow_unicode:
         value = unicodedata.normalize('NFKC', value)
     else:

+ 1 - 2
django/utils/translation/__init__.py

@@ -6,7 +6,6 @@ import warnings
 from contextlib import ContextDecorator
 
 from django.utils.deprecation import RemovedInDjango21Warning
-from django.utils.encoding import force_text
 from django.utils.functional import lazy
 
 __all__ = [
@@ -226,7 +225,7 @@ def _string_concat(*strings):
         'django.utils.translate.string_concat() is deprecated in '
         'favor of django.utils.text.format_lazy().',
         RemovedInDjango21Warning, stacklevel=2)
-    return ''.join(force_text(s) for s in strings)
+    return ''.join(str(s) for s in strings)
 
 
 string_concat = lazy(_string_concat, str)

+ 2 - 2
django/views/debug.py

@@ -287,7 +287,7 @@ class ExceptionReporter:
             user_str = None
         else:
             try:
-                user_str = force_text(self.request.user)
+                user_str = str(self.request.user)
             except Exception:
                 # request.user may raise OperationalError if the database is
                 # unavailable, for example.
@@ -318,7 +318,7 @@ class ExceptionReporter:
         if self.exc_type:
             c['exception_type'] = self.exc_type.__name__
         if self.exc_value:
-            c['exception_value'] = force_text(self.exc_value, errors='replace')
+            c['exception_value'] = str(self.exc_value)
         if frames:
             c['lastframe'] = frames[-1]
         return c

+ 1 - 2
django/views/defaults.py

@@ -3,7 +3,6 @@ from django.http import (
     HttpResponseServerError,
 )
 from django.template import Context, Engine, TemplateDoesNotExist, loader
-from django.utils.encoding import force_text
 from django.views.decorators.csrf import requires_csrf_token
 
 ERROR_404_TEMPLATE_NAME = '404.html'
@@ -117,5 +116,5 @@ def permission_denied(request, exception, template_name=ERROR_403_TEMPLATE_NAME)
             raise
         return HttpResponseForbidden('<h1>403 Forbidden</h1>', content_type='text/html')
     return HttpResponseForbidden(
-        template.render(request=request, context={'exception': force_text(exception)})
+        template.render(request=request, context={'exception': str(exception)})
     )

+ 3 - 8
django/views/generic/edit.py

@@ -1,7 +1,6 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.forms import models as model_forms
 from django.http import HttpResponseRedirect
-from django.utils.encoding import force_text
 from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
 from django.views.generic.detail import (
     BaseDetailView, SingleObjectMixin, SingleObjectTemplateResponseMixin,
@@ -49,13 +48,9 @@ class FormMixin(ContextMixin):
 
     def get_success_url(self):
         """Return the URL to redirect to after processing a valid form."""
-        if self.success_url:
-            # Forcing possible reverse_lazy evaluation
-            url = force_text(self.success_url)
-        else:
-            raise ImproperlyConfigured(
-                "No URL to redirect to. Provide a success_url.")
-        return url
+        if not self.success_url:
+            raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
+        return str(self.success_url)  # success_url may be lazy
 
     def form_valid(self, form):
         """If the form is valid, redirect to the supplied URL."""

+ 4 - 4
docs/howto/custom-model-fields.txt

@@ -706,10 +706,10 @@ smoothly:
 
 2. Put a ``__str__()`` method on the class you're wrapping up as a field. There
    are a lot of places where the default behavior of the field code is to call
-   :func:`~django.utils.encoding.force_text` on the value. (In our
-   examples in this document, ``value`` would be a ``Hand`` instance, not a
-   ``HandField``). So if your ``__str__()`` method automatically converts to
-   the string form of your Python object, you can save yourself a lot of work.
+   ``str()`` on the value. (In our examples in this document, ``value`` would
+   be a ``Hand`` instance, not a ``HandField``). So if your ``__str__()``
+   method automatically converts to the string form of your Python object, you
+   can save yourself a lot of work.
 
 Writing a ``FileField`` subclass
 ================================

+ 1 - 2
docs/topics/serialization.txt

@@ -256,13 +256,12 @@ For example, if you have some custom type in an object to be serialized, you'll
 have to write a custom :mod:`json` encoder for it. Something like this will
 work::
 
-    from django.utils.encoding import force_text
     from django.core.serializers.json import DjangoJSONEncoder
 
     class LazyEncoder(DjangoJSONEncoder):
         def default(self, obj):
             if isinstance(obj, YourCustomType):
-                return force_text(obj)
+                return str(obj)
             return super().default(obj)
 
 You can then pass ``cls=LazyEncoder`` to the ``serializers.serialize()``