From 5c47a3b42536032608ef66d43b0412e01a87fe59 Mon Sep 17 00:00:00 2001
From: Ayub khan <muhammadayubkhan6@gmail.com>
Date: Fri, 20 Sep 2019 18:15:54 +0500
Subject: [PATCH] BOM Project Updated __unicode__ to __str__

---
 cms/djangoapps/course_creators/models.py      |  4 ++-
 cms/djangoapps/xblock_config/models.py        |  4 ++-
 cms/lib/xblock/tagging/models.py              |  7 +++--
 common/djangoapps/course_modes/models.py      |  7 +++--
 common/djangoapps/entitlements/models.py      |  7 +++--
 common/djangoapps/static_replace/models.py    |  7 +++--
 common/djangoapps/status/models.py            |  7 +++--
 common/djangoapps/student/models.py           | 23 +++++++++------
 common/djangoapps/track/backends/django.py    |  4 ++-
 common/djangoapps/xblock_django/models.py     | 10 +++++--
 common/lib/capa/capa/capa_problem.py          |  4 ++-
 common/lib/capa/capa/inputtypes.py            |  5 ++--
 common/lib/capa/capa/responsetypes.py         |  6 ++--
 .../xmodule/xmodule/library_root_xblock.py    |  9 +++---
 common/lib/xmodule/xmodule/modulestore/xml.py |  4 ++-
 common/lib/xmodule/xmodule/tests/__init__.py  |  4 ++-
 common/lib/xmodule/xmodule/x_module.py        |  4 ++-
 lms/djangoapps/badges/models.py               | 13 ++++++---
 lms/djangoapps/bulk_email/models.py           | 19 +++++++++----
 .../bulk_email/tests/test_models.py           |  4 +--
 lms/djangoapps/certificates/models.py         | 13 ++++++---
 lms/djangoapps/certificates/queue.py          |  4 ++-
 lms/djangoapps/commerce/models.py             |  4 ++-
 lms/djangoapps/course_goals/models.py         |  4 ++-
 lms/djangoapps/courseware/models.py           | 16 +++++++----
 .../coursewarehistoryextended/models.py       |  4 ++-
 lms/djangoapps/email_marketing/models.py      |  4 ++-
 lms/djangoapps/grades/config/models.py        |  8 ++++--
 lms/djangoapps/grades/course_grade.py         |  4 ++-
 lms/djangoapps/grades/models.py               | 12 +++++---
 lms/djangoapps/instructor_task/models.py      |  4 ++-
 lms/djangoapps/instructor_task/subtasks.py    |  4 ++-
 lms/djangoapps/mobile_api/models.py           |  4 ++-
 lms/djangoapps/shoppingcart/models.py         | 13 ++++++---
 .../shoppingcart/tests/test_reports.py        |  4 +--
 .../shoppingcart/tests/test_views.py          |  2 +-
 lms/djangoapps/verify_student/models.py       |  7 +++--
 openedx/core/djangoapps/api_admin/models.py   |  6 ++--
 openedx/core/djangoapps/bookmarks/models.py   |  7 +++--
 openedx/core/djangoapps/ccxcon/models.py      |  6 ++--
 .../content/block_structure/config/models.py  |  4 ++-
 .../content/block_structure/models.py         |  4 ++-
 .../content/block_structure/store.py          |  4 ++-
 .../content/course_overviews/models.py        | 13 ++++++---
 .../djangoapps/content_libraries/models.py    |  5 ++--
 .../core/djangoapps/contentserver/models.py   |  7 +++--
 openedx/core/djangoapps/cors_csrf/models.py   |  4 ++-
 .../core/djangoapps/course_groups/models.py   |  4 ++-
 openedx/core/djangoapps/crawlers/models.py    |  4 ++-
 openedx/core/djangoapps/credentials/models.py |  7 +++--
 openedx/core/djangoapps/credit/models.py      | 21 +++++++++-----
 openedx/core/djangoapps/dark_lang/models.py   |  4 ++-
 .../django_comment_common/models.py           |  6 ++--
 openedx/core/djangoapps/embargo/models.py     |  6 ++--
 .../djangoapps/site_configuration/models.py   | 11 +++++---
 .../core/djangoapps/theming/helpers_dirs.py   |  6 ++--
 openedx/core/djangoapps/theming/models.py     |  4 ++-
 openedx/core/djangoapps/user_api/models.py    |  9 ++++--
 .../core/djangoapps/video_config/models.py    | 28 +++++++++++++------
 .../core/djangoapps/video_pipeline/models.py  |  7 +++--
 .../core/djangoapps/waffle_utils/models.py    |  4 ++-
 openedx/features/announcements/models.py      |  4 ++-
 62 files changed, 306 insertions(+), 142 deletions(-)

diff --git a/cms/djangoapps/course_creators/models.py b/cms/djangoapps/course_creators/models.py
index 998096473c5..fd80213757a 100644
--- a/cms/djangoapps/course_creators/models.py
+++ b/cms/djangoapps/course_creators/models.py
@@ -8,6 +8,7 @@ from django.db import models
 from django.db.models.signals import post_init, post_save
 from django.dispatch import Signal, receiver
 from django.utils import timezone
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
 # A signal that will be sent when users should be added or removed from the creator group
@@ -20,6 +21,7 @@ send_admin_notification = Signal(providing_args=["user"])
 send_user_notification = Signal(providing_args=["user", "state"])
 
 
+@python_2_unicode_compatible
 class CourseCreator(models.Model):
     """
     Creates the database table model.
@@ -47,7 +49,7 @@ class CourseCreator(models.Model):
     note = models.CharField(max_length=512, blank=True, help_text=_("Optional notes about this user (for example, "
                                                                     "why course creation access was denied)"))
 
-    def __unicode__(self):
+    def __str__(self):
         return u"{0} | {1} [{2}]".format(self.user, self.state, self.state_changed)
 
 
diff --git a/cms/djangoapps/xblock_config/models.py b/cms/djangoapps/xblock_config/models.py
index 08d0f3d786f..5eac8045c9e 100644
--- a/cms/djangoapps/xblock_config/models.py
+++ b/cms/djangoapps/xblock_config/models.py
@@ -10,6 +10,7 @@ from __future__ import absolute_import
 import six
 from config_models.models import ConfigurationModel
 from django.db.models import TextField
+from django.utils.encoding import python_2_unicode_compatible
 from opaque_keys.edx.django.models import CourseKeyField
 
 from openedx.core.lib.cache_utils import request_cached
@@ -37,6 +38,7 @@ class StudioConfig(ConfigurationModel):
 
 # TODO: Move CourseEditLTIFieldsEnabledFlag to LTI XBlock as a part of EDUCATOR-121
 # reference: https://openedx.atlassian.net/browse/EDUCATOR-121
+@python_2_unicode_compatible
 class CourseEditLTIFieldsEnabledFlag(ConfigurationModel):
     """
     Enables the editing of "request username" and "request email" fields
@@ -77,7 +79,7 @@ class CourseEditLTIFieldsEnabledFlag(ConfigurationModel):
 
         return course_specific_config.enabled if course_specific_config else False
 
-    def __unicode__(self):
+    def __str__(self):
         en = "Not "
         if self.enabled:
             en = ""
diff --git a/cms/lib/xblock/tagging/models.py b/cms/lib/xblock/tagging/models.py
index de1704f7f64..78b819f0a5d 100644
--- a/cms/lib/xblock/tagging/models.py
+++ b/cms/lib/xblock/tagging/models.py
@@ -4,8 +4,10 @@ Django Model for tags
 from __future__ import absolute_import
 
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class TagCategories(models.Model):
     """
     This model represents tag categories.
@@ -21,7 +23,7 @@ class TagCategories(models.Model):
         verbose_name = "tag category"
         verbose_name_plural = "tag categories"
 
-    def __unicode__(self):
+    def __str__(self):
         return "[TagCategories] {}: {}".format(self.name, self.title)
 
     def get_values(self):
@@ -31,6 +33,7 @@ class TagCategories(models.Model):
         return [t.value for t in TagAvailableValues.objects.filter(category=self)]
 
 
+@python_2_unicode_compatible
 class TagAvailableValues(models.Model):
     """
     This model represents available values for tags.
@@ -45,5 +48,5 @@ class TagAvailableValues(models.Model):
         ordering = ('id',)
         verbose_name = "available tag value"
 
-    def __unicode__(self):
+    def __str__(self):
         return "[TagAvailableValues] {}: {}".format(self.category, self.value)
diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py
index cc32e231687..04588a34287 100644
--- a/common/djangoapps/course_modes/models.py
+++ b/common/djangoapps/course_modes/models.py
@@ -14,6 +14,7 @@ from django.core.validators import validate_comma_separated_integer_list
 from django.db import models
 from django.db.models import Q
 from django.dispatch import receiver
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.timezone import now
 from django.utils.translation import ugettext_lazy as _
 from edx_django_utils.cache import RequestCache
@@ -38,6 +39,7 @@ Mode = namedtuple('Mode',
                   ])
 
 
+@python_2_unicode_compatible
 class CourseMode(models.Model):
     """
     We would like to offer a course in a variety of modes.
@@ -791,7 +793,7 @@ class CourseMode(models.Model):
             self.bulk_sku
         )
 
-    def __unicode__(self):
+    def __str__(self):
         return u"{} : {}, min={}".format(
             self.course_id, self.mode_slug, self.min_price
         )
@@ -902,6 +904,7 @@ class CourseModesArchive(models.Model):
     expiration_datetime = models.DateTimeField(default=None, null=True, blank=True)
 
 
+@python_2_unicode_compatible
 class CourseModeExpirationConfig(ConfigurationModel):
     """
     Configuration for time period from end of course to auto-expire a course mode.
@@ -918,6 +921,6 @@ class CourseModeExpirationConfig(ConfigurationModel):
         )
     )
 
-    def __unicode__(self):
+    def __str__(self):
         """ Returns the unicode date of the verification window. """
         return six.text_type(self.verification_window)
diff --git a/common/djangoapps/entitlements/models.py b/common/djangoapps/entitlements/models.py
index 3f20ca975ca..364d43f4546 100644
--- a/common/djangoapps/entitlements/models.py
+++ b/common/djangoapps/entitlements/models.py
@@ -9,6 +9,7 @@ from datetime import timedelta
 from django.conf import settings
 from django.contrib.sites.models import Site
 from django.db import IntegrityError, models, transaction
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.timezone import now
 from model_utils import Choices
 from model_utils.models import TimeStampedModel
@@ -26,6 +27,7 @@ from util.date_utils import strftime_localized
 log = logging.getLogger("common.entitlements.models")
 
 
+@python_2_unicode_compatible
 class CourseEntitlementPolicy(models.Model):
     """
     Represents the Entitlement's policy for expiration, refunds, and regaining a used certificate
@@ -138,7 +140,7 @@ class CourseEntitlementPolicy(models.Model):
                 and not entitlement.enrollment_course_run
                 and not entitlement.expired_at)
 
-    def __unicode__(self):
+    def __str__(self):
         return u'Course Entitlement Policy: expiration_period: {}, refund_period: {}, regain_period: {}, mode: {}'\
             .format(
                 self.expiration_period,
@@ -448,6 +450,7 @@ class CourseEntitlement(TimeStampedModel):
             raise IntegrityError
 
 
+@python_2_unicode_compatible
 class CourseEntitlementSupportDetail(TimeStampedModel):
     """
     Table recording support interactions with an entitlement
@@ -492,7 +495,7 @@ class CourseEntitlementSupportDetail(TimeStampedModel):
         on_delete=models.CASCADE,
     )
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of an Entitlement"""
         return u'Course Entitlement Support Detail: entitlement: {}, support_user: {}, reason: {}'.format(
             self.entitlement,
diff --git a/common/djangoapps/static_replace/models.py b/common/djangoapps/static_replace/models.py
index f89eea1b8e2..2012832eaf0 100644
--- a/common/djangoapps/static_replace/models.py
+++ b/common/djangoapps/static_replace/models.py
@@ -9,8 +9,10 @@ from six.moves import map
 
 from config_models.models import ConfigurationModel
 from django.db.models.fields import TextField
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class AssetBaseUrlConfig(ConfigurationModel):
     """
     Configuration for the base URL used for static assets.
