From 7cdf830a7f381985a866d320fe6d7eaf31ec32d4 Mon Sep 17 00:00:00 2001
From: Tore Brede <Tore.Brede@uib.no>
Date: Tue, 13 Jul 2021 08:51:57 +0200
Subject: [PATCH] GREG-3: Adding more model classes

---
 greg/migrations/0001_initial.py | 135 +++++++++++++++++++-
 greg/models.py                  | 214 ++++++++++++++++++++++++++++++--
 greg/tests/test_api_person.py   |   6 +
 greg/tests/test_models.py       | 182 +++++++++++++++++++++++++++
 4 files changed, 523 insertions(+), 14 deletions(-)
 create mode 100644 greg/tests/test_models.py

diff --git a/greg/migrations/0001_initial.py b/greg/migrations/0001_initial.py
index aa796fd9..4d782f4e 100644
--- a/greg/migrations/0001_initial.py
+++ b/greg/migrations/0001_initial.py
@@ -1,5 +1,6 @@
-# Generated by Django 3.2.5 on 2021-07-09 07:27
+# Generated by Django 3.2.5 on 2021-07-13 06:47
 
+import datetime
 import dirtyfields.dirtyfields
 from django.db import migrations, models
 import django.db.models.deletion
@@ -13,6 +14,27 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
+        migrations.CreateModel(
+            name='Consent',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('type', models.SlugField(max_length=64, unique=True)),
+                ('consent_name_en', models.CharField(max_length=256)),
+                ('consent_name_nb', models.CharField(max_length=256)),
+                ('consent_description_en', models.TextField()),
+                ('consent_description_nb', models.TextField()),
+                ('consent_link_en', models.URLField(null=True)),
+                ('consent_link_nb', models.URLField(null=True)),
+                ('valid_from', models.DateField(default=datetime.date.today)),
+                ('user_allowed_to_change', models.BooleanField()),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
+        ),
         migrations.CreateModel(
             name='Notification',
             fields=[
@@ -30,6 +52,19 @@ class Migration(migrations.Migration):
             },
             bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
         ),
+        migrations.CreateModel(
+            name='OrganizationalUnit',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('orgreg_id', models.CharField(max_length=256)),
+                ('name_nb', models.CharField(max_length=256)),
+                ('name_en', models.CharField(max_length=256)),
+                ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='greg.organizationalunit')),
+            ],
+            bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
+        ),
         migrations.CreateModel(
             name='Person',
             fields=[
@@ -38,6 +73,13 @@ class Migration(migrations.Migration):
                 ('updated', models.DateTimeField(auto_now=True)),
                 ('first_name', models.CharField(max_length=256)),
                 ('last_name', models.CharField(max_length=256)),
+                ('date_of_birth', models.DateField()),
+                ('email', models.EmailField(max_length=254)),
+                ('email_verified_date', models.DateField(blank=True, null=True)),
+                ('mobile_phone', models.CharField(max_length=15)),
+                ('mobile_phone_verified_date', models.DateField(blank=True, null=True)),
+                ('registration_completed_date', models.DateField(blank=True, null=True)),
+                ('token', models.CharField(blank=True, max_length=32)),
             ],
             options={
                 'abstract': False,
@@ -53,30 +95,119 @@ class Migration(migrations.Migration):
                 ('type', models.SlugField(max_length=64, unique=True)),
                 ('name_nb', models.CharField(max_length=256)),
                 ('name_en', models.CharField(max_length=256)),
-                ('meta', models.JSONField(blank=True, null=True)),
+                ('description_nb', models.TextField()),
+                ('description_en', models.TextField()),
+                ('default_duration_days', models.IntegerField(null=True)),
             ],
             options={
                 'abstract': False,
             },
             bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
         ),
