From f8d8bcb60c57bdab362e0add2a6c13c6d95efa5b Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Thu, 24 Jan 2019 16:18:11 +0100
Subject: [PATCH] python3: remove deprecated unicode builtin (#31151)

---
 src/authentic2/a2_rbac/admin.py                 |  3 ++-
 src/authentic2/a2_rbac/management.py            |  7 ++++---
 src/authentic2/a2_rbac/models.py                |  5 +++--
 src/authentic2/api_views.py                     |  1 +
 src/authentic2/attribute_kinds.py               |  3 ++-
 .../sources/computed_targeted_id.py             |  3 ++-
 .../attributes_ng/sources/django_user.py        |  3 ++-
 src/authentic2/backends/ldap_backend.py         |  3 ++-
 src/authentic2/custom_user/models.py            |  3 ++-
 src/authentic2/decorators.py                    |  5 +++--
 src/authentic2/exponential_retry_timeout.py     |  3 ++-
 src/authentic2/idp/saml/saml2_endpoints.py      |  5 +++--
 src/authentic2/log_filters.py                   |  3 ++-
 .../management/commands/slapd-shell.py          |  3 ++-
 src/authentic2/manager/forms.py                 |  5 +++--
 src/authentic2/manager/ou_views.py              |  3 ++-
 src/authentic2/manager/resources.py             |  5 +++--
 src/authentic2/manager/service_views.py         |  3 ++-
 src/authentic2/manager/views.py                 |  9 +++++----
 src/authentic2/manager/widgets.py               |  3 ++-
 src/authentic2/models.py                        |  5 +++--
 src/authentic2/saml/admin.py                    |  3 ++-
 src/authentic2/saml/common.py                   |  3 ++-
 src/authentic2/saml/models.py                   |  3 ++-
 src/authentic2/utils.py                         |  9 +++++----
 src/authentic2/views.py                         |  5 +++--
 src/authentic2_auth_oidc/backends.py            |  3 ++-
 .../management/commands/oidc-register-issuer.py |  1 +
 src/authentic2_auth_oidc/utils.py               | 17 +++++++++--------
 src/authentic2_idp_cas/views.py                 |  9 +++++----
 src/authentic2_idp_oidc/models.py               | 15 ++++++++-------
 src/authentic2_idp_oidc/utils.py                |  5 +++--
 src/authentic2_idp_oidc/views.py                |  5 +++--
 .../management/commands/provision.py            |  3 ++-
 src/django_rbac/models.py                       |  5 +++--
 src/django_rbac/utils.py                        |  5 +++--
 tests/test_all.py                               |  3 ++-
 tests/test_user_manager.py                      |  9 +++++----
 38 files changed, 112 insertions(+), 74 deletions(-)

diff --git a/src/authentic2/a2_rbac/admin.py b/src/authentic2/a2_rbac/admin.py
index 723d44a3..35f469bc 100644
--- a/src/authentic2/a2_rbac/admin.py
+++ b/src/authentic2/a2_rbac/admin.py
@@ -1,5 +1,6 @@
 from django.contrib import admin
 from django.utils.translation import ugettext_lazy as _
+from django.utils.six import text_type
 
 from . import models
 
@@ -56,7 +57,7 @@ class PermissionAdmin(admin.ModelAdmin):
     list_select_related = True
 
     def name(self, obj):
-        return unicode(obj)
+        return text_type(obj)
     name.short_description = _('name')
 
 admin.site.register(models.Role, RoleAdmin)
diff --git a/src/authentic2/a2_rbac/management.py b/src/authentic2/a2_rbac/management.py
index 8ba66319..e32480f9 100644
--- a/src/authentic2/a2_rbac/management.py
+++ b/src/authentic2/a2_rbac/management.py
@@ -1,3 +1,4 @@
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _
 from django.utils.text import slugify
 from django.contrib.contenttypes.models import ContentType
@@ -27,9 +28,9 @@ def update_ou_admin_roles(ou):
         # do not create scoped admin roles if the model is not scopable
         if not ou_model:
             continue
-        name = unicode(MANAGED_CT[key]['name'])
+        name = text_type(MANAGED_CT[key]['name'])
         slug = '_a2-' + slugify(name)
-        scoped_name = unicode(MANAGED_CT[key]['scoped_name'])
+        scoped_name = text_type(MANAGED_CT[key]['scoped_name'])
         name = scoped_name.format(ou=ou)
         ou_slug = slug + '-' + ou.slug
         if app_settings.MANAGED_CONTENT_TYPES == ():
@@ -112,7 +113,7 @@ def update_content_types_roles():
         if ct_tuple not in MANAGED_CT:
             continue
         # General admin role
-        name = unicode(MANAGED_CT[ct_tuple]['name'])
+        name = text_type(MANAGED_CT[ct_tuple]['name'])
         slug = '_a2-' + slugify(name)
         if app_settings.MANAGED_CONTENT_TYPES is not None and ct_tuple not in \
                 app_settings.MANAGED_CONTENT_TYPES:
diff --git a/src/authentic2/a2_rbac/models.py b/src/authentic2/a2_rbac/models.py
index 27388f05..afa04767 100644
--- a/src/authentic2/a2_rbac/models.py
+++ b/src/authentic2/a2_rbac/models.py
@@ -1,5 +1,6 @@
 from collections import namedtuple
 from django.core.exceptions import ValidationError
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _
 from django.utils.text import slugify
 from django.db import models
@@ -186,9 +187,9 @@ class Role(RoleAbstractBase):
         admin_role = self.__class__.objects.get_admin_role(
             self, ou=self.ou,
             name=_('Managers of role "{role}"').format(
-                role=unicode(self)),
+                role=text_type(self)),
             slug='_a2-managers-of-role-{role}'.format(
-                role=slugify(unicode(self))),
+                role=slugify(text_type(self))),
             permissions=(utils.get_view_user_perm(),),
             self_administered=True)
         return admin_role
