diff --git a/greg/admin.py b/greg/admin.py
index 2c8dcb5e401b70a5dcaff39904d3ad26cad131e4..d73bdab829f508534fac1eea3473a2ef7a1a1a35 100644
--- a/greg/admin.py
+++ b/greg/admin.py
@@ -1,4 +1,5 @@
 from django.contrib import admin
+from reversion.admin import VersionAdmin
 
 from greg.models import (
     Person,
@@ -30,7 +31,7 @@ class ConsentInline(admin.TabularInline):
     extra = 1
 
 
-class PersonAdmin(admin.ModelAdmin):
+class PersonAdmin(VersionAdmin):
     list_display = (
         "first_name",
         "last_name",
@@ -46,7 +47,7 @@ class PersonAdmin(admin.ModelAdmin):
     role_count.short_description = "# roles"  # type: ignore
 
 
-class PersonRoleAdmin(admin.ModelAdmin):
+class PersonRoleAdmin(VersionAdmin):
     list_display = ("id", "person", "role")
     search_fields = (
         "person__id",
@@ -56,12 +57,12 @@ class PersonRoleAdmin(admin.ModelAdmin):
     readonly_fields = ("id", "created", "updated")
 
 
-class RoleAdmin(admin.ModelAdmin):
+class RoleAdmin(VersionAdmin):
     list_display = ("id", "type", "name_nb", "name_en")
     readonly_fields = ("id", "created", "updated")
 
 
-class PersonIdentityAdmin(admin.ModelAdmin):
+class PersonIdentityAdmin(VersionAdmin):
     list_display = ("id", "person", "type", "verified")
     list_filter = ("verified",)
     readonly_fields = ("id", "created", "updated")
@@ -72,7 +73,7 @@ class ConsentAdmin(admin.ModelAdmin):
     readonly_fields = ("id", "created", "updated")
 
 
-class PersonConsentAdmin(admin.ModelAdmin):
+class PersonConsentAdmin(VersionAdmin):
     list_display = ("id", "person", "get_consent_name_en")
     readonly_fields = ("id", "created", "updated")
 
@@ -82,7 +83,7 @@ class PersonConsentAdmin(admin.ModelAdmin):
     get_consent_name_en.short_description = "Consent name"  # type: ignore
 
 
-class OrganizationalUnitAdmin(admin.ModelAdmin):
+class OrganizationalUnitAdmin(VersionAdmin):
     list_display = ("id", "name_en", "parent")
     readonly_fields = ("id", "created", "updated")
 
@@ -92,13 +93,13 @@ class OrganizationalUnitInline(admin.TabularInline):
     extra = 1
 
 
-class SponsorAdmin(admin.ModelAdmin):
+class SponsorAdmin(VersionAdmin):
     list_display = ("id", "feide_id")
     inlines = (OrganizationalUnitInline,)
     readonly_fields = ("id", "created", "updated")
 
 
-class SponsorOrganizationalUnitAdmin(admin.ModelAdmin):
+class SponsorOrganizationalUnitAdmin(VersionAdmin):
     readonly_fields = ("id", "created", "updated")
 
 
diff --git a/gregsite/middleware/__init__.py b/gregsite/middleware/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gregsite/middleware/revision_user_middleware.py b/gregsite/middleware/revision_user_middleware.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddb449bdd12e7560a32912c2e7b1ad6baadc821a
--- /dev/null
+++ b/gregsite/middleware/revision_user_middleware.py
@@ -0,0 +1,81 @@
+from functools import wraps
+
+# from reversion.views import _request_creates_revision, create_revision
+from reversion.revisions import (
+    create_revision as create_revision_base,
+    set_user,
+    get_user,
+    set_comment,
+)
+
+
+class RevisionUserMiddleware:
+
+    """Wraps the entire request in a revision."""
+
+    manage_manually = False
+
+    using = None
+
+    atomic = True
+
+    def __init__(self, get_response):
+        self.get_response = create_revision(
+            manage_manually=self.manage_manually,
+            using=self.using,
+            atomic=self.atomic,
+            request_creates_revision=self.request_creates_revision,
+        )(get_response)
+
+    def request_creates_revision(self, request):
+        return _request_creates_revision(request)
+
+    def __call__(self, request):
+        return self.get_response(request)
+
+
+def _request_creates_revision(request):
+    return request.method not in ("OPTIONS", "GET", "HEAD")
+
+
+def _set_user_from_request(request):
+    if (
+        getattr(request, "user", None)
+        and request.user.is_authenticated
+        and get_user() is None
+    ):
+        set_user(request.user)
+
+
+def _set_comment_to_external_user_from_request(request):
+    if request.headers.get("External-User") is not None:
+        set_comment("User: " + request.headers["External-User"])
+
+
+def create_revision(
+    manage_manually=False, using=None, atomic=True, request_creates_revision=None
+):
+    """
+    View decorator that wraps the request in a revision.
+
+    The revision will have it's user set from the request automatically.
+    """
+    request_creates_revision = request_creates_revision or _request_creates_revision
+
+    def decorator(func):
+        @wraps(func)
+        def do_revision_view(request, *args, **kwargs):
+            if request_creates_revision(request):
+                with create_revision_base(
+                    manage_manually=manage_manually, using=using, atomic=atomic
+                ):
+                    response = func(request, *args, **kwargs)
+                    # Otherwise, we're good.
+                    _set_user_from_request(request)
+                    _set_comment_to_external_user_from_request(request)
+                    return response
+            return func(request, *args, **kwargs)
+
+        return do_revision_view
+
+    return decorator
diff --git a/gregsite/settings/base.py b/gregsite/settings/base.py
index 11236a8a01cee893538b3e94dea43b32bfa6d865..b80718ce8c7e74da5e1835aafed752d5713a01e3 100644
--- a/gregsite/settings/base.py
+++ b/gregsite/settings/base.py
@@ -44,6 +44,7 @@ INSTALLED_APPS = [
     "django_extensions",
     "django_filters",
     "greg",
+    "reversion",
 ]
 
 MIDDLEWARE = [
@@ -55,6 +56,7 @@ MIDDLEWARE = [
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    "gregsite.middleware.revision_user_middleware.RevisionUserMiddleware",
 ]
 
 REST_FRAMEWORK = {
diff --git a/mypy.ini b/mypy.ini
index 309034f487d4b2e9a90d061b9405b0c5e348b8f0..4c67fd45b03f3a9c9ff3912cbda60058ca0d2599 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -25,3 +25,6 @@ ignore_missing_imports = True
 
 [mypy-faker.*]
 ignore_missing_imports = True
+
+[mypy-reversion.*]
+ignore_missing_imports = True
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
index f7372317ca9f13e2f50d5da9ed0c72fba63e6bee..4b37ce902ee030af285cfdcf53ae171aa4fbaa1e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -210,6 +210,17 @@ python-versions = ">=3.5"
 [package.dependencies]
 Django = ">=2.2"
 
+[[package]]
+name = "django-reversion"
+version = "4.0.0"
+description = "An extension to the Django web framework that provides version control for model instances."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+django = ">=2.0"
+
 [[package]]
 name = "django-stubs"
 version = "1.8.0"
@@ -939,7 +950,7 @@ python-versions = "*"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.9"
-content-hash = "afc64275e89091b126ea8c086a48db4714542e0abf1fd2ca475f6448045bdae1"
+content-hash = "c6f254aafa86cea77b194f1f39e11f7ee6c8225f81041e053f0b498343f90d7d"
 
 [metadata.files]
 appdirs = [
@@ -1018,6 +1029,10 @@ django-filter = [
     {file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"},
     {file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"},
 ]
+django-reversion = [
+    {file = "django-reversion-4.0.0.tar.gz", hash = "sha256:ad6d714b4b9b824e22b88d47201cc0f74b5c4294c8d4e1f8d7ac7c3631ef3188"},
+    {file = "django_reversion-4.0.0-py3-none-any.whl", hash = "sha256:f059c654e38c0dd8dccd7f0990aa2f6d9ad22dab55c5e095f9596aeda8079dcd"},
+]
 django-stubs = [
     {file = "django-stubs-1.8.0.tar.gz", hash = "sha256:717967d7fee0a6af0746724a0be80d72831a982a40fa8f245a6a46f4cafd157b"},
     {file = "django_stubs-1.8.0-py3-none-any.whl", hash = "sha256:bde9e44e3c4574c2454e74a3e607cc3bc23b0441bb7d1312cd677d5e30984b74"},
@@ -1329,18 +1344,26 @@ pyyaml = [
     {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
     {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
     {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
+    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
     {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
     {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
     {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
+    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
     {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
     {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
     {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
+    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
     {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
     {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
     {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
+    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
     {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
     {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
     {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
diff --git a/pyproject.toml b/pyproject.toml
index 3c37be1f19cc9916333fd80334804e810a682f3c..90b2373de6d04aece61eea173bed1ddcf4e2380e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,6 +18,7 @@ python = "^3.9"
 python-daemon = "*"
 sentry-sdk = "*"
 whitenoise = "*"
+django-reversion = "^4.0.0"
 
 [tool.poetry.dev-dependencies]
 Faker = "^8.10.1"