diff --git a/Makefile b/Makefile
index 10de836a3643e896093cd5ef118041f41c77286a..0ba8c72028107058bde3ade7598291267d169b9d 100644
--- a/Makefile
+++ b/Makefile
@@ -5,12 +5,14 @@ VENV     = .venv
 PORT     = 8013
 SETTINGS = registry.settings.dev
 
-.PHONY: help venv install build run shell migrations migrate
+.PHONY: help venv install install-hooks hooks build run shell migrations migrate
 
 help:
 	@echo "Setup:"
 	@echo "  venv           Setup virtual environment"
 	@echo "  install        Install dependencies to venv"
+	@echo "  install-hooks  Install pre-commit hooks"
+	@echo "  hooks          Run pre-commit hooks manually"
 	@echo "  build          Build CSS and JS files"
 	@echo ""
 	@echo "Application:"
diff --git a/shared/migrations/__init__.py b/contracts/__init__.py
similarity index 100%
rename from shared/migrations/__init__.py
rename to contracts/__init__.py
diff --git a/contracts/admin.py b/contracts/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/contracts/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/contracts/apps.py b/contracts/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3cd662eed4aff0423a4500a18c15f1479ac238a
--- /dev/null
+++ b/contracts/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ContractsConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "contracts"
diff --git a/shared/migrations/0001_initial.py b/contracts/migrations/0001_initial.py
similarity index 68%
rename from shared/migrations/0001_initial.py
rename to contracts/migrations/0001_initial.py
index c0a4def389d06410cc2907e8d84eb59175788879..c049786ba448c708abb09138dd3a185d6dc0399b 100644
--- a/shared/migrations/0001_initial.py
+++ b/contracts/migrations/0001_initial.py
@@ -1,121 +1,16 @@
-# Generated by Django 4.1.4 on 2023-02-03 15:13
+# Generated by Django 4.1.4 on 2023-02-03 15:25
 
-import django.db.models.deletion
-import django.utils.timezone
 import django_countries.fields
 import markdownx.models
-from django.conf import settings
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
     initial = True
 
-    dependencies = [
-        ("auth", "0012_alter_user_first_name_max_length"),
-    ]
+    dependencies = []
 
     operations = [
-        migrations.CreateModel(
-            name="User",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                (
-                    "is_superuser",
-                    models.BooleanField(
-                        default=False,
-                        help_text="Designates that this user has all permissions without explicitly assigning them.",
-                        verbose_name="superuser status",
-                    ),
-                ),
-                (
-                    "sso_id",
-                    models.CharField(
-                        error_messages={
-                            "unique": "A user with that SSO ID already exists."
-                        },
-                        max_length=150,
-                        unique=True,
-                        verbose_name="SSO ID",
-                    ),
-                ),
-                (
-                    "first_name",
-                    models.CharField(
-                        blank=True, max_length=150, verbose_name="first name"
-                    ),
-                ),
-                (
-                    "last_name",
-                    models.CharField(
-                        blank=True, max_length=150, verbose_name="last name"
-                    ),
-                ),
-                (
-                    "email",
-                    models.EmailField(
-                        blank=True, max_length=254, verbose_name="email address"
-                    ),
-                ),
-                (
-                    "is_staff",
-                    models.BooleanField(
-                        default=False,
-                        help_text="Designates whether the user can log into this admin site.",
-                        verbose_name="staff status",
-                    ),
-                ),
-                (
-                    "is_active",
-                    models.BooleanField(
-                        default=True,
-                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
-                        verbose_name="active",
-                    ),
-                ),
-                (
-                    "date_joined",
-                    models.DateTimeField(
-                        default=django.utils.timezone.now, verbose_name="date joined"
-                    ),
-                ),
-                (
-                    "groups",
-                    models.ManyToManyField(
-                        blank=True,
-                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
-                        related_name="user_set",
-                        related_query_name="user",
-                        to="auth.group",
-                        verbose_name="groups",
-                    ),
-                ),
-                (
-                    "user_permissions",
-                    models.ManyToManyField(
-                        blank=True,
-                        help_text="Specific permissions for this user.",
-                        related_name="user_set",
-                        related_query_name="user",
-                        to="auth.permission",
-                        verbose_name="user permissions",
-                    ),
-                ),
-            ],
-            options={
-                "verbose_name": "user",
-                "verbose_name_plural": "users",
-                "abstract": False,
-            },
-        ),
         migrations.CreateModel(
             name="Contract",
             fields=[
@@ -281,6 +176,21 @@ class Migration(migrations.Migration):
                 "verbose_name_plural": "Smlouvy",
             },
         ),
