diff --git a/AKDashboard/fixtures/dashboard.json b/AKDashboard/fixtures/dashboard.json
new file mode 100644
index 0000000000000000000000000000000000000000..dde1a64d08ef64648bf7e862ef2aa33e15805c0a
--- /dev/null
+++ b/AKDashboard/fixtures/dashboard.json
@@ -0,0 +1,13 @@
+[
+{
+    "model": "AKDashboard.dashboardbutton",
+    "pk": 1,
+    "fields": {
+        "text": "Wiki",
+        "url": "http://wiki.kif.rocks",
+        "icon": "fab,wikipedia-w",
+        "color": 2,
+        "event": 2
+    }
+}
+]
diff --git a/AKDashboard/tests.py b/AKDashboard/tests.py
index f4fbff87e12c28df6ca244a5cf046d939d5b06cc..dc77d528ebee1d08a422e5d65db5f1f626159a9b 100644
--- a/AKDashboard/tests.py
+++ b/AKDashboard/tests.py
@@ -6,6 +6,7 @@ from django.utils.timezone import now
 
 from AKDashboard.models import DashboardButton
 from AKModel.models import Event, AK, AKCategory
+from AKModel.tests import BasicViewTests
 
 
 class DashboardTests(TestCase):
@@ -126,3 +127,14 @@ class DashboardTests(TestCase):
 
         response = self.client.get(url_event_dashboard)
         self.assertContains(response, "Dashboard Button Test")
