diff --git a/.gitignore b/.gitignore
index 0bfe066ca2b05822fc5894d0fe721a8d99141b45..a6f1e7cba73e51fe9ea6c8c46071019bdeedc42f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,8 +10,7 @@
 .settings/
 .venv/
 .vscode/
-venv/
+venv*/
 
 gregsite/db.sqlite3
 gregsite/settings/local.py
-gregsite/static/
diff --git a/.pylintrc b/.pylintrc
index 5774872c0bb9c0b5ec06a055bdaee2760c318fc3..8bde7c7fa4876a5d976408de308cc62eb530c74a 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -5,6 +5,7 @@ django-settings-module=gregsite.settings
 
 [MESSAGES CONTROL]
 disable=
+    duplicate-code,
     fixme,
     import-outside-toplevel,
     invalid-name,
@@ -13,6 +14,7 @@ disable=
     missing-function-docstring,
     missing-module-docstring,
     no-self-use,
+    redefined-outer-name,
     too-few-public-methods,
     too-many-ancestors,
     unused-argument,
diff --git a/Makefile b/Makefile
index 1172547917e9365efa0a0576cd694c6fe157e6ae..2b5bb24f46695e771b18d2afb1ff472dd92297b9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,18 @@
+DJANGO_SETTINGS_MODULE ?= gregsite.settings.dev
+
 BLACK ?= black -q
 MYPY ?= mypy
 PIP ?= pip -q
 POETRY ?= poetry
 PYLINT ?= pylint -sn
+PYTEST ?= pytest -v -s --no-header
 PYTHON ?= python3.9
 VENV ?= venv
 
 mypy = $(MYPY) --config-file mypy.ini
 pip = python -m $(PIP)
 poetry = python -m $(POETRY)
+pytest = DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) python -m $(PYTEST)
 venv = . $(VENV)/bin/activate &&
 
 PACKAGES = greg/ gregsite/
@@ -30,7 +34,7 @@ $(VENV)/touchfile:
 .PHONY: test
 test: $(VENV)
 	$(venv) $(mypy) -p greg
-	$(venv) python manage.py test
+	$(venv) $(pytest)
 
 .PHONY: lint
 lint: $(VENV)
diff --git a/greg/api/filters.py b/greg/api/filters.py
index 226b3142277e532d7f54e475d4df83e331c1d97f..307fe3202146880b9c943e485356ac6e95558476 100644
--- a/greg/api/filters.py
+++ b/greg/api/filters.py
@@ -12,6 +12,10 @@ class PersonRoleFilter(filters.FilterSet):
 
 
 class PersonFilter(filters.FilterSet):
+    verified = filters.BooleanFilter(
+        field_name="person__verified_by_id", lookup_expr="isnull", exclude=True
+    )
+
     class Meta:
         model = Person
-        fields = ["first_name"]
+        fields = ["first_name", "last_name", "verified"]
diff --git a/greg/api/serializers/consent.py b/greg/api/serializers/consent.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c1663f2da723d94f477a82e3fb97d9351892e35
--- /dev/null
+++ b/greg/api/serializers/consent.py
@@ -0,0 +1,9 @@
+from rest_framework.serializers import ModelSerializer
+
+from greg.models import Consent
+
+
+class ConsentSerializer(ModelSerializer):
+    class Meta:
+        model = Consent
+        fields = "__all__"
diff --git a/greg/api/serializers/organizational_unit.py b/greg/api/serializers/organizational_unit.py
new file mode 100644
index 0000000000000000000000000000000000000000..12251bffff79f9119d76e9e1f7ddcc58c2984b68
--- /dev/null
+++ b/greg/api/serializers/organizational_unit.py
@@ -0,0 +1,9 @@
+from rest_framework.serializers import ModelSerializer
+
+from greg.models import OrganizationalUnit
+
+
+class OrganizationalUnitSerializer(ModelSerializer):
+    class Meta:
+        model = OrganizationalUnit
+        fields = "__all__"
diff --git a/greg/api/serializers/person.py b/greg/api/serializers/person.py
index ba5b55ed0cfdaeb67930fa4b0e5b2e6ea5a34434..77eec13afec9c235552e84b503b085ee80c44a77 100644
--- a/greg/api/serializers/person.py
+++ b/greg/api/serializers/person.py
@@ -6,7 +6,14 @@ from greg.models import Person, PersonRole, Role
 class PersonSerializer(serializers.ModelSerializer):
     class Meta:
         model = Person
-        fields = ("id", "first_name", "last_name", "date_of_birth", "email", "mobile_phone")
+        fields = [
+            "id",
+            "first_name",
+            "last_name",
+            "date_of_birth",
+            "email",
+            "mobile_phone",
+        ]
 
 
 class PersonRoleSerializer(serializers.ModelSerializer):
