Repository: jazzband/django-simple-history

Issue:
Django 3 support
**Problem Statement**
Running my CI with Django 3.0b1 breaks because of incompatibility of django-simple-history.
My guess is that Django 3 drops Python 2 support and therefor `python_2_unicode_compatible` isn't available anymore.

**Describe the solution you'd like**
Make it compatible with Django 3

**Describe alternatives you've considered**
There are none I guess

**Additional context**
Stacktrace:
```bash
  File "/root/.vscode-server/extensions/ms-python.python-2019.10.41019/pythonFiles/ptvsd_launcher.py", line 43, in <module>
    main(ptvsdArgs)
  File "/root/.vscode-server/extensions/ms-python.python-2019.10.41019/pythonFiles/lib/python/old_ptvsd/ptvsd/__main__.py", line 432, in main
    run()
  File "/root/.vscode-server/extensions/ms-python.python-2019.10.41019/pythonFiles/lib/python/old_ptvsd/ptvsd/__main__.py", line 316, in run_file
    runpy.run_path(target, run_name='__main__')
  File "/usr/local/lib/python3.7/runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "/usr/local/lib/python3.7/runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "/usr/local/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/app/paper/manage.py", line 21, in <module>
    main()
  File "/app/paper/manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 377, in execute
    django.setup()
  File "/usr/local/lib/python3.7/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/local/lib/python3.7/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/usr/local/lib/python3.7/site-packages/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/local/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/usr/local/lib/python3.7/site-packages/simple_history/models.py", line 19, in <module>
    from django.utils.encoding import python_2_unicode_compatible, smart_text
ImportError: cannot import name 'python_2_unicode_compatible' from 'django.utils.encoding' (/usr/local/lib/python3.7/site-packages/django/utils/encoding.py)
```



Discussion:

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -3,49 +3,50 @@
 
 tests_require = [
     "Django>=1.11",
-    "WebTest==2.0.24",
-    "django-webtest==1.8.0",
     "mock==1.0.1",
     "six",
 ]
 
-setup(
-    name="django-simple-history",
-    version=simple_history.__version__,
-    description="Store model history and view/revert changes from admin site.",
-    long_description="\n".join((open("README.rst").read(), open("CHANGES.rst").read())),
-    author="Corey Bertram",
-    author_email="corey@qr7.com",
-    maintainer="Trey Hunner",
-    url="https://github.com/treyhunner/django-simple-history",
-    packages=[
-        "simple_history",
-        "simple_history.management",
-        "simple_history.management.commands",
-        "simple_history.templatetags",
-    ],
-    classifiers=[
-        "Development Status :: 5 - Production/Stable",
-        "Framework :: Django",
-        "Environment :: Web Environment",
-        "Intended Audience :: Developers",
-        "Framework :: Django",
-        "Framework :: Django :: 1.11",
-        "Framework :: Django :: 2.0",
-        "Framework :: Django :: 2.1",
-        "Framework :: Django :: 2.2",
-        "Programming Language :: Python",
-        "Programming Language :: Python :: 2.7",
-        "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.4",
-        "Programming Language :: Python :: 3.5",
-        "Programming Language :: Python :: 3.6",
-        "Programming Language :: Python :: 3.7",
-        "License :: OSI Approved :: BSD License",
-    ],
-    python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
-    tests_require=tests_require,
-    install_requires=["six"],
-    include_package_data=True,
-    test_suite="runtests.main",
-)
+with open("README.rst") as readme, open("CHANGES.rst") as changes:
+    setup(
+        name="django-simple-history",
+        version=simple_history.__version__,
+        description="Store model history and view/revert changes from admin site.",
+        long_description="\n".join((readme.read(), changes.read())),
+        author="Corey Bertram",
+        author_email="corey@qr7.com",
+        maintainer="Trey Hunner",
+        url="https://github.com/treyhunner/django-simple-history",
+        packages=[
+            "simple_history",
+            "simple_history.management",
+            "simple_history.management.commands",
+            "simple_history.templatetags",
+        ],
+        classifiers=[
+            "Development Status :: 5 - Production/Stable",
+            "Framework :: Django",
+            "Environment :: Web Environment",
+            "Intended Audience :: Developers",
+            "Framework :: Django",
+            "Framework :: Django :: 1.11",
+            "Framework :: Django :: 2.0",
+            "Framework :: Django :: 2.1",
+            "Framework :: Django :: 2.2",
+            "Framework :: Django :: 3.0",
+            "Programming Language :: Python",
+            "Programming Language :: Python :: 2.7",
+            "Programming Language :: Python :: 3",
+            "Programming Language :: Python :: 3.4",
+            "Programming Language :: Python :: 3.5",
+            "Programming Language :: Python :: 3.6",
+            "Programming Language :: Python :: 3.7",
+            "Programming Language :: Python :: 3.8",
+            "License :: OSI Approved :: BSD License",
+        ],
+        python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+        tests_require=tests_require,
+        install_requires=["six"],
+        include_package_data=True,
+        test_suite="runtests.main",
+    )
diff --git a/simple_history/admin.py b/simple_history/admin.py
--- a/simple_history/admin.py
+++ b/simple_history/admin.py
@@ -1,5 +1,6 @@
 from __future__ import unicode_literals
 