+
+
+class DashboardViewTests(BasicViewTests, TestCase):
+    fixtures = ['model.json', 'dashboard.json']
+
+    APP_NAME = 'dashboard'
+
+    VIEWS = [
+        ('dashboard', {}),
+        ('dashboard_event', {'slug': 'kif42'}),
+    ]
diff --git a/AKModel/fixtures/model.json b/AKModel/fixtures/model.json
new file mode 100644
index 0000000000000000000000000000000000000000..b75d5093156cead8c107d0493b64ac4515fe641b
--- /dev/null
+++ b/AKModel/fixtures/model.json
@@ -0,0 +1,670 @@
+[
+{
+    "model": "AKModel.event",
+    "pk": 1,
+    "fields": {
+        "name": "KIF 23",
+        "slug": "kif23",
+        "place": "Neuland",
+        "timezone": "Europe/Berlin",
+        "start": "2020-10-01T17:41:22Z",
+        "end": "2020-10-04T17:41:30Z",
+        "reso_deadline": "2020-10-03T10:00:00Z",
+        "interest_start": null,
+        "interest_end": null,
+        "public": true,
+        "active": false,
+        "plan_hidden": true,
+        "plan_published_at": null,
+        "base_url": "",
+        "wiki_export_template_name": "",
+        "default_slot": "2.00",
+        "contact_email": "dummy@eyample.org"
+    }
+},
+{
+    "model": "AKModel.event",
+    "pk": 2,
+    "fields": {
+        "name": "KIF 42",
+        "slug": "kif42",
+        "place": "Neuland noch neuer",
+        "timezone": "Europe/Berlin",
+        "start": "2020-11-06T17:51:26Z",
+        "end": "2020-11-10T17:51:27Z",
+        "reso_deadline": "2020-11-08T17:51:42Z",
+        "interest_start": null,
+        "interest_end": null,
+        "public": true,
+        "active": true,
+        "plan_hidden": false,
+        "plan_published_at": "2022-12-01T21:50:01Z",
+        "base_url": "",
+        "wiki_export_template_name": "",
+        "default_slot": "2.00",
+        "contact_email": "dummy@example.org"
+    }
+},
+{
+    "model": "AKModel.akowner",
+    "pk": 1,
+    "fields": {
+        "name": "a",
+        "slug": "a",
+        "institution": "Uni X",
+        "link": "",
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.akowner",
+    "pk": 2,
+    "fields": {
+        "name": "b",
+        "slug": "b",
+        "institution": "Hochschule Y",
+        "link": "",
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.akowner",
+    "pk": 3,
+    "fields": {
+        "name": "c",
+        "slug": "c",
+        "institution": "",
+        "link": "",
+        "event": 1
+    }
+},
+{
+    "model": "AKModel.akowner",
+    "pk": 4,
+    "fields": {
+        "name": "d",
+        "slug": "d",
+        "institution": "",
+        "link": "",
+        "event": 1
+    }
+},
+{
+    "model": "AKModel.akcategory",
+    "pk": 1,
+    "fields": {
+        "name": "Spaâ–€",
+        "color": "275246",
+        "description": "",
+        "present_by_default": true,
+        "event": 1
+    }
+},
+{
+    "model": "AKModel.akcategory",
+    "pk": 2,
+    "fields": {
+        "name": "Ernst",
+        "color": "234567",
+        "description": "",
+        "present_by_default": true,
+        "event": 1
+    }
+},
+{
+    "model": "AKModel.akcategory",
+    "pk": 3,
+    "fields": {
+        "name": "Spaâ–€/Kultur",
+        "color": "333333",
+        "description": "",
+        "present_by_default": true,
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.akcategory",
+    "pk": 4,
+    "fields": {
+        "name": "Inhalt",
+        "color": "456789",
+        "description": "",
+        "present_by_default": true,
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.akcategory",
+    "pk": 5,
+    "fields": {
+        "name": "Meta",
+        "color": "111111",
+        "description": "",
+        "present_by_default": true,
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.aktrack",
+    "pk": 1,
+    "fields": {
+        "name": "Track 1",
+        "color": "",
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.aktag",
+    "pk": 1,
+    "fields": {
+        "name": "metametameta"
+    }
+},
+{
+    "model": "AKModel.akrequirement",
+    "pk": 1,
+    "fields": {
+        "name": "Beamer",
+        "event": 1
+    }
+},
+{
+    "model": "AKModel.akrequirement",
+    "pk": 2,
+    "fields": {
+        "name": "Internet",
+        "event": 1
+    }
+},
+{
+    "model": "AKModel.akrequirement",
+    "pk": 3,
+    "fields": {
+        "name": "BBB",
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.akrequirement",
+    "pk": 4,
+    "fields": {
+        "name": "Mumble",
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.akrequirement",
+    "pk": 5,
+    "fields": {
+        "name": "Matrix",
+        "event": 2
+    }
+},
+{
+    "model": "AKModel.historicalak",
+    "pk": 1,
+    "fields": {
+        "id": 1,
+        "name": "Test AK Inhalt",
+        "short_name": "test1",
+        "description": "",
+        "link": "",
+        "protocol_link": "",
+        "reso": false,
+        "present": true,
+        "notes": "",
+        "interest": -1,
+        "category": 4,
+        "track": null,
+        "event": 2,
+        "history_date": "2021-05-04T10:28:59.265Z",
+        "history_change_reason": null,
+        "history_type": "+",
+        "history_user": null
+    }
+},
+{
+    "model": "AKModel.historicalak",
+    "pk": 2,
+    "fields": {
+        "id": 1,
+        "name": "Test AK Inhalt",
+        "short_name": "test1",
+        "description": "",
+        "link": "",
+        "protocol_link": "",
+        "reso": false,
+        "present": true,
+        "notes": "",
+        "interest": -1,
+        "category": 4,
+        "track": null,
+        "event": 2,
+        "history_date": "2021-05-04T10:28:59.305Z",
+        "history_change_reason": null,
+        "history_type": "~",
+        "history_user": null
+    }
+},
+{
+    "model": "AKModel.historicalak",
+    "pk": 3,
+    "fields": {
+        "id": 2,
+        "name": "Test AK Meta",
+        "short_name": "test2",
+        "description": "",
+        "link": "",
+        "protocol_link": "",
+        "reso": false,
+        "present": null,
+        "notes": "",
+        "interest": -1,
+        "category": 5,
+        "track": null,
+        "event": 2,
+        "history_date": "2021-05-04T10:29:40.296Z",
+        "history_change_reason": null,
+        "history_type": "+",
+        "history_user": null
+    }
+},
+{
+    "model": "AKModel.historicalak",
+    "pk": 4,
+    "fields": {
+        "id": 2,
+        "name": "Test AK Meta",
+        "short_name": "test2",
+        "description": "",
+        "link": "",
+        "protocol_link": "",
+        "reso": false,
+        "present": null,
+        "notes": "",
+        "interest": -1,
+        "category": 5,
+        "track": null,
+        "event": 2,
+        "history_date": "2021-05-04T10:29:40.355Z",
+        "history_change_reason": null,
+        "history_type": "~",
+        "history_user": null
+    }
+},
+{
+    "model": "AKModel.historicalak",
+    "pk": 5,
+    "fields": {
+        "id": 3,
+        "name": "AK Wish",
+        "short_name": "wish1",
+        "description": "Description of my Wish",
+        "link": "",
+        "protocol_link": "",
+        "reso": false,
+        "present": null,
+        "notes": "We need to find a volunteer first...",
+        "interest": -1,
+        "category": 3,
+        "track": null,
+        "event": 2,
+        "history_date": "2021-05-04T10:30:59.469Z",
+        "history_change_reason": null,
+        "history_type": "+",
+        "history_user": null
+    }
+},
+{
+    "model": "AKModel.historicalak",
+    "pk": 6,
+    "fields": {
+        "id": 3,
+        "name": "AK Wish",
+        "short_name": "wish1",
+        "description": "Description of my Wish",
+        "link": "",
+        "protocol_link": "",
+        "reso": false,
+        "present": null,
+        "notes": "We need to find a volunteer first...",
+        "interest": -1,
+        "category": 3,
+        "track": null,
+        "event": 2,
+        "history_date": "2021-05-04T10:30:59.509Z",
+        "history_change_reason": null,
+        "history_type": "~",
+        "history_user": null
+    }
+},
+{
+    "model": "AKModel.historicalak",
+    "pk": 7,
+    "fields": {
+        "id": 2,
+        "name": "Test AK Meta",
+        "short_name": "test2",
+        "description": "",
+        "link": "",
+        "protocol_link": "",
+        "reso": false,
+        "present": null,
+        "notes": "",
+        "interest": -1,
+        "category": 5,
+        "track": 1,
+        "event": 2,
+        "history_date": "2022-12-02T12:27:14.277Z",
+        "history_change_reason": null,
+        "history_type": "~",
+        "history_user": null
+    }
+},
+{
+    "model": "AKModel.ak",
+    "pk": 1,
+    "fields": {
+        "name": "Test AK Inhalt",
+        "short_name": "test1",
+        "description": "",
+        "link": "",
+        "protocol_link": "",
+        "category": 4,
+        "track": null,
+        "reso": false,
+        "present": true,
+        "notes": "",
+        "interest": -1,
+        "interest_counter": 0,
+        "event": 2,
+        "owners": [
+            1
+        ],
+        "tags": [],
+        "requirements": [
+            3
+        ],
+        "conflicts": [],
+        "prerequisites": []
+    }
+},
+{
+    "model": "AKModel.ak",
+    "pk": 2,
+    "fields": {
+        "name": "Test AK Meta",
+        "short_name": "test2",
+        "description": "",
+        "link": "",
+        "protocol_link": "",
+        "category": 5,
+        "track": 1,
+        "reso": false,
+        "present": null,
+        "notes": "",
+        "interest": -1,
+        "interest_counter": 0,
+        "event": 2,
+        "owners": [
+            2
+        ],
+        "tags": [
+            1
+        ],
+        "requirements": [],
+        "conflicts": [],
+        "prerequisites": []
+    }
+},
+{
+    "model": "AKModel.ak",
+    "pk": 3,
+    "fields": {
+        "name": "AK Wish",
+        "short_name": "wish1",
+        "description": "Description of my Wish",
+        "link": "",
+        "protocol_link": "",
+        "category": 3,
+        "track": null,
+        "reso": false,
+        "present": null,
+        "notes": "We need to find a volunteer first...",
+        "interest": -1,
+        "interest_counter": 0,
+        "event": 2,
+        "owners": [],
+        "tags": [],
+        "requirements": [
+            4
+        ],
+        "conflicts": [
+            1
+        ],
+        "prerequisites": [
+            2
+        ]
+    }
+},
+{
+    "model": "AKModel.room",
+    "pk": 1,
+    "fields": {
+        "name": "Testraum",
+        "location": "BBB",
+        "capacity": -1,
+        "event": 2,
+        "properties": [
+            3
+        ]
+    }
+},
+{
+    "model": "AKModel.room",
+    "pk": 2,
+    "fields": {
+        "name": "Testraum 2",
+        "location": "",
+        "capacity": 30,
+        "event": 2,
+        "properties": []
+    }
+},
+{
+    "model": "AKModel.akslot",
+    "pk": 1,
+    "fields": {
+        "ak": 1,
+        "room": 2,
+        "start": "2020-11-06T18:30:00Z",
+        "duration": "2.00",
+        "fixed": false,
+        "event": 2,
+        "updated": "2022-12-02T12:23:11.856Z"
+    }
+},
+{
+    "model": "AKModel.akslot",
+    "pk": 2,
+    "fields": {
+        "ak": 2,
+        "room": 1,
+        "start": "2020-11-08T12:30:00Z",
+        "duration": "2.00",
+        "fixed": false,
+        "event": 2,
+        "updated": "2022-12-02T12:23:18.279Z"
+    }
+},
+{
+    "model": "AKModel.akslot",
+    "pk": 3,
+    "fields": {
+        "ak": 3,
+        "room": null,
+        "start": null,
+        "duration": "1.50",
+        "fixed": false,
+        "event": 2,
+        "updated": "2021-05-04T10:30:59.523Z"
+    }
+},
+{
+    "model": "AKModel.akslot",
+    "pk": 4,
+    "fields": {
+        "ak": 3,
+        "room": null,
+        "start": null,
+        "duration": "3.00",
+        "fixed": false,
+        "event": 2,
+        "updated": "2021-05-04T10:30:59.528Z"
+    }
+},
+{
+    "model": "AKModel.constraintviolation",
+    "pk": 1,
+    "fields": {
+        "type": "soa",
+        "level": 10,
+        "event": 2,
+        "ak_owner": null,
+        "room": null,
+        "requirement": null,
+        "category": null,
+        "comment": "",
+        "timestamp": "2022-12-02T12:23:11.875Z",
+        "manually_resolved": false,
+        "aks": [
+            1
+        ],
+        "ak_slots": [
+            1
+        ]
+    }
+},
+{
+    "model": "AKModel.constraintviolation",
+    "pk": 2,
+    "fields": {
+        "type": "rng",
+        "level": 10,
+        "event": 2,
+        "ak_owner": null,
+        "room": 2,
+        "requirement": 3,
+        "category": null,
+        "comment": "",
+        "timestamp": "2022-12-02T12:23:11.890Z",
+        "manually_resolved": false,
+        "aks": [
+            1
+        ],
+        "ak_slots": [
+            1
+        ]
+    }
+},
+{
+    "model": "AKModel.constraintviolation",
+    "pk": 3,
+    "fields": {
+        "type": "soa",
+        "level": 10,
+        "event": 2,
+        "ak_owner": null,
+        "room": null,
+        "requirement": null,
+        "category": null,
+        "comment": "",
+        "timestamp": "2022-12-02T12:23:18.301Z",
+        "manually_resolved": false,
+        "aks": [
+            2
+        ],
+        "ak_slots": [
+            2
+        ]
+    }
+},
+{
+    "model": "AKModel.availability",
+    "pk": 1,
+    "fields": {
+        "event": 2,
+        "person": null,
+        "room": null,
+        "ak": 1,
+        "ak_category": null,
+        "start": "2020-11-07T08:00:00Z",
+        "end": "2020-11-07T18:00:00Z"
+    }
+},
+{
+    "model": "AKModel.availability",
+    "pk": 2,
+    "fields": {
+        "event": 2,
+        "person": null,
+        "room": null,
+        "ak": 1,
+        "ak_category": null,
+        "start": "2020-11-09T10:00:00Z",
+        "end": "2020-11-09T16:30:00Z"
+    }
+},
+{
+    "model": "AKModel.availability",
+    "pk": 3,
+    "fields": {
+        "event": 2,
+        "person": null,
+        "room": 1,
+        "ak": null,
+        "ak_category": null,
+        "start": "2020-11-06T17:51:26Z",
+        "end": "2020-11-10T23:00:00Z"
+    }
+},
+{
+    "model": "AKModel.availability",
+    "pk": 4,
+    "fields": {
+        "event": 2,
+        "person": null,
+        "room": 2,
+        "ak": null,
+        "ak_category": null,
+        "start": "2020-11-06T17:51:26Z",
+        "end": "2020-11-06T21:00:00Z"
+    }
+},
+{
+    "model": "AKModel.availability",
+    "pk": 5,
+    "fields": {
+        "event": 2,
+        "person": null,
+        "room": 2,
+        "ak": null,
+        "ak_category": null,
+        "start": "2020-11-08T15:30:00Z",
+        "end": "2020-11-08T19:30:00Z"
+    }
+},
+{
+    "model": "AKModel.availability",
+    "pk": 6,
+    "fields": {
+        "event": 2,
+        "person": null,
+        "room": 2,
+        "ak": null,
+        "ak_category": null,
+        "start": "2020-11-07T18:30:00Z",
+        "end": "2020-11-07T21:30:00Z"
+    }
+}
+]
diff --git a/AKModel/tests.py b/AKModel/tests.py
index a39b155ac3ee946fb97efafe6ecbb42f571cd7ad..4ba5d32f8d132ff39d7a4c58fb2297eb05263226 100644
--- a/AKModel/tests.py
+++ b/AKModel/tests.py
@@ -1 +1,95 @@
-# Create your tests here.
+from django.contrib.auth.models import User
+from django.test import TestCase
+from django.urls import reverse_lazy
+
+from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKRequirement, AK, Room, AKSlot, AKOrgaMessage, \
+    ConstraintViolation, DefaultSlot
+
+
+class BasicViewTests:
+    VIEWS = []
+    APP_NAME = ''
+    VIEWS_STAFF_ONLY = []
+
+    def setUp(self):
+        self.staff_user = User.objects.create(
+            username='Test Staff User', email='teststaff@example.com', password='staffpw',
+            is_staff=True, is_active=True
+        )
+        self.admin_user = User.objects.create(
+            username='Test Admin User', email='testadmin@example.com', password='adminpw',
+            is_staff=True, is_superuser=True, is_active=True
+        )
+        self.deactivated_user = User.objects.create(
+            username='Test Deactivated User', email='testdeactivated@example.com', password='deactivatedpw',
+            is_staff=True, is_active=False
+        )
+
+    def _name_and_url(self, view_name):
+        """
+        Get full view name (with prefix if there is one) and url from raw view definition
+
+        :param view_name: raw definition of a view
+        :type view_name: (str, dict)
+        :return: full view name with prefix if applicable, url of the view
+        :rtype: str, str
+        """
+        view_name_with_prefix = f"{self.APP_NAME}:{view_name[0]}" if self.APP_NAME != "" else view_name[0]
+        url = reverse_lazy(view_name_with_prefix, kwargs=view_name[1])
+        return view_name_with_prefix, url
+
+    def testViewsFor200(self):
+        for view_name in self.VIEWS:
+            view_name_with_prefix, url = self._name_and_url(view_name)
+            response = self.client.get(url)
+            self.assertEqual(response.status_code, 200, msg=f"{view_name_with_prefix} ({url}) broken")
+
+    def testAccessControlStaffOnly(self):
+        self.client.logout()
+        for view_name in self.VIEWS_STAFF_ONLY:
+            view_name_with_prefix, url = self._name_and_url(view_name)
+            response = self.client.get(url)
+            self.assertEqual(response.status_code, 302, msg=f"{view_name_with_prefix} ({url}) accessible by non-staff")
+
+        self.client.force_login(self.staff_user)
+        for view_name in self.VIEWS_STAFF_ONLY:
+            view_name_with_prefix, url = self._name_and_url(view_name)
+            response = self.client.get(url)
+            self.assertEqual(response.status_code, 200,
+                             msg=f"{view_name_with_prefix} ({url}) should be accessible for staff (but isn't)")
+
+        self.client.force_login(self.deactivated_user)
+        for view_name in self.VIEWS_STAFF_ONLY:
+            view_name_with_prefix, url = self._name_and_url(view_name)
+            response = self.client.get(url)
+            self.assertEqual(response.status_code, 302,
+                             msg=f"{view_name_with_prefix} ({url}) still accessible for deactivated user")
+
+
+class ModelViewTests(BasicViewTests, TestCase):
+    fixtures = ['model.json']
+
+    ADMIN_MODELS = [
+        (Event, 'event'), (AKOwner, 'akowner'), (AKCategory, 'akcategory'), (AKTrack, 'aktrack'),
+        (AKRequirement, 'akrequirement'), (AK, 'ak'), (Room, 'room'), (AKSlot, 'akslot'),
+        (AKOrgaMessage, 'akorgamessage'), (ConstraintViolation, 'constraintviolation'),
+        (DefaultSlot, 'defaultslot')
+    ]
+
+    def testAdmin(self):
+        self.client.force_login(self.admin_user)
+
+        for model in self.ADMIN_MODELS:
+            if model[1] == "event":
+                continue
+            view_name_with_prefix, url = self._name_and_url((f'admin:AKModel_{model[1]}_add', {}))
+            response = self.client.get(url)
+            self.assertEqual(response.status_code, 200, msg=f"Add form for model {model[1]} ({url}) broken")
+
+        for model in self.ADMIN_MODELS:
+            m = model[0].objects.first()
+            if m is not None:
+                view_name_with_prefix, url = self._name_and_url((f'admin:AKModel_{model[1]}_change', {'object_id': m.pk}))
+                print(view_name_with_prefix)
+                response = self.client.get(url)
+                self.assertEqual(response.status_code, 200, msg=f"Edit form for model {model[1]} ({url}) broken")
diff --git a/AKPlan/tests.py b/AKPlan/tests.py
index a39b155ac3ee946fb97efafe6ecbb42f571cd7ad..8a7bd4e1c196076ede94a321bfdf74c6d6b994d1 100644
--- a/AKPlan/tests.py
+++ b/AKPlan/tests.py
@@ -1 +1,28 @@
-# Create your tests here.
+from django.test import TestCase
+
+from AKModel.tests import BasicViewTests
+
+
+class PlanViewTests(BasicViewTests, TestCase):
+    fixtures = ['model.json']
+    APP_NAME = 'plan'
+
+    VIEWS = [
+        ('plan_overview', {'event_slug': 'kif42'}),
+        ('plan_wall', {'event_slug': 'kif42'}),
+        ('plan_room', {'event_slug': 'kif42', 'pk': 2}),
+        ('plan_track', {'event_slug': 'kif42', 'pk': 1}),
+    ]
+
+    def testPlanHidden(self):
+        view_name_with_prefix, url = self._name_and_url(('plan_overview', {'event_slug': 'kif23'}))
+
+        self.client.logout()
+        response = self.client.get(url)
+        self.assertContains(response, "Plan is not visible (yet).",
+                            msg_prefix="Plan is visible even though it shouldn't be")
+
+        self.client.force_login(self.staff_user)
+        response = self.client.get(url)
+        self.assertNotContains(response, "Plan is not visible (yet).",
+                               msg_prefix="Plan is not visible for staff user")