@@ -16,6 +23,10 @@ class PersonRoleSerializer(serializers.ModelSerializer):
         model = PersonRole
         fields = [
             "id",
+            "start_date",
+            "end_date",
+            "registered_by",
+            "unit",
             "created",
             "updated",
             "role",
diff --git a/greg/api/serializers/sponsor.py b/greg/api/serializers/sponsor.py
new file mode 100644
index 0000000000000000000000000000000000000000..57326558e9999d307412bed70d79665ae63c8821
--- /dev/null
+++ b/greg/api/serializers/sponsor.py
@@ -0,0 +1,9 @@
+from rest_framework import serializers
+
+from greg.models import Sponsor
+
+
+class SponsorSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Sponsor
+        fields = ["id", "feide_id"]
diff --git a/greg/api/urls.py b/greg/api/urls.py
index 62ee2d499a1cc1ab3e866522c8dd7b8e6c1159f3..69743c28704421b8128bbe25b19f66d3e7412a42 100644
--- a/greg/api/urls.py
+++ b/greg/api/urls.py
@@ -1,15 +1,30 @@
-from django.conf.urls import url
-from django.urls import path
+from django.urls import (
+    path,
+    re_path,
+)
 from rest_framework.routers import DefaultRouter
-from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
+from drf_spectacular.views import (
+    SpectacularAPIView,
+    SpectacularSwaggerView,
+)
 
-from greg.api.views.person import PersonViewSet, PersonRoleViewSet
+from greg.api.views.consent import ConsentViewSet
+from greg.api.views.organizational_unit import OrganizationalUnitViewSet
+from greg.api.views.person import (
+    PersonRoleViewSet,
+    PersonViewSet,
+)
 from greg.api.views.role import RoleViewSet
 from greg.api.views.health import Health
+from greg.api.views.sponsor import SponsorViewSet
 
 router = DefaultRouter()
 router.register(r"persons", PersonViewSet, basename="person")
 router.register(r"roles", RoleViewSet, basename="role")
+router.register(r"consents", ConsentViewSet, basename="consent")
+router.register(r"sponsors", SponsorViewSet, basename="sponsor")
+router.register(r"orgunit", OrganizationalUnitViewSet, basename="orgunit")
+
 
 urlpatterns = router.urls
 
@@ -21,12 +36,12 @@ urlpatterns += [
         name="swagger-ui",
     ),
     path("health/", Health.as_view()),
-    url(
+    re_path(
         r"^persons/(?P<person_id>[0-9]+)/roles/$",
-        PersonRoleViewSet.as_view({"get": "list"}),
+        PersonRoleViewSet.as_view({"get": "list", "post": "create"}),
         name="person_role-list",
     ),
-    url(
+    re_path(
         r"^persons/(?P<person_id>[0-9]+)/roles/(?P<id>[0-9]+)/$",
         PersonRoleViewSet.as_view({"get": "retrieve"}),
         name="person_role-detail",
diff --git a/greg/api/views/consent.py b/greg/api/views/consent.py
new file mode 100644
index 0000000000000000000000000000000000000000..4996325e7cb9b40ffb64fde25a8d6b374e81d3e6
--- /dev/null
+++ b/greg/api/views/consent.py
@@ -0,0 +1,14 @@
+from rest_framework import viewsets
+
+from greg.api.pagination import PrimaryKeyCursorPagination
+from greg.api.serializers.consent import ConsentSerializer
+from greg.models import Consent
+
+
+class ConsentViewSet(viewsets.ModelViewSet):
+    """Consent API"""
+
+    queryset = Consent.objects.all().order_by("id")
+    serializer_class = ConsentSerializer
+    pagination_class = PrimaryKeyCursorPagination
+    lookup_field = "id"
diff --git a/greg/api/views/organizational_unit.py b/greg/api/views/organizational_unit.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6d2236cd68f3318629a4595f4ee6d3f1755918e
--- /dev/null
+++ b/greg/api/views/organizational_unit.py
@@ -0,0 +1,14 @@
+from rest_framework import viewsets
+
+from greg.api.pagination import PrimaryKeyCursorPagination
+from greg.api.serializers.organizational_unit import OrganizationalUnitSerializer
+from greg.models import OrganizationalUnit
+
+
+class OrganizationalUnitViewSet(viewsets.ModelViewSet):
+    """OrganizationalUnit API"""
+
+    queryset = OrganizationalUnit.objects.all().order_by("id")
+    serializer_class = OrganizationalUnitSerializer
+    pagination_class = PrimaryKeyCursorPagination
+    lookup_field = "id"
diff --git a/greg/api/views/person.py b/greg/api/views/person.py
index 556cc8ce2ade09badc0c386cd6c9da67869d4cee..3566e930e53ba46bc3f6682d0b59eddbc7e30632 100644
--- a/greg/api/views/person.py
+++ b/greg/api/views/person.py
@@ -1,10 +1,11 @@
+from django.core.exceptions import ValidationError
 from django_filters import rest_framework as filters
-
+from drf_spectacular.utils import extend_schema, OpenApiParameter
 from rest_framework import viewsets
 
+from greg.api.filters import PersonFilter, PersonRoleFilter
 from greg.api.pagination import PrimaryKeyCursorPagination
 from greg.api.serializers.person import PersonSerializer, PersonRoleSerializer
-from greg.api.filters import PersonFilter, PersonRoleFilter
 from greg.models import Person, PersonRole
 
 
@@ -18,6 +19,20 @@ class PersonViewSet(viewsets.ModelViewSet):
     filter_backends = (filters.DjangoFilterBackend,)
     filterset_class = PersonFilter
 
+    @extend_schema(
+        parameters=[
+            OpenApiParameter(
+                name="verified",
+                description="Only include verified or only not verified. When nothing is set, "
+                "both verified and not verified are returned",
+                required=False,
+                type=bool,
+            )
+        ]
+    )
+    def list(self, request, *args, **kwargs):
+        return super().list(request)
+
 
 class PersonRoleViewSet(viewsets.ModelViewSet):
     """Person role API"""
@@ -38,3 +53,12 @@ class PersonRoleViewSet(viewsets.ModelViewSet):
         if person_role_id:
             qs = qs.filter(id=person_role_id)
         return qs
+
+    def perform_create(self, serializer):
+        person_id = self.kwargs["person_id"]
+
+        if person_id is None:
+            # Should not happen, the person ID is part of the API path
+            raise ValidationError("No person id")
+
+        serializer.save(person_id=person_id)
diff --git a/greg/api/views/sponsor.py b/greg/api/views/sponsor.py
new file mode 100644
index 0000000000000000000000000000000000000000..0251a2e86274d368d61508c2c937b1205e8ea6e3
--- /dev/null
+++ b/greg/api/views/sponsor.py
@@ -0,0 +1,14 @@
+from rest_framework.viewsets import ReadOnlyModelViewSet
+
+from greg.api.pagination import PrimaryKeyCursorPagination
+from greg.api.serializers.sponsor import SponsorSerializer
+from greg.models import Sponsor
+
+
+class SponsorViewSet(ReadOnlyModelViewSet):
+    """Sponsor API"""
+
+    queryset = Sponsor.objects.all().order_by("id")
+    serializer_class = SponsorSerializer
+    pagination_class = PrimaryKeyCursorPagination
+    lookup_field = "id"
diff --git a/greg/apps.py b/greg/apps.py
index d3307bd4c24714e32a3e648524e5a9724a5a8a5a..64d48ad212bcd358df71dbe338d43a234baa98fe 100644
--- a/greg/apps.py
+++ b/greg/apps.py
@@ -1,5 +1,5 @@
 from django.apps import AppConfig
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
 
 
 class GregAppConfig(AppConfig):
diff --git a/greg/migrations/0001_initial.py b/greg/migrations/0001_initial.py
index 7b7d22a9ab6213f16c893aeb695899daf2016709..45ff7241e90d4f660c2c1dccde38bcbddf44fd76 100644
--- a/greg/migrations/0001_initial.py
+++ b/greg/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.2.5 on 2021-07-14 12:28
+# Generated by Django 3.2.5 on 2021-07-15 13:31
 
 import datetime
 import dirtyfields.dirtyfields
@@ -147,6 +147,9 @@ class Migration(migrations.Migration):
                 ('role', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='person_roles', to='greg.role')),
                 ('unit', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='unit_person_role', to='greg.organizationalunit')),
             ],
+            options={
+                'abstract': False,
+            },
             bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
         ),
         migrations.CreateModel(
@@ -198,10 +201,6 @@ class Migration(migrations.Migration):
             model_name='sponsor',
             constraint=models.UniqueConstraint(fields=('feide_id',), name='unique_feide_id'),
         ),