+        migrations.CreateModel(
+            name='Sponsor',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('feide_id', models.CharField(max_length=256)),
+            ],
+            bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
+        ),
+        migrations.CreateModel(
+            name='SponsorOrganizationalUnit',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('hierarchical_access', models.BooleanField()),
+                ('organizational_unit', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='link_unit', to='greg.organizationalunit')),
+                ('sponsor', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='link_sponsor', to='greg.sponsor')),
+            ],
+            bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
+        ),
+        migrations.AddField(
+            model_name='sponsor',
+            name='units',
+            field=models.ManyToManyField(related_name='sponsor_unit', through='greg.SponsorOrganizationalUnit', to='greg.OrganizationalUnit'),
+        ),
         migrations.CreateModel(
             name='PersonRole',
             fields=[
                 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('created', models.DateTimeField(auto_now_add=True)),
                 ('updated', models.DateTimeField(auto_now=True)),
+                ('start_date', models.DateField()),
+                ('end_date', models.DateField()),
+                ('contact_person_unit', models.TextField()),
+                ('comments', models.TextField(blank=True)),
+                ('available_in_search', models.BooleanField(default=False)),
                 ('person', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='person_roles', to='greg.person')),
+                ('registered_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sponsor_role', to='greg.sponsor')),
                 ('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')),
+            ],
+            bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
+        ),
+        migrations.CreateModel(
+            name='PersonIdentity',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('type', models.CharField(choices=[('PASSPORT_NUMBER', 'Passport Number'), ('FEIDE_ID', 'Feide Id')], max_length=15)),
+                ('source', models.CharField(max_length=256)),
+                ('value', models.CharField(max_length=256)),
+                ('verified', models.CharField(blank=True, choices=[('AUTOMATIC', 'Automatic'), ('MANUAL', 'Manual')], max_length=9)),
+                ('verified_when', models.DateField(blank=True)),
+                ('person', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='person', to='greg.person')),
+                ('verified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sponsor', to='greg.sponsor')),
             ],
             options={
                 'abstract': False,
             },
             bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
         ),
+        migrations.CreateModel(
+            name='PersonConsent',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('updated', models.DateTimeField(auto_now=True)),
+                ('consent_given_at', models.DateField()),
+                ('consent', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='link_person_consent', to='greg.consent')),
+                ('person', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='link_person_consent', to='greg.person')),
+            ],
+            bases=(dirtyfields.dirtyfields.DirtyFieldsMixin, models.Model),
+        ),
+        migrations.AddField(
+            model_name='person',
+            name='consents',
+            field=models.ManyToManyField(related_name='consent', through='greg.PersonConsent', to='greg.Consent'),
+        ),
         migrations.AddField(
             model_name='person',
             name='roles',
             field=models.ManyToManyField(related_name='persons', through='greg.PersonRole', to='greg.Role'),
         ),
+        migrations.AddConstraint(
+            model_name='sponsororganizationalunit',
+            constraint=models.UniqueConstraint(fields=('sponsor', 'organizational_unit'), name='sponsor_organizational_unit_unique'),
+        ),
+        migrations.AddConstraint(
+            model_name='sponsor',
+            constraint=models.UniqueConstraint(fields=('feide_id',), name='unique_feide_id'),
+        ),
+        migrations.AddConstraint(
+            model_name='personrole',
+            constraint=models.UniqueConstraint(fields=('person', 'role'), name='personrole_person_role_unique'),
+        ),
+        migrations.AddConstraint(
+            model_name='personconsent',
+            constraint=models.UniqueConstraint(fields=('person', 'consent'), name='person_consent_unique'),
+        ),
+        migrations.AddConstraint(
+            model_name='organizationalunit',
+            constraint=models.UniqueConstraint(fields=('orgreg_id',), name='unique_orgreg_id'),
+        ),
     ]
diff --git a/greg/models.py b/greg/models.py
index 16440ecc..9e3a0304 100644
--- a/greg/models.py
+++ b/greg/models.py
@@ -1,9 +1,10 @@
+from datetime import date
+
+from dirtyfields import DirtyFieldsMixin
 from django.db import models
 from django.db.models import Lookup
 from django.db.models.fields import Field
 
-from dirtyfields import DirtyFieldsMixin
-
 
 @Field.register_lookup
 class Like(Lookup):
@@ -29,11 +30,21 @@ class BaseModel(DirtyFieldsMixin, models.Model):
 
 
 class Person(BaseModel):
-    """A person."""
+    """A person is someone who has requested guest access."""
 
     first_name = models.CharField(max_length=256)
     last_name = models.CharField(max_length=256)
+    date_of_birth = models.DateField()
+    email = models.EmailField()
+    email_verified_date = models.DateField(null=True, blank=True)
+    mobile_phone = models.CharField(max_length=15)
+    mobile_phone_verified_date = models.DateField(null=True, blank=True)
+    registration_completed_date = models.DateField(null=True, blank=True)
+    token = models.CharField(max_length=32, blank=True)
     roles = models.ManyToManyField("Role", through="PersonRole", related_name="persons")