@@ -34,10 +36,11 @@ class AssetBaseUrlConfig(ConfigurationModel):
     def __repr__(self):
         return '<AssetBaseUrlConfig(base_url={})>'.format(self.get_base_url())
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
 
 
+@python_2_unicode_compatible
 class AssetExcludedExtensionsConfig(ConfigurationModel):
     """
     Configuration for the the excluded file extensions when canonicalizing static asset paths.
@@ -63,5 +66,5 @@ class AssetExcludedExtensionsConfig(ConfigurationModel):
     def __repr__(self):
         return '<AssetExcludedExtensionsConfig(extensions={})>'.format(self.get_excluded_extensions())
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
diff --git a/common/djangoapps/status/models.py b/common/djangoapps/status/models.py
index fdd1ee74913..0ad62f2d122 100644
--- a/common/djangoapps/status/models.py
+++ b/common/djangoapps/status/models.py
@@ -10,11 +10,13 @@ from config_models.models import ConfigurationModel
 from django.contrib import admin
 from django.core.cache import cache
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from opaque_keys.edx.django.models import CourseKeyField
 
 from openedx.core.djangolib.markup import HTML
 
 
+@python_2_unicode_compatible
 class GlobalStatusMessage(ConfigurationModel):
     """
     Model that represents the current status message.
@@ -51,10 +53,11 @@ class GlobalStatusMessage(ConfigurationModel):
         cache.set(cache_key, msg)
         return msg
 
-    def __unicode__(self):
+    def __str__(self):
         return "{} - {} - {}".format(self.change_date, self.enabled, self.message)
 
 
+@python_2_unicode_compatible
 class CourseMessage(models.Model):
     """
     Model that allows the administrator to specify banner messages for individual courses.
@@ -68,7 +71,7 @@ class CourseMessage(models.Model):
     course_key = CourseKeyField(max_length=255, blank=True, db_index=True)
     message = models.TextField(blank=True, null=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(self.course_key)
 
 
diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py
index 9a7e521eb89..83a22d2d7e4 100644
--- a/common/djangoapps/student/models.py
+++ b/common/djangoapps/student/models.py
@@ -858,7 +858,7 @@ EVENT_NAME_ENROLLMENT_DEACTIVATED = 'edx.course.enrollment.deactivated'
 EVENT_NAME_ENROLLMENT_MODE_CHANGED = 'edx.course.enrollment.mode_changed'
 
 
-@six.python_2_unicode_compatible
+@python_2_unicode_compatible
 class LoginFailures(models.Model):
     """
     This model will keep track of failed login attempts.
@@ -1084,6 +1084,7 @@ class CourseEnrollmentManager(models.Manager):
 CourseEnrollmentState = namedtuple('CourseEnrollmentState', 'mode, is_active')
 
 
+@python_2_unicode_compatible
 class CourseEnrollment(models.Model):
     """
     Represents a Student's Enrollment record for a single Course. You should
@@ -1160,7 +1161,7 @@ class CourseEnrollment(models.Model):
         # When the property .course_overview is accessed for the first time, this variable will be set.
         self._course_overview = None
 
-    def __unicode__(self):
+    def __str__(self):
         return (
             "[CourseEnrollment] {}: {} ({}); active: ({})"
         ).format(self.user, self.course_id, self.created, self.is_active)
@@ -2139,6 +2140,7 @@ class ManualEnrollmentAudit(models.Model):
         return cls.objects.filter(id__in=manual_enrollment_ids).update(reason="", enrolled_email=retired_email)
 
 
+@python_2_unicode_compatible
 class CourseEnrollmentAllowed(DeletableByUserValue, models.Model):
     """
     Table of users (specified by email address strings) who are allowed to enroll in a specified course.
@@ -2166,7 +2168,7 @@ class CourseEnrollmentAllowed(DeletableByUserValue, models.Model):
     class Meta(object):
         unique_together = (('email', 'course_id'),)
 
-    def __unicode__(self):
+    def __str__(self):
         return "[CourseEnrollmentAllowed] %s: %s (%s)" % (self.email, self.course_id, self.created)
 
     @classmethod
@@ -2199,6 +2201,7 @@ class CourseEnrollmentAllowed(DeletableByUserValue, models.Model):
 
 
 @total_ordering
+@python_2_unicode_compatible
 class CourseAccessRole(models.Model):
     """
     Maps users to org, courses, and roles. Used by student.roles.CourseRole and OrgRole.
@@ -2244,7 +2247,7 @@ class CourseAccessRole(models.Model):
         """
         return self._key < other._key  # pylint: disable=protected-access
 
-    def __unicode__(self):
+    def __str__(self):
         return "[CourseAccessRole] user: {}   role: {}   org: {}   course: {}".format(self.user.username, self.role, self.org, self.course_id)
 
 
@@ -2576,6 +2579,7 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
         )
 
 
+@python_2_unicode_compatible
 class EntranceExamConfiguration(models.Model):
     """
     Represents a Student's entrance exam specific data for a single Course
@@ -2595,7 +2599,7 @@ class EntranceExamConfiguration(models.Model):
     class Meta(object):
         unique_together = (('user', 'course_id'), )
 
-    def __unicode__(self):
+    def __str__(self):
         return "[EntranceExamConfiguration] %s: %s (%s) = %s" % (
             self.user, self.course_id, self.created, self.skip_entrance_exam
         )
@@ -2683,6 +2687,7 @@ class SocialLink(models.Model):  # pylint: disable=model-missing-unicode
     social_link = models.CharField(max_length=100, blank=True)
 
 
+@python_2_unicode_compatible
 class CourseEnrollmentAttribute(models.Model):
     """
     Provide additional information about the user's enrollment.
@@ -2703,7 +2708,7 @@ class CourseEnrollmentAttribute(models.Model):
         help_text=_("Value of the enrollment attribute")
     )
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of the attribute. """
         return u"{namespace}:{name}, {value}".format(
             namespace=self.namespace,
@@ -2790,6 +2795,7 @@ class EnrollmentRefundConfiguration(ConfigurationModel):
         self.refund_window_microseconds = int(refund_window.total_seconds() * 1000000)
 
 
+@python_2_unicode_compatible
 class RegistrationCookieConfiguration(ConfigurationModel):
     """
     Configuration for registration cookies.
@@ -2806,7 +2812,7 @@ class RegistrationCookieConfiguration(ConfigurationModel):
         help_text=_("Name of the affiliate cookie")
     )
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of this config. """
         return u"UTM: {utm_name}; AFFILIATE: {affiliate_name}".format(
             utm_name=self.utm_cookie_name,
@@ -2858,6 +2864,7 @@ class UserAttribute(TimeStampedModel):
             return None
 
 
+@python_2_unicode_compatible
 class LogoutViewConfiguration(ConfigurationModel):
     """
     DEPRECATED: Configuration for the logout view.
@@ -2865,7 +2872,7 @@ class LogoutViewConfiguration(ConfigurationModel):
     .. no_pii:
     """
 
-    def __unicode__(self):
+    def __str__(self):
         """
         Unicode representation of the instance.
         """
diff --git a/common/djangoapps/track/backends/django.py b/common/djangoapps/track/backends/django.py
index d04103a1a45..d093004bb54 100644
--- a/common/djangoapps/track/backends/django.py
+++ b/common/djangoapps/track/backends/django.py
@@ -12,6 +12,7 @@ from __future__ import absolute_import
 import logging
 
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 from track.backends import BaseBackend
 
@@ -31,6 +32,7 @@ LOGFIELDS = [
 ]
 
 
+@python_2_unicode_compatible
 class TrackingLog(models.Model):
     """
     Defines the fields that are stored in the tracking log database.
@@ -55,7 +57,7 @@ class TrackingLog(models.Model):
         app_label = 'track'
         db_table = 'track_trackinglog'
 