diff --git a/src/authentic2/api_views.py b/src/authentic2/api_views.py
index 54e3a626..27a308ca 100644
--- a/src/authentic2/api_views.py
+++ b/src/authentic2/api_views.py
@@ -20,6 +20,7 @@ import smtplib
 from django.db import models
 from django.contrib.auth import get_user_model
 from django.core.exceptions import MultipleObjectsReturned
+from django.utils.six import text_type
 from django.utils.translation import ugettext as _
 from django.utils.encoding import force_text
 from django.views.decorators.vary import vary_on_headers
diff --git a/src/authentic2/attribute_kinds.py b/src/authentic2/attribute_kinds.py
index 271b74fb..31061768 100644
--- a/src/authentic2/attribute_kinds.py
+++ b/src/authentic2/attribute_kinds.py
@@ -10,6 +10,7 @@ from itertools import chain
 from django import forms
 from django.core.exceptions import ValidationError
 from django.core.validators import RegexValidator
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _, pgettext_lazy
 from django.utils.functional import allow_lazy
 from django.utils import html
@@ -24,7 +25,7 @@ from .plugins import collect_from_plugins
 from . import app_settings
 from .forms import widgets, fields
 
-capfirst = allow_lazy(capfirst, unicode)
+capfirst = allow_lazy(capfirst, text_type)
 
 DEFAULT_TITLE_CHOICES = (
     (pgettext_lazy('title', 'Mrs'), pgettext_lazy('title', 'Mrs')),
diff --git a/src/authentic2/attributes_ng/sources/computed_targeted_id.py b/src/authentic2/attributes_ng/sources/computed_targeted_id.py
index e0633d60..d4abeb9a 100644
--- a/src/authentic2/attributes_ng/sources/computed_targeted_id.py
+++ b/src/authentic2/attributes_ng/sources/computed_targeted_id.py
@@ -16,6 +16,7 @@ import hashlib
 import base64
 
 from django.core.exceptions import ImproperlyConfigured
+from django.utils.six import text_type
 
 from ...decorators import to_list
 
@@ -80,7 +81,7 @@ def get_attributes(instance, ctx):
     source_attributes_values.append(instance['salt'])
     hash_algo = instance.get('hash', 'sha1')
     hasher = getattr(hashlib, hash_algo)
-    source_attributes_values = map(unicode, source_attributes_values)
+    source_attributes_values = map(text_type, source_attributes_values)
     value = u'!'.join(source_attributes_values)
     value = value.encode('utf-8')
     value = hasher(value).digest()
diff --git a/src/authentic2/attributes_ng/sources/django_user.py b/src/authentic2/attributes_ng/sources/django_user.py
index 2c65b218..7c67d517 100644
--- a/src/authentic2/attributes_ng/sources/django_user.py
+++ b/src/authentic2/attributes_ng/sources/django_user.py
@@ -1,3 +1,4 @@
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _
 
 from django_rbac.utils import get_role_model
@@ -75,7 +76,7 @@ def get_attributes(instance, ctx):
         ctx['django_user_' + str(av.attribute.name)] = serialized
         ctx['django_user_' + str(av.attribute.name) + ':verified'] = av.verified
     ctx['django_user_groups'] = [group for group in user.groups.all()]
-    ctx['django_user_group_names'] = [unicode(group) for group in user.groups.all()]
+    ctx['django_user_group_names'] = [text_type(group) for group in user.groups.all()]
     if user.username:
         splitted = user.username.rsplit('@', 1)
         ctx['django_user_domain'] = splitted[1] if '@' in user.username else ''
diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py
index dc084407..b59e3c69 100644
--- a/src/authentic2/backends/ldap_backend.py
+++ b/src/authentic2/backends/ldap_backend.py
@@ -26,6 +26,7 @@ from django.core.exceptions import ImproperlyConfigured
 from django.conf import settings
 from django.contrib.auth.models import Group
 from django.utils.encoding import force_bytes, force_text
+from django.utils.six import text_type
 from django.utils.six.moves.urllib import parse as urlparse
 
 from authentic2.a2_rbac.models import Role
@@ -1142,7 +1143,7 @@ class LDAPBackend(object):
 
     @classmethod
     def ad_encoding(cls, s):
-        '''Encode an unicode string for AD consumption as a password'''
+        '''Encode a string for AD consumption as a password'''
         return (u'"{0}"'.format(s)).encode('utf-16-le')
 
     @classmethod
diff --git a/src/authentic2/custom_user/models.py b/src/authentic2/custom_user/models.py
index fe92fabd..29327de8 100644
--- a/src/authentic2/custom_user/models.py
+++ b/src/authentic2/custom_user/models.py
@@ -1,6 +1,7 @@
 from django.db import models
 from django.utils import timezone
 from django.core.mail import send_mail
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _
 from django.core.exceptions import ValidationError, MultipleObjectsReturned
 try:
@@ -185,7 +186,7 @@ class User(AbstractBaseUser, PermissionMixin):
         return u'%s (%s)' % (human_name, short_id)
 
     def __repr__(self):
-        return '<User: %r>' % unicode(self)
+        return '<User: %r>' % text_type(self)
 
     def clean(self):
         pass
diff --git a/src/authentic2/decorators.py b/src/authentic2/decorators.py
index 21eaa93f..f6ed6e05 100644
--- a/src/authentic2/decorators.py
+++ b/src/authentic2/decorators.py
@@ -10,6 +10,7 @@ from django.views.debug import technical_404_response
 from django.http import Http404, HttpResponseForbidden, HttpResponse, HttpResponseBadRequest
 from django.core.cache import cache as django_cache
 from django.core.exceptions import ValidationError
+from django.utils.six import text_type
 
 from . import utils, app_settings, middleware
 from .utils import to_list, to_iter
@@ -164,12 +165,12 @@ class CacheDecoratorBase(object):
         for i, arg in enumerate(args):
             if self.args and i not in self.args:
                 continue
-            parts.append(unicode(arg))
+            parts.append(text_type(arg))
 
         for kw, arg in sorted(kwargs.iteritems(), key=lambda x: x[0]):
             if kw not in self.kwargs:
                 continue
-            parts.append(u'%s-%s' % (unicode(kw), unicode(arg)))
+            parts.append(u'%s-%s' % (text_type(kw), text_type(arg)))
         return u'|'.join(parts)
 
 
diff --git a/src/authentic2/exponential_retry_timeout.py b/src/authentic2/exponential_retry_timeout.py
index e21ada1d..12ac08b9 100644
--- a/src/authentic2/exponential_retry_timeout.py
+++ b/src/authentic2/exponential_retry_timeout.py
@@ -2,6 +2,7 @@ import time
 import logging
 import hashlib
 
+from django.utils.six import text_type
 from django.core.cache import cache
 
 
@@ -26,7 +27,7 @@ class ExponentialRetryTimeout(object):
         self.key_prefix = key_prefix
 
     def key(self, keys):
-        key = u'-'.join(map(unicode, keys))
+        key = u'-'.join(map(text_type, keys))
         key = key.encode('utf-8')
         return '%s%s' % (self.key_prefix or self.KEY_PREFIX, hashlib.md5(key).hexdigest())
 
diff --git a/src/authentic2/idp/saml/saml2_endpoints.py b/src/authentic2/idp/saml/saml2_endpoints.py
index 2f85f22c..58f3d8c6 100644
--- a/src/authentic2/idp/saml/saml2_endpoints.py
+++ b/src/authentic2/idp/saml/saml2_endpoints.py
@@ -30,6 +30,7 @@ from django.contrib.auth.decorators import login_required
 from django.core.exceptions import ObjectDoesNotExist
 from django.http import HttpResponse, HttpResponseRedirect, \
     HttpResponseForbidden, HttpResponseBadRequest
+from django.utils.six import text_type
 from django.utils.translation import ugettext as _, ugettext_noop as N_
 from django.views.decorators.csrf import csrf_exempt
 from django.views.decorators.cache import never_cache
@@ -706,7 +707,7 @@ def sso_after_process_request(request, login, consent_obtained=False,
         if dic and 'authz' in dic:
 	    logger.info('decision is %s', dic['authz'])
             if 'message' in dic:
-		logger.info(u'with message %s', unicode(dic['message']))
+		logger.info(u'with message %s', text_type(dic['message']))
             if not dic['authz']:
                 logger.info('access denied by an external function')
                 access_granted = False
@@ -718,7 +719,7 @@ def sso_after_process_request(request, login, consent_obtained=False,
             'to the requester')
         set_saml2_response_responder_status_code(login.response,
                 lasso.SAML2_STATUS_CODE_REQUEST_DENIED,
-                msg=unicode(dic['message']))
+                msg=text_type(dic['message']))
         return finish_sso(request, login)
 
     provider = load_provider(request, login.remoteProviderId,
diff --git a/src/authentic2/log_filters.py b/src/authentic2/log_filters.py
index dfcddb7e..b2f467dd 100644
--- a/src/authentic2/log_filters.py
+++ b/src/authentic2/log_filters.py
@@ -1,4 +1,5 @@
 import logging
+from django.utils.six import text_type
 
 class RequestContextFilter(logging.Filter):
     DEFAULT_USERNAME = '-'
@@ -17,7 +18,7 @@ class RequestContextFilter(logging.Filter):
         request_id = self.DEFAULT_REQUEST_ID
         if not request is None:
             if hasattr(request, 'user') and request.user.is_authenticated():
-                user = unicode(request.user)
+                user = text_type(request.user)
             ip = request.META.get('REMOTE_ADDR', self.DEFAULT_IP)
             request_id = request.request_id
         record.user = user
diff --git a/src/authentic2/management/commands/slapd-shell.py b/src/authentic2/management/commands/slapd-shell.py
index 388aa7e3..1d68a8f4 100644
--- a/src/authentic2/management/commands/slapd-shell.py
+++ b/src/authentic2/management/commands/slapd-shell.py
@@ -11,6 +11,7 @@ from ldif import LDIFWriter
 
 from django.contrib.auth import get_user_model
 from django.core.management.base import BaseCommand
+from django.utils.six import text_type
 from optparse import make_option
 
 COMMAND = 1
@@ -71,7 +72,7 @@ class Command(BaseCommand):
             for user in qs:
                 o = {}
                 for user_attribute, ldap_attribute in MAPPING.iteritems():
-                    o[ldap_attribute] = [unicode(getattr(user, user_attribute)).encode('utf-8')]
+                    o[ldap_attribute] = [text_type(getattr(user, user_attribute)).encode('utf-8')]
                 o['objectClass'] = ['inetOrgPerson']
                 dn = 'uid=%s,%s' % (escape_dn_chars(o['uid'][0]), attrs['suffix'])
                 self.logger.debug(u'sending entry %s %s', dn, o)
diff --git a/src/authentic2/manager/forms.py b/src/authentic2/manager/forms.py
index e97602a5..c62f2f41 100644
--- a/src/authentic2/manager/forms.py
+++ b/src/authentic2/manager/forms.py
@@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _, pgettext
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.db.models.query import Q
+from django.utils.six import text_type
 from django.utils.text import slugify
 from django.core.exceptions import ValidationError
 
@@ -48,7 +49,7 @@ class SlugMixin(forms.ModelForm):
     def save(self, commit=True):
         instance = self.instance
         if not instance.slug:
-            instance.slug = slugify(unicode(instance.name)).lstrip('_')
+            instance.slug = slugify(text_type(instance.name)).lstrip('_')
             qs = instance.__class__.objects.all()
             if instance.pk:
                 qs = qs.exclude(pk=instance.pk)
@@ -477,7 +478,7 @@ class OUSearchForm(FormWithRequest):
             if self.show_all_ou and (len(self.ou_qs) > 1 or self.search_all_ous):
                 choices.append(('all', all_ou_label))
             for ou in self.ou_qs:
-                choices.append((str(ou.pk), unicode(ou)))
+                choices.append((str(ou.pk), text_type(ou)))
             if self.search_all_ous:
                 choices.append(('none', pgettext('organizational unit', 'None')))
 
diff --git a/src/authentic2/manager/ou_views.py b/src/authentic2/manager/ou_views.py
index ed8a001e..dc2bae02 100644
--- a/src/authentic2/manager/ou_views.py
+++ b/src/authentic2/manager/ou_views.py
@@ -3,6 +3,7 @@ import json
 from django_rbac.utils import get_ou_model
 from django.http import HttpResponseRedirect
 from django.contrib import messages
+from django.utils.six import text_type
 from django.utils.translation import ugettext as _
 
 from authentic2 import data_transfer
@@ -42,7 +43,7 @@ class OrganizationalUnitDetailView(views.BaseDetailView):
 
     @property
     def title(self):
-        return unicode(self.object)
+        return text_type(self.object)
 
     def authorize(self, request, *args, **kwargs):
         super(OrganizationalUnitDetailView, self).authorize(request, *args, **kwargs)
diff --git a/src/authentic2/manager/resources.py b/src/authentic2/manager/resources.py
index e196a089..b3e9cf88 100644
--- a/src/authentic2/manager/resources.py
+++ b/src/authentic2/manager/resources.py
@@ -1,3 +1,4 @@
+from django.utils.six import text_type
 from import_export.resources import ModelResource
 from import_export.fields import Field
 from import_export.widgets import Widget
@@ -11,7 +12,7 @@ class ListWidget(Widget):
         raise NotImplementedError
 
     def render(self, value):
-        return u', '.join(map(unicode, value.all()))
+        return u', '.join(map(text_type, value.all()))
 
 
 class UserResource(ModelResource):
@@ -23,7 +24,7 @@ class UserResource(ModelResource):
             result.add(role)
             for pr in role.parent_relation.all():
                 result.add(pr.parent)
-        return ', '.join(map(unicode, result))
+        return ', '.join(map(text_type, result))
 
     class Meta:
         model = get_user_model()
diff --git a/src/authentic2/manager/service_views.py b/src/authentic2/manager/service_views.py
index 344409fb..dd1fdc31 100644
--- a/src/authentic2/manager/service_views.py
+++ b/src/authentic2/manager/service_views.py
@@ -1,3 +1,4 @@
+from django.utils.six import text_type
 from django.utils.translation import ugettext as _
 from django.contrib import messages
 from django.shortcuts import get_object_or_404
@@ -30,7 +31,7 @@ class ServiceView(views.SimpleSubTableView, role_views.RoleViewMixin, views.Medi
 
     @property
     def title(self):
-        return unicode(self.object)
+        return text_type(self.object)
 
     def get_table_queryset(self):
         return self.object.authorized_roles.all()
diff --git a/src/authentic2/manager/views.py b/src/authentic2/manager/views.py
index 89ddafc4..4fb5154c 100644
--- a/src/authentic2/manager/views.py
+++ b/src/authentic2/manager/views.py
@@ -9,6 +9,7 @@ from django.views.generic import (FormView, UpdateView, CreateView, DeleteView,
 from django.views.generic.detail import SingleObjectMixin
 from django.http import HttpResponse, Http404
 from django.utils.encoding import force_text
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _
 from django.utils.timezone import now
 from django.core.urlresolvers import reverse, reverse_lazy
@@ -382,7 +383,7 @@ class ModelNameMixin(MediaMixin):
 
     def get_instance_name(self):
         if hasattr(self, 'get_object'):
-            return unicode(self.get_object())
+            return text_type(self.get_object())
         return u''
 
     def get_context_data(self, **kwargs):
@@ -628,9 +629,9 @@ class MenuJson(HomepageView):
         menu_entries = []
         for entry in self.get_homepage_entries():
             menu_entries.append({
-                'label': unicode(entry['label']),
+                'label': text_type(entry['label']),
                 'slug': entry.get('slug', ''),
-                'url': request.build_absolute_uri(unicode(entry['href'])),
+                'url': request.build_absolute_uri(text_type(entry['href'])),
             })
         return menu_entries
 
@@ -696,7 +697,7 @@ class SiteImportView(MediaMixin, FormView):
             with transaction.atomic():
                 import_site(json_site, ImportContext())
         except DataImportError as e:
-            form.add_error('site_json', unicode(e))
+            form.add_error('site_json', text_type(e))
             return self.form_invalid(form)
 
         return super(SiteImportView, self).form_valid(form)
diff --git a/src/authentic2/manager/widgets.py b/src/authentic2/manager/widgets.py
index 9fc8d15f..15c10222 100644
--- a/src/authentic2/manager/widgets.py
+++ b/src/authentic2/manager/widgets.py
@@ -4,6 +4,7 @@ from six.moves import reduce
 from django_select2.forms import ModelSelect2Widget, ModelSelect2MultipleWidget
 
 from django.contrib.auth import get_user_model
+from django.utils.six import text_type
 
 from django_rbac.backends import DjangoRBACBackend
 from django_rbac.utils import get_role_model, get_ou_model
@@ -57,7 +58,7 @@ class SecurityCheckMixin(SplitTermMixin):
 
 class RoleLabelMixin(object):
     def label_from_instance(self, obj):
-        label = unicode(obj)
+        label = text_type(obj)
         if obj.ou and utils.get_ou_count() > 1:
             label = u'{ou} - {obj}'.format(
                 ou=obj.ou, obj=obj)
diff --git a/src/authentic2/models.py b/src/authentic2/models.py
index 2080fc02..f2628216 100644
--- a/src/authentic2/models.py
+++ b/src/authentic2/models.py
@@ -4,6 +4,7 @@ from django.utils.http import urlquote
 from django.conf import settings
 from django.db import models
 from django.db.models.query import Q
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _
 from django.utils.six.moves.urllib import parse as urlparse
 from django.core.exceptions import ValidationError, FieldDoesNotExist
@@ -317,7 +318,7 @@ class PasswordReset(models.Model):
         verbose_name_plural = _('password reset')
 
     def __unicode__(self):
-        return unicode(self.user)
+        return text_type(self.user)
 
 
 class Service(models.Model):
@@ -373,7 +374,7 @@ class Service(models.Model):
         return self.name
 
     def __repr__(self):
-        return '<%s %r>' % (self.__class__.__name__, unicode(self))
+        return '<%s %r>' % (self.__class__.__name__, text_type(self))
 
     def authorize(self, user):
         if not self.authorized_roles.exists():
diff --git a/src/authentic2/saml/admin.py b/src/authentic2/saml/admin.py
index 3698ce3c..9657444c 100644
--- a/src/authentic2/saml/admin.py
+++ b/src/authentic2/saml/admin.py
@@ -1,6 +1,7 @@
 import logging
 
 from django.contrib import admin
+from django.utils.six import text_type
 from django.utils.translation import ugettext as _
 from django.conf.urls import url
 from django.conf import settings
@@ -50,7 +51,7 @@ class TextAndFileWidget(forms.widgets.MultiWidget):
     def render(self, name, value, attrs=None):
         if attrs is None:
             attrs = {}
-        if isinstance(value, (str, unicode)):
+        if isinstance(value, (str, text_type)):
             attrs['rows'] = value.count('\n') + 5
             attrs['cols'] = min(max((len(x) for x in value.split('\n'))), 150)
         return super(TextAndFileWidget, self).render(name, value, attrs)
diff --git a/src/authentic2/saml/common.py b/src/authentic2/saml/common.py
index 4b5e02ef..e8dbfd11 100644
--- a/src/authentic2/saml/common.py
+++ b/src/authentic2/saml/common.py
@@ -10,6 +10,7 @@ from authentic2.compat_lasso import lasso
 from django.conf import settings
 from django.http import HttpResponseRedirect, Http404, HttpResponse
 from django.shortcuts import render
+from django.utils.six import text_type
 from django.utils.six.moves.urllib import parse as urlparse
 from django.core.exceptions import ValidationError
 
@@ -318,7 +319,7 @@ def retrieve_metadata_and_create(request, provider_id, sp_or_idp):
         return None
     logger.debug('loaded %d bytes', len(metadata))
     try:
-        metadata = unicode(metadata, 'utf8')
+        metadata = text_type(metadata, 'utf8')
     except:
         logging.error('SAML metadata autoload: retrieved metadata for entity '
                       'id %s is not UTF-8', provider_id)
diff --git a/src/authentic2/saml/models.py b/src/authentic2/saml/models.py
index 6736afb2..595c5e97 100644
--- a/src/authentic2/saml/models.py
+++ b/src/authentic2/saml/models.py
@@ -12,6 +12,7 @@ from django.db import models
 from django.db.models import Q
 from django.conf import settings
 from django.core.exceptions import ValidationError
+from django.utils.six import text_type
 from django.utils.translation import ugettext_lazy as _
 from django.core.exceptions import ObjectDoesNotExist
 from django.contrib.contenttypes.models import ContentType
@@ -411,7 +412,7 @@ class LibertyServiceProvider(models.Model):
         return (self.liberty_provider.slug,)
 
     def __unicode__(self):
-        return unicode(self.liberty_provider)
+        return text_type(self.liberty_provider)
 
     class Meta:
         verbose_name = _('SAML service provider')
diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py
index 92d519de..a862ba38 100644
--- a/src/authentic2/utils.py
+++ b/src/authentic2/utils.py
@@ -34,6 +34,7 @@ from django.contrib import messages
 from django.utils.functional import empty, allow_lazy
 from django.utils.http import urlsafe_base64_encode
 from django.utils.encoding import iri_to_uri, force_bytes, uri_to_iri
+from django.utils.six import text_type
 from django.shortcuts import render
 
 
@@ -357,14 +358,14 @@ def record_authentication_event(request, how, nonce=None):
     # explicitly state that the session has been modified
     request.session.modified = True
     event = {
-        'who': unicode(request.user),
+        'who': text_type(request.user),
         'who_id': getattr(request.user, 'pk', None),
         'how': how,
         'when': int(time.time()),
 
     }
     kwargs = {
-        'who': unicode(request.user)[:80],
+        'who': text_type(request.user)[:80],
         'how': how,
     }
     nonce = nonce or get_nonce(request)
@@ -540,7 +541,7 @@ def normalize_attribute_values(values):
     for value in values:
         if isinstance(value, bool):
             value = str(value).lower()
-        values_set.add(unicode(value))
+        values_set.add(text_type(value))
     return values_set
 
 
@@ -846,7 +847,7 @@ def utf8_encode(v):
         return dict((utf8_encode(a), utf8_encode(b)) for a, b in v.iteritems())
     if isinstance(v, (list, tuple)):
         return type(v)(utf8_encode(a) for a in v)
-    if isinstance(v, unicode):
+    if isinstance(v, text_type):
         return v.encode('utf-8')
     return v
 
diff --git a/src/authentic2/views.py b/src/authentic2/views.py
index 5c234fb5..797ee5bb 100644
--- a/src/authentic2/views.py
+++ b/src/authentic2/views.py
@@ -18,6 +18,7 @@ from django.core import mail, signing
 from django.core.urlresolvers import reverse
 from django.core.exceptions import ValidationError
 from django.contrib import messages
+from django.utils.six import text_type
 from django.utils.translation import ugettext as _
 from django.contrib.auth import logout as auth_logout
 from django.contrib.auth import REDIRECT_FIELD_NAME
@@ -452,7 +453,7 @@ class ProfileView(cbv.TemplateNamesMixin, TemplateView):
                 value = filter(None, value)
                 value = [html_value(attribute, at_value) for at_value in value]
                 if not title:
-                    title = unicode(attribute)
+                    title = text_type(attribute)
             else:
                 # fallback to model attributes
                 try:
@@ -471,7 +472,7 @@ class ProfileView(cbv.TemplateNamesMixin, TemplateView):
                 if not isinstance(value, (list, tuple)):
                     value = (value,)
                 raw_value = value
-                value = map(unicode, value)
+                value = map(text_type, value)
             if value or app_settings.A2_PROFILE_DISPLAY_EMPTY_FIELDS:
                 profile.append((title, value))
                 attributes.append({'attribute': attribute, 'values': raw_value})
diff --git a/src/authentic2_auth_oidc/backends.py b/src/authentic2_auth_oidc/backends.py
index d03b959e..49ba16ad 100644
--- a/src/authentic2_auth_oidc/backends.py
+++ b/src/authentic2_auth_oidc/backends.py
@@ -6,6 +6,7 @@ import requests
 from jwcrypto.jwt import JWT
 from jwcrypto.jwk import JWK
 
+from django.utils.six import text_type
 from django.utils.timezone import now
 from django.contrib.auth import get_user_model
 from django.contrib.auth.backends import ModelBackend
@@ -61,7 +62,7 @@ class OIDCBackend(ModelBackend):
                       check_claims={}, algs=algs)
             jwt.claims
 
-        if isinstance(id_token.aud, unicode) and provider.client_id != id_token.aud:
+        if isinstance(id_token.aud, text_type) and provider.client_id != id_token.aud:
             logger.warning(u'auth_oidc: invalid id_token audience %s != provider client_id %s',
                            id_token.aud, provider.client_id)
             return None
diff --git a/src/authentic2_auth_oidc/management/commands/oidc-register-issuer.py b/src/authentic2_auth_oidc/management/commands/oidc-register-issuer.py
index 12f104e1..4d5199fb 100644
--- a/src/authentic2_auth_oidc/management/commands/oidc-register-issuer.py
+++ b/src/authentic2_auth_oidc/management/commands/oidc-register-issuer.py
@@ -6,6 +6,7 @@ import pprint
 
 from django.core.management.base import BaseCommand, CommandError
 from django.core.exceptions import ValidationError
+from django.utils.six import text_type
 
 from authentic2.compat import atomic
 
diff --git a/src/authentic2_auth_oidc/utils.py b/src/authentic2_auth_oidc/utils.py
index 1f2956e7..95923fe1 100644
--- a/src/authentic2_auth_oidc/utils.py
+++ b/src/authentic2_auth_oidc/utils.py
@@ -4,6 +4,7 @@ import json
 
 import requests
 
+from django.utils.six import text_type
 from django.utils.timezone import utc
 from django.shortcuts import get_object_or_404
 from django.utils.translation import ugettext as _
@@ -96,14 +97,14 @@ def parse_id_token(id_token):
 
 REQUIRED_ID_TOKEN_KEYS = set(['iss', 'sub', 'aud', 'exp', 'iat'])
 KEY_TYPES = {
-    'iss': unicode,
-    'sub': unicode,
+    'iss': text_type,
+    'sub': text_type,
     'exp': int,
     'iat': int,
     'auth_time': int,
-    'nonce': unicode,
-    'acr': unicode,
-    'azp': unicode,
+    'nonce': text_type,
+    'acr': text_type,
+    'azp': text_type,
     # aud and amr havec specific checks
 }
 
@@ -131,15 +132,15 @@ class IDToken(str):
             raise ValueError('missing field: %s' % (REQUIRED_ID_TOKEN_KEYS - keys))
         for key in keys:
             if key == 'aud':
-                if not isinstance(decoded['aud'], (unicode, list)):
+                if not isinstance(decoded['aud'], (text_type, list)):
                     raise ValueError('invalid aud value: %r' % decoded['aud'])
-                if isinstance(decoded['aud'], list) and not all(isinstance(v, unicode) for v in
+                if isinstance(decoded['aud'], list) and not all(isinstance(v, text_type) for v in
                                                                 decoded['aud']):
                     raise ValueError('invalid aud value: %r' % decoded['aud'])
             elif key == 'amr':
                 if not isinstance(decoded['amr'], list):
                     raise ValueError('invalid amr value: %s' % decoded['amr'])
-                if not all(isinstance(v, unicode) for v in decoded['amr']):
+                if not all(isinstance(v, text_type) for v in decoded['amr']):
                     raise ValueError('invalid amr value: %s' % decoded['amr'])
             elif key in KEY_TYPES:
                 if not isinstance(decoded[key], KEY_TYPES[key]):
diff --git a/src/authentic2_idp_cas/views.py b/src/authentic2_idp_cas/views.py
index 7fb27728..5f449958 100644
--- a/src/authentic2_idp_cas/views.py
+++ b/src/authentic2_idp_cas/views.py
@@ -7,6 +7,7 @@ import requests
 
 from django.http import HttpResponseBadRequest, HttpResponse
 from django.views.generic.base import View
+from django.utils.six import text_type
 from django.utils.timezone import now
 
 from authentic2.utils import (get_user_from_session_key, make_url,
@@ -213,7 +214,7 @@ class ValidateBaseView(CasMixin, View):
             attributes = self.get_attributes(request, st)
             if st.service.identifier_attribute not in attributes:
                 self.logger.error('unable to compute an identifier for user %r and service %s',
-                        unicode(st.user), st.service_url)
+                        text_type(st.user), st.service_url)
                 return self.validation_failure(request, service, INTERNAL_ERROR)
             # Compute user identifier
             identifier = attribute_values_to_identifier(
@@ -245,7 +246,7 @@ class ValidateBaseView(CasMixin, View):
 
     def validation_success(self, request, st, identifier):
         self.logger.info('validation success service: %r ticket: %s '
-                'user: %r identifier: %r', st.service_url, st.ticket_id, unicode(st.user), identifier)
+                'user: %r identifier: %r', st.service_url, st.ticket_id, text_type(st.user), identifier)
         return self.real_validation_success(request, st, identifier)
 
 
@@ -274,7 +275,7 @@ class ServiceValidateView(ValidateBaseView):
         root = ET.Element(SERVICE_RESPONSE_ELT)
         success = ET.SubElement(root, AUTHENTICATION_SUCCESS_ELT)
         user = ET.SubElement(success, USER_ELT)
-        user.text = unicode(identifier)
+        user.text = text_type(identifier)
         self.provision_pgt(request, st, success)
         self.provision_attributes(request, st, success)
         return HttpResponse(ET.tostring(root, encoding='utf-8'),
@@ -297,7 +298,7 @@ class ServiceValidateView(ValidateBaseView):
         for key, values in values.iteritems():
             for value in values:
                 attribute_elt = ET.SubElement(attributes_elt, '{%s}%s' % (CAS_NAMESPACE, key))
-                attribute_elt.text = unicode(value)
+                attribute_elt.text = text_type(value)
 
 
     def provision_pgt(self, request, st, success):
diff --git a/src/authentic2_idp_oidc/models.py b/src/authentic2_idp_oidc/models.py
index 58775e61..a696a5a1 100644
--- a/src/authentic2_idp_oidc/models.py
+++ b/src/authentic2_idp_oidc/models.py
@@ -7,6 +7,7 @@ from django.core.validators import URLValidator
 from django.core.exceptions import ValidationError, ImproperlyConfigured
 from django.utils.translation import ugettext_lazy as _
 from django.conf import settings
+from django.utils.six import text_type
 from django.utils.timezone import now
 from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
 
@@ -18,7 +19,7 @@ from . import utils, managers
 
 
 def generate_uuid():
-    return unicode(uuid.uuid4())
+    return text_type(uuid.uuid4())
 
 
 def validate_https_url(data):
@@ -188,8 +189,8 @@ class OIDCAuthorization(models.Model):
 
     def __repr__(self):
         return '<OIDCAuthorization client:%r user:%r scopes:%r>' % (
-            self.client_id and unicode(self.client),
-            self.user_id and unicode(self.user),
+            self.client_id and text_type(self.client),
+            self.user_id and text_type(self.user),
             self.scopes)
 
 
@@ -248,8 +249,8 @@ class OIDCCode(models.Model):
     def __repr__(self):
         return '<OIDCCode uuid:%s client:%s user:%s expired:%s scopes:%s>' % (
             self.uuid,
-            self.client_id and unicode(self.client),
-            self.user_id and unicode(self.user),
+            self.client_id and text_type(self.client),
+            self.user_id and text_type(self.user),
             self.expired,
             self.scopes)
 
@@ -298,8 +299,8 @@ class OIDCAccessToken(models.Model):
     def __repr__(self):
         return '<OIDCAccessToken uuid:%s client:%s user:%s expired:%s scopes:%s>' % (
             self.uuid,
-            self.client_id and unicode(self.client),
-            self.user_id and unicode(self.user),
+            self.client_id and text_type(self.client),
+            self.user_id and text_type(self.user),
             self.expired,
             self.scopes)
 
diff --git a/src/authentic2_idp_oidc/utils.py b/src/authentic2_idp_oidc/utils.py
index 88e0dbf3..bd135c55 100644
--- a/src/authentic2_idp_oidc/utils.py
+++ b/src/authentic2_idp_oidc/utils.py
@@ -9,6 +9,7 @@ from jwcrypto.jwt import JWT
 from django.core.exceptions import ImproperlyConfigured
 from django.conf import settings
 from django.utils.encoding import smart_bytes
+from django.utils.six import text_type
 from django.utils.six.moves.urllib import parse as urlparse
 
 from authentic2 import hooks, crypto
@@ -70,7 +71,7 @@ def scope_set(data):
 
 def clean_words(data):
     '''Clean and order a list of words'''
-    return u' '.join(sorted(map(unicode.strip, data.split())))
+    return u' '.join(sorted(map(text_type.strip, data.split())))
 
 
 def url_domain(url):
@@ -81,7 +82,7 @@ def make_sub(client, user):
     if client.identifier_policy in (client.POLICY_PAIRWISE, client.POLICY_PAIRWISE_REVERSIBLE):
         return make_pairwise_sub(client, user)
     elif client.identifier_policy == client.POLICY_UUID:
-        return unicode(user.uuid)
+        return text_type(user.uuid)
     elif client.identifier_policy == client.POLICY_EMAIL:
         return user.email
     else:
diff --git a/src/authentic2_idp_oidc/views.py b/src/authentic2_idp_oidc/views.py
index 137282cb..e7726241 100644
--- a/src/authentic2_idp_oidc/views.py
+++ b/src/authentic2_idp_oidc/views.py
@@ -5,6 +5,7 @@ import base64
 import time
 
 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed
+from django.utils.six import text_type
 from django.utils.timezone import now, utc
 from django.utils.http import urlencode
 from django.shortcuts import render
@@ -265,7 +266,7 @@ def authorize(request, *args, **kwargs):
                     code.uuid, ' '.join(scopes),
                     client.name)
         params = {
-            'code': unicode(code.uuid),
+            'code': text_type(code.uuid),
         }
         if state is not None:
             params['state'] = state
@@ -413,7 +414,7 @@ def token(request, *args, **kwargs):
     if oidc_code.nonce is not None:
         id_token['nonce'] = oidc_code.nonce
     response = HttpResponse(json.dumps({
-        'access_token': unicode(access_token.uuid),
+        'access_token': text_type(access_token.uuid),
         'token_type': 'Bearer',
         'expires_in': expires_in,
         'id_token': utils.make_idtoken(client, id_token),
diff --git a/src/authentic2_provisionning_ldap/management/commands/provision.py b/src/authentic2_provisionning_ldap/management/commands/provision.py
index 241e3a39..55adeea9 100644
--- a/src/authentic2_provisionning_ldap/management/commands/provision.py
+++ b/src/authentic2_provisionning_ldap/management/commands/provision.py
@@ -9,6 +9,7 @@ except ImportError:
 from ldaptools import paged
 
 from django.core.management.base import BaseCommand
+from django.utils.six import text_type
 
 from authentic2.attributes_ng.engine import get_attributes
 from authentic2 import compat, utils
@@ -52,7 +53,7 @@ class Command(BaseCommand):
             values = [values]
         ldap_values = ldap_attributes.setdefault(ldap_attribute, [])
         for value in values:
-            if isinstance(value, unicode):
+            if isinstance(value, text_type):
                 value = value.encode('utf-8')
             elif isinstance(value, str):
                 pass # must be well encoded
diff --git a/src/django_rbac/models.py b/src/django_rbac/models.py
index da1514b3..2dd2473f 100644
--- a/src/django_rbac/models.py
+++ b/src/django_rbac/models.py
@@ -1,6 +1,7 @@
 import operator
 import hashlib
 
+from django.utils.six import text_type
 from django.utils.text import slugify
 from django.utils.translation import ugettext_lazy as _
 from django.db import models
@@ -53,7 +54,7 @@ class AbstractBase(models.Model):
     def save(self, *args, **kwargs):
         # truncate slug and add a hash if it's too long
         if not self.slug:
-            self.slug = slugify(unicode(self.name)).lstrip('_')
+            self.slug = slugify(text_type(self.name)).lstrip('_')
         if len(self.slug) > 256:
             self.slug = self.slug[:252] + \
                 hashlib.md5(self.slug).hexdigest()[:4]
@@ -112,7 +113,7 @@ class Operation(models.Model):
         return [self.slug]
 
     def __unicode__(self):
-        return unicode(_(self.name))
+        return text_type(_(self.name))
 
     def export_json(self):
         return {'slug': self.slug, 'name': self.name}
diff --git a/src/django_rbac/utils.py b/src/django_rbac/utils.py
index 1757b3ea..498f7487 100644
--- a/src/django_rbac/utils.py
+++ b/src/django_rbac/utils.py
@@ -2,6 +2,7 @@ from authentic2.utils import get_hex_uuid
 from authentic2.decorators import GlobalCache
 from django.conf import settings
 from django.apps import apps
+from django.utils.six import text_type
 
 from . import constants
 
@@ -64,6 +65,6 @@ def get_permission_model():
 def get_operation(operation_tpl):
     from . import models
     operation, created = models.Operation.objects.get_or_create(
-        slug=unicode(operation_tpl.slug),
-        defaults={'name': unicode(operation_tpl.name)})
+        slug=text_type(operation_tpl.slug),
+        defaults={'name': text_type(operation_tpl.name)})
     return operation
diff --git a/tests/test_all.py b/tests/test_all.py
index 71f2278e..2061044e 100644
--- a/tests/test_all.py
+++ b/tests/test_all.py
@@ -13,6 +13,7 @@ from django.core.serializers.json import DjangoJSONEncoder
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.utils.translation import ugettext as _
+from django.utils.six import text_type
 from django.utils.six.moves.urllib import parse as urlparse
 
 from rest_framework import test
@@ -403,7 +404,7 @@ class AttributeKindsTest(TestCase):
         for i, name in enumerate(attribute_kinds.get_attribute_kinds()):
             fields['field_%d' % i] = attribute_kinds.get_form_field(name)
         AttributeKindForm = type('AttributeKindForm', (forms.Form,), fields)
-        unicode(AttributeKindForm().as_p())
+        text_type(AttributeKindForm().as_p())
 
 
 class APITest(TestCase):
diff --git a/tests/test_user_manager.py b/tests/test_user_manager.py
index c5298751..b69611dc 100644
--- a/tests/test_user_manager.py
+++ b/tests/test_user_manager.py
@@ -3,6 +3,7 @@ import csv
 from django.core.urlresolvers import reverse
 
 from django.contrib.contenttypes.models import ContentType
+from django.utils.six import text_type
 
 from authentic2.custom_user.models import User
 from authentic2.models import Attribute, AttributeValue
@@ -27,11 +28,11 @@ def test_manager_user_change_email(app, superuser_or_admin, simple_user, mailout
 
     response = login(app, superuser_or_admin,
                      reverse('a2-manager-user-by-uuid-detail',
-                             kwargs={'slug': unicode(simple_user.uuid)}))
+                             kwargs={'slug': text_type(simple_user.uuid)}))
     assert 'Change user email' in response.content
     # cannot click it's a submit button :/
     response = app.get(reverse('a2-manager-user-by-uuid-change-email',
-                               kwargs={'slug': unicode(simple_user.uuid)}))
+                               kwargs={'slug': text_type(simple_user.uuid)}))
     assert response.form['new_email'].value == simple_user.email
     response.form.set('new_email', NEW_EMAIL)
     assert len(mailoutbox) == 0
@@ -66,11 +67,11 @@ def test_manager_user_change_email_no_change(app, superuser_or_admin, simple_use
 
     response = login(app, superuser_or_admin,
                      reverse('a2-manager-user-by-uuid-detail',
-                             kwargs={'slug': unicode(simple_user.uuid)}))
+                             kwargs={'slug': text_type(simple_user.uuid)}))
     assert 'Change user email' in response.content
     # cannot click it's a submit button :/
     response = app.get(reverse('a2-manager-user-by-uuid-change-email',
-                               kwargs={'slug': unicode(simple_user.uuid)}))
+                               kwargs={'slug': text_type(simple_user.uuid)}))
     assert response.form['new_email'].value == simple_user.email
     assert len(mailoutbox) == 0
     response = response.form.submit().follow()
-- 
2.20.1