+    consents = models.ManyToManyField(
+        "Consent", through="PersonConsent", related_name="consent"
+    )
 
     def __str__(self):
         return "{} {} ({})".format(self.first_name, self.last_name, self.pk)
@@ -53,19 +64,20 @@ class Role(BaseModel):
     type = models.SlugField(max_length=64, unique=True)
     name_nb = models.CharField(max_length=256)
     name_en = models.CharField(max_length=256)
-    meta = models.JSONField(null=True, blank=True)
+    description_nb = models.TextField()
+    description_en = models.TextField()
+    default_duration_days = models.IntegerField(null=True)
 
     def __str__(self):
         return str(self.name_nb or self.name_en or self.slug)
 
     def __repr__(self):
-        return "{}(id={!r}, type={!r}, name_nb={!r}, name_en={!r}, meta={!r})".format(
+        return "{}(id={!r}, type={!r}, name_nb={!r}, name_en={!r})".format(
             self.__class__.__name__,
             self.pk,
             self.type,
             self.name_nb,
             self.name_en,
-            self.meta,
         )
 
 
@@ -78,13 +90,25 @@ class PersonRole(BaseModel):
     role = models.ForeignKey(
         "Role", on_delete=models.PROTECT, related_name="person_roles"
     )
+    unit = models.ForeignKey(
+        "OrganizationalUnit", on_delete=models.PROTECT, related_name="unit_person_role"
+    )
+    start_date = models.DateField()
+    end_date = models.DateField()
+    # TODO Is this field needed?
+    contact_person_unit = models.TextField()
+    comments = models.TextField(blank=True)
+    available_in_search = models.BooleanField(default=False)
+    registered_by = models.ForeignKey(
+        "Sponsor", on_delete=models.PROTECT, related_name="sponsor_role"
+    )
 
-    # class Meta:
-    #     constraints = [
-    #         models.UniqueConstraint(
-    #             fields=["person", "role"], name="personrole_person_role_unique"
-    #         )
-    #     ]
+    class Meta:
+        constraints = [
+            models.UniqueConstraint(
+                fields=["person", "role"], name="personrole_person_role_unique"
+            )
+        ]
 
     def __repr__(self):
         return "{}(id={!r}, person={!r}, role={!r})".format(
@@ -111,3 +135,169 @@ class Notification(BaseModel):
             self.issued_at,
             self.meta,
         )
+
+
+class PersonIdentity(BaseModel):
+    # TODO: Add more types
+    class IdentityType(models.TextChoices):
+        PASSPORT_NUMBER = "PASSPORT_NUMBER"
+        FEIDE_ID = "FEIDE_ID"
+
+    class Verified(models.TextChoices):
+        AUTOMATIC = "AUTOMATIC"
+        MANUAL = "MANUAL"
+
+    person = models.ForeignKey(
+        "Person", on_delete=models.PROTECT, related_name="person"
+    )
+    type = models.CharField(max_length=15, choices=IdentityType.choices)
+    source = models.CharField(max_length=256)
+    value = models.CharField(max_length=256)
+    verified = models.CharField(max_length=9, choices=Verified.choices, blank=True)
+    verified_by = models.ForeignKey(
+        "Sponsor", on_delete=models.PROTECT, related_name="sponsor", null=True
+    )
+    verified_when = models.DateField(blank=True)
+
+    def __repr__(self):
+        return (
+            "{}(id={!r}, type={!r}, source={!r}, value={!r}, verified_by={!r})".format(
+                self.__class__.__name__,
+                self.pk,
+                self.type,
+                self.source,
+                self.value,
+                self.verified_by,
+            )
+        )
+
+
+class Consent(BaseModel):
+    """
+    Describes some consent, like acknowledging the IT department guidelines, a guest can give.
+    """
+
+    type = models.SlugField(max_length=64, unique=True)
+    consent_name_en = models.CharField(max_length=256)
+    consent_name_nb = models.CharField(max_length=256)
+    consent_description_en = models.TextField()
+    consent_description_nb = models.TextField()
+    consent_link_en = models.URLField(null=True)
+    consent_link_nb = models.URLField(null=True)
+    valid_from = models.DateField(default=date.today)
+    user_allowed_to_change = models.BooleanField()
+
+    def __repr__(self):
+        return "{}(id={!r}, type={!r}, consent_name_en={!r}, valid_from={!r}, user_allowed_to_change={!r})".format(
+            self.__class__.__name__,
+            self.pk,
+            self.type,
+            self.consent_name_en,
+            self.valid_from,
+            self.user_allowed_to_change,
+        )
+
+
+class PersonConsent(BaseModel):
+    """
+    Links a person and a consent he has given.
+    """
+
+    person = models.ForeignKey(
+        "Person", on_delete=models.PROTECT, related_name="link_person_consent"
+    )
+    consent = models.ForeignKey(
+        "Consent", on_delete=models.PROTECT, related_name="link_person_consent"
+    )
+    consent_given_at = models.DateField()
+
+    class Meta:
+        constraints = [
+            models.UniqueConstraint(
+                fields=["person", "consent"], name="person_consent_unique"
+            )
+        ]
+
+    def __repr__(self):
+        return "{}(id={!r}, person={!r}, consent={!r}, consent_given_at={!r})".format(
+            self.__class__.__name__,
+            self.pk,
+            self.person,
+            self.consent,
+            self.consent_given_at,
+        )
+
+
+class OrganizationalUnit(BaseModel):
+    """
+    An organizational unit. Units can be organized in a hierarchical manner.
+    """
+
+    orgreg_id = models.CharField(max_length=256)
+    name_nb = models.CharField(max_length=256)
+    name_en = models.CharField(max_length=256)
+    parent = models.ForeignKey("self", on_delete=models.PROTECT, null=True)
+
+    def __repr__(self):
+        return "{}(id={!r}, orgreg_id={!r}, name_en={!r}, parent={!r})".format(
+            self.__class__.__name__, self.pk, self.orgreg_id, self.name_en, self.parent
+        )
+
+    class Meta:
+        constraints = [
+            models.UniqueConstraint(name="unique_orgreg_id", fields=["orgreg_id"])
+        ]
+
+
+class Sponsor(BaseModel):
+    """
+    A sponsor is someone who is allowed, with some restrictions, to send out invitations to guests and to verify their identity.
+    """
+
+    feide_id = models.CharField(max_length=256)
+    units = models.ManyToManyField(
+        "OrganizationalUnit",
+        through="SponsorOrganizationalUnit",
+        related_name="sponsor_unit",
+    )
+
+    def __repr__(self):
+        return "{}(id={!r}, feide_id={!r})".format(
+            self.__class__.__name__, self.pk, self.feide_id
+        )
+
+    class Meta:
+        constraints = [
+            models.UniqueConstraint(name="unique_feide_id", fields=["feide_id"])
+        ]
+
+
+class SponsorOrganizationalUnit(BaseModel):
+    """
+    A link between a sponsor and an organizational unit.
+    """
+
+    sponsor = models.ForeignKey(
+        "Sponsor", on_delete=models.PROTECT, related_name="link_sponsor"
+    )
+    organizational_unit = models.ForeignKey(
+        "OrganizationalUnit", on_delete=models.PROTECT, related_name="link_unit"
+    )
+    hierarchical_access = models.BooleanField()
+
+    class Meta:
+        constraints = [
+            models.UniqueConstraint(
+                fields=["sponsor", "organizational_unit"],
+                name="sponsor_organizational_unit_unique",
+            )
+        ]
+
+    def __repr__(self):
+        return "{}(id={!r}, sponsor={!r}, organizational_unit={!r}, hierarchical_access={!r})".format(
+            self.__class__.__name__,
+            self.pk,
+            self.sponsor,
+            self.organizational_unit,
+            self.hierarchical_access,
+        )
diff --git a/greg/tests/test_api_person.py b/greg/tests/test_api_person.py
index d4480409..b504534d 100644
--- a/greg/tests/test_api_person.py
+++ b/greg/tests/test_api_person.py
@@ -28,10 +28,16 @@ class PersonTestData:
         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)
diff --git a/greg/tests/test_models.py b/greg/tests/test_models.py
new file mode 100644
index 00000000..0c587924
--- /dev/null
+++ b/greg/tests/test_models.py
@@ -0,0 +1,182 @@
+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)
-- 
GitLab