+        migrations.CreateModel(
+            name="ContractExternalSignature",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("date", models.DateField(verbose_name="Datum podpisu")),
+            ],
+        ),
         migrations.CreateModel(
             name="ContractExternalSigner",
             fields=[
@@ -411,6 +321,21 @@ class Migration(migrations.Migration):
                 "verbose_name_plural": "Problémy se smlouvou",
             },
         ),
+        migrations.CreateModel(
+            name="ContractLocalSignature",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("date", models.DateField(verbose_name="Datum podpisu")),
+            ],
+        ),
         migrations.CreateModel(
             name="ContractLocalSigner",
             fields=[
@@ -498,25 +423,6 @@ class Migration(migrations.Migration):
                 "verbose_name_plural": "Naše smlouvní strany",
             },
         ),
-        migrations.CreateModel(
-            name="ContractSubtype",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=32, verbose_name="Jméno")),
-            ],
-            options={
-                "verbose_name": "Podtyp smlouvy",
-                "verbose_name_plural": "Podtypy smlouvy",
-            },
-        ),
         migrations.CreateModel(
             name="ContractNote",
             fields=[
@@ -531,22 +437,6 @@ class Migration(migrations.Migration):
                 ),
                 ("created_date", models.DateTimeField(verbose_name="Datum vytvoření")),
                 ("content", markdownx.models.MarkdownxField(verbose_name="Obsah")),
-                (
-                    "author",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        related_name="contract_notes",
-                        to=settings.AUTH_USER_MODEL,
-                        verbose_name="Autor",
-                    ),
-                ),
-                (
-                    "contract",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        to="shared.contract",
-                    ),
-                ),
             ],
             options={
                 "verbose_name": "Poznámka ke smlouvě",
@@ -554,29 +444,7 @@ class Migration(migrations.Migration):
             },
         ),
         migrations.CreateModel(
-            name="ContractLocalSignature",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("date", models.DateField(verbose_name="Datum podpisu")),
-                (
-                    "signer",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        to="shared.contractlocalsigner",
-                    ),
-                ),
-            ],
-        ),
-        migrations.CreateModel(
-            name="ContractExternalSignature",
+            name="ContractSubtype",
             fields=[
                 (
                     "id",
@@ -587,82 +455,11 @@ class Migration(migrations.Migration):
                         verbose_name="ID",
                     ),
                 ),
-                ("date", models.DateField(verbose_name="Datum podpisu")),
-                (
-                    "signer",
-                    models.ForeignKey(
-                        on_delete=django.db.models.deletion.CASCADE,
-                        to="shared.contractexternalsigner",
-                    ),
-                ),
+                ("name", models.CharField(max_length=32, verbose_name="Jméno")),
             ],
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="external_signature",
-            field=models.ManyToManyField(to="shared.contractexternalsignature"),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="filing_area",
-            field=models.ForeignKey(
-                blank=True,
-                help_text="Obsah není veřejně přístupný.",
-                null=True,
-                on_delete=django.db.models.deletion.CASCADE,
-                to="shared.contractfilingarea",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="issues",
-            field=models.ManyToManyField(to="shared.contractissue"),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="local_signature",
-            field=models.ManyToManyField(to="shared.contractlocalsignature"),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="primary_contract",
-            field=models.ForeignKey(
-                blank=True,
-                null=True,
-                on_delete=django.db.models.deletion.CASCADE,
-                to="shared.contract",
-                verbose_name="Hlavní smlouva",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="public_status_set_by",
-            field=models.ForeignKey(
-                help_text="Obsah není veřejně přístupný.",
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="public_status_altered_contracts",
-                to=settings.AUTH_USER_MODEL,
-                verbose_name="Zveřejněno / nezveřejněno uživatelem",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="subtype",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                to="shared.contractsubtype",
-                verbose_name="Podtyp",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="uploaded_by",
-            field=models.ForeignKey(
-                help_text="Informace není veřejně přístupná.",
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="uploaded_contracts",
-                to=settings.AUTH_USER_MODEL,
-                verbose_name="Nahráno uživatelem",
-            ),
+            options={
+                "verbose_name": "Podtyp smlouvy",
+                "verbose_name_plural": "Podtypy smlouvy",
+            },
         ),
     ]
diff --git a/contracts/migrations/0002_initial.py b/contracts/migrations/0002_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..f85a350f4139cb677dc01ddd42d22a5b6ccf2ef1
--- /dev/null
+++ b/contracts/migrations/0002_initial.py
@@ -0,0 +1,118 @@
+# Generated by Django 4.1.4 on 2023-02-03 15:25
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("contracts", "0001_initial"),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="contractnote",
+            name="author",
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="contract_notes",
+                to=settings.AUTH_USER_MODEL,
+                verbose_name="Autor",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contractnote",
+            name="contract",
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE, to="contracts.contract"
+            ),
+        ),
+        migrations.AddField(
+            model_name="contractlocalsignature",
+            name="signer",
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                to="contracts.contractlocalsigner",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contractexternalsignature",
+            name="signer",
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                to="contracts.contractexternalsigner",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="external_signature",
+            field=models.ManyToManyField(to="contracts.contractexternalsignature"),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="filing_area",
+            field=models.ForeignKey(
+                blank=True,
+                help_text="Obsah není veřejně přístupný.",
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                to="contracts.contractfilingarea",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="issues",
+            field=models.ManyToManyField(to="contracts.contractissue"),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="local_signature",
+            field=models.ManyToManyField(to="contracts.contractlocalsignature"),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="primary_contract",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                to="contracts.contract",
+                verbose_name="Hlavní smlouva",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="public_status_set_by",
+            field=models.ForeignKey(
+                help_text="Obsah není veřejně přístupný.",
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="public_status_altered_contracts",
+                to=settings.AUTH_USER_MODEL,
+                verbose_name="Zveřejněno / nezveřejněno uživatelem",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="subtype",
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                to="contracts.contractsubtype",
+                verbose_name="Podtyp",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="uploaded_by",
+            field=models.ForeignKey(
+                help_text="Informace není veřejně přístupná.",
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="uploaded_contracts",
+                to=settings.AUTH_USER_MODEL,
+                verbose_name="Nahráno uživatelem",
+            ),
+        ),
+    ]
diff --git a/contracts/migrations/__init__.py b/contracts/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/contracts/models.py b/contracts/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b04f2042085cab494c1e1d6f4f01fd5fde121fa
--- /dev/null
+++ b/contracts/models.py
@@ -0,0 +1,405 @@
+from django.conf import settings
+from django.db import models
+from django_countries.fields import CountryField
+from markdownx.models import MarkdownxField
+
+from users.models import User
+
+
+class ContractExternalSigner(models.Model):
+    name = models.CharField(
+        max_length=256,
+        verbose_name="Jméno",
+    )
+
+    is_legal_entity = models.BooleanField(
+        verbose_name="Je právnická osoba",
+        help_text="Důležité označit správně! Pokud není osoba právnická, zveřejňujeme pouze obec a zemi.",
+    )
+
+    address_street_with_number = models.CharField(
+        max_length=256,
+        verbose_name="Ulice, č.p.",
+        help_text="Viditelné pouze u právnických osob.",
+    )  # WARNING: Legal entity status dependent!
+
+    address_district = models.CharField(
+        max_length=256,
+        verbose_name="Obec",
+    )
+
+    address_zip = models.CharField(
+        max_length=16,
+        verbose_name="PSČ",
+        help_text="Viditelné pouze u právnických osob.",
+    )  # WARNING: Legal entity status dependent!
+
+    address_country = CountryField(
+        verbose_name="Země",
+    )
+
+    ico_number = models.CharField(
+        max_length=16,
+        blank=True,
+        null=True,
+        verbose_name="IČO",
+    )  # WARNING: Legal entity status dependent!
+
+    date_of_birth = models.DateField(
+        blank=True,
+        null=True,
+        verbose_name="Datum narození",
+    )  # WARNING: Legal entity status dependent!
+
+    representative_name = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Zástupce",
+    )
+
+    representative_role = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Funkce zástupce",
+    )
+
+    department = models.CharField(
+        max_length=128,
+        blank=True,
+        null=True,
+        verbose_name="Organizační složka",
+    )
+
+    class Meta:
+        verbose_name = "Druhá smluvní strana"
+        verbose_name_plural = "Druhé smluvní strany"
+
+
+class ContractExternalSignature(models.Model):
+    signer = models.ForeignKey(
+        ContractExternalSigner,
+        on_delete=models.CASCADE,
+    )
+
+    date = models.DateField(
+        verbose_name="Datum podpisu",
+    )
+
+
+class ContractLocalSigner(models.Model):
+    name = models.CharField(
+        max_length=256,
+        default=settings.DEFAULT_LOCAL_SIGNER_NAME,
+        verbose_name="Jméno",
+    )
+
+    address_street_with_number = models.CharField(
+        max_length=256,
+        default=settings.DEFAULT_LCOAL_SIGNER_STREET,
+        verbose_name="Ulice, č.p.",
+    )
+
+    address_district = models.CharField(
+        max_length=256,
+        default=settings.DEFAULT_LOCAL_SIGNER_DISTRICT,
+        verbose_name="Obec",
+    )
+
+    address_zip = models.CharField(
+        max_length=16,
+        default=settings.DEFAULT_LOCAL_SIGNER_ZIP,
+        verbose_name="PSČ",
+    )
+
+    address_country = CountryField(
+        default=settings.DEFAULT_LOCAL_SIGNER_COUNTRY,
+        verbose_name="Země",
+    )
+
+    ico_number = models.CharField(
+        max_length=16,
+        blank=True,
+        null=True,
+        default=settings.DEFAULT_LOCAL_SIGNER_ICO_NUMBER,
+        verbose_name="IČO",
+    )
+
+    representative_name = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Zástupce",
+    )
+
+    representative_role = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Funkce zástupce",
+    )
+
+    department = models.CharField(
+        max_length=128,
+        blank=True,
+        null=True,
+        verbose_name="Organizační složka",
+    )
+
+    # TODO: Input validation
+    color = models.CharField(
+        max_length=6,  # e.g. "ffffff"
+        verbose_name="Barva",
+    )
+
+    class Meta:
+        verbose_name = "Naše smluvní strana"
+        verbose_name_plural = "Naše smlouvní strany"
+
+
+class ContractLocalSignature(models.Model):
+    signer = models.ForeignKey(
+        ContractLocalSigner,
+        on_delete=models.CASCADE,
+    )
+
+    date = models.DateField(
+        verbose_name="Datum podpisu",
+    )
+
+
+class ContractSubtype(models.Model):
+    name = models.CharField(
+        max_length=32,
+        verbose_name="Jméno",
+    )
+
+    class Meta:
+        verbose_name = "Podtyp smlouvy"
+        verbose_name_plural = "Podtypy smlouvy"
+
+
+class ContractIssue(models.Model):
+    name = models.CharField(
+        max_length=32,
+        verbose_name="Jméno",
+    )
+
+    class Meta:
+        verbose_name = "Problém se smlouvou"
+        verbose_name_plural = "Problémy se smlouvou"
+
+
+class ContractFilingArea(models.Model):
+    name = models.CharField(
+        max_length=32,
+        verbose_name="Jméno",
+    )
+
+    person_responsible = models.CharField(
+        max_length=256,
+        verbose_name="Odpovědná osoba",
+    )
+
+    class Meta:
+        verbose_name = "Spisovna"
+        verbose_name_plural = "Spisovny"
+
+
+class Contract(models.Model):
+    class ContractTypes(models.TextChoices):
+        PRIMARY = "primary", "Hlavní"
+        AMENDMENT = "amendment", "Dodatek"
+        FRAMEWORK_ORDER = "framework_order", "Objednávka u rámcové smlouvy"
+
+    type = models.CharField(
+        max_length=15,
+        choices=ContractTypes.choices,
+        default=ContractTypes.PRIMARY,
+        verbose_name="Typ",
+    )
+
+    subtype = models.ForeignKey(
+        ContractSubtype,
+        on_delete=models.CASCADE,
+        verbose_name="Podtyp",
+    )
+
+    contains_nda = models.BooleanField(
+        default=False,
+        verbose_name="Obsahuje NDA",
+    )
+
+    external_signature = models.ManyToManyField(ContractExternalSignature)
+
+    local_signature = models.ManyToManyField(ContractLocalSignature)
+
+    all_parties_sign_date = models.DateField(
+        verbose_name="Datum podpisu všech stran",
+    )  # WARNING: Exclude in admin, autofill
+
+    valid_start_date = models.DateField(
+        verbose_name="Začátek účinnosti",
+    )
+    valid_end_date = models.DateField(
+        verbose_name="Konec platnosti",
+    )
+
+    uploaded_by = models.ForeignKey(
+        User,
+        on_delete=models.CASCADE,
+        related_name="uploaded_contracts",
+        verbose_name="Nahráno uživatelem",
+        help_text="Informace není veřejně přístupná.",
+    )  # WARNING: exclude in admin
+
+    class LegalStates(models.TextChoices):
+        VALID = "valid", "Platná"
+        EFFECTIVE = "effective", "Účinná"
+        NOT_EFFECTIVE = "not_effective", "Neúčinná"
+        INVALID = "invalid", "Neplatná"
+
+    class PublicStates(models.TextChoices):
+        UNKNOWN = "unknown", "Nová"
+        YES = "yes", "Zveřejněná"
+        NO = "no", "Neveřejná"
+
+    class PaperFormStates(models.TextChoices):
+        SENT = "sent", "Odeslaná"
+        STORED = "stored", "Uložená"
+        TO_SHRED = "to_shred", "Ke skartaci"
+        SHREDDED = "shredded", "Skartovaná"
+
+    legal_state = models.CharField(
+        max_length=13,
+        choices=LegalStates.choices,
+        verbose_name="Stav právního ujednání",
+    )
+
+    public_state = models.CharField(
+        max_length=7,
+        choices=PublicStates.choices,
+        verbose_name="Veřejnost smlouvy",
+    )
+
+    paper_form_state = models.CharField(
+        max_length=8,
+        choices=PaperFormStates.choices,
+        verbose_name="Stav papírové formy",
+    )
+
+    public_status_set_by = models.ForeignKey(
+        User,
+        on_delete=models.CASCADE,
+        related_name="public_status_altered_contracts",
+        verbose_name="Zveřejněno / nezveřejněno uživatelem",
+        help_text="Obsah není veřejně přístupný.",
+    )  # WARNING: exclude in admin
+
+    publishing_rejection_comment = models.CharField(
+        max_length=65536,
+        blank=True,
+        null=True,
+        verbose_name="Důvod nezveřejnění",
+        help_text="Obsah není veřejně přístupný.",
+    )  # WARNING: exclude in admin
+
+    tender_url = models.URLField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Odkaz na výběrové řízení",
+    )
+
+    identifier = models.CharField(
+        max_length=128,
+        verbose_name="Identifikační číslo",
+    )
+
+    issues = models.ManyToManyField(ContractIssue)
+
+    summary = models.CharField(
+        max_length=65536,
+        blank=True,
+        null=True,
+        verbose_name="Rekapitulace",
+        help_text="Obsah není veřejně přístupný.",
+    )
+
+    anonymized_contract_file = models.FileField(
+        verbose_name="Anonymizovaná smlouva (PDF)",
+    )
+
+    original_contract_file = models.FileField(
+        verbose_name="Originální verze smlouvy (PDF)",
+        help_text="Obsah není veřejně přístupný.",
+    )
+
+    primary_contract = models.ForeignKey(
+        "Contract",
+        on_delete=models.CASCADE,
+        blank=True,
+        null=True,
+        verbose_name="Hlavní smlouva",
+    )  # WARNING: Dependent on the type!
+
+    expected_cost_total = models.IntegerField(verbose_name="Očekáváná celková cena")
+
+    expected_cost_year = models.IntegerField(verbose_name="Očekáváná cena za rok")
+
+    expected_cost_month = models.IntegerField(verbose_name="Očekáváná cena za měsíc")
+
+    expected_cost_hour = models.IntegerField(verbose_name="Očekáváná cena za hodinu")
+
+    intent_url = models.URLField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Odkaz na záměr",
+    )
+
+    agreement_url = models.URLField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Odkaz na schválení",
+    )  # WARNING: Dependent on the type!
+
+    filing_area = models.ForeignKey(
+        ContractFilingArea,
+        on_delete=models.CASCADE,
+        blank=True,
+        null=True,
+        help_text="Obsah není veřejně přístupný.",
+    )  # WARNING: Dependent on the type!
+
+    class Meta:
+        verbose_name = "Smlouva"
+        verbose_name_plural = "Smlouvy"
+
+
+class ContractNote(models.Model):
+    contract = models.ForeignKey(
+        Contract,
+        on_delete=models.CASCADE,
+    )
+
+    author = models.ForeignKey(
+        User,
+        related_name="contract_notes",
+        on_delete=models.CASCADE,
+        verbose_name="Autor",
+    )
+
+    created_date = models.DateTimeField(
+        verbose_name="Datum vytvoření",
+    )
+
+    content = MarkdownxField(
+        verbose_name="Obsah",
+    )
+
+    class Meta:
+        verbose_name = "Poznámka ke smlouvě"
+        verbose_name_plural = "Poznámky ke smlouvě"
diff --git a/contracts/tests.py b/contracts/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/contracts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/contracts/views.py b/contracts/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..91ea44a218fbd2f408430959283f0419c921093e
--- /dev/null
+++ b/contracts/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/registry/settings/base.py b/registry/settings/base.py
index 93c9200ab4a598fc91fbc1ba8bd3dc6b759e22ca..7fa8ccf883d258a48cf5995450e18c4faa4e4bb6 100644
--- a/registry/settings/base.py
+++ b/registry/settings/base.py
@@ -45,7 +45,9 @@ INSTALLED_APPS = [
     "django.contrib.sessions",
     "django.contrib.messages",
     "django.contrib.staticfiles",
+    "contracts",
     "shared",
+    "users",
 ]
 
 MIDDLEWARE = [
@@ -102,7 +104,7 @@ AUTH_PASSWORD_VALIDATORS = [
         "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
     },
 ]