-    def __unicode__(self):
+    def __str__(self):
         fmt = (
             u"[{self.time}] {self.username}@{self.ip}: "
             u"{self.event_source}| {self.event_type} | "
diff --git a/common/djangoapps/xblock_django/models.py b/common/djangoapps/xblock_django/models.py
index 48d8b5b8d68..a33af2cb08d 100644
--- a/common/djangoapps/xblock_django/models.py
+++ b/common/djangoapps/xblock_django/models.py
@@ -7,8 +7,10 @@ from __future__ import absolute_import
 from config_models.models import ConfigurationModel
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class XBlockConfiguration(ConfigurationModel):
     """
     XBlock configuration used by both LMS and Studio, and not specific to a particular template.
@@ -28,12 +30,13 @@ class XBlockConfiguration(ConfigurationModel):
         verbose_name=_('show deprecation messaging in Studio')
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return (
             "XBlockConfiguration(name={}, enabled={}, deprecated={})"
         ).format(self.name, self.enabled, self.deprecated)
 
 
+@python_2_unicode_compatible
 class XBlockStudioConfigurationFlag(ConfigurationModel):
     """
     Enables site-wide Studio configuration for XBlocks.
@@ -46,10 +49,11 @@ class XBlockStudioConfigurationFlag(ConfigurationModel):
 
     # boolean field 'enabled' inherited from parent ConfigurationModel
 
-    def __unicode__(self):
+    def __str__(self):
         return "XBlockStudioConfigurationFlag(enabled={})".format(self.enabled)
 
 
+@python_2_unicode_compatible
 class XBlockStudioConfiguration(ConfigurationModel):
     """
     Studio editing configuration for a specific XBlock/template combination.
@@ -76,7 +80,7 @@ class XBlockStudioConfiguration(ConfigurationModel):
     class Meta(object):
         app_label = "xblock_django"
 
-    def __unicode__(self):
+    def __str__(self):
         return (
             "XBlockStudioConfiguration(name={}, template={}, enabled={}, support_level={})"
         ).format(self.name, self.template, self.enabled, self.support_level)
diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py
index 5471bc4ea26..c54d5f83b07 100644
--- a/common/lib/capa/capa/capa_problem.py
+++ b/common/lib/capa/capa/capa_problem.py
@@ -34,6 +34,7 @@ import capa.xqueue_interface as xqueue_interface
 from capa.correctmap import CorrectMap
 from capa.safe_exec import safe_exec
 from capa.util import contextualize_text, convert_files_to_filenames
+from django.utils.encoding import python_2_unicode_compatible
 from openedx.core.djangolib.markup import HTML, Text
 from xmodule.stringify import stringify_children
 
@@ -126,6 +127,7 @@ class LoncapaSystem(object):
         self.matlab_api_key = matlab_api_key
 
 
+@python_2_unicode_compatible
 class LoncapaProblem(object):
     """
     Main class for capa Problems.
@@ -283,7 +285,7 @@ class LoncapaProblem(object):
 
         self.student_answers = initial_answers
 
-    def __unicode__(self):
+    def __str__(self):
         return u"LoncapaProblem ({0})".format(self.problem_id)
 
     def get_state(self):
diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py
index b0e35139eac..1f3bba51dc2 100644
--- a/common/lib/capa/capa/inputtypes.py
+++ b/common/lib/capa/capa/inputtypes.py
@@ -58,6 +58,7 @@ from six import text_type
 
 from capa.xqueue_interface import XQUEUE_TIMEOUT
 from chem import chemcalc
+from django.utils.encoding import python_2_unicode_compatible
 from openedx.core.djangolib.markup import HTML, Text
 from openedx.core.lib import edx_six
 from xmodule.stringify import stringify_children
@@ -74,6 +75,7 @@ log = logging.getLogger(__name__)
 registry = TagRegistry()  # pylint: disable=invalid-name
 
 
+@python_2_unicode_compatible
 class Status(object):
     """
     Problem status
@@ -119,9 +121,6 @@ class Status(object):
     def __str__(self):
         return self._status
 
-    def __unicode__(self):
-        return self._status.decode('utf8')
-
     def __repr__(self):
         return 'Status(%r)' % self._status
 
diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py
index 12b7c6db134..b0c5829d01c 100644
--- a/common/lib/capa/capa/responsetypes.py
+++ b/common/lib/capa/capa/responsetypes.py
@@ -35,6 +35,7 @@ import requests
 import six
 # specific library imports
 from calc import UndefinedVariable, UnmatchedParenthesis, evaluator
+from django.utils.encoding import python_2_unicode_compatible
 from lxml import etree
 from lxml.html.soupparser import fromstring as fromstring_bs  # uses Beautiful Soup!!! FIXME?
 from pyparsing import ParseException
@@ -108,6 +109,7 @@ class StudentInputError(Exception):
 # Main base class for CAPA responsetypes
 
 
+@python_2_unicode_compatible
 class LoncapaResponse(six.with_metaclass(abc.ABCMeta, object)):
     """
     Base class for CAPA responsetypes.  Each response type (ie a capa question,
@@ -130,7 +132,7 @@ class LoncapaResponse(six.with_metaclass(abc.ABCMeta, object)):
                                condition for a hint to be displayed
 
       - render_html          : render this Response as HTML (must return XHTML-compliant string)
-      - __unicode__          : unicode representation of this Response
+      - __str__              : unicode representation of this Response
 
     Each response type may also specify the following attributes:
 
@@ -574,7 +576,7 @@ class LoncapaResponse(six.with_metaclass(abc.ABCMeta, object)):
     def setup_response(self):
         pass
 
-    def __unicode__(self):
+    def __str__(self):
         return u'LoncapaProblem Response %s' % self.xml.tag
 
     def _render_response_msg_html(self, response_msg):
diff --git a/common/lib/xmodule/xmodule/library_root_xblock.py b/common/lib/xmodule/xmodule/library_root_xblock.py
index 6494bc4440f..17b2c856df4 100644
--- a/common/lib/xmodule/xmodule/library_root_xblock.py
+++ b/common/lib/xmodule/xmodule/library_root_xblock.py
@@ -4,8 +4,9 @@
 from __future__ import absolute_import
 
 import logging
-
 import six
+
+from django.utils.encoding import python_2_unicode_compatible
 from web_fragments.fragment import Fragment
 from xblock.core import XBlock
 from xblock.fields import Boolean, List, Scope, String
@@ -18,6 +19,7 @@ log = logging.getLogger(__name__)
 _ = lambda text: text
 
 
+@python_2_unicode_compatible
 class LibraryRoot(XBlock):
     """
     The LibraryRoot is the root XBlock of a content library. All other blocks in
@@ -47,11 +49,8 @@ class LibraryRoot(XBlock):
     has_children = True
     has_author_view = True
 
-    def __unicode__(self):
-        return u"Library: {}".format(self.display_name)
-
     def __str__(self):
-        return six.text_type(self).encode('utf-8')
+        return u"Library: {}".format(self.display_name)
 
     def author_view(self, context):
         """
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index 0da3a4b3689..d23f4acdadf 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -15,6 +15,7 @@ from contextlib import contextmanager
 from importlib import import_module
 
 import six
+from django.utils.encoding import python_2_unicode_compatible
 from fs.osfs import OSFS
 from lazy import lazy
 from lxml import etree
@@ -302,6 +303,7 @@ class CourseImportLocationManager(CourseLocationManager):
         self.target_course_id = target_course_id
 
 
+@python_2_unicode_compatible
 class XMLModuleStore(ModuleStoreReadBase):
     """
     An XML backed ModuleStore
@@ -395,7 +397,7 @@ class XMLModuleStore(ModuleStoreReadBase):
             course_id = self.id_from_descriptor(course_descriptor)
             self._course_errors[course_id] = errorlog
 
-    def __unicode__(self):
+    def __str__(self):
         '''
         String representation - for debugging
         '''
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index 2113f766783..739a0d883dd 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -21,6 +21,7 @@ from functools import wraps
 
 import six
 from django.test import TestCase
+from django.utils.encoding import python_2_unicode_compatible
 from mock import Mock
 from opaque_keys.edx.keys import CourseKey
 from path import Path as path
@@ -225,6 +226,7 @@ def map_references(value, field, actual_course_key):
     return value
 
 
+@python_2_unicode_compatible
 class LazyFormat(object):
     """
     An stringy object that delays formatting until it's put into a string context.
@@ -237,7 +239,7 @@ class LazyFormat(object):
         self.kwargs = kwargs
         self._message = None
 
-    def __unicode__(self):
+    def __str__(self):
         if self._message is None:
             self._message = self.template.format(*self.args, **self.kwargs)
         return self._message
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 26692e0d55b..26eeb4cc2aa 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -12,6 +12,7 @@ from pkg_resources import resource_exists, resource_isdir, resource_listdir, res
 import six
 import yaml
 from contracts import contract, new_contract
+from django.utils.encoding import python_2_unicode_compatible
 from lazy import lazy
 from lxml import etree
 from opaque_keys.edx.asides import AsideDefinitionKeyV2, AsideUsageKeyV2
@@ -921,6 +922,7 @@ class XModuleToXBlockMixin(object):
 
 
 @XBlock.needs("i18n")
+@python_2_unicode_compatible
 class XModule(XModuleToXBlockMixin, HTMLSnippet, XModuleMixin):
     """ Implements a generic learning module.
 
@@ -965,7 +967,7 @@ class XModule(XModuleToXBlockMixin, HTMLSnippet, XModuleMixin):
     def runtime(self, value):  # pylint: disable=arguments-differ
         self._runtime = value
 
-    def __unicode__(self):
+    def __str__(self):
         # xss-lint: disable=python-wrap-html
         return u'<x_module(id={0})>'.format(self.id)
 
diff --git a/lms/djangoapps/badges/models.py b/lms/djangoapps/badges/models.py
index 5e479ffe26d..da7cf2e0679 100644
--- a/lms/djangoapps/badges/models.py
+++ b/lms/djangoapps/badges/models.py
@@ -11,6 +11,7 @@ from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from jsonfield import JSONField
 from lazy import lazy
@@ -48,6 +49,7 @@ class CourseBadgesDisabledError(Exception):
     """
 
 
+@python_2_unicode_compatible
 class BadgeClass(models.Model):
     """
     Specifies a badge class to be registered with a backend.
@@ -64,7 +66,7 @@ class BadgeClass(models.Model):
     mode = models.CharField(max_length=100, default='', blank=True)
     image = models.ImageField(upload_to='badge_classes', validators=[validate_badge_image])
 
-    def __unicode__(self):
+    def __str__(self):
         return HTML(u"<Badge '{slug}' for '{issuing_component}'>").format(
             slug=HTML(self.slug), issuing_component=HTML(self.issuing_component)
         )
@@ -143,6 +145,7 @@ class BadgeClass(models.Model):
         verbose_name_plural = "Badge Classes"
 
 
+@python_2_unicode_compatible
 class BadgeAssertion(TimeStampedModel):
     """
     Tracks badges on our side of the badge baking transaction
@@ -156,7 +159,7 @@ class BadgeAssertion(TimeStampedModel):
     image_url = models.URLField()
     assertion_url = models.URLField()
 
-    def __unicode__(self):
+    def __str__(self):
         return HTML(u"<{username} Badge Assertion for {slug} for {issuing_component}").format(
             username=HTML(self.user.username),
             slug=HTML(self.badge_class.slug),
@@ -180,6 +183,7 @@ class BadgeAssertion(TimeStampedModel):
 BadgeAssertion._meta.get_field('created').db_index = True
 
 
+@python_2_unicode_compatible
 class CourseCompleteImageConfiguration(models.Model):
     """
     Contains the icon configuration for badges for a specific course mode.
@@ -207,7 +211,7 @@ class CourseCompleteImageConfiguration(models.Model):
         default=False,
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return HTML(u"<CourseCompleteImageConfiguration for '{mode}'{default}>").format(
             mode=HTML(self.mode),
             default=HTML(u" (default)") if self.default else HTML(u'')
@@ -235,6 +239,7 @@ class CourseCompleteImageConfiguration(models.Model):
         app_label = "badges"
 
 
+@python_2_unicode_compatible
 class CourseEventBadgesConfiguration(ConfigurationModel):
     """
     Determines the settings for meta course awards-- such as completing a certain
@@ -268,7 +273,7 @@ class CourseEventBadgesConfiguration(ConfigurationModel):
         )
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return HTML(u"<CourseEventBadgesConfiguration ({})>").format(
             Text(u"Enabled") if self.enabled else Text(u"Disabled")
         )
diff --git a/lms/djangoapps/bulk_email/models.py b/lms/djangoapps/bulk_email/models.py
index e9cf7856e73..85a3e380ced 100644
--- a/lms/djangoapps/bulk_email/models.py
+++ b/lms/djangoapps/bulk_email/models.py
@@ -10,6 +10,7 @@ import six
 from config_models.models import ConfigurationModel
 from django.contrib.auth.models import User
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from opaque_keys.edx.django.models import CourseKeyField
 from six import text_type
 from six.moves import zip
@@ -60,6 +61,7 @@ EMAIL_TARGET_CHOICES = list(zip(
 EMAIL_TARGETS = {target[0] for target in EMAIL_TARGET_CHOICES}
 
 
+@python_2_unicode_compatible
 class Target(models.Model):
     """
     A way to refer to a particular group (within a course) as a "Send to:" target.
@@ -79,7 +81,7 @@ class Target(models.Model):
     class Meta(object):
         app_label = "bulk_email"
 
-    def __unicode__(self):
+    def __str__(self):
         return "CourseEmail Target: {}".format(self.short_display())
 
     def short_display(self):
@@ -143,6 +145,7 @@ class Target(models.Model):
             raise ValueError(u"Unrecognized target type {}".format(self.target_type))
 
 
+@python_2_unicode_compatible
 class CohortTarget(Target):
     """
     Subclass of Target, specifically referring to a cohort.
@@ -158,7 +161,7 @@ class CohortTarget(Target):
         kwargs['target_type'] = SEND_TO_COHORT
         super(CohortTarget, self).__init__(*args, **kwargs)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.short_display()
 
     def short_display(self):
@@ -188,6 +191,7 @@ class CohortTarget(Target):
         return cohort
 
 
+@python_2_unicode_compatible
 class CourseModeTarget(Target):
     """
     Subclass of Target, specifically for course modes.
@@ -203,7 +207,7 @@ class CourseModeTarget(Target):
         kwargs['target_type'] = SEND_TO_TRACK
         super(CourseModeTarget, self).__init__(*args, **kwargs)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.short_display()
 
     def short_display(self):
@@ -235,6 +239,7 @@ class CourseModeTarget(Target):
             )
 
 
+@python_2_unicode_compatible
 class CourseEmail(Email):
     """
     Stores information for an email to a course.
@@ -251,7 +256,7 @@ class CourseEmail(Email):
     template_name = models.CharField(null=True, max_length=255)
     from_addr = models.CharField(null=True, max_length=255)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.subject
 
     @classmethod
@@ -429,6 +434,7 @@ class CourseEmailTemplate(models.Model):
         return CourseEmailTemplate._render(self.html_template, htmltext, context)
 
 
+@python_2_unicode_compatible
 class CourseAuthorization(models.Model):
     """
     Enable the course email feature on a course-by-course basis.
@@ -455,7 +461,7 @@ class CourseAuthorization(models.Model):
         except cls.DoesNotExist:
             return False
 
-    def __unicode__(self):
+    def __str__(self):
         not_en = "Not "
         if self.email_enabled:
             not_en = ""
@@ -474,6 +480,7 @@ class CourseAuthorization(models.Model):
 # .. toggle_warnings: None
 # .. toggle_tickets: None
 # .. toggle_status: supported
+@python_2_unicode_compatible
 class BulkEmailFlag(ConfigurationModel):
     """
     Enables site-wide configuration for the bulk_email feature.
@@ -512,7 +519,7 @@ class BulkEmailFlag(ConfigurationModel):
     class Meta(object):
         app_label = "bulk_email"
 
-    def __unicode__(self):
+    def __str__(self):
         current_model = BulkEmailFlag.current()
         return u"BulkEmailFlag: enabled {}, require_course_email_auth: {}".format(
             current_model.is_enabled(),
diff --git a/lms/djangoapps/bulk_email/tests/test_models.py b/lms/djangoapps/bulk_email/tests/test_models.py
index 1865a57208b..03b3f6115ca 100644
--- a/lms/djangoapps/bulk_email/tests/test_models.py
+++ b/lms/djangoapps/bulk_email/tests/test_models.py
@@ -288,7 +288,7 @@ class CourseAuthorizationTest(TestCase):
         # Now, course should be authorized
         self.assertTrue(is_bulk_email_feature_enabled(course_id))
         self.assertEqual(
-            cauth.__unicode__(),
+            str(cauth),
             "Course 'abc/123/doremi': Instructor Email Enabled"
         )
 
@@ -298,7 +298,7 @@ class CourseAuthorizationTest(TestCase):
         # Test that course is now unauthorized
         self.assertFalse(is_bulk_email_feature_enabled(course_id))
         self.assertEqual(
-            cauth.__unicode__(),
+            str(cauth),
             "Course 'abc/123/doremi': Instructor Email Not Enabled"
         )
 
diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py
index e3b355bb465..6a0737a5f7c 100644
--- a/lms/djangoapps/certificates/models.py
+++ b/lms/djangoapps/certificates/models.py
@@ -60,6 +60,7 @@ from django.core.exceptions import ValidationError
 from django.db import models, transaction
 from django.db.models import Count
 from django.dispatch import receiver
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from model_utils import Choices
 from model_utils.fields import AutoCreatedField
@@ -404,6 +405,7 @@ class GeneratedCertificate(models.Model):
             )
 
 
+@python_2_unicode_compatible
 class CertificateGenerationHistory(TimeStampedModel):
     """
     Model for storing Certificate Generation History.
@@ -463,11 +465,12 @@ class CertificateGenerationHistory(TimeStampedModel):
     class Meta(object):
         app_label = "certificates"
 
-    def __unicode__(self):
+    def __str__(self):
         return u"certificates %s by %s on %s for %s" % \
                ("regenerated" if self.is_regeneration else "generated", self.generated_by, self.created, self.course_id)
 
 
+@python_2_unicode_compatible
 class CertificateInvalidation(TimeStampedModel):
     """
     Model for storing Certificate Invalidation.
@@ -482,7 +485,7 @@ class CertificateInvalidation(TimeStampedModel):
     class Meta(object):
         app_label = "certificates"
 
-    def __unicode__(self):
+    def __str__(self):
         return u"Certificate %s, invalidated by %s on %s." % \
                (self.generated_certificate, self.invalidated_by, self.created)
 
@@ -1067,6 +1070,7 @@ class CertificateHtmlViewConfiguration(ConfigurationModel):
         return json_data
 
 
+@python_2_unicode_compatible
 class CertificateTemplate(TimeStampedModel):
     """A set of custom web certificate templates.
 
@@ -1123,7 +1127,7 @@ class CertificateTemplate(TimeStampedModel):
                   u'Course language is determined by the first two letters of the language code.'
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return u'%s' % (self.name, )
 
     class Meta(object):
@@ -1147,6 +1151,7 @@ def template_assets_path(instance, filename):
     return name
 
 
+@python_2_unicode_compatible
 class CertificateTemplateAsset(TimeStampedModel):
     """A set of assets to be used in custom web certificate templates.
 
@@ -1183,7 +1188,7 @@ class CertificateTemplateAsset(TimeStampedModel):
 
         super(CertificateTemplateAsset, self).save(*args, **kwargs)
 
-    def __unicode__(self):
+    def __str__(self):
         return u'%s' % (self.asset.url, )
 
     class Meta(object):
diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py
index 78ac989b9a6..46d756d5f18 100644
--- a/lms/djangoapps/certificates/queue.py
+++ b/lms/djangoapps/certificates/queue.py
@@ -11,6 +11,7 @@ import six
 from django.conf import settings
 from django.test.client import RequestFactory
 from django.urls import reverse
+from django.utils.encoding import python_2_unicode_compatible
 from lxml.etree import ParserError, XMLSyntaxError
 from requests.auth import HTTPBasicAuth
 
@@ -31,6 +32,7 @@ from xmodule.modulestore.django import modulestore
 LOGGER = logging.getLogger(__name__)
 
 
+@python_2_unicode_compatible
 class XQueueAddToQueueError(Exception):
     """An error occurred when adding a certificate task to the queue. """
 
@@ -39,7 +41,7 @@ class XQueueAddToQueueError(Exception):
         self.error_msg = error_msg
         super(XQueueAddToQueueError, self).__init__(six.text_type(self))
 
-    def __unicode__(self):
+    def __str__(self):
         return (
             u"Could not add certificate to the XQueue.  "
             u"The error code was '{code}' and the message was '{msg}'."
diff --git a/lms/djangoapps/commerce/models.py b/lms/djangoapps/commerce/models.py
index bf7d907dde1..8d8fb019446 100644
--- a/lms/djangoapps/commerce/models.py
+++ b/lms/djangoapps/commerce/models.py
@@ -5,9 +5,11 @@ from __future__ import absolute_import
 
 from config_models.models import ConfigurationModel
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
 
+@python_2_unicode_compatible
 class CommerceConfiguration(ConfigurationModel):
     """
     Commerce configuration
@@ -52,7 +54,7 @@ class CommerceConfiguration(ConfigurationModel):
         help_text=_('Automatically approve valid refund requests, without manual processing')
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return "Commerce configuration"
 
     @property
diff --git a/lms/djangoapps/course_goals/models.py b/lms/djangoapps/course_goals/models.py
index 34866705bbe..62ea1d7c496 100644
--- a/lms/djangoapps/course_goals/models.py
+++ b/lms/djangoapps/course_goals/models.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import
 
 from django.contrib.auth.models import User
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from model_utils import Choices
 from opaque_keys.edx.django.models import CourseKeyField
@@ -18,6 +19,7 @@ GOAL_KEY_CHOICES = Choices(
 )
 
 
+@python_2_unicode_compatible
 class CourseGoal(models.Model):
     """
     Represents a course goal set by a user on the course home page.
@@ -32,7 +34,7 @@ class CourseGoal(models.Model):
     course_key = CourseKeyField(max_length=255, db_index=True)
     goal_key = models.CharField(max_length=100, choices=GOAL_KEY_CHOICES, default=GOAL_KEY_CHOICES.unsure)
 
-    def __unicode__(self):
+    def __str__(self):
         return 'CourseGoal: {user} set goal to {goal} for course {course}'.format(
             user=self.user.username,
             goal=self.goal_key,
diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py
index c25d142bfe7..0dfa1b892e5 100644
--- a/lms/djangoapps/courseware/models.py
+++ b/lms/djangoapps/courseware/models.py
@@ -23,6 +23,7 @@ from django.conf import settings
 from django.contrib.auth.models import User
 from django.db import models
 from django.db.models.signals import post_save
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from model_utils.models import TimeStampedModel
 from opaque_keys.edx.django.models import BlockTypeKeyField, CourseKeyField, LearningContextKeyField, UsageKeyField
@@ -77,6 +78,7 @@ class ChunkingManager(models.Manager):
         return res
 
 
+@python_2_unicode_compatible
 class StudentModule(models.Model):
     """
     Keeps student state for a particular XBlock usage and particular student.
@@ -149,7 +151,7 @@ class StudentModule(models.Model):
                 'state': str(self.state)[:20],
             },)
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
 
     @classmethod
@@ -232,6 +234,7 @@ class BaseStudentModuleHistory(models.Model):
         return history_entries
 
 
+@python_2_unicode_compatible
 class StudentModuleHistory(BaseStudentModuleHistory):
     """Keeps a complete history of state changes for a given XModule for a given
     Student. Right now, we restrict this to problems so that the table doesn't
@@ -243,7 +246,7 @@ class StudentModuleHistory(BaseStudentModuleHistory):
 
     student_module = models.ForeignKey(StudentModule, db_index=True, db_constraint=False, on_delete=models.CASCADE)
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
 
     def save_history(sender, instance, **kwargs):  # pylint: disable=no-self-argument, unused-argument
@@ -268,6 +271,7 @@ class StudentModuleHistory(BaseStudentModuleHistory):
         post_save.connect(save_history, sender=StudentModule)
 
 
+@python_2_unicode_compatible
 class XBlockFieldBase(models.Model):
     """
     Base class for all XBlock field storage.
@@ -289,7 +293,7 @@ class XBlockFieldBase(models.Model):
     created = models.DateTimeField(auto_now_add=True, db_index=True)
     modified = models.DateTimeField(auto_now=True, db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         keys = [field.name for field in self._meta.get_fields() if field.name not in ('created', 'modified')]
         return HTML(u'{}<{!r}').format(
             HTML(self.__class__.__name__),
@@ -337,6 +341,7 @@ class XModuleStudentInfoField(XBlockFieldBase):
     student = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
 
 
+@python_2_unicode_compatible
 class OfflineComputedGrade(models.Model):
     """
     Table of grades computed offline for a given user and course.
@@ -355,10 +360,11 @@ class OfflineComputedGrade(models.Model):
         app_label = "courseware"
         unique_together = (('user', 'course_id'),)
 
-    def __unicode__(self):
+    def __str__(self):
         return "[OfflineComputedGrade] %s: %s (%s) = %s" % (self.user, self.course_id, self.created, self.gradeset)
 
 
+@python_2_unicode_compatible
 class OfflineComputedGradeLog(models.Model):
     """
     Log of when offline grades are computed.
@@ -377,7 +383,7 @@ class OfflineComputedGradeLog(models.Model):
     seconds = models.IntegerField(default=0)  # seconds elapsed for computation
     nstudents = models.IntegerField(default=0)
 
-    def __unicode__(self):
+    def __str__(self):
         return "[OCGLog] %s: %s" % (text_type(self.course_id), self.created)
 
 
diff --git a/lms/djangoapps/coursewarehistoryextended/models.py b/lms/djangoapps/coursewarehistoryextended/models.py
index 9df1277c485..b9347f8ec41 100644
--- a/lms/djangoapps/coursewarehistoryextended/models.py
+++ b/lms/djangoapps/coursewarehistoryextended/models.py
@@ -18,11 +18,13 @@ import six
 from django.db import models
 from django.db.models.signals import post_delete, post_save
 from django.dispatch import receiver
+from django.utils.encoding import python_2_unicode_compatible
 
 from courseware.models import BaseStudentModuleHistory, StudentModule
 from courseware.fields import UnsignedBigIntAutoField
 
 
+@python_2_unicode_compatible
 class StudentModuleHistoryExtended(BaseStudentModuleHistory):
     """Keeps a complete history of state changes for a given XModule for a given
     Student. Right now, we restrict this to problems so that the table doesn't
@@ -64,5 +66,5 @@ class StudentModuleHistoryExtended(BaseStudentModuleHistory):
         """
         StudentModuleHistoryExtended.objects.filter(student_module=instance).all().delete()
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
diff --git a/lms/djangoapps/email_marketing/models.py b/lms/djangoapps/email_marketing/models.py
index 28124f321bd..1822a18bd6c 100644
--- a/lms/djangoapps/email_marketing/models.py
+++ b/lms/djangoapps/email_marketing/models.py
@@ -5,9 +5,11 @@ from __future__ import absolute_import
 
 from config_models.models import ConfigurationModel
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
 
+@python_2_unicode_compatible
 class EmailMarketingConfiguration(ConfigurationModel):
     """
     Email marketing configuration
@@ -166,6 +168,6 @@ class EmailMarketingConfiguration(ConfigurationModel):
         )
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return u"Email marketing configuration: New user list %s, Welcome template: %s" % \
                (self.sailthru_new_user_list, self.sailthru_welcome_template)
diff --git a/lms/djangoapps/grades/config/models.py b/lms/djangoapps/grades/config/models.py
index 34726a6eea4..bc433198c44 100644
--- a/lms/djangoapps/grades/config/models.py
+++ b/lms/djangoapps/grades/config/models.py
@@ -7,12 +7,15 @@ from __future__ import absolute_import
 from config_models.models import ConfigurationModel
 from django.conf import settings
 from django.db.models import BooleanField, IntegerField, TextField
+from django.utils.encoding import python_2_unicode_compatible
 from opaque_keys.edx.django.models import CourseKeyField
+
 from six import text_type
 
 from openedx.core.lib.cache_utils import request_cached
 
 
+@python_2_unicode_compatible
 class PersistentGradesEnabledFlag(ConfigurationModel):
     """
     Enables persistent grades across the platform.
@@ -50,13 +53,14 @@ class PersistentGradesEnabledFlag(ConfigurationModel):
     class Meta(object):
         app_label = "grades"
 
-    def __unicode__(self):
+    def __str__(self):
         current_model = PersistentGradesEnabledFlag.current()
         return u"PersistentGradesEnabledFlag: enabled {}".format(
             current_model.is_enabled()
         )
 
 
+@python_2_unicode_compatible
 class CoursePersistentGradesFlag(ConfigurationModel):
     """
     Enables persistent grades for a specific
@@ -73,7 +77,7 @@ class CoursePersistentGradesFlag(ConfigurationModel):
     # The course that these features are attached to.
     course_id = CourseKeyField(max_length=255, db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         not_en = "Not "
         if self.enabled:
             not_en = ""
diff --git a/lms/djangoapps/grades/course_grade.py b/lms/djangoapps/grades/course_grade.py
index 1af0421ee31..dcc5bf363b7 100644
--- a/lms/djangoapps/grades/course_grade.py
+++ b/lms/djangoapps/grades/course_grade.py
@@ -9,6 +9,7 @@ from collections import OrderedDict, defaultdict
 import six
 from ccx_keys.locator import CCXLocator
 from django.conf import settings
+from django.utils.encoding import python_2_unicode_compatible
 from lazy import lazy
 
 from xmodule import block_metadata_utils
@@ -19,6 +20,7 @@ from .subsection_grade import ZeroSubsectionGrade
 from .subsection_grade_factory import SubsectionGradeFactory
 
 
+@python_2_unicode_compatible
 class CourseGradeBase(object):
     """
     Base class for Course Grades.
@@ -34,7 +36,7 @@ class CourseGradeBase(object):
         self.letter_grade = letter_grade or None
         self.force_update_subsections = force_update_subsections
 
-    def __unicode__(self):
+    def __str__(self):
         return u'Course Grade: percent: {}, letter_grade: {}, passed: {}'.format(
             six.text_type(self.percent),
             self.letter_grade,
diff --git a/lms/djangoapps/grades/models.py b/lms/djangoapps/grades/models.py
index 55efa8cff07..dc412594fea 100644
--- a/lms/djangoapps/grades/models.py
+++ b/lms/djangoapps/grades/models.py
@@ -265,6 +265,7 @@ class VisibleBlocks(models.Model):
         return u"visible_blocks_cache.{}.{}".format(course_key, user_id)
 
 
+@python_2_unicode_compatible
 class PersistentSubsectionGrade(TimeStampedModel):
     """
     A django model tracking persistent grades at the subsection level.
@@ -336,7 +337,7 @@ class PersistentSubsectionGrade(TimeStampedModel):
         else:
             return self.usage_key
 
-    def __unicode__(self):
+    def __str__(self):
         """
         Returns a string representation of this model.
         """
@@ -507,6 +508,7 @@ class PersistentSubsectionGrade(TimeStampedModel):
         return u"subsection_grades_cache.{}".format(course_id)
 
 
+@python_2_unicode_compatible
 class PersistentCourseGrade(TimeStampedModel):
     """
     A django model tracking persistent course grades.
@@ -550,7 +552,7 @@ class PersistentCourseGrade(TimeStampedModel):
 
     _CACHE_NAMESPACE = u"grades.models.PersistentCourseGrade"
 
-    def __unicode__(self):
+    def __str__(self):
         """
         Returns a string representation of this model.
         """
@@ -643,6 +645,7 @@ class PersistentCourseGrade(TimeStampedModel):
         events.course_grade_calculated(grade)
 
 
+@python_2_unicode_compatible
 class PersistentSubsectionGradeOverride(models.Model):
     """
     A django model tracking persistent grades overrides at the subsection level.
@@ -677,7 +680,7 @@ class PersistentSubsectionGradeOverride(models.Model):
     if 'grades' in apps.app_configs:
         history = HistoricalRecords()
 
-    def __unicode__(self):
+    def __str__(self):
         return u', '.join([
             u"{}".format(type(self).__name__),
             u"earned_all_override: {}".format(self.earned_all_override),
@@ -763,6 +766,7 @@ class PersistentSubsectionGradeOverride(models.Model):
         return cleaned_data
 
 
+@python_2_unicode_compatible
 class PersistentSubsectionGradeOverrideHistory(models.Model):
     """
     A django model tracking persistent grades override audit records.
@@ -799,7 +803,7 @@ class PersistentSubsectionGradeOverrideHistory(models.Model):
     comments = models.CharField(max_length=300, blank=True, null=True)
     created = models.DateTimeField(auto_now_add=True, db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         """
         String representation of this model.
         """
diff --git a/lms/djangoapps/instructor_task/models.py b/lms/djangoapps/instructor_task/models.py
index 0bcfa4a2890..3efe99ba4d9 100644
--- a/lms/djangoapps/instructor_task/models.py
+++ b/lms/djangoapps/instructor_task/models.py
@@ -28,6 +28,7 @@ from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.files.base import ContentFile
 from django.db import models, transaction
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext as _
 from opaque_keys.edx.django.models import CourseKeyField
 from six import text_type
@@ -42,6 +43,7 @@ PROGRESS = 'PROGRESS'
 TASK_INPUT_LENGTH = 10000
 
 
+@python_2_unicode_compatible
 class InstructorTask(models.Model):
     """
     Stores information about background tasks that have been submitted to
@@ -91,7 +93,7 @@ class InstructorTask(models.Model):
             'task_output': self.task_output,
         },)
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
 
     @classmethod
diff --git a/lms/djangoapps/instructor_task/subtasks.py b/lms/djangoapps/instructor_task/subtasks.py
index 374b9ed69e7..75ec200325e 100644
--- a/lms/djangoapps/instructor_task/subtasks.py
+++ b/lms/djangoapps/instructor_task/subtasks.py
@@ -15,6 +15,7 @@ import six
 from celery.states import READY_STATES, RETRY, SUCCESS
 from django.core.cache import cache
 from django.db import DatabaseError, transaction
+from django.utils.encoding import python_2_unicode_compatible
 from six.moves import range, zip
 
 from util.db import outer_atomic
@@ -121,6 +122,7 @@ def _generate_items_for_subtask(
         TASK_LOG.info(u"Number of items generated by chunking %s not equal to original total %s", num_items_queued, total_num_items)
 
 
+@python_2_unicode_compatible
 class SubtaskStatus(object):
     """
     Create and return a dict for tracking the status of a subtask.
@@ -205,7 +207,7 @@ class SubtaskStatus(object):
         """Return print representation of a SubtaskStatus object."""
         return 'SubtaskStatus<%r>' % (self.to_dict(),)
 
-    def __unicode__(self):
+    def __str__(self):
         """Return unicode version of a SubtaskStatus object representation."""
         return six.text_type(repr(self))
 
diff --git a/lms/djangoapps/mobile_api/models.py b/lms/djangoapps/mobile_api/models.py
index bb8c023183d..9ab57d36a0b 100644
--- a/lms/djangoapps/mobile_api/models.py
+++ b/lms/djangoapps/mobile_api/models.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import
 
 from config_models.models import ConfigurationModel
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 from . import utils
 from .mobile_platform import PLATFORM_CLASSES
@@ -35,6 +36,7 @@ class MobileApiConfig(ConfigurationModel):
         return [profile.strip() for profile in cls.current().video_profiles.split(",") if profile]
 
 
+@python_2_unicode_compatible
 class AppVersionConfig(models.Model):
     """
     Configuration for mobile app versions available.
@@ -64,7 +66,7 @@ class AppVersionConfig(models.Model):
         unique_together = ('platform', 'version',)
         ordering = ['-major_version', '-minor_version', '-patch_version']
 
-    def __unicode__(self):
+    def __str__(self):
         return "{}_{}".format(self.platform, self.version)
 
     @classmethod
diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py
index 9cf522807d8..75b9b23ca01 100644
--- a/lms/djangoapps/shoppingcart/models.py
+++ b/lms/djangoapps/shoppingcart/models.py
@@ -26,6 +26,7 @@ from django.db.models import Count, F, Q, Sum
 from django.db.models.signals import post_delete, post_save
 from django.dispatch import receiver
 from django.urls import reverse
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext_lazy
 from model_utils.managers import InheritanceManager
@@ -827,6 +828,7 @@ class OrderItem(TimeStampedModel):
         self.save()
 
 
+@python_2_unicode_compatible
 class Invoice(TimeStampedModel):
     """
     This table capture all the information needed to support "invoicing"
@@ -960,7 +962,7 @@ class Invoice(TimeStampedModel):
             ],
         }
 
-    def __unicode__(self):
+    def __str__(self):
         label = (
             six.text_type(self.internal_reference)
             if self.internal_reference
@@ -1340,6 +1342,7 @@ class RegistrationCodeRedemption(models.Model):
         return code_redemption
 
 
+@python_2_unicode_compatible
 class Coupon(models.Model):
     """
     This table contains coupon codes
@@ -1359,7 +1362,7 @@ class Coupon(models.Model):
     is_active = models.BooleanField(default=True)
     expiration_date = models.DateTimeField(null=True, blank=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return "[Coupon] code: {} course: {}".format(self.code, self.course_id)
 
     @property
@@ -1850,6 +1853,7 @@ class CourseRegCodeItem(OrderItem):
         return data
 
 
+@python_2_unicode_compatible
 class CourseRegCodeItemAnnotation(models.Model):
     """
     A model that maps course_id to an additional annotation.  This is specifically needed because when Stanford
@@ -1865,10 +1869,11 @@ class CourseRegCodeItemAnnotation(models.Model):
     course_id = CourseKeyField(unique=True, max_length=128, db_index=True)
     annotation = models.TextField(null=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return u"{} : {}".format(text_type(self.course_id), self.annotation)
 
 
+@python_2_unicode_compatible
 class PaidCourseRegistrationAnnotation(models.Model):
     """
     A model that maps course_id to an additional annotation.  This is specifically needed because when Stanford
@@ -1884,7 +1889,7 @@ class PaidCourseRegistrationAnnotation(models.Model):
     course_id = CourseKeyField(unique=True, max_length=128, db_index=True)
     annotation = models.TextField(null=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return u"{} : {}".format(text_type(self.course_id), self.annotation)
 
 
diff --git a/lms/djangoapps/shoppingcart/tests/test_reports.py b/lms/djangoapps/shoppingcart/tests/test_reports.py
index 002b936926b..c9ea37f6155 100644
--- a/lms/djangoapps/shoppingcart/tests/test_reports.py
+++ b/lms/djangoapps/shoppingcart/tests/test_reports.py
@@ -245,12 +245,12 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
 
     def test_paidcourseregistrationannotation_unicode(self):
         """
-        Fill in gap in test coverage.  __unicode__ method of PaidCourseRegistrationAnnotation
+        Fill in gap in test coverage.  __str__ method of PaidCourseRegistrationAnnotation
         """
         self.assertEqual(text_type(self.annotation), u'{} : {}'.format(text_type(self.course_key), self.TEST_ANNOTATION))
 
     def test_courseregcodeitemannotationannotation_unicode(self):
         """
-        Fill in gap in test coverage.  __unicode__ method of CourseRegCodeItemAnnotation
+        Fill in gap in test coverage.  __str__ method of CourseRegCodeItemAnnotation
         """
         self.assertEqual(text_type(self.course_reg_code_annotation), u'{} : {}'.format(text_type(self.course_key), self.TEST_ANNOTATION))
diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py
index f83aa49dafa..43dc7c01dea 100644
--- a/lms/djangoapps/shoppingcart/tests/test_views.py
+++ b/lms/djangoapps/shoppingcart/tests/test_views.py
@@ -592,7 +592,7 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin):
         coupon = Coupon(code='TestCode', description='testing', course_id=self.course_key,
                         percentage_discount=12, created_by=self.user, is_active=True)
         coupon.save()
-        self.assertEquals(coupon.__unicode__(), '[Coupon] code: TestCode course: MITx/999/Robot_Super_Course')
+        self.assertEquals(str(coupon), '[Coupon] code: TestCode course: MITx/999/Robot_Super_Course')
         admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo')
         admin.is_staff = True
         get_coupon = Coupon.objects.get(id=1)
diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py
index 5c5377cb04b..0aabe59090e 100644
--- a/lms/djangoapps/verify_student/models.py
+++ b/lms/djangoapps/verify_student/models.py
@@ -28,6 +28,7 @@ from django.contrib.auth.models import User
 from django.core.files.base import ContentFile
 from django.db import models
 from django.urls import reverse
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.functional import cached_property
 from django.utils.timezone import now
 from django.utils.translation import ugettext_lazy
@@ -144,6 +145,7 @@ class IDVerificationAttempt(StatusModel):
         )
 
 
+@python_2_unicode_compatible
 class ManualVerification(IDVerificationAttempt):
     """
     Each ManualVerification represents a user's verification that bypasses the need for
@@ -165,7 +167,7 @@ class ManualVerification(IDVerificationAttempt):
     class Meta(object):
         app_label = 'verify_student'
 
-    def __unicode__(self):
+    def __str__(self):
         return 'ManualIDVerification for {name}, status: {status}'.format(
             name=self.name,
             status=self.status,
@@ -178,6 +180,7 @@ class ManualVerification(IDVerificationAttempt):
         return False
 
 
+@python_2_unicode_compatible
 class SSOVerification(IDVerificationAttempt):
     """
     Each SSOVerification represents a Student's attempt to establish their identity
@@ -215,7 +218,7 @@ class SSOVerification(IDVerificationAttempt):
     class Meta(object):
         app_label = "verify_student"
 
-    def __unicode__(self):
+    def __str__(self):
         return 'SSOIDVerification for {name}, status: {status}'.format(
             name=self.name,
             status=self.status,
diff --git a/openedx/core/djangoapps/api_admin/models.py b/openedx/core/djangoapps/api_admin/models.py
index f5f4bd89255..316fe00c771 100644
--- a/openedx/core/djangoapps/api_admin/models.py
+++ b/openedx/core/djangoapps/api_admin/models.py
@@ -26,6 +26,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
 log = logging.getLogger(__name__)
 
 
+@python_2_unicode_compatible
 class ApiAccessRequest(TimeStampedModel):
     """
     Model to track API access for a user.
@@ -127,7 +128,7 @@ class ApiAccessRequest(TimeStampedModel):
         self.status = self.DENIED
         self.save()
 
-    def __unicode__(self):
+    def __str__(self):
         return u'ApiAccessRequest {website} [{status}]'.format(website=self.website, status=self.status)
 
 
@@ -223,6 +224,7 @@ def _send_decision_email(instance):
         log.exception(u'Error sending API user notification email for request [%s].', instance.id)
 
 
+@python_2_unicode_compatible
 class Catalog(models.Model):
     """
     A (non-Django-managed) model for Catalogs in the course discovery service.
@@ -265,5 +267,5 @@ class Catalog(models.Model):
             'viewers': self.viewers,
         }
 
-    def __unicode__(self):
+    def __str__(self):
         return u'Catalog {name} [{query}]'.format(name=self.name, query=self.query)
diff --git a/openedx/core/djangoapps/bookmarks/models.py b/openedx/core/djangoapps/bookmarks/models.py
index 34493e36f28..60ce8d5f063 100644
--- a/openedx/core/djangoapps/bookmarks/models.py
+++ b/openedx/core/djangoapps/bookmarks/models.py
@@ -8,6 +8,7 @@ import logging
 import six
 from django.contrib.auth.models import User
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from jsonfield.fields import JSONField
 from model_utils.models import TimeStampedModel
 from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField
@@ -41,6 +42,7 @@ def parse_path_data(path_data):
     return path
 
 
+@python_2_unicode_compatible
 class Bookmark(TimeStampedModel):
     """
     Bookmarks model.
@@ -60,7 +62,7 @@ class Bookmark(TimeStampedModel):
         """
         unique_together = ('user', 'usage_key')
 
-    def __unicode__(self):
+    def __str__(self):
         return self.resource_id
 
     @classmethod
@@ -191,6 +193,7 @@ class Bookmark(TimeStampedModel):
         return path_data
 
 
+@python_2_unicode_compatible
 class XBlockCache(TimeStampedModel):
     """
     XBlockCache model to store info about xblocks.
@@ -206,7 +209,7 @@ class XBlockCache(TimeStampedModel):
         db_column='paths', default=[], help_text='All paths in course tree to the corresponding block.'
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(self.usage_key)
 
     @property
diff --git a/openedx/core/djangoapps/ccxcon/models.py b/openedx/core/djangoapps/ccxcon/models.py
index f240216f098..99b68fd27ad 100644
--- a/openedx/core/djangoapps/ccxcon/models.py
+++ b/openedx/core/djangoapps/ccxcon/models.py
@@ -4,10 +4,11 @@ Models for the ccxcon
 
 from __future__ import absolute_import
 
-import six
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class CCXCon(models.Model):
     """
     Definition of the CCXCon model.
@@ -30,6 +31,3 @@ class CCXCon(models.Model):
 
     def __str__(self):
         return self.title
-
-    def __unicode__(self):
-        return six.text_type(self.__str__())
diff --git a/openedx/core/djangoapps/content/block_structure/config/models.py b/openedx/core/djangoapps/content/block_structure/config/models.py
index ee7606d3499..2aa34d75eaa 100644
--- a/openedx/core/djangoapps/content/block_structure/config/models.py
+++ b/openedx/core/djangoapps/content/block_structure/config/models.py
@@ -5,8 +5,10 @@ from __future__ import absolute_import
 
 from config_models.models import ConfigurationModel
 from django.db.models import IntegerField
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class BlockStructureConfiguration(ConfigurationModel):
     """
     Configuration model for Block Structures.
@@ -23,7 +25,7 @@ class BlockStructureConfiguration(ConfigurationModel):
     num_versions_to_keep = IntegerField(blank=True, null=True, default=DEFAULT_PRUNE_KEEP_COUNT)
     cache_timeout_in_seconds = IntegerField(blank=True, null=True, default=DEFAULT_CACHE_TIMEOUT_IN_SECONDS)
 
-    def __unicode__(self):
+    def __str__(self):
         return u"BlockStructureConfiguration: num_versions_to_keep: {}, cache_timeout_in_seconds: {}".format(
             self.num_versions_to_keep,
             self.cache_timeout_in_seconds,
diff --git a/openedx/core/djangoapps/content/block_structure/models.py b/openedx/core/djangoapps/content/block_structure/models.py
index 290f7e3d52e..f8dc317fde7 100644
--- a/openedx/core/djangoapps/content/block_structure/models.py
+++ b/openedx/core/djangoapps/content/block_structure/models.py
@@ -14,6 +14,7 @@ from django.conf import settings
 from django.core.exceptions import SuspiciousOperation
 from django.core.files.base import ContentFile
 from django.db import models, transaction
+from django.utils.encoding import python_2_unicode_compatible
 from model_utils.models import TimeStampedModel
 
 from openedx.core.djangoapps.xmodule_django.models import UsageKeyWithRunField
@@ -125,6 +126,7 @@ def _storage_error_handling(bs_model, operation, is_read_operation=False):
             raise
 
 
+@python_2_unicode_compatible
 class BlockStructureModel(TimeStampedModel):
     """
     Model for storing Block Structure information.
@@ -218,7 +220,7 @@ class BlockStructureModel(TimeStampedModel):
 
         return bs_model, created
 
-    def __unicode__(self):
+    def __str__(self):
         """
         Returns a string representation of this model.
         """
diff --git a/openedx/core/djangoapps/content/block_structure/store.py b/openedx/core/djangoapps/content/block_structure/store.py
index 204ee3606a5..c6568f031f6 100644
--- a/openedx/core/djangoapps/content/block_structure/store.py
+++ b/openedx/core/djangoapps/content/block_structure/store.py
@@ -8,6 +8,7 @@ from logging import getLogger
 
 import six
 
+from django.utils.encoding import python_2_unicode_compatible
 from openedx.core.lib.cache_utils import zpickle, zunpickle
 
 from . import config
@@ -20,6 +21,7 @@ from .transformer_registry import TransformerRegistry
 logger = getLogger(__name__)  # pylint: disable=C0103
 
 
+@python_2_unicode_compatible
 class StubModel(object):
     """
     Stub model to use when storage backing is disabled.
@@ -29,7 +31,7 @@ class StubModel(object):
     def __init__(self, root_block_usage_key):
         self.data_usage_key = root_block_usage_key
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(self.data_usage_key)
 
     def delete(self):
diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py
index 71658cfccf9..2ffe4c1e34f 100644
--- a/openedx/core/djangoapps/content/course_overviews/models.py
+++ b/openedx/core/djangoapps/content/course_overviews/models.py
@@ -14,6 +14,7 @@ from django.db import models, transaction
 from django.db.models.fields import BooleanField, DateTimeField, DecimalField, FloatField, IntegerField, TextField
 from django.db.utils import IntegrityError
 from django.template import defaultfilters
+from django.utils.encoding import python_2_unicode_compatible
 from model_utils.models import TimeStampedModel
 from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField
 from six import text_type  # pylint: disable=ungrouped-imports
@@ -33,6 +34,7 @@ from xmodule.modulestore.django import modulestore
 log = logging.getLogger(__name__)
 
 
+@python_2_unicode_compatible
 class CourseOverview(TimeStampedModel):
     """
     Model for storing and caching basic information about a course.
@@ -725,7 +727,7 @@ class CourseOverview(TimeStampedModel):
 
         return urlunparse((None, base_url, path, params, query, fragment))
 
-    def __unicode__(self):
+    def __str__(self):
         """Represent ourselves with the course key."""
         return six.text_type(self.id)
 
@@ -740,6 +742,7 @@ class CourseOverviewTab(models.Model):
     course_overview = models.ForeignKey(CourseOverview, db_index=True, related_name="tabs", on_delete=models.CASCADE)
 
 
+@python_2_unicode_compatible
 class CourseOverviewImageSet(TimeStampedModel):
     """
     Model for Course overview images. Each column is an image type/size.
@@ -876,12 +879,13 @@ class CourseOverviewImageSet(TimeStampedModel):
             #          to unsaved related object 'course_overview'.")
             pass
 
-    def __unicode__(self):
+    def __str__(self):
         return u"CourseOverviewImageSet({}, small_url={}, large_url={})".format(
             self.course_overview_id, self.small_url, self.large_url
         )
 
 
+@python_2_unicode_compatible
 class CourseOverviewImageConfig(ConfigurationModel):
     """
     This sets the size of the thumbnail images that Course Overviews will generate
@@ -911,12 +915,13 @@ class CourseOverviewImageConfig(ConfigurationModel):
         """Tuple for large image dimensions in pixels -- (width, height)"""
         return (self.large_width, self.large_height)
 
-    def __unicode__(self):
+    def __str__(self):
         return u"CourseOverviewImageConfig(enabled={}, small={}, large={})".format(
             self.enabled, self.small, self.large
         )
 
 
+@python_2_unicode_compatible
 class SimulateCoursePublishConfig(ConfigurationModel):
     """
     Manages configuration for a run of the simulate_publish management command.
@@ -935,5 +940,5 @@ class SimulateCoursePublishConfig(ConfigurationModel):
         default='',
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(self.arguments)
diff --git a/openedx/core/djangoapps/content_libraries/models.py b/openedx/core/djangoapps/content_libraries/models.py
index f1680c18d6e..44f8f133ee9 100644
--- a/openedx/core/djangoapps/content_libraries/models.py
+++ b/openedx/core/djangoapps/content_libraries/models.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
 
 from django.contrib.auth import get_user_model
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from opaque_keys.edx.locator import LibraryLocatorV2
 from organizations.models import Organization
@@ -25,7 +26,7 @@ class ContentLibraryManager(models.Manager):
         return self.get(org__short_name=library_key.org, slug=library_key.slug)
 
 
-@six.python_2_unicode_compatible  # pylint: disable=model-missing-unicode
+@python_2_unicode_compatible
 class ContentLibrary(models.Model):
     """
     A Content Library is a collection of content (XBlocks and/or static assets)
@@ -84,7 +85,7 @@ class ContentLibrary(models.Model):
         return "ContentLibrary ({})".format(six.text_type(self.library_key))
 
 
-@six.python_2_unicode_compatible  # pylint: disable=model-missing-unicode
+@python_2_unicode_compatible
 class ContentLibraryPermission(models.Model):
     """
     Row recording permissions for a content library
diff --git a/openedx/core/djangoapps/contentserver/models.py b/openedx/core/djangoapps/contentserver/models.py
index 13463ad6435..f7d30e21162 100644
--- a/openedx/core/djangoapps/contentserver/models.py
+++ b/openedx/core/djangoapps/contentserver/models.py
@@ -8,8 +8,10 @@ import six
 
 from config_models.models import ConfigurationModel
 from django.db.models.fields import PositiveIntegerField, TextField
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class CourseAssetCacheTtlConfig(ConfigurationModel):
     """
     Configuration for the TTL of course assets.
@@ -33,10 +35,11 @@ class CourseAssetCacheTtlConfig(ConfigurationModel):
     def __repr__(self):
         return '<CourseAssetCacheTtlConfig(cache_ttl={})>'.format(self.get_cache_ttl())
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
 
 
+@python_2_unicode_compatible
 class CdnUserAgentsConfig(ConfigurationModel):
     """
     Configuration for the user agents we expect to see from CDNs.
@@ -60,5 +63,5 @@ class CdnUserAgentsConfig(ConfigurationModel):
     def __repr__(self):
         return '<WhitelistedCdnConfig(cdn_user_agents={})>'.format(self.get_cdn_user_agents())
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(repr(self))
diff --git a/openedx/core/djangoapps/cors_csrf/models.py b/openedx/core/djangoapps/cors_csrf/models.py
index d0ec0d1b1b3..62e3dbbaa2e 100644
--- a/openedx/core/djangoapps/cors_csrf/models.py
+++ b/openedx/core/djangoapps/cors_csrf/models.py
@@ -3,9 +3,11 @@ from __future__ import absolute_import
 
 from config_models.models import ConfigurationModel
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
 
+@python_2_unicode_compatible
 class XDomainProxyConfiguration(ConfigurationModel):
     """
     Cross-domain proxy configuration.
@@ -22,5 +24,5 @@ class XDomainProxyConfiguration(ConfigurationModel):
         )
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return "XDomainProxyConfiguration()"
diff --git a/openedx/core/djangoapps/course_groups/models.py b/openedx/core/djangoapps/course_groups/models.py
index 9af25a42bdf..9c43ec7379c 100644
--- a/openedx/core/djangoapps/course_groups/models.py
+++ b/openedx/core/djangoapps/course_groups/models.py
@@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError
 from django.db import models, transaction
 from django.db.models.signals import pre_delete
 from django.dispatch import receiver
+from django.utils.encoding import python_2_unicode_compatible
 from opaque_keys.edx.django.models import CourseKeyField
 
 from openedx.core.djangolib.model_mixins import DeletableByUserValue
@@ -19,6 +20,7 @@ from openedx.core.djangolib.model_mixins import DeletableByUserValue
 log = logging.getLogger(__name__)
 
 
+@python_2_unicode_compatible
 class CourseUserGroup(models.Model):
     """
     This model represents groups of users in a course.  Groups may have different types,
@@ -66,7 +68,7 @@ class CourseUserGroup(models.Model):
             name=name
         )
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
 
 
diff --git a/openedx/core/djangoapps/crawlers/models.py b/openedx/core/djangoapps/crawlers/models.py
index 24263e5c045..e3c35b673d0 100644
--- a/openedx/core/djangoapps/crawlers/models.py
+++ b/openedx/core/djangoapps/crawlers/models.py
@@ -7,8 +7,10 @@ from __future__ import absolute_import
 import six
 from config_models.models import ConfigurationModel
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class CrawlersConfig(ConfigurationModel):
     """
     Configuration for the crawlers django app.
@@ -24,7 +26,7 @@ class CrawlersConfig(ConfigurationModel):
         default='edX-downloader',
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return u'CrawlersConfig("{}")'.format(self.known_user_agents)
 
     @classmethod
diff --git a/openedx/core/djangoapps/credentials/models.py b/openedx/core/djangoapps/credentials/models.py
index ad39709f54d..af32e4c9b1c 100644
--- a/openedx/core/djangoapps/credentials/models.py
+++ b/openedx/core/djangoapps/credentials/models.py
@@ -8,6 +8,7 @@ import six
 from config_models.models import ConfigurationModel
 from django.conf import settings
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from six.moves.urllib.parse import urljoin  # pylint: disable=import-error
 
@@ -16,6 +17,7 @@ from openedx.core.djangoapps.site_configuration import helpers
 API_VERSION = 'v2'
 
 
+@python_2_unicode_compatible
 class CredentialsApiConfig(ConfigurationModel):
     """
     Manages configuration for connecting to the Credential service and using its
@@ -62,7 +64,7 @@ class CredentialsApiConfig(ConfigurationModel):
         )
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return self.public_api_url
 
     @property
@@ -114,6 +116,7 @@ class CredentialsApiConfig(ConfigurationModel):
         return self.cache_ttl > 0
 
 
+@python_2_unicode_compatible
 class NotifyCredentialsConfig(ConfigurationModel):
     """
     Manages configuration for a run of the notify_credentials management command.
@@ -131,5 +134,5 @@ class NotifyCredentialsConfig(ConfigurationModel):
         default='',
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return six.text_type(self.arguments)
diff --git a/openedx/core/djangoapps/credit/models.py b/openedx/core/djangoapps/credit/models.py
index 73c8c0bf549..dcd483ee23c 100644
--- a/openedx/core/djangoapps/credit/models.py
+++ b/openedx/core/djangoapps/credit/models.py
@@ -20,6 +20,7 @@ from django.core.cache import cache
 from django.core.validators import RegexValidator
 from django.db import IntegrityError, models, transaction
 from django.dispatch import receiver
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext_lazy
 from edx_django_utils.cache import RequestCache
@@ -33,6 +34,7 @@ CREDIT_PROVIDER_ID_REGEX = r"[a-z,A-Z,0-9,\-]+"
 log = logging.getLogger(__name__)
 
 
+@python_2_unicode_compatible
 class CreditProvider(TimeStampedModel):
     """
     This model represents an institution that can grant credit for a course.
@@ -203,7 +205,7 @@ class CreditProvider(TimeStampedModel):
         except cls.DoesNotExist:
             return None
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of the credit provider. """
         return self.provider_id
 
@@ -215,6 +217,7 @@ def invalidate_provider_cache(sender, **kwargs):  # pylint: disable=unused-argum
     cache.delete(CreditProvider.CREDIT_PROVIDERS_CACHE_KEY)
 
 
+@python_2_unicode_compatible
 class CreditCourse(models.Model):
     """
     Model for tracking a credit course.
@@ -265,7 +268,7 @@ class CreditCourse(models.Model):
         """
         return cls.objects.get(course_key=course_key, enabled=True)
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of the credit course. """
         return six.text_type(self.course_key)
 
@@ -277,6 +280,7 @@ def invalidate_credit_courses_cache(sender, **kwargs):   # pylint: disable=unuse
     cache.delete(CreditCourse.CREDIT_COURSES_CACHE_KEY)
 
 
+@python_2_unicode_compatible
 class CreditRequirement(TimeStampedModel):
     """
     This model represents a credit requirement.
@@ -307,8 +311,8 @@ class CreditRequirement(TimeStampedModel):
         unique_together = ('namespace', 'name', 'course')
         ordering = ["order"]
 
-    def __unicode__(self):
-        return '{course_id} - {name}'.format(course_id=self.course.course_key, name=self.display_name)
+    def __str__(self):
+        return u'{course_id} - {name}'.format(course_id=self.course.course_key, name=self.display_name)
 
     @classmethod
     def add_or_update_course_requirement(cls, credit_course, requirement, order):
@@ -545,6 +549,7 @@ def default_deadline_for_credit_eligibility():
     )
 
 
+@python_2_unicode_compatible
 class CreditEligibility(TimeStampedModel):
     """
     A record of a user's eligibility for credit for a specific course.
@@ -643,7 +648,7 @@ class CreditEligibility(TimeStampedModel):
             deadline__gt=datetime.datetime.now(pytz.UTC),
         ).exists()
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of the credit eligibility. """
         return u"{user}, {course}".format(
             user=self.username,
@@ -651,6 +656,7 @@ class CreditEligibility(TimeStampedModel):
         )
 
 
+@python_2_unicode_compatible
 class CreditRequest(TimeStampedModel):
     """
     A request for credit from a particular credit provider.
@@ -768,7 +774,7 @@ class CreditRequest(TimeStampedModel):
         except cls.DoesNotExist:
             return None
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of a credit request."""
         return u"{course}, {provider}, {status}".format(
             course=self.course.course_key,
@@ -777,6 +783,7 @@ class CreditRequest(TimeStampedModel):
         )
 
 
+@python_2_unicode_compatible
 class CreditConfig(ConfigurationModel):
     """
     Manage credit configuration
@@ -798,6 +805,6 @@ class CreditConfig(ConfigurationModel):
         """Whether responses from the commerce API will be cached."""
         return self.enabled and self.cache_ttl > 0
 
-    def __unicode__(self):
+    def __str__(self):
         """Unicode representation of the config. """
         return 'Credit Configuration'
diff --git a/openedx/core/djangoapps/dark_lang/models.py b/openedx/core/djangoapps/dark_lang/models.py
index 5a0a5e8606a..e67f03259d7 100644
--- a/openedx/core/djangoapps/dark_lang/models.py
+++ b/openedx/core/djangoapps/dark_lang/models.py
@@ -5,8 +5,10 @@ from __future__ import absolute_import
 
 from config_models.models import ConfigurationModel
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class DarkLangConfig(ConfigurationModel):
     """
     Configuration for the dark_lang django app.
@@ -26,7 +28,7 @@ class DarkLangConfig(ConfigurationModel):
         help_text="A comma-separated list of language codes to release to the public as beta languages."
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return u"DarkLangConfig()"
 
     @property
diff --git a/openedx/core/djangoapps/django_comment_common/models.py b/openedx/core/djangoapps/django_comment_common/models.py
index e1925e15214..41ba5a4f7bc 100644
--- a/openedx/core/djangoapps/django_comment_common/models.py
+++ b/openedx/core/djangoapps/django_comment_common/models.py
@@ -68,6 +68,7 @@ def assign_role(course_id, user, rolename):
     user.roles.add(role)
 
 
+@python_2_unicode_compatible
 class Role(models.Model):
     """
     Maps users to django_comment_client roles for a given course
@@ -85,7 +86,7 @@ class Role(models.Model):
         # use existing table that was originally created from lms.djangoapps.discussion.django_comment_client app
         db_table = 'django_comment_client_role'
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name + " for " + (text_type(self.course_id) if self.course_id else "all courses")
 
     # TODO the name of this method is a little bit confusing,
@@ -194,6 +195,7 @@ def all_permissions_for_user_in_course(user, course_id):
     return permission_names
 
 
+@python_2_unicode_compatible
 class ForumsConfig(ConfigurationModel):
     """
     Config for the connection to the cs_comments_service forums backend.
@@ -215,7 +217,7 @@ class ForumsConfig(ConfigurationModel):
         """The API key used to authenticate to the comments service."""
         return getattr(settings, "COMMENTS_SERVICE_KEY", None)
 
-    def __unicode__(self):
+    def __str__(self):
         """
         Simple representation so the admin screen looks less ugly.
         """
diff --git a/openedx/core/djangoapps/embargo/models.py b/openedx/core/djangoapps/embargo/models.py
index 3cbd93b255f..53b2c7d69b7 100644
--- a/openedx/core/djangoapps/embargo/models.py
+++ b/openedx/core/djangoapps/embargo/models.py
@@ -76,6 +76,7 @@ class EmbargoedCourse(models.Model):
         return u"Course '{}' is {}Embargoed".format(text_type(self.course_id), not_em)
 
 
+@python_2_unicode_compatible
 class EmbargoedState(ConfigurationModel):
     """
     Register countries to be embargoed.
@@ -99,7 +100,7 @@ class EmbargoedState(ConfigurationModel):
             return []
         return [country.strip().upper() for country in self.embargoed_countries.split(',')]
 
-    def __unicode__(self):
+    def __str__(self):
         return self.embargoed_countries
 
 
@@ -684,6 +685,7 @@ post_delete.connect(CourseAccessRuleHistory.snapshot_post_delete_receiver, sende
 post_delete.connect(CourseAccessRuleHistory.snapshot_post_delete_receiver, sender=CountryAccessRule)
 
 
+@python_2_unicode_compatible
 class IPFilter(ConfigurationModel):
     """
     Register specific IP addresses to explicitly block or unblock.
@@ -742,5 +744,5 @@ class IPFilter(ConfigurationModel):
             return []
         return self.IPFilterList([addr.strip() for addr in self.blacklist.split(',')])
 
-    def __unicode__(self):
+    def __str__(self):
         return "Whitelist: {} - Blacklist: {}".format(self.whitelist_ips, self.blacklist_ips)
diff --git a/openedx/core/djangoapps/site_configuration/models.py b/openedx/core/djangoapps/site_configuration/models.py
index 6af78a171ea..0be707f8641 100644
--- a/openedx/core/djangoapps/site_configuration/models.py
+++ b/openedx/core/djangoapps/site_configuration/models.py
@@ -10,12 +10,14 @@ from django.contrib.sites.models import Site
 from django.db import models
 from django.db.models.signals import post_save
 from django.dispatch import receiver
+from django.utils.encoding import python_2_unicode_compatible
 from jsonfield.fields import JSONField
 from model_utils.models import TimeStampedModel
 
 logger = getLogger(__name__)  # pylint: disable=invalid-name
 
 
+@python_2_unicode_compatible
 class SiteConfiguration(models.Model):
     """
     Model for storing site configuration. These configuration override OpenEdx configurations and settings.
@@ -35,11 +37,11 @@ class SiteConfiguration(models.Model):
         load_kwargs={'object_pairs_hook': collections.OrderedDict}
     )
 
-    def __unicode__(self):
+    def __str__(self):
         return u"<SiteConfiguration: {site} >".format(site=self.site)  # xss-lint: disable=python-wrap-html
 
     def __repr__(self):
-        return self.__unicode__()
+        return self.__str__()
 
     def get_value(self, name, default=None):
         """
@@ -136,6 +138,7 @@ class SiteConfiguration(models.Model):
         return org in cls.get_all_orgs()
 
 
+@python_2_unicode_compatible
 class SiteConfigurationHistory(TimeStampedModel):
     """
     This is an archive table for SiteConfiguration, so that we can maintain a history of
@@ -159,7 +162,7 @@ class SiteConfigurationHistory(TimeStampedModel):
         get_latest_by = 'modified'
         ordering = ('-modified', '-created',)
 
-    def __unicode__(self):
+    def __str__(self):
         # pylint: disable=line-too-long
         return u"<SiteConfigurationHistory: {site}, Last Modified: {modified} >".format(  # xss-lint: disable=python-wrap-html
             modified=self.modified,
@@ -167,7 +170,7 @@ class SiteConfigurationHistory(TimeStampedModel):
         )
 
     def __repr__(self):
-        return self.__unicode__()
+        return self.__str__()
 
 
 @receiver(post_save, sender=SiteConfiguration)
diff --git a/openedx/core/djangoapps/theming/helpers_dirs.py b/openedx/core/djangoapps/theming/helpers_dirs.py
index 73f8777bc13..ca849faa9a7 100644
--- a/openedx/core/djangoapps/theming/helpers_dirs.py
+++ b/openedx/core/djangoapps/theming/helpers_dirs.py
@@ -6,6 +6,7 @@ from __future__ import absolute_import
 
 import os
 
+from django.utils.encoding import python_2_unicode_compatible
 from path import Path
 
 
@@ -92,6 +93,7 @@ def get_project_root_name_from_settings(project_root):
     return root.name
 
 
+@python_2_unicode_compatible
 class Theme(object):
     """
     class to encapsulate theme related information.
@@ -129,12 +131,12 @@ class Theme(object):
     def __hash__(self):
         return hash((self.theme_dir_name, self.path))
 
-    def __unicode__(self):
+    def __str__(self):
         # pylint: disable=line-too-long
         return u"<Theme: {name} at '{path}'>".format(name=self.name, path=self.path)  # xss-lint: disable=python-wrap-html
 
     def __repr__(self):
-        return self.__unicode__()
+        return self.__str__()
 
     @property
     def path(self):
diff --git a/openedx/core/djangoapps/theming/models.py b/openedx/core/djangoapps/theming/models.py
index 04e77f6ebf8..88afba3c62f 100644
--- a/openedx/core/djangoapps/theming/models.py
+++ b/openedx/core/djangoapps/theming/models.py
@@ -5,8 +5,10 @@ from __future__ import absolute_import
 
 from django.contrib.sites.models import Site
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class SiteTheme(models.Model):
     """
     This is where the information about the site's theme gets stored to the db.
@@ -19,7 +21,7 @@ class SiteTheme(models.Model):
     site = models.ForeignKey(Site, related_name='themes', on_delete=models.CASCADE)
     theme_dir_name = models.CharField(max_length=255)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.theme_dir_name
 
     @staticmethod
diff --git a/openedx/core/djangoapps/user_api/models.py b/openedx/core/djangoapps/user_api/models.py
index 0980da53175..256bda5b6b8 100644
--- a/openedx/core/djangoapps/user_api/models.py
+++ b/openedx/core/djangoapps/user_api/models.py
@@ -183,6 +183,7 @@ class RetirementState(models.Model):
         return cls.objects.all().values_list('state_name', flat=True)
 
 
+@python_2_unicode_compatible
 class UserRetirementPartnerReportingStatus(TimeStampedModel):
     """
     When a user has been retired from LMS it will still need to be reported out to
@@ -205,13 +206,14 @@ class UserRetirementPartnerReportingStatus(TimeStampedModel):
         verbose_name = 'User Retirement Reporting Status'
         verbose_name_plural = 'User Retirement Reporting Statuses'
 
-    def __unicode__(self):
+    def __str__(self):
         return u'UserRetirementPartnerReportingStatus: {} is being processed: {}'.format(
             self.user,
             self.is_being_processed
         )
 
 
+@python_2_unicode_compatible
 class UserRetirementRequest(TimeStampedModel):
     """
     Records and perists every user retirement request.
@@ -242,10 +244,11 @@ class UserRetirementRequest(TimeStampedModel):
         """
         return cls.objects.filter(user=user).exists()
 
-    def __unicode__(self):
+    def __str__(self):
         return u'User: {} Requested: {}'.format(self.user.id, self.created)
 
 
+@python_2_unicode_compatible
 class UserRetirementStatus(TimeStampedModel):
     """
     Tracks the progress of a user's retirement request
@@ -385,7 +388,7 @@ class UserRetirementStatus(TimeStampedModel):
 
         return retirement
 
-    def __unicode__(self):
+    def __str__(self):
         return u'User: {} State: {} Last Updated: {}'.format(self.user.id, self.current_state, self.modified)
 
 
diff --git a/openedx/core/djangoapps/video_config/models.py b/openedx/core/djangoapps/video_config/models.py
index f04696c256c..6837b10141d 100644
--- a/openedx/core/djangoapps/video_config/models.py
+++ b/openedx/core/djangoapps/video_config/models.py
@@ -7,12 +7,14 @@ import six
 from config_models.models import ConfigurationModel
 from django.db import models
 from django.db.models import BooleanField, PositiveIntegerField, TextField
+from django.utils.encoding import python_2_unicode_compatible
 from model_utils.models import TimeStampedModel
 from opaque_keys.edx.django.models import CourseKeyField
 
 URL_REGEX = r'^[a-zA-Z0-9\-_]*$'
 
 
+@python_2_unicode_compatible
 class HLSPlaybackEnabledFlag(ConfigurationModel):
     """
     Enables HLS Playback across the platform.
@@ -49,13 +51,14 @@ class HLSPlaybackEnabledFlag(ConfigurationModel):
             return feature.enabled if feature else False
         return True
 
-    def __unicode__(self):
+    def __str__(self):
         current_model = HLSPlaybackEnabledFlag.current()
         return u"HLSPlaybackEnabledFlag: enabled {is_enabled}".format(
             is_enabled=current_model.is_enabled()
         )
 
 
+@python_2_unicode_compatible
 class CourseHLSPlaybackEnabledFlag(ConfigurationModel):
     """
     Enables HLS Playback for a specific course. Global feature must be
@@ -67,7 +70,7 @@ class CourseHLSPlaybackEnabledFlag(ConfigurationModel):
 
     course_id = CourseKeyField(max_length=255, db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         not_en = "Not "
         if self.enabled:
             not_en = ""
@@ -78,6 +81,7 @@ class CourseHLSPlaybackEnabledFlag(ConfigurationModel):
         )
 
 
+@python_2_unicode_compatible
 class CourseYoutubeBlockedFlag(ConfigurationModel):
     """
     Disables the playback of youtube videos for a given course.
@@ -102,7 +106,7 @@ class CourseYoutubeBlockedFlag(ConfigurationModel):
                    .first())
         return feature.enabled if feature else False
 
-    def __unicode__(self):
+    def __str__(self):
         not_en = "Not "
         if self.enabled:
             not_en = ""
@@ -113,6 +117,7 @@ class CourseYoutubeBlockedFlag(ConfigurationModel):
         )
 
 
+@python_2_unicode_compatible
 class VideoTranscriptEnabledFlag(ConfigurationModel):
     """
     Enables Video Transcript across the platform.
@@ -151,13 +156,14 @@ class VideoTranscriptEnabledFlag(ConfigurationModel):
             return feature.enabled if feature else False
         return True
 
-    def __unicode__(self):
+    def __str__(self):
         current_model = VideoTranscriptEnabledFlag.current()
         return u"VideoTranscriptEnabledFlag: enabled {is_enabled}".format(
             is_enabled=current_model.is_enabled()
         )
 
 
+@python_2_unicode_compatible
 class CourseVideoTranscriptEnabledFlag(ConfigurationModel):
     """
     Enables Video Transcript for a specific course. Global feature must be
@@ -171,7 +177,7 @@ class CourseVideoTranscriptEnabledFlag(ConfigurationModel):
 
     course_id = CourseKeyField(max_length=255, db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         not_en = "Not "
         if self.enabled:
             not_en = ""
@@ -182,13 +188,14 @@ class CourseVideoTranscriptEnabledFlag(ConfigurationModel):
         )
 
 
+@python_2_unicode_compatible
 class TranscriptMigrationSetting(ConfigurationModel):
     """
     Arguments for the Transcript Migration management command
 
     .. no_pii:
     """
-    def __unicode__(self):
+    def __str__(self):
         return (
             u"[TranscriptMigrationSetting] Courses {courses} with update if already present as {force}"
             u" and commit as {commit}"
@@ -225,6 +232,7 @@ class TranscriptMigrationSetting(ConfigurationModel):
         return self.command_run
 
 
+@python_2_unicode_compatible
 class MigrationEnqueuedCourse(TimeStampedModel):
     """
     Temporary model to persist the course IDs who has been enqueued for transcripts migration to S3.
@@ -234,12 +242,13 @@ class MigrationEnqueuedCourse(TimeStampedModel):
     course_id = CourseKeyField(db_index=True, primary_key=True, max_length=255)
     command_run = PositiveIntegerField(default=0)
 
-    def __unicode__(self):
+    def __str__(self):
         return u'MigrationEnqueuedCourse: ID={course_id}, Run={command_run}'.format(
             course_id=self.course_id, command_run=self.command_run
         )
 
 
+@python_2_unicode_compatible
 class VideoThumbnailSetting(ConfigurationModel):
     """
     Arguments for the Video Thumbnail management command
@@ -275,13 +284,14 @@ class VideoThumbnailSetting(ConfigurationModel):
         self.offset += self.batch_size
         self.save()
 
-    def __unicode__(self):
+    def __str__(self):
         return "[VideoThumbnailSetting] update for {courses} courses if commit as {commit}".format(
             courses='ALL' if self.all_course_videos else self.course_ids,
             commit=self.commit,
         )
 
 
+@python_2_unicode_compatible
 class UpdatedCourseVideos(TimeStampedModel):
     """
     Temporary model to persist the course videos which have been enqueued to update video thumbnails.
@@ -295,7 +305,7 @@ class UpdatedCourseVideos(TimeStampedModel):
     class Meta:
         unique_together = ('course_id', 'edx_video_id')
 
-    def __unicode__(self):
+    def __str__(self):
         return u'UpdatedCourseVideos: CourseID={course_id}, VideoID={video_id}, Run={command_run}'.format(
             course_id=self.course_id, video_id=self.edx_video_id, command_run=self.command_run
         )
diff --git a/openedx/core/djangoapps/video_pipeline/models.py b/openedx/core/djangoapps/video_pipeline/models.py
index 25f57cff24e..87a2a10dfd6 100644
--- a/openedx/core/djangoapps/video_pipeline/models.py
+++ b/openedx/core/djangoapps/video_pipeline/models.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import
 from config_models.models import ConfigurationModel
 from django.contrib.auth import get_user_model
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from opaque_keys.edx.django.models import CourseKeyField
 import six
@@ -44,6 +45,7 @@ class VideoPipelineIntegration(ConfigurationModel):
         return User.objects.get(username=self.service_username)
 
 
+@python_2_unicode_compatible
 class VideoUploadsEnabledByDefault(ConfigurationModel):
     """
     Enables video uploads enabled By default feature across the platform.
@@ -78,13 +80,14 @@ class VideoUploadsEnabledByDefault(ConfigurationModel):
             return feature.enabled if feature else False
         return True
 
-    def __unicode__(self):
+    def __str__(self):
         current_model = VideoUploadsEnabledByDefault.current()
         return u"VideoUploadsEnabledByDefault: enabled {is_enabled}".format(
             is_enabled=current_model.is_enabled()
         )
 
 
+@python_2_unicode_compatible
 class CourseVideoUploadsEnabledByDefault(ConfigurationModel):
     """
     Enables video uploads enabled by default feature for a specific course. Its global feature must be
@@ -96,7 +99,7 @@ class CourseVideoUploadsEnabledByDefault(ConfigurationModel):
 
     course_id = CourseKeyField(max_length=255, db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         not_en = "Not "
         if self.enabled:
             not_en = ""
diff --git a/openedx/core/djangoapps/waffle_utils/models.py b/openedx/core/djangoapps/waffle_utils/models.py
index 4f84b684f60..1083d719ced 100644
--- a/openedx/core/djangoapps/waffle_utils/models.py
+++ b/openedx/core/djangoapps/waffle_utils/models.py
@@ -3,6 +3,7 @@ Models for configuring waffle utils.
 """
 from __future__ import absolute_import
 from django.db.models import CharField
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from model_utils import Choices
 from opaque_keys.edx.django.models import CourseKeyField
@@ -12,6 +13,7 @@ from config_models.models import ConfigurationModel
 from openedx.core.lib.cache_utils import request_cached
 
 
+@python_2_unicode_compatible
 class WaffleFlagCourseOverrideModel(ConfigurationModel):
     """
     Used to force a waffle flag on or off for a course.
@@ -59,6 +61,6 @@ class WaffleFlagCourseOverrideModel(ConfigurationModel):
         verbose_name = 'Waffle flag course override'
         verbose_name_plural = 'Waffle flag course overrides'
 
-    def __unicode__(self):
+    def __str__(self):
         enabled_label = "Enabled" if self.enabled else "Not Enabled"
         return u"Course '{}': Persistent Grades {}".format(text_type(self.course_id), enabled_label)
diff --git a/openedx/features/announcements/models.py b/openedx/features/announcements/models.py
index 74ae3bc9322..295e1b10f22 100644
--- a/openedx/features/announcements/models.py
+++ b/openedx/features/announcements/models.py
@@ -5,8 +5,10 @@ Models for Announcements
 from __future__ import absolute_import
 
 from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
 
 
+@python_2_unicode_compatible
 class Announcement(models.Model):
     """Site-wide announcements to be displayed on the dashboard"""
     class Meta(object):
@@ -15,5 +17,5 @@ class Announcement(models.Model):
     content = models.CharField(max_length=1000, null=False, default="lorem ipsum")
     active = models.BooleanField(default=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.content
-- 
GitLab