-        migrations.AddConstraint(
-            model_name='personrole',
-            constraint=models.UniqueConstraint(fields=('person', 'role', 'unit'), name='personrole_person_role_unit_unique'),
-        ),
         migrations.AddConstraint(
             model_name='personconsent',
             constraint=models.UniqueConstraint(fields=('person', 'consent'), name='person_consent_unique'),
diff --git a/greg/models.py b/greg/models.py
index 4db81b57b4d6150a68a501e9ffb786ef927e3fbc..fc471b9133b4e04fb5d530d02d54441e70711bb3 100644
--- a/greg/models.py
+++ b/greg/models.py
@@ -103,14 +103,6 @@ class PersonRole(BaseModel):
         "Sponsor", on_delete=models.PROTECT, related_name="sponsor_role"
     )
 
-    class Meta:
-        constraints = [
-            models.UniqueConstraint(
-                fields=["person", "role", "unit"],
-                name="personrole_person_role_unit_unique",
-            )
-        ]
-
     def __repr__(self):
         return "{}(id={!r}, person={!r}, role={!r})".format(
             self.__class__.__name__, self.pk, self.person, self.role
diff --git a/greg/tests/api/__init__.py b/greg/tests/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/greg/tests/api/test_consent.py b/greg/tests/api/test_consent.py
new file mode 100644
index 0000000000000000000000000000000000000000..24ae8c837c73054d8edcad98abcefa1f39624e59
--- /dev/null
+++ b/greg/tests/api/test_consent.py
@@ -0,0 +1,30 @@
+import pytest
+from rest_framework import status
+from rest_framework.reverse import reverse
+
+from greg.models import Consent
+
+
+@pytest.fixture
+def consent_foo() -> Consent:
+    return Consent.objects.create(
+        type="test_consent",
+        consent_name_en="Test1",
+        consent_name_nb="Test2",
+        consent_description_en="Test description",
+        consent_description_nb="Test beskrivelse",
+        consent_link_en="https://example.org",
+        consent_link_nb="https://example.org",
+        valid_from="2018-01-20",
+        user_allowed_to_change=True,
+    )
+
+
+@pytest.mark.django_db
+def test_get_consent(client, consent_foo):
+    resp = client.get(reverse("consent-detail", kwargs={"id": consent_foo.id}))
+    assert resp.status_code == status.HTTP_200_OK
+    data = resp.json()
+    assert data.get("id") == consent_foo.id
+    assert data.get("type") == consent_foo.type
+    assert data.get("consent_name_en") == consent_foo.consent_name_en
diff --git a/greg/tests/api/test_person.py b/greg/tests/api/test_person.py
new file mode 100644
index 0000000000000000000000000000000000000000..f79d9d26a2475b542cc22dda4c9dbb7ed7104ce4
--- /dev/null
+++ b/greg/tests/api/test_person.py
@@ -0,0 +1,96 @@
+import pytest
+
+from rest_framework import status
+from rest_framework.reverse import reverse
+from rest_framework.status import HTTP_200_OK
+
+from greg.models import Person
+
+
+@pytest.fixture
+def person_foo() -> Person:
+    return Person.objects.create(
+        first_name="Foo",
+        last_name="Foo",
+        date_of_birth="2001-01-27",
+        email="test@example.org",
+        mobile_phone="123456788",
+    )
+
+
+@pytest.fixture
+def person_bar() -> Person:
+    return Person.objects.create(
+        first_name="Bar",
+        last_name="Bar",
+        date_of_birth="2000-07-01",
+        email="test2@example.org",
+        mobile_phone="123456789",
+    )
+
+
+@pytest.mark.django_db
+def test_get_person(client, person_foo):
+    resp = client.get(reverse("person-detail", kwargs={"id": person_foo.id}))
+    assert resp.status_code == HTTP_200_OK
+    data = resp.json()
+    assert data.get("id") == person_foo.id
+    assert data.get("first_name") == person_foo.first_name
+    assert data.get("last_name") == person_foo.last_name
+
+
+@pytest.mark.django_db
+def test_persons(client, person_foo, person_bar):
+    resp = client.get(reverse("person-list"))
+    assert resp.status_code == HTTP_200_OK
+    data = resp.json()
+    assert len(data["results"]) == 2
+
+
+@pytest.mark.django_db
+def test_persons_verified_filter_include(client, setup_db_test_data):
+    url = reverse("person-list")
+    response = client.get(url, {"verified": "true"})
+    results = response.json()["results"]
+    assert len(results) == 1
+    # The following person will have a verified identity set up for him
+    # in the test data
+    assert results[0]["first_name"] == "Christopher"
+    assert results[0]["last_name"] == "Flores"
+
+
+@pytest.mark.django_db
+def test_persons_verified_filter_exclude(client, setup_db_test_data):
+    url = reverse("person-list")
+    response = client.get(url, {"verified": "false"})
+    results = response.json()["results"]
+    names = [(result["first_name"], result["last_name"]) for result in results]
+    assert len(results) == 9
+    assert ("Christopher", "Flores") not in names
+
+
+@pytest.mark.django_db
+def test_add_role(client, person_foo):
+    url = reverse("person_role-list", kwargs={"person_id": person_foo.id})
+    roles_for_person = client.get(url).json()["results"]
+
+    # Check that there are no roles for the person, and then add one
+    assert len(roles_for_person) == 0
+
+    role_data = {
+        "role": "Visiting Professor",
+        "start_date": "2021-06-10",
+        "end_date": "2021-08-10",
+        "registered_by": "1",
+        "unit": "1",
+    }
+    response = client.post(url, role_data)
+
+    assert response.status_code == status.HTTP_201_CREATED
+
+    response_data = response.json()
+    roles_for_person = client.get(url).json()["results"]
+
+    # Check that the role shows up when listing roles for the person now
+    assert len(roles_for_person) == 1
+    assert roles_for_person[0]["id"] == response_data["id"]
diff --git a/greg/tests/conftest.py b/greg/tests/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..bbdf24d3973f2d452e7e45e22ae1a81e2bcda7b5
--- /dev/null
+++ b/greg/tests/conftest.py
@@ -0,0 +1,34 @@
+import logging
+
+from rest_framework.authtoken.models import Token
+from rest_framework.test import APIClient
+from django.contrib.auth import get_user_model
+
+
+# faker spams the logs with localisation warnings
+# see https://github.com/joke2k/faker/issues/753
+import pytest
+
+from greg.tests.populate_database import DatabasePopulation
+
+logging.getLogger("faker").setLevel(logging.ERROR)
+
+
+# The database is populated once when scope is session.
+# If the scope is changed to function some additional
+# logic is needed to make sure the old data is cleaned
+# before the seeding is run again
+@pytest.fixture(scope="session")
+def setup_db_test_data(django_db_setup, django_db_blocker):
+    with django_db_blocker.unblock():
+        database_seeder = DatabasePopulation()
+        database_seeder.populate_database()
+
+
+@pytest.fixture
+def client() -> APIClient:
+    user, _ = get_user_model().objects.get_or_create(username="test")
+    token, _ = Token.objects.get_or_create(user=user)
+    client = APIClient()
+    client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}")
+    return client
diff --git a/greg/tests/models/__init__.py b/greg/tests/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/greg/tests/models/test_consent.py b/greg/tests/models/test_consent.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad800bd596686e43520556a1acb2ec22aa2a76c6
--- /dev/null
+++ b/greg/tests/models/test_consent.py
@@ -0,0 +1,31 @@
+import pytest
+
+from greg.models import (
+    Person,
+    Consent,
+)
+
+
+@pytest.fixture
+def person() -> Person:
+    return Person.objects.create(
+        first_name="Test",
+        last_name="Tester",
+        date_of_birth="2000-01-27",
+        email="test@example.org",
+        mobile_phone="123456789",
+    )
+
+
+@pytest.mark.django_db
+def test_add_consent_to_person(person):
+    consent = Consent.objects.create(
+        type="it_guidelines",
+        consent_name_en="IT Guidelines",
+        consent_name_nb="IT Regelverk",
+        consent_description_en="IT Guidelines description",
+        consent_description_nb="IT Regelverk beskrivelse",
+        consent_link_en="https://example.org/it_guidelines",
+        user_allowed_to_change=False,
+    )
+    person.consents.add(consent, through_defaults={"consent_given_at": "2021-06-20"})
diff --git a/greg/tests/models/test_organizational_unit.py b/greg/tests/models/test_organizational_unit.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd61b87e016fc572805b2cb936c96f051a87f5d2
--- /dev/null
+++ b/greg/tests/models/test_organizational_unit.py
@@ -0,0 +1,10 @@
+import pytest
+
+from greg.models import OrganizationalUnit
+
+
+@pytest.mark.django_db
+def test_set_parent():
+    parent = OrganizationalUnit.objects.create(orgreg_id="parent")
+    child = OrganizationalUnit.objects.create(orgreg_id="child", parent=parent)
+    assert list(OrganizationalUnit.objects.filter(parent__id=parent.id)) == [child]
diff --git a/greg/tests/models/test_person.py b/greg/tests/models/test_person.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb851f06594a6aa9e9e5996c52cb794f9ff3622d
--- /dev/null
+++ b/greg/tests/models/test_person.py
@@ -0,0 +1,76 @@
+from functools import partial
+
+import pytest
+
+from django.db.models import ProtectedError
+
+from greg.models import (
+    OrganizationalUnit,
+    Person,
+    PersonRole,
+    Role,
+    Sponsor,
+)
+
+
+person_role_with = partial(
+    PersonRole.objects.create,
+    start_date="2020-03-05",
+    end_date="2020-06-10",
+    contact_person_unit="Contact Person",
+    available_in_search=True,
+)
+
+
+@pytest.fixture
+def role_foo() -> Role:
+    return Role.objects.create(type="role_foo", name_en="Role Foo")
+
+
+@pytest.fixture
+def role_bar() -> Role:
+    return Role.objects.create(type="role_bar", name_en="Role Bar")
+
+
+@pytest.fixture
+def person(role_foo, role_bar) -> Person:
+    person = Person.objects.create(
+        first_name="Test",
+        last_name="Tester",
+        date_of_birth="2000-01-27",
+        email="test@example.org",
+        mobile_phone="123456789",
+    )
+
+    ou = OrganizationalUnit.objects.create(orgreg_id="12345", name_en="Test unit")
+    person_role_with(
+        person=person,
+        role=role_foo,
+        unit=ou,
+        registered_by=Sponsor.objects.create(feide_id="foosponsor@uio.no"),
+    )
+    person_role_with(
+        person=person,
+        role=role_bar,
+        unit=ou,
+        registered_by=Sponsor.objects.create(feide_id="barsponsor@uio.no"),
+    )
+
+    return person
+
+
+@pytest.mark.django_db
+def test_add_multiple_roles_to_person(person, role_foo, role_bar):
+    person_roles = person.roles.all()
+    assert len(person_roles) == 2
+    assert role_foo in person_roles
+    assert role_bar in person_roles
+
+
+@pytest.mark.django_db
+def test_delete_person_with_roles(person):
+    # it is not clear what cleanup needs to be done when removing a person,
+    # so for now it is prohibited to delete a person with role relationships
+    # attached in other tables
+    with pytest.raises(ProtectedError):
+        person.delete()
diff --git a/greg/tests/models/test_sponsor.py b/greg/tests/models/test_sponsor.py
new file mode 100644
index 0000000000000000000000000000000000000000..e82ec2a29d5276bace3bf1dde8c07c4456b37729
--- /dev/null
+++ b/greg/tests/models/test_sponsor.py
@@ -0,0 +1,52 @@
+from functools import partial
+
+import pytest
+
+from greg.models import (
+    OrganizationalUnit,
+    Sponsor,
+    SponsorOrganizationalUnit,
+)
+
+
+sponsor_ou_relation = partial(
+    SponsorOrganizationalUnit.objects.create,
+    hierarchical_access=False,
+)
+
+
+@pytest.fixture
+def sponsor_foo() -> Sponsor:
+    return Sponsor.objects.create(feide_id="foosponsor@uio.no")
+
+
+@pytest.fixture
+def sponsor_bar() -> Sponsor:
+    return Sponsor.objects.create(feide_id="barsponsor@uio.no")
+
+
+@pytest.fixture
+def unit1() -> OrganizationalUnit:
+    return OrganizationalUnit.objects.create(orgreg_id="1", name_en="First unit")
+
+
+@pytest.fixture
+def unit2() -> OrganizationalUnit:
+    return OrganizationalUnit.objects.create(orgreg_id="2", name_en="Second unit")
+
+
+@pytest.mark.django_db
+def test_add_sponsor_to_multiple_units(sponsor_foo, unit1, unit2):
+    sponsor_ou_relation(sponsor=sponsor_foo, organizational_unit=unit1)
+    sponsor_ou_relation(sponsor=sponsor_foo, organizational_unit=unit2)
+    assert list(sponsor_foo.units.all()) == [unit1, unit2]
+
+
+@pytest.mark.django_db
+def test_add_muliple_sponsors_to_unit(sponsor_foo, sponsor_bar, unit1, unit2):
+    sponsor_ou_relation(sponsor=sponsor_foo, organizational_unit=unit1)
+    sponsor_ou_relation(sponsor=sponsor_bar, organizational_unit=unit1)
+    assert list(sponsor_foo.units.all()) == [unit1]
+    assert list(sponsor_bar.units.all()) == [unit1]
+    assert list(Sponsor.objects.filter(units=unit1.id)) == [sponsor_foo, sponsor_bar]
+    assert list(Sponsor.objects.filter(units=unit2.id)) == []
diff --git a/greg/tests/populate_database.py b/greg/tests/populate_database.py
index 9d999aa55c216ca98504f144930b9f00d373ec01..1dd064aabab4ffaea36a2546180f7ce6a3f310e8 100644
--- a/greg/tests/populate_database.py
+++ b/greg/tests/populate_database.py
@@ -13,17 +13,9 @@ from greg.models import (
     PersonIdentity,
 )
 