-AUTH_USER_MODEL = "shared.User"
+AUTH_USER_MODEL = "users.User"
 
 
 # Internationalization
diff --git a/shared/models.py b/shared/models.py
index e48deae16e8c2c6a048db431fd5f7582764cf369..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/shared/models.py
+++ b/shared/models.py
@@ -1,408 +0,0 @@
-from django.conf import settings
-from django.db import models
-from django_countries.fields import CountryField
-from markdownx.models import MarkdownxField
-from pirates import models as pirates_models
-
-
-class User(pirates_models.AbstractUser):
-    pass
-
-
-class ContractExternalSigner(models.Model):
-    name = models.CharField(
-        max_length=256,
-        verbose_name="Jméno",
-    )
-
-    is_legal_entity = models.BooleanField(
-        verbose_name="Je právnická osoba",
-        help_text="Důležité označit správně! Pokud není osoba právnická, zveřejňujeme pouze obec a zemi.",
-    )
-
-    address_street_with_number = models.CharField(
-        max_length=256,
-        verbose_name="Ulice, č.p.",
-        help_text="Viditelné pouze u právnických osob.",
-    )  # WARNING: Legal entity status dependent!
-
-    address_district = models.CharField(
-        max_length=256,
-        verbose_name="Obec",
-    )
-
-    address_zip = models.CharField(
-        max_length=16,
-        verbose_name="PSČ",
-        help_text="Viditelné pouze u právnických osob.",
-    )  # WARNING: Legal entity status dependent!
-
-    address_country = CountryField(
-        verbose_name="Země",
-    )
-
-    ico_number = models.CharField(
-        max_length=16,
-        blank=True,
-        null=True,
-        verbose_name="IČO",
-    )  # WARNING: Legal entity status dependent!
-
-    date_of_birth = models.DateField(
-        blank=True,
-        null=True,
-        verbose_name="Datum narození",
-    )  # WARNING: Legal entity status dependent!
-
-    representative_name = models.CharField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Zástupce",
-    )
-
-    representative_role = models.CharField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Funkce zástupce",
-    )
-
-    department = models.CharField(
-        max_length=128,
-        blank=True,
-        null=True,
-        verbose_name="Organizační složka",
-    )
-
-    class Meta:
-        verbose_name = "Druhá smluvní strana"
-        verbose_name_plural = "Druhé smluvní strany"
-
-
-class ContractExternalSignature(models.Model):
-    signer = models.ForeignKey(
-        ContractExternalSigner,
-        on_delete=models.CASCADE,
-    )
-
-    date = models.DateField(
-        verbose_name="Datum podpisu",
-    )
-
-
-class ContractLocalSigner(models.Model):
-    name = models.CharField(
-        max_length=256,
-        default=settings.DEFAULT_LOCAL_SIGNER_NAME,
-        verbose_name="Jméno",
-    )
-
-    address_street_with_number = models.CharField(
-        max_length=256,
-        default=settings.DEFAULT_LCOAL_SIGNER_STREET,
-        verbose_name="Ulice, č.p.",
-    )
-
-    address_district = models.CharField(
-        max_length=256,
-        default=settings.DEFAULT_LOCAL_SIGNER_DISTRICT,
-        verbose_name="Obec",
-    )
-
-    address_zip = models.CharField(
-        max_length=16,
-        default=settings.DEFAULT_LOCAL_SIGNER_ZIP,
-        verbose_name="PSČ",
-    )
-
-    address_country = CountryField(
-        default=settings.DEFAULT_LOCAL_SIGNER_COUNTRY,
-        verbose_name="Země",
-    )
-
-    ico_number = models.CharField(
-        max_length=16,
-        blank=True,
-        null=True,
-        default=settings.DEFAULT_LOCAL_SIGNER_ICO_NUMBER,
-        verbose_name="IČO",
-    )
-
-    representative_name = models.CharField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Zástupce",
-    )
-
-    representative_role = models.CharField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Funkce zástupce",
-    )
-
-    department = models.CharField(
-        max_length=128,
-        blank=True,
-        null=True,
-        verbose_name="Organizační složka",
-    )
-
-    # TODO: Input validation
-    color = models.CharField(
-        max_length=6,  # e.g. "ffffff"
-        verbose_name="Barva",
-    )
-
-    class Meta:
-        verbose_name = "Naše smluvní strana"
-        verbose_name_plural = "Naše smlouvní strany"
-
-
-class ContractLocalSignature(models.Model):
-    signer = models.ForeignKey(
-        ContractLocalSigner,
-        on_delete=models.CASCADE,
-    )
-
-    date = models.DateField(
-        verbose_name="Datum podpisu",
-    )
-
-
-class ContractSubtype(models.Model):
-    name = models.CharField(
-        max_length=32,
-        verbose_name="Jméno",
-    )
-
-    class Meta:
-        verbose_name = "Podtyp smlouvy"
-        verbose_name_plural = "Podtypy smlouvy"
-
-
-class ContractIssue(models.Model):
-    name = models.CharField(
-        max_length=32,
-        verbose_name="Jméno",
-    )
-
-    class Meta:
-        verbose_name = "Problém se smlouvou"
-        verbose_name_plural = "Problémy se smlouvou"
-
-
-class ContractFilingArea(models.Model):
-    name = models.CharField(
-        max_length=32,
-        verbose_name="Jméno",
-    )
-
-    person_responsible = models.CharField(
-        max_length=256,
-        verbose_name="Odpovědná osoba",
-    )
-
-    class Meta:
-        verbose_name = "Spisovna"
-        verbose_name_plural = "Spisovny"
-
-
-class Contract(models.Model):
-    class ContractTypes(models.TextChoices):
-        PRIMARY = "primary", "Hlavní"
-        AMENDMENT = "amendment", "Dodatek"
-        FRAMEWORK_ORDER = "framework_order", "Objednávka u rámcové smlouvy"
-
-    type = models.CharField(
-        max_length=15,
-        choices=ContractTypes.choices,
-        default=ContractTypes.PRIMARY,
-        verbose_name="Typ",
-    )
-
-    subtype = models.ForeignKey(
-        ContractSubtype,
-        on_delete=models.CASCADE,
-        verbose_name="Podtyp",
-    )
-
-    contains_nda = models.BooleanField(
-        default=False,
-        verbose_name="Obsahuje NDA",
-    )
-
-    external_signature = models.ManyToManyField(ContractExternalSignature)
-
-    local_signature = models.ManyToManyField(ContractLocalSignature)
-
-    all_parties_sign_date = models.DateField(
-        verbose_name="Datum podpisu všech stran",
-    )  # WARNING: Exclude in admin, autofill
-
-    valid_start_date = models.DateField(
-        verbose_name="Začátek účinnosti",
-    )
-    valid_end_date = models.DateField(
-        verbose_name="Konec platnosti",
-    )
-
-    uploaded_by = models.ForeignKey(
-        User,
-        on_delete=models.CASCADE,
-        related_name="uploaded_contracts",
-        verbose_name="Nahráno uživatelem",
-        help_text="Informace není veřejně přístupná.",
-    )  # WARNING: exclude in admin
-
-    class LegalStates(models.TextChoices):
-        VALID = "valid", "Platná"
-        EFFECTIVE = "effective", "Účinná"
-        NOT_EFFECTIVE = "not_effective", "Neúčinná"
-        INVALID = "invalid", "Neplatná"
-
-    class PublicStates(models.TextChoices):
-        UNKNOWN = "unknown", "Nová"
-        YES = "yes", "Zveřejněná"
-        NO = "no", "Neveřejná"
-
-    class PaperFormStates(models.TextChoices):
-        SENT = "sent", "Odeslaná"
-        STORED = "stored", "Uložená"
-        TO_SHRED = "to_shred", "Ke skartaci"
-        SHREDDED = "shredded", "Skartovaná"
-
-    legal_state = models.CharField(
-        max_length=13,
-        choices=LegalStates.choices,
-        verbose_name="Stav právního ujednání",
-    )
-
-    public_state = models.CharField(
-        max_length=7,
-        choices=PublicStates.choices,
-        verbose_name="Veřejnost smlouvy",
-    )
-
-    paper_form_state = models.CharField(
-        max_length=8,
-        choices=PaperFormStates.choices,
-        verbose_name="Stav papírové formy",
-    )
-
-    public_status_set_by = models.ForeignKey(
-        User,
-        on_delete=models.CASCADE,
-        related_name="public_status_altered_contracts",
-        verbose_name="Zveřejněno / nezveřejněno uživatelem",
-        help_text="Obsah není veřejně přístupný.",
-    )  # WARNING: exclude in admin
-
-    publishing_rejection_comment = models.CharField(
-        max_length=65536,
-        blank=True,
-        null=True,
-        verbose_name="Důvod nezveřejnění",
-        help_text="Obsah není veřejně přístupný.",
-    )  # WARNING: exclude in admin
-
-    tender_url = models.URLField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Odkaz na výběrové řízení",
-    )
-
-    identifier = models.CharField(
-        max_length=128,
-        verbose_name="Identifikační číslo",
-    )
-
-    issues = models.ManyToManyField(ContractIssue)
-
-    summary = models.CharField(
-        max_length=65536,
-        blank=True,
-        null=True,
-        verbose_name="Rekapitulace",
-        help_text="Obsah není veřejně přístupný.",
-    )
-
-    anonymized_contract_file = models.FileField(
-        verbose_name="Anonymizovaná smlouva (PDF)",
-    )
-
-    original_contract_file = models.FileField(
-        verbose_name="Originální verze smlouvy (PDF)",
-        help_text="Obsah není veřejně přístupný.",
-    )
-
-    primary_contract = models.ForeignKey(
-        "Contract",
-        on_delete=models.CASCADE,
-        blank=True,
-        null=True,
-        verbose_name="Hlavní smlouva",
-    )  # WARNING: Dependent on the type!
-
-    expected_cost_total = models.IntegerField(verbose_name="Očekáváná celková cena")
-
-    expected_cost_year = models.IntegerField(verbose_name="Očekáváná cena za rok")
-
-    expected_cost_month = models.IntegerField(verbose_name="Očekáváná cena za měsíc")
-
-    expected_cost_hour = models.IntegerField(verbose_name="Očekáváná cena za hodinu")
-
-    intent_url = models.URLField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Odkaz na záměr",
-    )
-
-    agreement_url = models.URLField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Odkaz na schválení",
-    )  # WARNING: Dependent on the type!
-
-    filing_area = models.ForeignKey(
-        ContractFilingArea,
-        on_delete=models.CASCADE,
-        blank=True,
-        null=True,
-        help_text="Obsah není veřejně přístupný.",
-    )  # WARNING: Dependent on the type!
-
-    class Meta:
-        verbose_name = "Smlouva"
-        verbose_name_plural = "Smlouvy"
-
-
-class ContractNote(models.Model):
-    contract = models.ForeignKey(
-        Contract,
-        on_delete=models.CASCADE,
-    )
-
-    author = models.ForeignKey(
-        User,
-        related_name="contract_notes",
-        on_delete=models.CASCADE,
-        verbose_name="Autor",
-    )
-
-    created_date = models.DateTimeField(
-        verbose_name="Datum vytvoření",
-    )
-
-    content = MarkdownxField(
-        verbose_name="Obsah",
-    )
-
-    class Meta:
-        verbose_name = "Poznámka ke smlouvě"
-        verbose_name_plural = "Poznámky ke smlouvě"
diff --git a/users/__init__.py b/users/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/users/admin.py b/users/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/users/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/users/apps.py b/users/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..88f7b1798e7f1150a0e4e86781d9533d9573db3d
--- /dev/null
+++ b/users/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class UsersConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "users"
diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..e975e4f7c2e0d4271cc7f3e7312d29168503a70a
--- /dev/null
+++ b/users/migrations/0001_initial.py
@@ -0,0 +1,115 @@
+# Generated by Django 4.1.4 on 2023-02-03 15:25
+
+import django.utils.timezone
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("auth", "0012_alter_user_first_name_max_length"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="User",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "is_superuser",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates that this user has all permissions without explicitly assigning them.",
+                        verbose_name="superuser status",
+                    ),
+                ),
+                (
+                    "sso_id",
+                    models.CharField(
+                        error_messages={
+                            "unique": "A user with that SSO ID already exists."
+                        },
+                        max_length=150,
+                        unique=True,
+                        verbose_name="SSO ID",
+                    ),
+                ),
+                (
+                    "first_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="first name"
+                    ),
+                ),
+                (
+                    "last_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="last name"
+                    ),
+                ),
+                (
+                    "email",
+                    models.EmailField(
+                        blank=True, max_length=254, verbose_name="email address"
+                    ),
+                ),
+                (
+                    "is_staff",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates whether the user can log into this admin site.",
+                        verbose_name="staff status",
+                    ),
+                ),
+                (
+                    "is_active",
+                    models.BooleanField(
+                        default=True,
+                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
+                        verbose_name="active",
+                    ),
+                ),
+                (
+                    "date_joined",
+                    models.DateTimeField(
+                        default=django.utils.timezone.now, verbose_name="date joined"
+                    ),
+                ),
+                (
+                    "groups",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.group",
+                        verbose_name="groups",
+                    ),
+                ),
+                (
+                    "user_permissions",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="Specific permissions for this user.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.permission",
+                        verbose_name="user permissions",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "user",
+                "verbose_name_plural": "users",
+                "abstract": False,
+            },
+        ),
+    ]
diff --git a/users/migrations/__init__.py b/users/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/users/models.py b/users/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea62fb3fa8eef5bddf9697561fab74f215460fd4
--- /dev/null
+++ b/users/models.py
@@ -0,0 +1,5 @@
+from pirates import models as pirates_models
+
+
+class User(pirates_models.AbstractUser):
+    pass
diff --git a/users/tests.py b/users/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/users/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/users/views.py b/users/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..91ea44a218fbd2f408430959283f0419c921093e
--- /dev/null
+++ b/users/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.