+import django
 from django import http
 from django.conf import settings
 from django.conf.urls import url
@@ -10,13 +11,18 @@
 from django.core.exceptions import PermissionDenied
 from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
-from django.utils.encoding import force_text
 from django.utils.html import mark_safe
 from django.utils.text import capfirst
-from django.utils.translation import ugettext as _
 
 from . import utils
 
+if django.VERSION < (2,):
+    from django.utils.encoding import force_text as force_str
+    from django.utils.translation import ugettext as _
+else:
+    from django.utils.encoding import force_str
+    from django.utils.translation import gettext as _
+
 USER_NATURAL_KEY = tuple(key.lower() for key in settings.AUTH_USER_MODEL.split(".", 1))
 
 SIMPLE_HISTORY_EDIT = getattr(settings, "SIMPLE_HISTORY_EDIT", False)
@@ -80,9 +86,9 @@ def history_view(self, request, object_id, extra_context=None):
             content_type.model,
         )
         context = {
-            "title": _("Change history: %s") % force_text(obj),
+            "title": _("Change history: %s") % force_str(obj),
             "action_list": action_list,
-            "module_name": capfirst(force_text(opts.verbose_name_plural)),
+            "module_name": capfirst(force_str(opts.verbose_name_plural)),
             "object": obj,
             "root_path": getattr(self.admin_site, "root_path", None),
             "app_label": app_label,
@@ -102,8 +108,8 @@ def response_change(self, request, obj):
             verbose_name = obj._meta.verbose_name
 
             msg = _('The %(name)s "%(obj)s" was changed successfully.') % {
-                "name": force_text(verbose_name),
-                "obj": force_text(obj),
+                "name": force_str(verbose_name),
+                "obj": force_str(obj),
             }
 
             self.message_user(
@@ -167,7 +173,7 @@ def history_form_view(self, request, object_id, version_id, extra_context=None):
         model_name = original_opts.model_name
         url_triplet = self.admin_site.name, original_opts.app_label, model_name
         context = {
-            "title": _("Revert %s") % force_text(obj),
+            "title": _("Revert %s") % force_str(obj),
             "adminform": admin_form,
             "object_id": object_id,
             "original": obj,
diff --git a/simple_history/models.py b/simple_history/models.py
--- a/simple_history/models.py
+++ b/simple_history/models.py
@@ -2,33 +2,36 @@
 
 import copy
 import importlib
-import six
 import threading
 import uuid
 import warnings
 
+import django
+import six
 from django.apps import apps
 from django.conf import settings
 from django.contrib import admin
 from django.contrib.auth import get_user_model
 from django.core.exceptions import ObjectDoesNotExist
-from django.core.serializers import serialize
 from django.db import models
 from django.db.models import Q
 from django.db.models.fields.proxy import OrderWrt
 from django.forms.models import model_to_dict
 from django.urls import reverse
-from django.utils.encoding import python_2_unicode_compatible, smart_text
 from django.utils.text import format_lazy
 from django.utils.timezone import now
-from django.utils.translation import ugettext_lazy as _
-
 from simple_history import utils
 from . import exceptions
 from .manager import HistoryDescriptor
 from .signals import post_create_historical_record, pre_create_historical_record
 
-import json
+if django.VERSION < (2,):
+    from django.utils.translation import ugettext_lazy as _
+    from django.utils.encoding import smart_text as smart_str
+    from django.utils.encoding import python_2_unicode_compatible
+else:
+    from django.utils.translation import gettext_lazy as _
+    from django.utils.encoding import smart_str
 
 registered_models = {}
 
@@ -223,7 +226,12 @@ def create_history_model(self, model, inherited):
         name = self.get_history_model_name(model)
 
         registered_models[model._meta.db_table] = model
-        return python_2_unicode_compatible(type(str(name), self.bases, attrs))
+        history_model = type(str(name), self.bases, attrs)
+        return (
+            python_2_unicode_compatible(history_model)
+            if django.VERSION < (2,)
+            else history_model
+        )
 
     def fields_included(self, model):
         fields = []
@@ -448,7 +456,7 @@ def get_meta_options(self, model):
         if self.user_set_verbose_name:
             name = self.user_set_verbose_name
         else:
-            name = format_lazy("historical {}", smart_text(model._meta.verbose_name))
+            name = format_lazy("historical {}", smart_str(model._meta.verbose_name))
         meta_fields["verbose_name"] = name
         if self.app:
             meta_fields["app_label"] = self.app


Test patch:
diff --git a/simple_history/tests/tests/test_admin.py b/simple_history/tests/tests/test_admin.py
--- a/simple_history/tests/tests/test_admin.py
+++ b/simple_history/tests/tests/test_admin.py
@@ -1,13 +1,14 @@
 from datetime import datetime, timedelta
+
 from django.contrib.admin import AdminSite
 from django.contrib.admin.utils import quote
 from django.contrib.auth import get_user_model
 from django.contrib.messages.storage.fallback import FallbackStorage
+from django.test import TestCase
 from django.test.client import RequestFactory
 from django.test.utils import override_settings
 from django.urls import reverse
-from django.utils.encoding import force_text
-from django_webtest import WebTest
+import django
 from mock import ANY, patch
 
 from simple_history.admin import SimpleHistoryAdmin
@@ -27,6 +28,11 @@
     State,
 )
 
+if django.VERSION < (2,):
+    from django.utils.encoding import force_text as force_str
+else:
+    from django.utils.encoding import force_str
+
 User = get_user_model()
 today = datetime(2021, 1, 1, 10, 0)
 tomorrow = today + timedelta(days=1)
@@ -49,7 +55,7 @@ def get_history_url(obj, history_index=None, site="admin"):
         )
 
 
-class AdminSiteTest(WebTest):
+class AdminSiteTest(TestCase):
     def setUp(self):
         self.user = User.objects.create_superuser("user_login", "u@example.com", "pass")
 
@@ -60,12 +66,7 @@ def tearDown(self):
             pass
 
     def login(self, user=None):
-        if user is None:
-            user = self.user
-        form = self.app.get(reverse("admin:index")).maybe_follow().form
-        form["username"] = user.username
-        form["password"] = "pass"
-        return form.submit()
+        self.client.force_login(user or self.user)
 
     def test_history_list(self):
         model_name = self.user._meta.model_name
@@ -76,14 +77,14 @@ def test_history_list(self):
         poll._history_user = self.user
         poll.save()
 
-        response = self.app.get(get_history_url(poll))
-        self.assertIn(get_history_url(poll, 0), response.unicode_normal_body)
-        self.assertIn("Poll object", response.unicode_normal_body)
-        self.assertIn("Created", response.unicode_normal_body)
-        self.assertIn("Changed by", response.unicode_normal_body)
-        self.assertIn("Change reason", response.unicode_normal_body)
-        self.assertIn("A random test reason", response.unicode_normal_body)
-        self.assertIn(self.user.username, response.unicode_normal_body)
+        response = self.client.get(get_history_url(poll))
+        self.assertContains(response, get_history_url(poll, 0))
+        self.assertContains(response, "Poll object")
+        self.assertContains(response, "Created")
+        self.assertContains(response, "Changed by")
+        self.assertContains(response, "Change reason")
+        self.assertContains(response, "A random test reason")
+        self.assertContains(response, self.user.username)
 
     def test_history_list_custom_fields(self):
         model_name = self.user._meta.model_name
@@ -97,14 +98,14 @@ def test_history_list_custom_fields(self):
         choice.save()
         choice.votes = 15
         choice.save()
-        response = self.app.get(get_history_url(choice))
-        self.assertIn(get_history_url(choice, 0), response.unicode_normal_body)
-        self.assertIn("Choice object", response.unicode_normal_body)
-        self.assertIn("Created", response.unicode_normal_body)
-        self.assertIn(self.user.username, response.unicode_normal_body)
-        self.assertIn("votes", response.unicode_normal_body)
-        self.assertIn("12", response.unicode_normal_body)
-        self.assertIn("15", response.unicode_normal_body)
+        response = self.client.get(get_history_url(choice))
+        self.assertContains(response, get_history_url(choice, 0))
+        self.assertContains(response, "Choice object")
+        self.assertContains(response, "Created")
+        self.assertContains(response, self.user.username)
+        self.assertContains(response, "votes")
+        self.assertContains(response, "12")
+        self.assertContains(response, "15")
 
     def test_history_list_custom_admin_methods(self):
         model_name = self.user._meta.model_name
@@ -115,14 +116,14 @@ def test_history_list_custom_admin_methods(self):
         file_model.save()
         file_model.title = "Title 2"
         file_model.save()
-        response = self.app.get(get_history_url(file_model))
-        self.assertIn(get_history_url(file_model, 0), response.unicode_normal_body)
-        self.assertIn("FileModel object", response.unicode_normal_body)
-        self.assertIn("Created", response.unicode_normal_body)
-        self.assertIn(self.user.username, response.unicode_normal_body)
-        self.assertIn("test_method_value", response.unicode_normal_body)
-        self.assertIn("Title 1", response.unicode_normal_body)
-        self.assertIn("Title 2", response.unicode_normal_body)
+        response = self.client.get(get_history_url(file_model))
+        self.assertContains(response, get_history_url(file_model, 0))
+        self.assertContains(response, "FileModel object")
+        self.assertContains(response, "Created")
+        self.assertContains(response, self.user.username)
+        self.assertContains(response, "test_method_value")
+        self.assertContains(response, "Title 1")
+        self.assertContains(response, "Title 2")
 
     def test_history_list_custom_user_id_field(self):
         instance = ExternalModelWithCustomUserIdField(name="random_name")
@@ -130,28 +131,26 @@ def test_history_list_custom_user_id_field(self):
         instance.save()
 
         self.login()
-        resp = self.app.get(get_history_url(instance))
+        resp = self.client.get(get_history_url(instance))
 
         self.assertEqual(200, resp.status_code)
 
     def test_history_view_permission(self):
         self.login()
         person = Person.objects.create(name="Sandra Hale")
-        self.app.get(get_history_url(person), status=403)
+        self.client.get(get_history_url(person), status=403)
 
     def test_history_form_permission(self):
         self.login(self.user)
         person = Person.objects.create(name="Sandra Hale")
-        self.app.get(get_history_url(person, 0), status=403)
+        self.client.get(get_history_url(person, 0), status=403)
 
     def test_invalid_history_form(self):
         self.login()
         poll = Poll.objects.create(question="why?", pub_date=today)
-        response = self.app.get(get_history_url(poll, 0))
-        response.form["question"] = ""
-        response = response.form.submit()
+        response = self.client.post(get_history_url(poll, 0), data={"question": ""})
         self.assertEqual(response.status_code, 200)
-        self.assertIn("This field is required", response.unicode_normal_body)
+        self.assertContains(response, "This field is required")
 
     def test_history_form(self):
         self.login()
@@ -160,33 +159,32 @@ def test_history_form(self):
         poll.save()
 
         # Make sure form for initial version is correct
-        response = self.app.get(get_history_url(poll, 0))
-        self.assertEqual(response.form["question"].value, "why?")
-        self.assertEqual(response.form["pub_date_0"].value, "2021-01-01")
-        self.assertEqual(response.form["pub_date_1"].value, "10:00:00")
+        response = self.client.get(get_history_url(poll, 0))
+        form = response.context.get("adminform").form
+        self.assertEqual(form["question"].value(), "why?")
+        self.assertEqual(form["pub_date"].value(), datetime(2021, 1, 1, 10, 0))
 
         # Create new version based on original version
-        response.form["question"] = "what?"
-        response.form["pub_date_0"] = "2021-01-02"
-        response = response.form.submit()
+        new_data = {
+            "pub_date_0": "2021-01-02",
+            "pub_date_1": "10:00:00",
+            "question": "what?",
+        }
+        response = self.client.post(get_history_url(poll, 0), data=new_data)
         self.assertEqual(response.status_code, 302)
-        self.assertTrue(
-            response.headers["location"].endswith(
-                reverse("admin:tests_poll_changelist")
-            )
-        )
+        self.assertTrue(response.url.endswith(reverse("admin:tests_poll_changelist")))
 
         # Ensure form for second version is correct
-        response = self.app.get(get_history_url(poll, 1))
-        self.assertEqual(response.form["question"].value, "how?")
-        self.assertEqual(response.form["pub_date_0"].value, "2021-01-01")
-        self.assertEqual(response.form["pub_date_1"].value, "10:00:00")
+        response = self.client.get(get_history_url(poll, 1))
+        form = response.context.get("adminform").form
+        self.assertEqual(form["question"].value(), "how?")
+        self.assertEqual(form["pub_date"].value(), datetime(2021, 1, 1, 10, 0))
 
         # Ensure form for new third version is correct
-        response = self.app.get(get_history_url(poll, 2))
-        self.assertEqual(response.form["question"].value, "what?")
-        self.assertEqual(response.form["pub_date_0"].value, "2021-01-02")
-        self.assertEqual(response.form["pub_date_1"].value, "10:00:00")
+        response = self.client.get(get_history_url(poll, 2))
+        form = response.context["adminform"].form
+        self.assertEqual(form["question"].value(), "what?")
+        self.assertEqual(form["pub_date"].value(), datetime(2021, 1, 2, 10, 0))
 
         # Ensure current version of poll is correct
         poll = Poll.objects.get()
@@ -200,16 +198,16 @@ def test_history_user_on_save_in_admin(self):
         self.login()
 
         # Ensure polls created via admin interface save correct user
-        add_page = self.app.get(reverse("admin:tests_poll_add"))
-        add_page.form["question"] = "new poll?"
-        add_page.form["pub_date_0"] = "2012-01-01"
-        add_page.form["pub_date_1"] = "10:00:00"
-        changelist_page = add_page.form.submit().follow()
+        poll_data = {
+            "question": "new poll?",
+            "pub_date_0": "2012-01-01",
+            "pub_date_1": "10:00:00",
+        }
+        self.client.post(reverse("admin:tests_poll_add"), data=poll_data)
         self.assertEqual(Poll.history.get().history_user, self.user)
 
         # Ensure polls saved on edit page in admin interface save correct user
-        change_page = changelist_page.click("Poll object", index=1)
-        change_page.form.submit()
+        self.client.post(reverse("admin:tests_poll_add"), data=poll_data)
         self.assertEqual(
             [p.history_user for p in Poll.history.all()], [self.user, self.user]
         )
@@ -219,8 +217,8 @@ def test_underscore_in_pk(self):
         book = Book(isbn="9780147_513731")
         book._history_user = self.user
         book.save()
-        response = self.app.get(get_history_url(book))
-        self.assertIn(book.history.all()[0].revert_url(), response.unicode_normal_body)
+        response = self.client.get(get_history_url(book))
+        self.assertContains(response, book.history.all()[0].revert_url())
 
     def test_historical_user_no_setter(self):
         """Demonstrate admin error without `_historical_user` setter.
@@ -228,14 +226,13 @@ def test_historical_user_no_setter(self):
 
         """
         self.login()
-        add_page = self.app.get(reverse("admin:tests_document_add"))
-        self.assertRaises(AttributeError, add_page.form.submit)
+        with self.assertRaises(AttributeError):
+            self.client.post(reverse("admin:tests_document_add"))
 
     def test_historical_user_with_setter(self):
         """Documented work-around for #43"""
         self.login()
-        add_page = self.app.get(reverse("admin:tests_paper_add"))
-        add_page.form.submit()
+        self.client.post(reverse("admin:tests_paper_add"))
 
     def test_history_user_not_saved(self):
         self.login()
@@ -249,9 +246,9 @@ def test_history_user_not_saved(self):
     @override_settings(**middleware_override_settings)
     def test_middleware_saves_user(self):
         self.login()
-        form = self.app.get(reverse("admin:tests_book_add")).form
-        form["isbn"] = "9780147_513731"
-        form.submit()
+        self.client.post(
+            reverse("admin:tests_book_add"), data={"isbn": "9780147_513731"}
+        )
         book = Book.objects.get()
         historical_book = book.history.all()[0]
 
@@ -264,7 +261,7 @@ def test_middleware_saves_user(self):
     @override_settings(**middleware_override_settings)
     def test_middleware_unsets_request(self):
         self.login()
-        self.app.get(reverse("admin:tests_book_add"))
+        self.client.get(reverse("admin:tests_book_add"))
         self.assertFalse(hasattr(HistoricalRecords.thread, "request"))
 
     @override_settings(**middleware_override_settings)
@@ -273,7 +270,9 @@ def test_rolled_back_user_does_not_lead_to_foreign_key_error(self):
         # happens, e.g. in test cases), and verifies that subsequently
         # creating a new entry does not fail with a foreign key error.
         self.login()
-        self.assertEqual(self.app.get(reverse("admin:tests_book_add")).status_code, 200)
+        self.assertEqual(
+            self.client.get(reverse("admin:tests_book_add")).status_code, 200
+        )
 
         book = Book.objects.create(isbn="9780147_513731")
 
@@ -286,7 +285,7 @@ def test_rolled_back_user_does_not_lead_to_foreign_key_error(self):
 
     @override_settings(**middleware_override_settings)
     def test_middleware_anonymous_user(self):
-        self.app.get(reverse("admin:index"))
+        self.client.get(reverse("admin:index"))
         poll = Poll.objects.create(question="why?", pub_date=today)
         historical_poll = poll.history.all()[0]
         self.assertEqual(
@@ -304,9 +303,9 @@ def test_other_admin(self):
         self.login()
         state = State.objects.create()
         history_url = get_history_url(state, site="other_admin")
-        self.app.get(history_url)
+        self.client.get(history_url)
         change_url = get_history_url(state, 0, site="other_admin")
-        self.app.get(change_url)
+        self.client.get(change_url)
 
     def test_deleting_user(self):
         """Test deletes of a user does not cascade delete the history"""
@@ -347,7 +346,7 @@ def test_missing_one_to_one(self):
         employee.manager = None
         employee.save()
         manager.delete()
-        response = self.app.get(get_history_url(employee, 0))
+        response = self.client.get(get_history_url(employee, 0))
         self.assertEqual(response.status_code, 200)
 
     def test_history_deleted_instance(self):
@@ -357,7 +356,7 @@ def test_history_deleted_instance(self):
         employee_pk = employee.pk
         employee.delete()
         employee.pk = employee_pk
-        response = self.app.get(get_history_url(employee))
+        response = self.client.get(get_history_url(employee))
         self.assertEqual(response.status_code, 200)
 
     def test_response_change(self):
@@ -450,7 +449,7 @@ def test_history_form_view_without_getting_history(self):
             # Verify this is set for original object
             "original": poll,
             "change_history": False,
-            "title": "Revert %s" % force_text(poll),
+            "title": "Revert %s" % force_str(poll),
             "adminform": ANY,
             "object_id": poll.id,
             "is_popup": False,
@@ -503,7 +502,7 @@ def test_history_form_view_getting_history(self):
             # Verify this is set for history object not poll object
             "original": history.instance,
             "change_history": True,
-            "title": "Revert %s" % force_text(history.instance),
+            "title": "Revert %s" % force_str(history.instance),
             "adminform": ANY,
             "object_id": poll.id,
             "is_popup": False,
@@ -556,7 +555,7 @@ def test_history_form_view_getting_history_with_setting_off(self):
             # Verify this is set for history object not poll object
             "original": poll,
             "change_history": False,
-            "title": "Revert %s" % force_text(poll),
+            "title": "Revert %s" % force_str(poll),
             "adminform": ANY,
             "object_id": poll.id,
             "is_popup": False,
@@ -609,7 +608,7 @@ def test_history_form_view_getting_history_abstract_external(self):
             # Verify this is set for history object
             "original": history.instance,
             "change_history": True,
-            "title": "Revert %s" % force_text(history.instance),
+            "title": "Revert %s" % force_str(history.instance),
             "adminform": ANY,
             "object_id": obj.id,
             "is_popup": False,
@@ -668,7 +667,7 @@ def test_history_form_view_accepts_additional_context(self):
             "anything_else": "will be merged into context",
             "original": poll,
             "change_history": False,
-            "title": "Revert %s" % force_text(poll),
+            "title": "Revert %s" % force_str(poll),
             "adminform": ANY,
             "object_id": poll.id,
             "is_popup": False,