-# Set seeds so that the generated data is always the same
-random.seed(0)
-Faker.seed(0)
-
 R = TypeVar("R")
 
 
-def get_random_element_from_list(input_list: List[R]) -> R:
-    return input_list[random.randint(0, len(input_list) - 1)]
-
-
 class DatabasePopulation:
     """
     Helper class for populating database with random data. It can be useful to see how things look in the interface
@@ -38,10 +30,21 @@ class DatabasePopulation:
     sponsors: List[Sponsor] = []
     role_types: List[Role] = []
     consents: List[Consent] = []
+    random: random.Random
 
-    def __init__(self):
+    def __init__(self, set_seed_to_zero=True):
         self.faker = Faker()
 
+        if set_seed_to_zero:
+            # Set seeds so that the generated data is always the same
+            self.random = random.Random(0)
+            # Note that it is possible the logic the Faker uses
+            # to compute the random sequences can change when
+            # the version changes
+            self.faker.seed_instance(0)
+        else:
+            self.random = random.Random()
+
     def populate_database(self):
         for i in range(10):
             first_name = self.faker.first_name()
@@ -85,7 +88,7 @@ class DatabasePopulation:
                     consent_description_en=self.faker.paragraph(nb_sentences=5),
                     consent_description_nb=self.faker.paragraph(nb_sentences=5),
                     consent_link_en=self.faker.url(),
-                    user_allowed_to_change=random.random() > 0.5,
+                    user_allowed_to_change=self.random.random() > 0.5,
                 )
             )
 
@@ -97,15 +100,15 @@ class DatabasePopulation:
     def __add_random_person_identification_connections(self, connections_to_create=5):
         person_identifier_count = 0
         while person_identifier_count < connections_to_create:
-            person = get_random_element_from_list(self.persons)
-            identity_type = get_random_element_from_list(
+            person = self.get_random_element_from_list(self.persons)
+            identity_type = self.get_random_element_from_list(
                 PersonIdentity.IdentityType.choices
             )[0]
 
-            if random.random() > 0.5:
-                sponsor = get_random_element_from_list(self.sponsors)
+            if self.random.random() > 0.5:
+                sponsor = self.get_random_element_from_list(self.sponsors)
                 verified_when = self.faker.date_this_year()
-                identity_type = get_random_element_from_list(
+                identity_type = self.get_random_element_from_list(
                     PersonIdentity.IdentityType.choices
                 )[0]
                 verified = self.faker.text(max_nb_chars=50)
@@ -129,11 +132,12 @@ class DatabasePopulation:
     def __add_random_sponsor_unit_connections(self, connections_to_create=5):
         sponsor_unit_count = 0
         while sponsor_unit_count < connections_to_create:
-            sponsor = get_random_element_from_list(self.sponsors)
-            unit = get_random_element_from_list(self.units)
+            sponsor = self.get_random_element_from_list(self.sponsors)
+            unit = self.get_random_element_from_list(self.units)
 
             sponsor.units.add(
-                unit, through_defaults={"hierarchical_access": random.random() > 0.5}
+                unit,
+                through_defaults={"hierarchical_access": self.random.random() > 0.5},
             )
             sponsor_unit_count += 1
 
@@ -142,16 +146,16 @@ class DatabasePopulation:
         while person_role_count < connections_to_create:
             try:
                 PersonRole.objects.create(
-                    person=get_random_element_from_list(self.persons),
-                    role=get_random_element_from_list(self.role_types),
-                    unit=get_random_element_from_list(self.units),
+                    person=self.get_random_element_from_list(self.persons),
+                    role=self.get_random_element_from_list(self.role_types),
+                    unit=self.get_random_element_from_list(self.units),
                     start_date=self.faker.date_this_decade(),
                     end_date=self.faker.date_this_decade(
                         before_today=False, after_today=True
                     ),
                     contact_person_unit=self.faker.name(),
-                    available_in_search=random.random() > 0.5,
-                    registered_by=get_random_element_from_list(self.sponsors),
+                    available_in_search=self.random.random() > 0.5,
+                    registered_by=self.get_random_element_from_list(self.sponsors),
                 )
                 person_role_count += 1
             except IntegrityError:
@@ -162,8 +166,8 @@ class DatabasePopulation:
     def __add_random_person_consent_connections(self, number_of_connections_to_make=5):
         person_consent_count = 0
         while person_consent_count < number_of_connections_to_make:
-            person = get_random_element_from_list(self.persons)
-            consent = get_random_element_from_list(self.consents)
+            person = self.get_random_element_from_list(self.persons)
+            consent = self.get_random_element_from_list(self.consents)
 
             person.consents.add(
                 consent,
@@ -187,6 +191,9 @@ class DatabasePopulation:
             ):
                 cursor.execute(f"DELETE FROM {table}")
 
+    def get_random_element_from_list(self, input_list: List[R]) -> R:
+        return input_list[self.random.randint(0, len(input_list) - 1)]
+
 
 if __name__ == "__main__":
     database_population = DatabasePopulation()
diff --git a/greg/tests/test_api_person.py b/greg/tests/test_api_person.py
deleted file mode 100644
index cc0b0932a73859aee37327cb38ced81f4187f77b..0000000000000000000000000000000000000000
--- a/greg/tests/test_api_person.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from django.contrib.auth import get_user_model
-from rest_framework import status
-from rest_framework.authtoken.models import Token
-from rest_framework.reverse import reverse
-from rest_framework.test import (
-    APIClient,
-    APITestCase,
-)
-
-from greg.models import Person
-
-
-class GregAPITestCase(APITestCase):
-    def setUp(self):
-        self.client = self.get_token_client()
-
-    def get_token_client(self, username="test") -> APIClient:
-        self.user, _ = get_user_model().objects.get_or_create(username=username)
-        token, _ = Token.objects.get_or_create(user=self.user)
-        client = APIClient()
-        client.credentials(HTTP_AUTHORIZATION="Token {}".format(token.key))
-        return client
-
-
-class PersonTestData(GregAPITestCase):
-    def setUp(self):
-        super().setUp()
-        self.person_foo_data = dict(
-            first_name="Foo",
-            last_name="Foo",
-            date_of_birth="2000-01-27",
-            email="test@example.org",
-            mobile_phone="123456788",
-        )
-        self.person_bar_data = dict(
-            first_name="Bar",
-            last_name="Bar",
-            date_of_birth="2000-07-01",
-            email="test2@example.org",
-            mobile_phone="123456789",
-        )
-        self.person_foo = Person.objects.create(**self.person_foo_data)
-        self.person_bar = Person.objects.create(**self.person_bar_data)
-
-
-class PersonAPITestCase(PersonTestData):
-    def test_get_person(self):
-        url = reverse("person-detail", kwargs={"id": self.person_foo.id})
-        response = self.client.get(url)
-        data = response.json()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(data.get("id"), self.person_foo.id)
-        self.assertEqual(data.get("first_name"), self.person_foo.first_name)
-        self.assertEqual(data.get("last_name"), self.person_foo.last_name)
-
-    def test_persons(self):
-        url = reverse("person-list")
-        response = self.client.get(url)
-        data = response.json()
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(2, len(data["results"]))
diff --git a/greg/tests/test_models.py b/greg/tests/test_models.py
deleted file mode 100644
index 0c5879241c3466d76d0f9fb1c46b329e233873bf..0000000000000000000000000000000000000000
--- a/greg/tests/test_models.py
+++ /dev/null
@@ -1,182 +0,0 @@
-from django.db.models import ProtectedError
-from django.test import TestCase
-
-from greg.models import (
-    Person,
-    Role,
-    PersonRole,
-    OrganizationalUnit,
-    Sponsor,
-    SponsorOrganizationalUnit,
-    Consent,
-)
-
-
-class PersonModelTests(TestCase):
-    test_person = dict(
-        first_name="Test",
-        last_name="Tester",
-        date_of_birth="2000-01-27",
-        email="test@example.org",
-        mobile_phone="123456789",
-    )
-
-    def test_add_multiple_roles_to_person(self):
-        role1 = Role.objects.create(type="role1", name_en="Role 1")
-        role2 = Role.objects.create(type="role2", name_en="Role 2")
-
-        unit = OrganizationalUnit.objects.create(orgreg_id="12345", name_en="Test unit")
-        sponsor = Sponsor.objects.create(feide_id="test@uio.no")
-        sponsor2 = Sponsor.objects.create(feide_id="test2@uio.no")
-
-        person = Person.objects.create(**self.test_person)
-
-        # Add two roles to the person and check that they appear when listing the roles for the person
-        PersonRole.objects.create(
-            person=person,
-            role=role1,
-            unit=unit,
-            start_date="2020-03-05",
-            end_date="2020-06-10",
-            contact_person_unit="Contact Person",
-            available_in_search=True,
-            registered_by=sponsor,
-        )
-
-        PersonRole.objects.create(
-            person=person,
-            role=role2,
-            unit=unit,
-            start_date="2021-03-05",
-            end_date="2021-06-10",
-            contact_person_unit="Contact Person",
-            available_in_search=True,
-            registered_by=sponsor2,
-        )
-
-        person_roles = person.roles.all()
-
-        self.assertEqual(2, len(person_roles))
-        self.assertIn(role1, person_roles)
-        self.assertIn(role2, person_roles)
-
-    def test_person_not_allowed_deleted_when_roles_are_present(self):
-        person = Person.objects.create(**self.test_person)
-        role = Role.objects.create(type="role1", name_en="Role 1")
-        unit = OrganizationalUnit.objects.create(orgreg_id="12345", name_en="Test unit")
-        sponsor = Sponsor.objects.create(feide_id="test@uio.no")
-
-        PersonRole.objects.create(
-            person=person,
-            role=role,
-            unit=unit,
-            start_date="2020-03-05",
-            end_date="2020-06-10",
-            contact_person_unit="Contact Person",
-            available_in_search=True,
-            registered_by=sponsor,
-        )
-
-        # It is not clear what cleanup needs to be done when a person is going to be
-        # removed, so for now is prohibited to delete a person if there is data
-        # attached to him in other tables
-        self.assertRaises(ProtectedError, person.delete)
-
-
-class SponsorModelTests(TestCase):
-    def test_add_sponsor_to_multiple_units(self):
-        sponsor = Sponsor.objects.create(feide_id="test@uio.no")
-
-        unit1 = OrganizationalUnit.objects.create(
-            orgreg_id="12345", name_en="Test unit"
-        )
-        unit2 = OrganizationalUnit.objects.create(
-            orgreg_id="123456", name_en="Test unit2"
-        )
-
-        SponsorOrganizationalUnit.objects.create(
-            sponsor=sponsor, organizational_unit=unit1, hierarchical_access=False
-        )
-        SponsorOrganizationalUnit.objects.create(
-            sponsor=sponsor, organizational_unit=unit2, hierarchical_access=False
-        )
-
-        sponsor_units = sponsor.units.all()
-
-        self.assertEqual(2, len(sponsor_units))
-        self.assertIn(unit1, sponsor_units)
-        self.assertIn(unit2, sponsor_units)
-
-    def test_add_multiple_sponsors_to_unit(self):
-        sponsor1 = Sponsor.objects.create(feide_id="test@uio.no")
-        sponsor2 = Sponsor.objects.create(feide_id="test2@uio.no")
-
-        unit = OrganizationalUnit.objects.create(orgreg_id="12345", name_en="Test unit")
-        unit2 = OrganizationalUnit.objects.create(
-            orgreg_id="123456", name_en="Test unit2"
-        )
-
-        SponsorOrganizationalUnit.objects.create(
-            sponsor=sponsor1, organizational_unit=unit, hierarchical_access=False
-        )
-        SponsorOrganizationalUnit.objects.create(
-            sponsor=sponsor2, organizational_unit=unit, hierarchical_access=True
-        )
-
-        sponsor1_units = sponsor1.units.all()
-        self.assertEqual(1, len(sponsor1_units))
-        self.assertIn(unit, sponsor1_units)
-
-        sponsor2_units = sponsor2.units.all()
-        self.assertEqual(1, len(sponsor2_units))
-        self.assertIn(unit, sponsor2_units)
-
-        sponsors_for_unit = Sponsor.objects.filter(units=unit.id)
-        self.assertEqual(2, len(sponsors_for_unit))
-        self.assertIn(sponsor1, sponsors_for_unit)
-        self.assertIn(sponsor2, sponsors_for_unit)
-
-        sponsors_for_unit2 = Sponsor.objects.filter(units=unit2.id)
-        self.assertEqual(0, len(sponsors_for_unit2))
-
-
-class ConsentModelTest(TestCase):
-    def test_add_consent_to_person(self):
-        person = Person.objects.create(
-            first_name="Test",
-            last_name="Tester",
-            date_of_birth="2000-01-27",
-            email="test@example.org",
-            mobile_phone="123456789",
-        )
-
-        consent = Consent.objects.create(
-            type="it_guidelines",
-            consent_name_en="IT Guidelines",
-            consent_name_nb="IT Regelverk",
-            consent_description_en="IT Guidelines description",
-            consent_description_nb="IT Regelverk beskrivelse",
-            consent_link_en="https://example.org/it_guidelines",
-            user_allowed_to_change=False,
-        )
-
-        person.consents.add(
-            consent, through_defaults={"consent_given_at": "2021-06-20"}
-        )
-
-
-class OrganizationalUnitTest(TestCase):
-    def test_set_parent_for_unit(self):
-        parent = OrganizationalUnit.objects.create(
-            orgreg_id="12345", name_en="Parent unit", name_nb="Foreldreseksjon"
-        )
-        child = OrganizationalUnit.objects.create(
-            orgreg_id="123456",
-            name_en="Child unit",
-            name_nb="Barneseksjon",
-            parent=parent,
-        )
-
-        query_result = OrganizationalUnit.objects.filter(parent__id=parent.id)
-        self.assertEqual(1, len(query_result))
-        self.assertIn(child, query_result)
diff --git a/gregsite/static/.gitignore b/gregsite/static/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/gregsite/static/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/poetry.lock b/poetry.lock
index 9a2c461654d98b329cd50623b97ac5410c1934ea..2946530def28779d1376ffe1a6c906930952f4dd 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -37,6 +37,14 @@ python-versions = "~=3.6"
 lazy-object-proxy = ">=1.4.0"
 wrapt = ">=1.11,<1.13"
 
+[[package]]
+name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
 [[package]]
 name = "attrs"
 version = "21.2.0"
@@ -51,18 +59,6 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
 tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
 tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
 
-[[package]]
-name = "autopep8"
-version = "1.5.7"
-description = "A tool that automatically formats Python code to conform to the PEP 8 style guide"
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-pycodestyle = ">=2.7.0"
-toml = "*"
-
 [[package]]
 name = "backcall"
 version = "0.2.0"
@@ -328,6 +324,14 @@ category = "main"
 optional = false
 python-versions = ">=3.5"
 
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "ipython"
 version = "7.25.0"
@@ -504,6 +508,17 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "packaging"
+version = "21.0"
+description = "Core utilities for Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2"
+
 [[package]]
 name = "parso"
 version = "0.8.2"
@@ -574,6 +589,17 @@ url = "https://git.app.uib.no/it-bott-integrasjoner/pika-context-manager.git"
 reference = "v1.2.0"
 resolved_reference = "32fc2e04b6fc6528056cd0d8d0ca716abb5ab3be"
 
+[[package]]
+name = "pluggy"
+version = "0.13.1"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+
 [[package]]
 name = "prompt-toolkit"
 version = "3.0.19"
@@ -602,9 +628,9 @@ optional = false
 python-versions = "*"
 
 [[package]]
-name = "pycodestyle"
-version = "2.7.0"
-description = "Python style guide checker"
+name = "py"
+version = "1.10.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
 category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@@ -659,6 +685,14 @@ python-versions = "*"
 [package.dependencies]
 pylint = ">=1.7"
 
+[[package]]
+name = "pyparsing"
+version = "2.4.7"
+description = "Python parsing module"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
 [[package]]
 name = "pyrsistent"
 version = "0.18.0"
@@ -667,6 +701,42 @@ category = "main"
 optional = false
 python-versions = ">=3.6"
 
+[[package]]
+name = "pytest"
+version = "6.2.4"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<1.0.0a1"
+py = ">=1.8.2"
+toml = "*"
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+name = "pytest-django"
+version = "4.4.0"
+description = "A Django plugin for pytest."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+pytest = ">=5.4.0"
+
+[package.extras]
+docs = ["sphinx", "sphinx-rtd-theme"]
+testing = ["django", "django-configurations (>=2.0)"]
+
 [[package]]
 name = "python-daemon"
 version = "2.3.0"
@@ -880,7 +950,7 @@ python-versions = "*"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.9"
-content-hash = "0c839c13a8f69537c16ef0d2b5bc1d5e103072a0b145c27b7b3ea08def26d3de"
+content-hash = "afc64275e89091b126ea8c086a48db4714542e0abf1fd2ca475f6448045bdae1"
 
 [metadata.files]
 appdirs = [
@@ -899,14 +969,14 @@ astroid = [
     {file = "astroid-2.6.2-py3-none-any.whl", hash = "sha256:606b2911d10c3dcf35e58d2ee5c97360e8477d7b9f3efc3f24811c93e6fc2cd9"},
     {file = "astroid-2.6.2.tar.gz", hash = "sha256:38b95085e9d92e2ca06cf8b35c12a74fa81da395a6f9e65803742e6509c05892"},
 ]
+atomicwrites = [
+    {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
+    {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
+]
 attrs = [
     {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
     {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
 ]
-autopep8 = [
-    {file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"},
-    {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"},
-]
 backcall = [
     {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
     {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
@@ -999,6 +1069,10 @@ inflection = [
     {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"},
     {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
 ]
+iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
 ipython = [
     {file = "ipython-7.25.0-py3-none-any.whl", hash = "sha256:aa21412f2b04ad1a652e30564fff6b4de04726ce875eab222c8430edc6db383a"},
     {file = "ipython-7.25.0.tar.gz", hash = "sha256:54bbd1fe3882457aaf28ae060a5ccdef97f212a741754e420028d4ec5c2291dc"},
@@ -1128,6 +1202,10 @@ mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
+packaging = [
+    {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
+    {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
+]
 parso = [
     {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"},
     {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"},
@@ -1149,6 +1227,10 @@ pika = [
     {file = "pika-1.2.0.tar.gz", hash = "sha256:f023d6ac581086b124190cb3dc81dd581a149d216fa4540ac34f9be1e3970b89"},
 ]
 pika-context-manager = []
+pluggy = [
+    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
+    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+]
 prompt-toolkit = [
     {file = "prompt_toolkit-3.0.19-py3-none-any.whl", hash = "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"},
     {file = "prompt_toolkit-3.0.19.tar.gz", hash = "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f"},
@@ -1188,9 +1270,9 @@ ptyprocess = [
     {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
     {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
 ]
-pycodestyle = [
-    {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
-    {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
+py = [
+    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
+    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
 ]
 pygments = [
     {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
@@ -1208,6 +1290,10 @@ pylint-plugin-utils = [
     {file = "pylint-plugin-utils-0.6.tar.gz", hash = "sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a"},
     {file = "pylint_plugin_utils-0.6-py3-none-any.whl", hash = "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a"},
 ]
+pyparsing = [
+    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
+    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+]
 pyrsistent = [
     {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"},
     {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"},
@@ -1231,6 +1317,14 @@ pyrsistent = [
     {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"},
     {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"},
 ]
+pytest = [
+    {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"},
+    {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"},
+]
+pytest-django = [
+    {file = "pytest-django-4.4.0.tar.gz", hash = "sha256:b5171e3798bf7e3fc5ea7072fe87324db67a4dd9f1192b037fed4cc3c1b7f455"},
+    {file = "pytest_django-4.4.0-py3-none-any.whl", hash = "sha256:65783e78382456528bd9d79a35843adde9e6a47347b20464eb2c885cb0f1f606"},
+]
 python-daemon = [
     {file = "python-daemon-2.3.0.tar.gz", hash = "sha256:bda993f1623b1197699716d68d983bb580043cf2b8a66a01274d9b8297b0aeaf"},
     {file = "python_daemon-2.3.0-py2.py3-none-any.whl", hash = "sha256:191c7b67b8f7aac58849abf54e19fe1957ef7290c914210455673028ad454989"},
diff --git a/pyproject.toml b/pyproject.toml
index 43c9785c889dd16dcf5d3733ffc1b43041d4dfc1..90b2373de6d04aece61eea173bed1ddcf4e2380e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,6 +29,7 @@ ipython = "*"
 mypy = "*"
 pylint = "*"
 pylint-django = "*"
+pytest-django = "^4.4.0"
 rope = "*"
 
 [tool.black]