diff --git a/.gitignore b/.gitignore
index 0a533b6336363200760160adadd001271a20108a..2f272e7c1b472b8ba25fa864a333bb76a4cd1161 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ staticfiles
 node_modules/*
 shared/static/*
 webpack-stats.json
+package-lock.json
 .venv
diff --git a/README.md b/README.md
index ed15f886bea2f0105b801a0f1bfd4523427ffd05..a2042d02b073bdedf3dbb92cb17e14af6911e389 100644
--- a/README.md
+++ b/README.md
@@ -21,12 +21,12 @@ Je třeba definovat minimálně následující environment proměnné:
 | --- | --- |
 | `DATABASE_URL` | URL pro připojení k databázi ve formátu `postgresql://username:password@host:5432/database_name` |
 | `SECRET_KEY` | Tajný klíč např. pro šifrování |
-| `DEFAULT_LOCAL_SIGNER_NAME` | Defaultní jméno naší podepisující strany |
-| `DEFAULT_LCOAL_SIGNER_STREET` | Defaultní ulice a č.p. naší podepisující strany |
-| `DEFAULT_LOCAL_SIGNER_ZIP` | Defaultní PSČ naší podepisující strany |
-| `DEFAULT_LOCAL_SIGNER_DISTRICT` | Defaultní obec naší podepisující strany |
-| `DEFAULT_LOCAL_SIGNER_COUNTRY` | Defaultní země naší podepisující strany, např. `CZ`, `DE` |
-| `DEFAULT_LOCAL_SIGNER_ICO_NUMBER` | Defaultní IČO naší podepisující strany |
+| `DEFAULT_CONTRACTEE_NAME` | Defaultní jméno naší podepisující strany |
+| `DEFAULT_CONTRACTEE_STREET` | Defaultní ulice a č.p. naší podepisující strany |
+| `DEFAULT_CONTRACTEE_ZIP` | Defaultní PSČ naší podepisující strany |
+| `DEFAULT_CONTRACTEE_DISTRICT` | Defaultní obec naší podepisující strany |
+| `DEFAULT_CONTRACTEE_COUNTRY` | Defaultní země naší podepisující strany, např. `CZ`, `DE` |
+| `DEFAULT_CONTRACTEE_ICO_NUMBER` | Defaultní IČO naší podepisující strany |
 
 V produkci je potřeba:
 | proměnná | popis |
diff --git a/contracts/admin.py b/contracts/admin.py
index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..a9093aa47b4f0520b2789d9801801bbc84e76e53 100644
--- a/contracts/admin.py
+++ b/contracts/admin.py
@@ -1,3 +1,42 @@
 from django.contrib import admin
 
-# Register your models here.
+from shared.admin import MarkdownxGuardedModelAdmin
+
+from .models import (
+    Contract,
+    Contractee,
+    ContracteeRepresentative,
+    ContracteeSignature,
+    ContractFilingArea,
+    ContractIntent,
+    ContractIssue,
+    ContractSubtype,
+    Signee,
+    SigneeRepresentative,
+    SigneeSignature,
+)
+
+
+class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
+    def has_module_permission(self, request):
+        return False
+
+
+for model in (
+    SigneeRepresentative,
+    SigneeSignature,
+    ContracteeRepresentative,
+    ContracteeSignature,
+    ContractSubtype,
+    ContractIntent,
+):
+    admin.site.register(model, IndexHiddenModelAdmin)
+
+for model in (
+    Signee,
+    Contractee,
+    ContractIssue,
+    ContractFilingArea,
+    Contract,
+):
+    admin.site.register(model, MarkdownxGuardedModelAdmin)
diff --git a/contracts/apps.py b/contracts/apps.py
index d3cd662eed4aff0423a4500a18c15f1479ac238a..d1698620e6fb592dd3b238ad4c6bf9d537585580 100644
--- a/contracts/apps.py
+++ b/contracts/apps.py
@@ -4,3 +4,4 @@ from django.apps import AppConfig
 class ContractsConfig(AppConfig):
     default_auto_field = "django.db.models.BigAutoField"
     name = "contracts"
+    verbose_name = "Smlouvy"
diff --git a/contracts/migrations/0001_initial.py b/contracts/migrations/0001_initial.py
index c049786ba448c708abb09138dd3a185d6dc0399b..7b1063e2c2157e2985b77d84616d85eebf1f2362 100644
--- a/contracts/migrations/0001_initial.py
+++ b/contracts/migrations/0001_initial.py
@@ -1,14 +1,18 @@
-# Generated by Django 4.1.4 on 2023-02-03 15:25
+# Generated by Django 4.1.4 on 2023-02-15 03:36
 
+import django.db.models.deletion
 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 = []
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
 
     operations = [
         migrations.CreateModel(
@@ -112,6 +116,15 @@ class Migration(migrations.Migration):
                         max_length=128, verbose_name="Identifikační číslo"
                     ),
                 ),
+                (
+                    "notes",
+                    markdownx.models.MarkdownxField(
+                        blank=True,
+                        help_text="Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat.",
+                        null=True,
+                        verbose_name="Obsah",
+                    ),
+                ),
                 (
                     "summary",
                     models.CharField(
@@ -152,15 +165,6 @@ class Migration(migrations.Migration):
                     "expected_cost_hour",
                     models.IntegerField(verbose_name="Očekáváná cena za hodinu"),
                 ),
-                (
-                    "intent_url",
-                    models.URLField(
-                        blank=True,
-                        max_length=256,
-                        null=True,
-                        verbose_name="Odkaz na záměr",
-                    ),
-                ),
                 (
                     "agreement_url",
                     models.URLField(
@@ -177,7 +181,7 @@ class Migration(migrations.Migration):
             },
         ),
         migrations.CreateModel(
-            name="ContractExternalSignature",
+            name="Contractee",
             fields=[
                 (
                     "id",
@@ -188,80 +192,48 @@ class Migration(migrations.Migration):
                         verbose_name="ID",
                     ),
                 ),
-                ("date", models.DateField(verbose_name="Datum podpisu")),
-            ],
-        ),
-        migrations.CreateModel(
-            name="ContractExternalSigner",
-            fields=[
                 (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=256, verbose_name="Jméno")),
-                (
-                    "is_legal_entity",
-                    models.BooleanField(
-                        help_text="Důležité označit správně! Pokud není osoba právnická, zveřejňujeme pouze obec a zemi.",
-                        verbose_name="Je právnická osoba",
+                    "name",
+                    models.CharField(
+                        default="Česká pirátská strana",
+                        max_length=256,
+                        verbose_name="Jméno",
                     ),
                 ),
                 (
                     "address_street_with_number",
                     models.CharField(
-                        help_text="Viditelné pouze u právnických osob.",
+                        default="Na Moráni 360/3",
                         max_length=256,
                         verbose_name="Ulice, č.p.",
                     ),
                 ),
                 (
                     "address_district",
-                    models.CharField(max_length=256, verbose_name="Obec"),
+                    models.CharField(
+                        default="Praha 2", max_length=256, verbose_name="Obec"
+                    ),
                 ),
                 (
                     "address_zip",
                     models.CharField(
-                        help_text="Viditelné pouze u právnických osob.",
-                        max_length=16,
-                        verbose_name="PSČ",
+                        default="128 00", max_length=16, verbose_name="PSČ"
                     ),
                 ),
                 (
                     "address_country",
                     django_countries.fields.CountryField(
-                        max_length=2, verbose_name="Země"
+                        default="CZ", max_length=2, verbose_name="Země"
                     ),
                 ),
                 (
                     "ico_number",
-                    models.CharField(
-                        blank=True, max_length=16, null=True, verbose_name="IČO"
-                    ),
-                ),
-                (
-                    "date_of_birth",
-                    models.DateField(
-                        blank=True, null=True, verbose_name="Datum narození"
-                    ),
-                ),
-                (
-                    "representative_name",
-                    models.CharField(
-                        blank=True, max_length=256, null=True, verbose_name="Zástupce"
-                    ),
-                ),
-                (
-                    "representative_role",
                     models.CharField(
                         blank=True,
-                        max_length=256,
+                        default="71339698",
+                        max_length=16,
                         null=True,
-                        verbose_name="Funkce zástupce",
+                        verbose_name="IČO",
                     ),
                 ),
                 (
@@ -273,10 +245,11 @@ class Migration(migrations.Migration):
                         verbose_name="Organizační složka",
                     ),
                 ),
+                ("color", models.CharField(max_length=6, verbose_name="Barva")),
             ],
             options={
-                "verbose_name": "Druhá smluvní strana",
-                "verbose_name_plural": "Druhé smluvní strany",
+                "verbose_name": "Naše smluvní strana",
+                "verbose_name_plural": "Naše smluvní strany",
             },
         ),
         migrations.CreateModel(
@@ -318,11 +291,11 @@ class Migration(migrations.Migration):
             ],
             options={
                 "verbose_name": "Problém se smlouvou",
-                "verbose_name_plural": "Problémy se smlouvou",
+                "verbose_name_plural": "Problémy se smlouvami",
             },
         ),
         migrations.CreateModel(
-            name="ContractLocalSignature",
+            name="ContractSubtype",
             fields=[
                 (
                     "id",
@@ -333,11 +306,15 @@ class Migration(migrations.Migration):
                         verbose_name="ID",
                     ),
                 ),
-                ("date", models.DateField(verbose_name="Datum podpisu")),
+                ("name", models.CharField(max_length=32, verbose_name="Jméno")),
             ],
+            options={
+                "verbose_name": "Podtyp smlouvy",
+                "verbose_name_plural": "Podtypy smlouvy",
+            },
         ),
         migrations.CreateModel(
-            name="ContractLocalSigner",
+            name="Signee",
             fields=[
                 (
                     "id",
@@ -348,63 +325,50 @@ class Migration(migrations.Migration):
                         verbose_name="ID",
                     ),
                 ),
+                ("name", models.CharField(max_length=256, verbose_name="Jméno")),
                 (
-                    "name",
-                    models.CharField(
-                        default="Česká pirátská strana",
-                        max_length=256,
-                        verbose_name="Jméno",
+                    "is_legal_entity",
+                    models.BooleanField(
+                        help_text="Důležité označit správně! Pokud není osoba právnická, zveřejňujeme pouze obec a zemi.",
+                        verbose_name="Je právnická osoba",
                     ),
                 ),
                 (
                     "address_street_with_number",
                     models.CharField(
-                        default="Na Moráni 360/3",
+                        help_text="Viditelné pouze u právnických osob.",
                         max_length=256,
                         verbose_name="Ulice, č.p.",
                     ),
                 ),
                 (
                     "address_district",
-                    models.CharField(
-                        default="Praha 2", max_length=256, verbose_name="Obec"
-                    ),
+                    models.CharField(max_length=256, verbose_name="Obec"),
                 ),
                 (
                     "address_zip",
                     models.CharField(
-                        default="128 00", max_length=16, verbose_name="PSČ"
+                        help_text="Viditelné pouze u právnických osob.",
+                        max_length=16,
+                        verbose_name="PSČ",
                     ),
                 ),
                 (
                     "address_country",
                     django_countries.fields.CountryField(
-                        default="CZ", max_length=2, verbose_name="Země"
+                        max_length=2, verbose_name="Země"
                     ),
                 ),
                 (
                     "ico_number",
                     models.CharField(
-                        blank=True,
-                        default="71339698",
-                        max_length=16,
-                        null=True,
-                        verbose_name="IČO",
-                    ),
-                ),
-                (
-                    "representative_name",
-                    models.CharField(
-                        blank=True, max_length=256, null=True, verbose_name="Zástupce"
+                        blank=True, max_length=16, null=True, verbose_name="IČO"
                     ),
                 ),
                 (
-                    "representative_role",
-                    models.CharField(
-                        blank=True,
-                        max_length=256,
-                        null=True,
-                        verbose_name="Funkce zástupce",
+                    "date_of_birth",
+                    models.DateField(
+                        blank=True, null=True, verbose_name="Datum narození"
                     ),
                 ),
                 (
@@ -416,15 +380,14 @@ class Migration(migrations.Migration):
                         verbose_name="Organizační složka",
                     ),
                 ),
-                ("color", models.CharField(max_length=6, verbose_name="Barva")),
             ],
             options={
-                "verbose_name": "Naše smluvní strana",
-                "verbose_name_plural": "Naše smlouvní strany",
+                "verbose_name": "Druhá smluvní strana",
+                "verbose_name_plural": "Druhé smluvní strany",
             },
         ),
         migrations.CreateModel(
-            name="ContractNote",
+            name="SigneeSignature",
             fields=[
                 (
                     "id",
@@ -435,16 +398,23 @@ class Migration(migrations.Migration):
                         verbose_name="ID",
                     ),
                 ),
-                ("created_date", models.DateTimeField(verbose_name="Datum vytvoření")),
-                ("content", markdownx.models.MarkdownxField(verbose_name="Obsah")),
+                ("date", models.DateField(verbose_name="Datum podpisu")),
+                (
+                    "signee",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="signatures",
+                        to="contracts.signee",
+                    ),
+                ),
             ],
             options={
-                "verbose_name": "Poznámka ke smlouvě",
-                "verbose_name_plural": "Poznámky ke smlouvě",
+                "verbose_name": "Podpis druhé smluvní strany",
+                "verbose_name_plural": "Podpisy druhé smluvní strany",
             },
         ),
         migrations.CreateModel(
-            name="ContractSubtype",
+            name="SigneeRepresentative",
             fields=[
                 (
                     "id",
@@ -455,11 +425,182 @@ class Migration(migrations.Migration):
                         verbose_name="ID",
                     ),
                 ),
-                ("name", models.CharField(max_length=32, verbose_name="Jméno")),
+                ("name", models.CharField(max_length=256, verbose_name="Jméno")),
+                ("role", models.CharField(max_length=256, verbose_name="Funkce")),
+                (
+                    "signee",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="representatives",
+                        to="contracts.signee",
+                    ),
+                ),
             ],
             options={
-                "verbose_name": "Podtyp smlouvy",
-                "verbose_name_plural": "Podtypy smlouvy",
+                "verbose_name": "Zástupce druhé smluvní strany",
+                "verbose_name_plural": "Zástupci druhé smluvní strany",
             },
         ),
+        migrations.CreateModel(
+            name="ContractIntent",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "url",
+                    models.URLField(
+                        blank=True, max_length=256, null=True, verbose_name="Odkaz"
+                    ),
+                ),
+                (
+                    "contract",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="intents",
+                        to="contracts.contract",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Záměr",
+                "verbose_name_plural": "Záměry",
+            },
+        ),
+        migrations.CreateModel(
+            name="ContracteeSignature",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("date", models.DateField(verbose_name="Datum podpisu")),
+                (
+                    "contractee",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="signatures",
+                        to="contracts.contractee",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Podpis naší smluvní strany",
+                "verbose_name_plural": "Podpisy naší smluvní strany",
+            },
+        ),
+        migrations.CreateModel(
+            name="ContracteeRepresentative",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=256, verbose_name="Jméno")),
+                ("role", models.CharField(max_length=256, verbose_name="Funkce")),
+                (
+                    "contractee",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="representatives",
+                        to="contracts.contractee",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Zástupce naší smluvní strany",
+                "verbose_name_plural": "Zástupci naší smluvní strany",
+            },
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="contractee_signatures",
+            field=models.ManyToManyField(
+                to="contracts.contracteesignature", verbose_name="Naše podpisy"
+            ),
+        ),
+        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,
+                related_name="filed_contracts",
+                to="contracts.contractfilingarea",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="issues",
+            field=models.ManyToManyField(
+                to="contracts.contractissue", verbose_name="Problémy"
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="primary_contract",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="subcontracts",
+                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="signee_signature",
+            field=models.ManyToManyField(
+                to="contracts.signeesignature",
+                verbose_name="Podpisy druhé smluvní strany",
+            ),
+        ),
+        migrations.AddField(
+            model_name="contract",
+            name="subtype",
+            field=models.ManyToManyField(
+                to="contracts.contractsubtype", verbose_name="Podtypy"
+            ),
+        ),
+        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/0002_initial.py b/contracts/migrations/0002_initial.py
deleted file mode 100644
index f85a350f4139cb677dc01ddd42d22a5b6ccf2ef1..0000000000000000000000000000000000000000
--- a/contracts/migrations/0002_initial.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# 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/0003_contractexternalsignerrepresentative_contractintent_and_more.py b/contracts/migrations/0003_contractexternalsignerrepresentative_contractintent_and_more.py
deleted file mode 100644
index 7353a6556bc4f029f92a522c7e513bd064e90203..0000000000000000000000000000000000000000
--- a/contracts/migrations/0003_contractexternalsignerrepresentative_contractintent_and_more.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Generated by Django 4.1.4 on 2023-02-11 09:11
-
-import django.db.models.deletion
-import markdownx.models
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-    dependencies = [
-        ("contracts", "0002_initial"),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name="ContractExternalSignerRepresentative",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=256, verbose_name="Jméno")),
-                ("role", models.CharField(max_length=256, verbose_name="Funkce")),
-            ],
-            options={
-                "verbose_name": "Zástupce druhé smluvní strany",
-                "verbose_name_plural": "Zástupci druhé smluvní strany",
-            },
-        ),
-        migrations.CreateModel(
-            name="ContractIntent",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                (
-                    "url",
-                    models.URLField(
-                        blank=True, max_length=256, null=True, verbose_name="Odkaz"
-                    ),
-                ),
-            ],
-            options={
-                "verbose_name": "Záměr",
-                "verbose_name_plural": "Záměry",
-            },
-        ),
-        migrations.CreateModel(
-            name="ContractLocalSignerRepresentative",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=256, verbose_name="Jméno")),
-                ("role", models.CharField(max_length=256, verbose_name="Funkce")),
-            ],
-            options={
-                "verbose_name": "Zástupce naší smluvní strany",
-                "verbose_name_plural": "Zástupci naší smluvní strany",
-            },
-        ),
-        migrations.RemoveField(
-            model_name="contract",
-            name="intent_url",
-        ),
-        migrations.RemoveField(
-            model_name="contractexternalsigner",
-            name="representative_name",
-        ),
-        migrations.RemoveField(
-            model_name="contractexternalsigner",
-            name="representative_role",
-        ),
-        migrations.RemoveField(
-            model_name="contractlocalsigner",
-            name="representative_name",
-        ),
-        migrations.RemoveField(
-            model_name="contractlocalsigner",
-            name="representative_role",
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="notes",
-            field=markdownx.models.MarkdownxField(
-                blank=True,
-                help_text="Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat.",
-                null=True,
-                verbose_name="Obsah",
-            ),
-        ),
-        migrations.AlterField(
-            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,
-                related_name="filed_contracts",
-                to="contracts.contractfilingarea",
-            ),
-        ),
-        migrations.AlterField(
-            model_name="contract",
-            name="primary_contract",
-            field=models.ForeignKey(
-                blank=True,
-                null=True,
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="subcontracts",
-                to="contracts.contract",
-                verbose_name="Hlavní smlouva",
-            ),
-        ),
-        migrations.RemoveField(
-            model_name="contract",
-            name="subtype",
-        ),
-        migrations.AlterField(
-            model_name="contractexternalsignature",
-            name="signer",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="signatures",
-                to="contracts.contractexternalsigner",
-            ),
-        ),
-        migrations.AlterField(
-            model_name="contractlocalsignature",
-            name="signer",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="signatures",
-                to="contracts.contractlocalsigner",
-            ),
-        ),
-        migrations.DeleteModel(
-            name="ContractNote",
-        ),
-        migrations.AddField(
-            model_name="contractlocalsignerrepresentative",
-            name="signer",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="representatives",
-                to="contracts.contractlocalsigner",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contractintent",
-            name="contract",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="intents",
-                to="contracts.contract",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contractexternalsignerrepresentative",
-            name="signer",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="representatives",
-                to="contracts.contractexternalsigner",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="subtype",
-            field=models.ManyToManyField(to="contracts.contractsubtype"),
-        ),
-    ]
diff --git a/contracts/migrations/0004_contracteerepresentative_contracteesignature_and_more.py b/contracts/migrations/0004_contracteerepresentative_contracteesignature_and_more.py
deleted file mode 100644
index 1f8e7fb62aed08c72a25a691841925c3cfe68dc4..0000000000000000000000000000000000000000
--- a/contracts/migrations/0004_contracteerepresentative_contracteesignature_and_more.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# Generated by Django 4.1.4 on 2023-02-11 12:22
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-    dependencies = [
-        (
-            "contracts",
-            "0003_contractexternalsignerrepresentative_contractintent_and_more",
-        ),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name="ContracteeRepresentative",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("name", models.CharField(max_length=256, verbose_name="Jméno")),
-                ("role", models.CharField(max_length=256, verbose_name="Funkce")),
-            ],
-            options={
-                "verbose_name": "Zástupce naší smluvní strany",
-                "verbose_name_plural": "Zástupci naší smluvní strany",
-            },
-        ),
-        migrations.CreateModel(
-            name="ContracteeSignature",
-            fields=[
-                (
-                    "id",
-                    models.BigAutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                ("date", models.DateField(verbose_name="Datum podpisu")),
-            ],
-        ),
-        migrations.RenameModel(
-            old_name="ContractExternalSigner",
-            new_name="Signee",
-        ),
-        migrations.RenameModel(
-            old_name="ContractExternalSignerRepresentative",
-            new_name="SigneeRepresentative",
-        ),
-        migrations.RenameModel(
-            old_name="ContractExternalSignature",
-            new_name="SigneeSignature",
-        ),
-        migrations.RemoveField(
-            model_name="contractlocalsignerrepresentative",
-            name="signer",
-        ),
-        migrations.RenameField(
-            model_name="contract",
-            old_name="external_signature",
-            new_name="signee_signature",
-        ),
-        migrations.RenameField(
-            model_name="signeerepresentative",
-            old_name="signer",
-            new_name="signee",
-        ),
-        migrations.RenameField(
-            model_name="signeesignature",
-            old_name="signer",
-            new_name="signee",
-        ),
-        migrations.RemoveField(
-            model_name="contract",
-            name="local_signature",
-        ),
-        migrations.RenameModel(
-            old_name="ContractLocalSigner",
-            new_name="Contractee",
-        ),
-        migrations.DeleteModel(
-            name="ContractLocalSignature",
-        ),
-        migrations.DeleteModel(
-            name="ContractLocalSignerRepresentative",
-        ),
-        migrations.AddField(
-            model_name="contracteesignature",
-            name="contractee",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="signatures",
-                to="contracts.contractee",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contracteerepresentative",
-            name="contractee",
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE,
-                related_name="representatives",
-                to="contracts.contractee",
-            ),
-        ),
-        migrations.AddField(
-            model_name="contract",
-            name="contractee_signatures",
-            field=models.ManyToManyField(to="contracts.contracteesignature"),
-        ),
-    ]
diff --git a/contracts/models.py b/contracts/models.py
index 8cc2d366f8c6a5c548c5843e76ea029e5ef90b93..f3b62b7e35fab401d49188d5d7b1737d6e40f77b 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -96,34 +96,38 @@ class SigneeSignature(models.Model):
         verbose_name="Datum podpisu",
     )
 
+    class Meta:
+        verbose_name = "Podpis druhé smluvní strany"
+        verbose_name_plural = "Podpisy druhé smluvní strany"
+
 
 class Contractee(models.Model):
     name = models.CharField(
         max_length=256,
-        default=settings.DEFAULT_LOCAL_SIGNER_NAME,
+        default=settings.DEFAULT_CONTRACTEE_NAME,
         verbose_name="Jméno",
     )
 
     address_street_with_number = models.CharField(
         max_length=256,
-        default=settings.DEFAULT_LCOAL_SIGNER_STREET,
+        default=settings.DEFAULT_CONTRACTEE_STREET,
         verbose_name="Ulice, č.p.",
     )
 
     address_district = models.CharField(
         max_length=256,
-        default=settings.DEFAULT_LOCAL_SIGNER_DISTRICT,
+        default=settings.DEFAULT_CONTRACTEE_DISTRICT,
         verbose_name="Obec",
     )
 
     address_zip = models.CharField(
         max_length=16,
-        default=settings.DEFAULT_LOCAL_SIGNER_ZIP,
+        default=settings.DEFAULT_CONTRACTEE_ZIP,
         verbose_name="PSČ",
     )
 
     address_country = CountryField(
-        default=settings.DEFAULT_LOCAL_SIGNER_COUNTRY,
+        default=settings.DEFAULT_CONTRACTEE_COUNTRY,
         verbose_name="Země",
     )
 
@@ -131,7 +135,7 @@ class Contractee(models.Model):
         max_length=16,
         blank=True,
         null=True,
-        default=settings.DEFAULT_LOCAL_SIGNER_ICO_NUMBER,
+        default=settings.DEFAULT_CONTRACTEE_ICO_NUMBER,
         verbose_name="IČO",
     )
 
@@ -150,7 +154,7 @@ class Contractee(models.Model):
 
     class Meta:
         verbose_name = "Naše smluvní strana"
-        verbose_name_plural = "Naše smlouvní strany"
+        verbose_name_plural = "Naše smluvní strany"
 
 
 class ContracteeRepresentative(models.Model):
@@ -186,6 +190,10 @@ class ContracteeSignature(models.Model):
         verbose_name="Datum podpisu",
     )
 
+    class Meta:
+        verbose_name = "Podpis naší smluvní strany"
+        verbose_name_plural = "Podpisy naší smluvní strany"
+
 
 class ContractSubtype(models.Model):
     name = models.CharField(
@@ -206,7 +214,7 @@ class ContractIssue(models.Model):
 
     class Meta:
         verbose_name = "Problém se smlouvou"
-        verbose_name_plural = "Problémy se smlouvou"
+        verbose_name_plural = "Problémy se smlouvami"
 
 
 class ContractFilingArea(models.Model):
@@ -238,16 +246,25 @@ class Contract(models.Model):
         verbose_name="Typ",
     )
 
-    subtype = models.ManyToManyField(ContractSubtype)
+    subtype = models.ManyToManyField(
+        ContractSubtype,
+        verbose_name="Podtypy",
+    )
 
     contains_nda = models.BooleanField(
         default=False,
         verbose_name="Obsahuje NDA",
     )
 
-    signee_signature = models.ManyToManyField(SigneeSignature)
+    signee_signature = models.ManyToManyField(
+        SigneeSignature,
+        verbose_name="Podpisy druhé smluvní strany",
+    )
 
-    contractee_signatures = models.ManyToManyField(ContracteeSignature)
+    contractee_signatures = models.ManyToManyField(
+        ContracteeSignature,
+        verbose_name="Naše podpisy",
+    )
 
     all_parties_sign_date = models.DateField(
         verbose_name="Datum podpisu všech stran",
@@ -331,7 +348,10 @@ class Contract(models.Model):
         verbose_name="Identifikační číslo",
     )
 
-    issues = models.ManyToManyField(ContractIssue)
+    issues = models.ManyToManyField(
+        ContractIssue,
+        verbose_name="Problémy",
+    )
     notes = MarkdownxField(
         blank=True,
         null=True,
diff --git a/contracts/urls.py b/contracts/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..b837598466bc6ed8d1a8c329c8a279607b3d1f0c
--- /dev/null
+++ b/contracts/urls.py
@@ -0,0 +1,6 @@
+from django.urls import path
+
+from . import views
+
+app_name = "contracts"
+urlpatterns = []
diff --git a/env.example b/env.example
index c771c2f1724ba1c170c691974ff286cb256a0cf8..94d143402bd81d31d93aff0790f114dbf60db0ca 100644
--- a/env.example
+++ b/env.example
@@ -2,9 +2,15 @@ DATABASE_URL="postgresql://contracts:contracts@localhost:5432/contracts"
 
 SECRET_KEY=supersecret
 
-DEFAULT_LOCAL_SIGNER_NAME="Česká pirátská strana"
-DEFAULT_LCOAL_SIGNER_STREET="Na Moráni 360/3"
-DEFAULT_LOCAL_SIGNER_ZIP="128 00"
-DEFAULT_LOCAL_SIGNER_DISTRICT="Praha 2"
-DEFAULT_LOCAL_SIGNER_COUNTRY="CZ"
-DEFAULT_LOCAL_SIGNER_ICO_NUMBER="71339698"
+SITE_URL="http://localhost:8006"
+
+OIDC_RP_REALM_URL="http://localhost:8080/realms/master/"
+OIDC_RP_CLIENT_ID=contracts
+OIDC_RP_CLIENT_SECRET=VCn4LVAUc6RGLSup7VaAKsmrKUbWguaP
+
+DEFAULT_CONTRACTEE_NAME="Česká pirátská strana"
+DEFAULT_CONTRACTEE_STREET="Na Moráni 360/3"
+DEFAULT_CONTRACTEE_ZIP="128 00"
+DEFAULT_CONTRACTEE_DISTRICT="Praha 2"
+DEFAULT_CONTRACTEE_COUNTRY="CZ"
+DEFAULT_CONTRACTEE_ICO_NUMBER="71339698"
diff --git a/oidc/__init__.py b/oidc/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/oidc/admin.py b/oidc/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/oidc/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/oidc/apps.py b/oidc/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..742acac1713eae78fd9b35b887dfc5dfe22b1989
--- /dev/null
+++ b/oidc/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class OidcConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "oidc"
diff --git a/oidc/auth.py b/oidc/auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c9b54f233e2ed60772fdae164aeef6fb4fc6df8
--- /dev/null
+++ b/oidc/auth.py
@@ -0,0 +1,57 @@
+import logging
+
+from django.conf import settings
+from django.contrib.auth.models import Group
+from pirates.auth import PiratesOIDCAuthenticationBackend
+
+logging.basicConfig(level=logging.DEBUG)
+
+
+class NastenkaOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend):
+    def _assign_new_user_groups(self, user, claims, user_groups=None) -> None:
+        if user_groups is None:
+            user_groups = user.groups.all()
+
+        for role in claims["resource_access"][settings.OIDC_RP_RESOURCE_ACCESS_CLIENT][
+            "roles"
+        ]:
+            group_name = f"sso_{role}"
+
+            group = Group.objects.filter(name=group_name)
+
+            if not group.exists():
+                group = Group(name=group_name)
+                group.save()
+            else:
+                group = group[0]
+
+            if group not in user_groups:
+                user.groups.add(group)
+
+        user.save()
+
+    def create_user(self, claims):
+        user = super().create_user(claims)
+
+        if "resource_access" not in claims:
+            return user
+
+        self._assign_new_user_groups(user, claims)
+
+        return user
+
+    def update_user(self, user, claims):
+        if "resource_access" not in claims:
+            return user
+
+        user_groups = user.groups.all()
+
+        for group in user_groups:
+            if group.name.replace("sso_", "") not in (
+                claims["resource_access"][settings.OIDC_RP_CLIENT_ID]["roles"]
+            ):
+                user.groups.remove(group)
+
+        self._assign_new_user_groups(user, claims, user_groups)
+
+        return user
diff --git a/oidc/models.py b/oidc/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91
--- /dev/null
+++ b/oidc/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/oidc/templates/oidc/base.html b/oidc/templates/oidc/base.html
new file mode 100644
index 0000000000000000000000000000000000000000..610ef367e1ed055f1bf237faa82ac0079fa6475d
--- /dev/null
+++ b/oidc/templates/oidc/base.html
@@ -0,0 +1,32 @@
+{% load static %}
+{% load render_bundle from webpack_loader %}
+
+<!DOCTYPE html>
+<html lang="cs" class="h-full">
+    <head>
+        <meta encoding="utf-8">
+        <link
+            rel="stylesheet"
+            type="text/css"
+            href="{% static "shared/style.css" %}"
+        >
+
+        <link
+            rel="stylesheet"
+            href="https://gfonts.pirati.cz/css2?family=Bebas+Neue&family=Roboto+Condensed:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap"
+        >
+
+        <link
+            rel="stylesheet"
+            type="text/css"
+            href="{% static "shared/fonts/pirati-ui/style.css" %}"
+        >
+
+        <title>{% block current_page %}{% endblock %}</title>
+
+        {% render_bundle "base" %}
+    </head>
+    <body>
+        {% block content %}{% endblock %}
+    </body>
+</html>
diff --git a/oidc/templates/oidc/login.html b/oidc/templates/oidc/login.html
new file mode 100644
index 0000000000000000000000000000000000000000..f00d86ae7671b9d0a0542260ad4c21ca0b76dc73
--- /dev/null
+++ b/oidc/templates/oidc/login.html
@@ -0,0 +1,13 @@
+{% extends "oidc/base.html" %}
+
+{% block content %}
+    {% if user.is_authenticated %}
+        <p>Current user: {{ user.email }}</p>
+        <form action="{% url "oidc_logout" %}" method="POST">
+            {% csrf_token %}
+            <input type="submit" value="logout">
+        </form>
+    {% else %}
+        <a href="{% url "oidc_authentication_init" %}">Přihlásit se</a>
+    {% endif %}
+{% endblock %}
diff --git a/oidc/tests.py b/oidc/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/oidc/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/oidc/urls.py b/oidc/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..a15ad64f99388e3d9dbdee1be71aadec38097a99
--- /dev/null
+++ b/oidc/urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+
+from . import views
+
+app_name = "oidc"
+urlpatterns = [
+    path("login", views.login, name="login"),
+]
diff --git a/oidc/views.py b/oidc/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..23dbba52821cdaeda52388fe8cd7da5fadf6c39d
--- /dev/null
+++ b/oidc/views.py
@@ -0,0 +1,8 @@
+from django.shortcuts import render
+
+
+def login(request):
+    return render(
+        request,
+        "oidc/login.html",
+    )
diff --git a/registry/settings/base.py b/registry/settings/base.py
index 7fa8ccf883d258a48cf5995450e18c4faa4e4bb6..5f69f7023e33d43cc9148c51928b6e917cdb8d0f 100644
--- a/registry/settings/base.py
+++ b/registry/settings/base.py
@@ -36,7 +36,7 @@ ALLOWED_HOSTS = []
 STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
 
 
-# Application definition
+## Application definition
 
 INSTALLED_APPS = [
     "django.contrib.admin",
@@ -45,7 +45,12 @@ INSTALLED_APPS = [
     "django.contrib.sessions",
     "django.contrib.messages",
     "django.contrib.staticfiles",
+    "guardian",
+    "markdownx",
+    "pirates",
+    "webpack_loader",
     "contracts",
+    "oidc",
     "shared",
     "users",
 ]
@@ -58,6 +63,8 @@ MIDDLEWARE = [
     "django.contrib.auth.middleware.AuthenticationMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
     "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    "django_http_exceptions.middleware.ExceptionHandlerMiddleware",
+    "django_http_exceptions.middleware.ThreadLocalRequestMiddleware",
 ]
 
 ROOT_URLCONF = "registry.urls"
@@ -81,14 +88,21 @@ TEMPLATES = [
 WSGI_APPLICATION = "registry.wsgi.application"
 
 
-# Database
+## Database
 # https://docs.djangoproject.com/en/4.0/ref/settings/#databases
 
 DATABASES = {"default": dj_database_url.config(conn_max_age=600)}
 
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
+
+
+## Authentication
 
 # Password validation
-# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
+# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
 
 AUTH_PASSWORD_VALIDATORS = [
     {
@@ -106,33 +120,67 @@ AUTH_PASSWORD_VALIDATORS = [
 ]
 AUTH_USER_MODEL = "users.User"
 
+AUTHENTICATION_BACKENDS = (
+    "oidc.auth.NastenkaOIDCAuthenticationBackend",
+    "django.contrib.auth.backends.ModelBackend",
+    "guardian.backends.ObjectPermissionBackend",
+)
 
-# Internationalization
-# https://docs.djangoproject.com/en/4.0/topics/i18n/
+LOGIN_URL = "/oidc/login"
+LOGIN_REDIRECT_URL = "/"
+LOGOUT_REDIRECT_URL = "/oidc/login"
 
-LANGUAGE_CODE = "en-us"
+OIDC_RP_CLIENT_ID = env.str("OIDC_RP_CLIENT_ID")
+OIDC_RP_CLIENT_SECRET = env.str("OIDC_RP_CLIENT_SECRET")
+OIDC_RP_REALM_URL = env.str("OIDC_RP_REALM_URL")
+OIDC_RP_SCOPES = "openid email roles"
+OIDC_RP_SIGN_ALGO = "RS256"
+OIDC_RP_RESOURCE_ACCESS_CLIENT = env.str(
+    "OIDC_RESOURCE_ACCESS_CLIENT", OIDC_RP_CLIENT_ID
+)
 
-TIME_ZONE = "UTC"
+OIDC_OP_JWKS_ENDPOINT = OIDC_RP_REALM_URL + "protocol/openid-connect/certs"
+OIDC_OP_AUTHORIZATION_ENDPOINT = OIDC_RP_REALM_URL + "protocol/openid-connect/auth"
+OIDC_OP_TOKEN_ENDPOINT = OIDC_RP_REALM_URL + "protocol/openid-connect/token"
+OIDC_OP_USER_ENDPOINT = OIDC_RP_REALM_URL + "protocol/openid-connect/userinfo"
 
-USE_I18N = True
 
+## Internationalization
+# https://docs.djangoproject.com/en/4.0/topics/i18n/
+
+LANGUAGE_CODE = "cs-cz"
+TIME_ZONE = "Europe/Prague"
+USE_I18N = True
 USE_TZ = True
 
 
-# Static files (CSS, JavaScript, Images)
+## Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/4.0/howto/static-files/
 
 STATIC_URL = "static/"
 
-# Default primary key field type
-# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
+WEBPACK_LOADER = {
+    "DEFAULT": {
+        "CACHE": not DEBUG,
+        "BUNDLE_DIR_NAME": "shared",
+        "STATS_FILE": os.path.join(BASE_DIR, "webpack-stats.json"),
+        "POLL_INTERVAL": 0.1,
+        "IGNORE": [r".+\.hot-update.js", r".+\.map"],
+    }
+}
+
+
+## Server
+
+USE_X_FORWARDED_HOST = True
+SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
 
-DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
 
+## App-specific
 
-DEFAULT_LOCAL_SIGNER_NAME = env.str("DEFAULT_LOCAL_SIGNER_NAME")
-DEFAULT_LCOAL_SIGNER_STREET = env.str("DEFAULT_LCOAL_SIGNER_STREET")
-DEFAULT_LOCAL_SIGNER_ZIP = env.str("DEFAULT_LOCAL_SIGNER_ZIP")
-DEFAULT_LOCAL_SIGNER_DISTRICT = env.str("DEFAULT_LOCAL_SIGNER_DISTRICT")
-DEFAULT_LOCAL_SIGNER_COUNTRY = env.str("DEFAULT_LOCAL_SIGNER_COUNTRY")
-DEFAULT_LOCAL_SIGNER_ICO_NUMBER = env.str("DEFAULT_LOCAL_SIGNER_ICO_NUMBER")
+DEFAULT_CONTRACTEE_NAME = env.str("DEFAULT_CONTRACTEE_NAME")
+DEFAULT_CONTRACTEE_STREET = env.str("DEFAULT_CONTRACTEE_STREET")
+DEFAULT_CONTRACTEE_ZIP = env.str("DEFAULT_CONTRACTEE_ZIP")
+DEFAULT_CONTRACTEE_DISTRICT = env.str("DEFAULT_CONTRACTEE_DISTRICT")
+DEFAULT_CONTRACTEE_COUNTRY = env.str("DEFAULT_CONTRACTEE_COUNTRY")
+DEFAULT_CONTRACTEE_ICO_NUMBER = env.str("DEFAULT_CONTRACTEE_ICO_NUMBER")
diff --git a/registry/urls.py b/registry/urls.py
index 417ef5128c133640e46663d4ae520e1aabcf100a..b80111c3fad8f33984c62bf7bf16cbf0e623baf1 100644
--- a/registry/urls.py
+++ b/registry/urls.py
@@ -1,7 +1,7 @@
-"""registry URL Configuration
+"""URL Configuration
 
 The `urlpatterns` list routes URLs to views. For more information please see:
-    https://docs.djangoproject.com/en/4.0/topics/http/urls/
+    https://docs.djangoproject.com/en/4.1/topics/http/urls/
 Examples:
 Function views
     1. Add an import:  from my_app import views
@@ -14,8 +14,12 @@ Including another URLconf
     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 """
 from django.contrib import admin
-from django.urls import path
+from django.urls import include, path
+from pirates.urls import urlpatterns as pirates_urlpatterns
 
 urlpatterns = [
+    path("markdownx/", include("markdownx.urls")),
+    path("contracts/", include("contracts.urls")),
+    path("oidc/", include("oidc.urls")),
     path("admin/", admin.site.urls),
-]
+] + pirates_urlpatterns
diff --git a/shared/admin.py b/shared/admin.py
index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..2b2f1ef9507424d98caa1d8d1ec9e3134deeec01 100644
--- a/shared/admin.py
+++ b/shared/admin.py
@@ -1,3 +1,7 @@
 from django.contrib import admin
+from guardian.admin import GuardedModelAdmin
+from markdownx.admin import MarkdownxModelAdmin
 
-# Register your models here.
+
+class MarkdownxGuardedModelAdmin(MarkdownxModelAdmin, GuardedModelAdmin):
+    pass
diff --git a/users/models.py b/users/models.py
index ea62fb3fa8eef5bddf9697561fab74f215460fd4..11e82baf8f2eae2b3caf8dcbdd10dce9d39caef4 100644
--- a/users/models.py
+++ b/users/models.py
@@ -2,4 +2,14 @@ from pirates import models as pirates_models
 
 
 class User(pirates_models.AbstractUser):
-    pass
+    def set_unusable_password(self) -> None:
+        # Purely for compatibility with Guardian
+        pass
+
+    def get_username(self) -> str:
+        first_name = self.first_name
+
+        if len(first_name) != 0:
+            first_name += " "
+
+        return f"{first_name}{self.last_name}"