diff --git a/Dockerfile b/Dockerfile
index ce121586502dc8cd65ed6c9387a44ace785af2f2..6985c0dd97f345f7008c931855866033f711fdbb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,24 @@
 FROM python:3.10
 
 RUN mkdir /app
-WORKDIR /app
 
 RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash -
-RUN apt-get install nodejs && rm -rf /var/lib/apt/lists/*
+RUN apt-get -y install autoconf automake libtool pkg-config nodejs git
+RUN rm -rf /var/lib/apt/lists/*
+
+RUN mkdir /temp
+RUN mkdir /temp/libpostal-build
+WORKDIR /temp
+
+RUN git clone https://github.com/openvenues/libpostal
+WORKDIR /temp/libpostal
+RUN ./bootstrap.sh
+RUN ./configure --datadir=/temp/libpostal-build
+RUN make -j4
+RUN make install
+RUN ldconfig
+
+WORKDIR /app
 
 COPY . .
 
diff --git a/Makefile b/Makefile
index 7c3c25a256b1cd92fb50cd551728a9a7e1fef5a0..3dd614c86ac23378503faeb83ea56ac0ea4bf49c 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,7 @@ help:
 	@echo "Application:"
 	@echo "  run            Run the application on port ${PORT}"
 	@echo "  shell          Access the Django shell"
+	@echo "  sync           Sync with the old contract registry"
 	@echo ""
 	@echo "Database:"
 	@echo "  migrations     Generate migrations"
@@ -47,6 +48,9 @@ run: venv
 shell: venv
 	${VENV}/bin/python manage.py shell --settings=${SETTINGS}
 
+sync:
+	${VENV}/bin/python manage.py import_old_contracts https://github.com/pirati-web/smlouvy.pirati.cz.git gh-pages --settings=${SETTINGS} --delete -v 3
+
 migrations: venv
 	${VENV}/bin/python manage.py makemigrations --settings=${SETTINGS}
 
diff --git a/contracts/admin.py b/contracts/admin.py
index e020611ab798557099a241de696b2aae3c94aea1..d80d314b534322dad59af72d9c5df6ea1c9dcee9 100644
--- a/contracts/admin.py
+++ b/contracts/admin.py
@@ -1,6 +1,7 @@
 import copy
 import typing
 
+from admin_auto_filters.filters import AutocompleteFilterFactory
 from django.contrib import admin
 from django.contrib.auth.models import Permission
 from django.db import models
@@ -41,16 +42,14 @@ class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
 def permissions_mixin_factory(
     change_permission: str,
     delete_permission: str,
-    obj_conditional: typing.Callable,
-    change_attr_func: typing.Callable = lambda obj: obj,
-    delete_attr_func: typing.Callable = lambda obj: obj,
+    obj_conditional: typing.Callable = lambda request, obj: True
 ) -> object:
     class Mixin:
         def has_change_permission(self, request, obj=None) -> bool:
             if (
                 obj is not None
                 and obj_conditional(request, obj)
-                and not request.user.has_perm(change_permission, change_attr_func(obj))
+                and not request.user.has_perm(change_permission)
             ):
                 return False
 
@@ -60,7 +59,7 @@ def permissions_mixin_factory(
             if (
                 obj is not None
                 and obj_conditional(request, obj)
-                and not request.user.has_perm(delete_permission, delete_attr_func(obj))
+                and not request.user.has_perm(change_permission)
             ):
                 return False
 
@@ -69,14 +68,14 @@ def permissions_mixin_factory(
     return Mixin
 
 
-get_obj_contract = lambda obj: obj.contract
+get_obj_contract = lambda request, obj: obj.contract
 
 
 class OwnPermissionsMixin(
     permissions_mixin_factory(
         "contracts.edit_others",
         "contracts.delete_others",
-        lambda request, obj: obj.created_by != request.user,
+        obj_conditional=lambda request, obj: obj.created_by != request.user,
     )
 ):
     def save_model(self, request, obj, form, change):
@@ -89,18 +88,14 @@ class OwnPermissionsMixin(
 ParentContractApprovedPermissionsMixin = permissions_mixin_factory(
     "contracts.edit_when_approved",
     "contracts.delete_when_approved",
-    lambda request, obj: get_obj_contract(obj).is_approved,
-    get_obj_contract,
-    get_obj_contract,
+    obj_conditional=lambda request, obj: get_obj_contract(obj).is_approved,
 )
 
 
 ParentContractOwnPermissionsMixin = permissions_mixin_factory(
     "contracts.edit_others",
     "contracts.delete_others",
-    lambda request, obj: get_obj_contract(obj).created_by != request.user,
-    get_obj_contract,
-    get_obj_contract,
+    obj_conditional=lambda request, obj: get_obj_contract(obj).created_by != request.user,
 )
 
 
@@ -161,18 +156,13 @@ class ContractAdmin(
     permissions_mixin_factory(
         "contracts.edit_when_approved",
         "contracts.delete_when_approved",
-        lambda request, obj: obj.is_approved,
+        obj_conditional=lambda request, obj: obj.is_approved,
     ),
     MarkdownxGuardedModelAdmin,
     NestedModelAdmin,
 ):
     form = ContractAdminForm
 
-    ordering = (
-        "-created_on",
-        "-updated_on",
-        "-name",
-    )
     search_fields = ("name",)
 
     readonly_fields = (
@@ -364,12 +354,17 @@ class ContractAdmin(
         return super().has_change_permission(request, obj)
 
     list_filter = (
-        "types",
+        AutocompleteFilterFactory("Typ", "types"),
+        AutocompleteFilterFactory("Spisovna", "filing_area"),
+        AutocompleteFilterFactory("Problém", "issues"),
+        AutocompleteFilterFactory(
+            "Naše smluvná strana", "contractee_signatures__contractee"
+        ),
+        AutocompleteFilterFactory("Jiná smluvní strana", "signee_signatures__signee"),
         "is_approved",
         "is_valid",
         "is_public",
         "paper_form_state",
-        "issues",
         ("all_parties_sign_date", DateRangeFilter),
         ("valid_start_date", DateRangeFilter),
         ("valid_end_date", DateRangeFilter),
@@ -385,19 +380,16 @@ class ContractAdmin(
 
 class ContractTypeAdmin(MarkdownxGuardedModelAdmin):
     model = ContractType
-    ordering = ("name",)
     search_fields = ("name",)
 
 
 class ContractIssueAdmin(MarkdownxGuardedModelAdmin):
     model = ContractIssue
-    ordering = ("name",)
     search_fields = ("name",)
 
 
 class ContractFilingAreaAdmin(MarkdownxGuardedModelAdmin):
     model = ContractFilingArea
-    ordering = ("name",)
     search_fields = (
         "name",
         "person_responsible",
@@ -425,7 +417,6 @@ class ContracteeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
         "name",
         "department",
     )
-    ordering = ("name",)
 
 
 class SigneeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
@@ -446,7 +437,6 @@ class SigneeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
         "name",
         "department",
     )
-    ordering = ("name",)
 
     form = SigneeAdminForm
 
@@ -498,8 +488,8 @@ class SigneeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
     load_ares_data_button.short_description = "ARES"
 
 
-get_obj_signee_contract = lambda obj: obj.signee.contract
-get_obj_contractee_contract = lambda obj: obj.contractee.contract
+get_obj_signee_contract = lambda request, obj: obj.signee.contract
+get_obj_contractee_contract = lambda request, obj: obj.contractee.contract
 
 
 class SigneeSignatureRepresentativeAdmin(
@@ -507,17 +497,12 @@ class SigneeSignatureRepresentativeAdmin(
     permissions_mixin_factory(
         "contracts.edit_when_approved",
         "contracts.delete_when_approved",
-        lambda request, obj: get_obj_signee_contract(obj).is_approved,
-        get_obj_signee_contract,
-        get_obj_signee_contract,
+        obj_conditional=lambda request, obj: get_obj_signee_contract(obj).is_approved,
     ),
     permissions_mixin_factory(
         "contracts.edit_others",
         "contracts.delete_others",
-        lambda request, obj: get_obj_contractee_contract(obj).created_by
-        != request.user,
-        get_obj_signee_contract,
-        get_obj_signee_contract,
+        obj_conditional=lambda request, obj: get_obj_contractee_contract(obj).created_by != request.user,
     ),
 ):
     pass
@@ -528,17 +513,12 @@ class ContracteeSignatureRepresentativeAdmin(
     permissions_mixin_factory(
         "contracts.edit_when_approved",
         "contracts.delete_when_approved",
-        lambda request, obj: get_obj_contractee_contract(obj).is_approved,
-        get_obj_contractee_contract,
-        get_obj_contractee_contract,
+        obj_conditional=lambda request, obj: get_obj_contractee_contract(obj).is_approved,
     ),
     permissions_mixin_factory(
         "contracts.edit_others",
         "contracts.delete_others",
-        lambda request, obj: get_obj_contractee_contract(obj).created_by
-        != request.user,
-        get_obj_contractee_contract,
-        get_obj_contractee_contract,
+        obj_conditional=lambda request, obj: get_obj_contractee_contract(obj).created_by != request.user,
     ),
 ):
     pass
diff --git a/contracts/management/commands/import_old_contracts.py b/contracts/management/commands/import_old_contracts.py
index 53c929456a7037f0d455db7ca258f74189c5f115..36a3b9e416f290261f1736fd9e15495626ec8f46 100644
--- a/contracts/management/commands/import_old_contracts.py
+++ b/contracts/management/commands/import_old_contracts.py
@@ -2,15 +2,31 @@ import io
 import os
 import re
 import string
+import shutil
 from datetime import date, datetime
 
 import yaml
 from django.conf import settings
+from django.core.files import File
 from django.core.management.base import BaseCommand
+from django.db import models
+from postal.parser import parse_address
 
 from git import Repo
 
-from ...models import Contract, ContractFilingArea, ContractType
+from ...models import (
+    Contract,
+    Contractee,
+    ContracteeSignature,
+    ContracteeSignatureRepresentative,
+    ContractFile,
+    ContractFilingArea,
+    ContractIssue,
+    ContractType,
+    Signee,
+    SigneeSignature,
+    SigneeSignatureRepresentative,
+)
 
 
 class Command(BaseCommand):
@@ -22,10 +38,11 @@ class Command(BaseCommand):
         self.normal_import_count = 0
         self.partial_import_count = 0
         self.already_imported_count = 0
+        self.normalization_count = 0
         self.issue_count = 0
         self.fatal_error_count = 0
 
-    def add_arguments(self, parser):
+    def add_arguments(self, parser) -> None:
         parser.add_argument(
             "repo_url",
             type=str,
@@ -41,6 +58,11 @@ class Command(BaseCommand):
             type=str,
             help="Directory to store the cloned repository in",
         )
+        parser.add_argument(
+            "--delete",
+            action="store_true",
+            help="Delete the old temporary storage directory, if it exists.",
+        )
         parser.add_argument(
             "--existing",
             action="store_true",
@@ -52,10 +74,19 @@ class Command(BaseCommand):
             help="Purge all previous contracts, types and filing areas before the import.",
         )
 
+    def use_issue(self, contract, name: str) -> None:
+        try:
+            issue = ContractIssue.objects.get(name=name)
+        except ContractIssue.DoesNotExist:
+            issue = ContractIssue(name=name)
+
+        return issue
+
     def normalize_type(self, type_name: str) -> str:
         type_name = string.capwords(type_name)
 
         patterns = (
+            (r"\s\s+", " "),
             (r" O ", " o "),
             (r" S ", " s "),
             (r" K ", " k "),
@@ -127,10 +158,74 @@ class Command(BaseCommand):
 
         return type_name
 
+    def normalize_department(self, type_name: str) -> str:
+        type_name = type_name.strip()
+
+        patterns = (
+            (r"\s\s+", " "),
+            (r"^Kraské sdružení Praha$", "Krajské sdružení Praha"),
+            (r"^republikové předsednictvo$", "Republikové předsednictvo"),
+            (r"^KS ", "Krajské sdružení "),
+            (r"^(MS |místní sdružení )", "Místní sdružení "),
+            (r"^(Ústecký kraj|Ustecký kraj)$", "Krajské sdružení Ústecký kraj"),
+            (
+                r"^Moravskoslezský kraj$",
+                "Krajské sdružení Moravskoslezský kraj",
+            ),
+            (
+                r"^Karlovarský kraj$",
+                "Krajské sdružení Karlovarský kraj",
+            ),
+            (
+                r"Jihočeská kraj",
+                "Jihočeský kraj",
+            ),
+            (r"^(Krajského |krajské |Kajské )", "Krajské "),
+            ("^Poslanecký klub České pirátské strany$", "Poslanecký klub"),
+            (r"Středočeký", "Středočeský"),
+            (r"^Zahraničního odboru$", "Zahraniční odbor"),
+            (r"JčK", "Jihočeský kraj"),
+            (r"SčK", "Středočeský kraj"),
+            (r"(ÚsK|UsK)", "Ústecký kraj"),
+            (r"JmK", "Jihomoravský kraj"),
+            (r"PaK", "Pardubický kraj"),
+            (r"KhK", "Královehradecký kraj"),
+            (r"(Prsonální|personální)", "Personální"),
+            (r"^administrativní ", "Administrativní "),
+            (r"technický", "Technický"),
+            (r"Mediálni", "Technický"),
+            (r"^řešitel ", "Řešitel "),
+            (r"^předsednictvo ", "Předsednictvo "),
+            (r"olomoucký", "Olomoucký"),
+            (r"^místní ", "Místní "),
+            (r"^celostátní ", "Celostátní "),
+            (r"odbor", "Odbor"),
+            (r"PKS", "Předsednictvo krajského sdružení"),
+            (r"( KS | Krajského sdružení )", " krajského sdružení "),
+            (
+                r"^(Předsednictvo krajského sdružení |Předsednictvo |Místní předsednictvo )",
+                "",
+            ),
+            (r"^Krajské předsednictvo ", "Krajské sdružení "),
+            (r"ého kraje$", "ý kraj"),
+            (r"^Olomouc$", "Místní sdružení Olomouc"),
+            (r"^Olomoucký kraj$", "Krajské sdružení Olomoucký kraj"),
+            (r"^Pardubický kraj$", "Krajské sdružení Pardubický kraj"),
+            (r"^Jihočeský kraj$", "Krajské sdružení Jihočeský kraj"),
+            (r"^Královehradecký kraj$", "Krajské sdružení Královehradecký kraj"),
+            (r"^Pardubický kraj$", "Krajské sdružení Pardubický kraj"),
+        )
+
+        for pattern in patterns:
+            type_name = re.sub(pattern[0], pattern[1], type_name)
+
+        return type_name
+
     def normalize_filing_area(self, area_name: str) -> str:
         area_name = string.capwords(area_name)
 
         patterns = (
+            (r"\s\s+", " "),
             (
                 r"^(Cenrální Spisovna|Censtrální Spisovna|Centrála|Centrálách Archiv Str"
                 r"any|Centrála Strany|Centrální Achiv Strany|Centrální Archiv|Centralni "
@@ -216,6 +311,24 @@ class Command(BaseCommand):
 
         return area_name
 
+    def normalize_filename(self, filename: str) -> str:
+        filename = string.capwords(filename)
+
+        patterns = (
+            (r"\s\s+", " "),
+            (
+                r"^((P|p)odepsaná (V|v)erze|Strojově Čitelná Verze)$",
+                "Anonymizovaná verze",
+            ),
+            (r"^(P|p)odepsaná (V|v)erze 2$", "Anonymizovaná verze 2"),
+            (r"^(U|u)pravitelná (V|v)erze$", "Upravitelná verze"),
+        )
+
+        for pattern in patterns:
+            filename = re.sub(pattern[0], pattern[1], filename)
+
+        return filename
+
     def parse_index(
         self,
         contract_root: str,
@@ -235,6 +348,7 @@ class Command(BaseCommand):
             .replace("-32", "-31")
             .replace("\t", " ")
         )
+        self.normalization_count += 1
 
         yaml_source = split_contents[1]
 
@@ -250,14 +364,407 @@ class Command(BaseCommand):
 
         return parsed_metadata
 
-    def assign_contract_metadata(
+    def assign_signing_party_metadata(
+        self, slug: str, contract: Contract, signing_party: dict
+    ) -> tuple[
+        Contract | Signee,
+        list[ContractIssue],
+        list[ContracteeSignatureRepresentative | SigneeSignatureRepresentative],
+        bool,
+        int,
+    ]:
+        issue_count = 0
+        issues = []
+
+        if (
+            "jméno" not in signing_party
+            and "název" not in signing_party
+            and "název společnosti" not in signing_party
+        ):
+            issue_count += 1
+            contract.notes += (
+                f"Nepojmenovaná smluvní strana, zdrojová data: {signing_party}\n"
+            )
+            issues.append(self.use_issue(contract, "Špatně pojmenovaná smluvní strana"))
+
+            if self.verbosity >= 2:
+                self.stderr.write(
+                    self.style.NOTICE(
+                        f"Contract {slug} has an unnamed signing party: {signing_party}."
+                    )
+                )
+
+            return issue_count
+
+        name = signing_party.get(
+            "jméno", signing_party.get("název", signing_party.get("název společnosti"))
+        ).strip()
+
+        patterns = (
+            (r"\s\s+", " "),
+            (r"^(1\. Pirátská s\.r\.o|1\.Pirátská s\.r\.o\.)$", "1. Pirátská s.r.o."),
+            (r"České pojišťovna, a.s.", "Česká pojišťovna, a.s."),
+            (r"Datrolex, s.r.o.", "DATROLEX, s.r.o."),
+            (r"^Jiri ", "Jiří "),
+            (
+                (
+                    r"^(Křesťanská a demokratická unie – Československá strana lidová|"
+                    r"Křesťansko demokratická unie – Československá strana lidová|Křes"
+                    r"ťanská a demokratická unie - Československá strana lidová)$"
+                ),
+                "Křesťanská a demokratická unie – Československá strana lidová",
+            ),
+            (r"LN - Audit s\.r\.o\." "LN-AUDIT s.r.o."),
+            (r"Olga Richteová", "Olga Richterová"),
+            (
+                r"^(politické hnutí Změna|PolitickéHnutí Změna)$",
+                "Politické hnutí Změna",
+            ),
+            (r"^Systemický institut s\.r\.o\$", "Systemický institut, s.r.o."),
+            (r"^Václav fořtík$", "Václav Fořtík"),
+            (r"^Vodafone$", "Vodafone Czech Republic a.s."),
+            (r"^VojtěchHolík$", "Vojtěch Holík"),
+            (r"^Vojtech ", "Vojtěch "),
+            (r"^Zdenek ", "Zdeněk "),
+            (r" Bohmova$", " Bohmová"),
+            (r" (KUdláčková|Kudlláčková)$", " Kudláčková"),
+            (r"^Jiří knotek$", "Jiří Knotek"),
+            (r"^JIří Roubíček$", "Jiří Roubíček"),
+            (r"^Koalice Vlasta\. z\.s\.$", "Koalice Vlasta, z.s."),
+            (r"^Mikuáš ", "Mikuláš "),
+            (r"^Strana zelených$", "Strana Zelených"),
+            (r"^Systemický institut s\.r\.o\.$", "Systemický institut, s.r.o."),
+            (r"^Adéla hradilová$", "Adéla Hradilová"),
+        )
+
+        for pattern in patterns:
+            name = re.sub(pattern[0], pattern[1], name)
+
+        self.normalization_count += 1
+
+        is_contractee = False
+        representatives = []
+
+        if name.lower() in (
+            "česká pirátská strana",
+            "česká pirástká strana",
+            "česká pirátkská strana",
+            "česká pirátská stran",
+        ):
+            model = Contractee
+            representative_model = ContracteeSignatureRepresentative
+            instance = model()
+            is_contractee = True
+        else:
+            model = Signee
+            representative_model = SigneeSignatureRepresentative
+            instance = model(name=name, address_country="Česká republika")
+
+        for signing_party_key, signing_party_value in signing_party.items():
+            if isinstance(signing_party_value, str):
+                signing_party_value = signing_party_value.strip()
+
+            match signing_party_key:
+                case ["sídlo" | "bydliště"]:
+                    if is_contractee:
+                        continue
+
+                    if not isinstance(signing_party_value, str):
+                        issue_count += 1
+                        contract.notes += f"Špatně zadané sídlo smluvní strany: {signing_party_value}\n"
+                        issues.append(
+                            self.use_issue(
+                                contract, "Špatně zadané sídlo smluvní strany"
+                            )
+                        )
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Contract {slug} has an invalid signing party address: {signing_party_value}."
+                                )
+                            )
+
+                        continue
+
+                    raw_parsed_address_data = parse_address(signing_party_value)
+                    address = {}
+
+                    # Convert to a dict first, so that we can access other keys in the loop later
+                    for address_info in raw_parsed_address_data:
+                        address_value, address_key = address_info
+                        address[address_key] = address_value
+
+                    instance.address_street_with_number = ""
+
+                    if "road" in address:
+                        instance.address_street_with_number = string.capwords(
+                            address["road"]
+                        )
+
+                    if "house_number" in address:
+                        instance.address_street_with_number += (
+                            f" {address['house_number']}"
+                        )
+
+                    for address_key, address_value in address.items():
+                        match address_key:
+                            case "city":
+                                if "district" not in address:
+                                    instance.address_district = string.capwords(
+                                        address_value
+                                    )
+                            case "house":
+                                if "city" not in address and "district" not in address:
+                                    instance.address_district = string.capwords(
+                                        address_value
+                                    )
+                            case "city_district":
+                                instance.address_district = string.capwords(
+                                    address_value
+                                )
+                            case "postcode":
+                                instance.address_zip = string.capwords(address_value)
+
+                    self.normalization_count += 1
+                case "IČ":
+                    if is_contractee:
+                        continue
+
+                    if not isinstance(signing_party_value, int | str):
+                        issue_count += 1
+                        contract.notes += (
+                            f"Špatně zadané IČO smluvní strany: {signing_party_value}\n"
+                        )
+                        issues.append(
+                            self.use_issue(contract, "Špatně zadané IČO smluvní strany")
+                        )
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Contract {slug} has an invalid signing party IČO: {signing_party_value}."
+                                )
+                            )
+
+                        continue
+
+                    instance.ico_number = str(signing_party_value)
+                case "zástupce":
+                    if not isinstance(signing_party_value, str | list):
+                        issue_count += 1
+                        contract.notes += f"Špatně zadaný zástupce smluvní strany: {signing_party_value}\n"
+                        issues.append(
+                            self.use_issue(
+                                contract, "Špatně zadaný zástupce smluvní strany"
+                            )
+                        )
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Contract {slug} has an invalid signing party "
+                                    f"representative: {signing_party_value}."
+                                )
+                            )
+
+                        continue
+
+                    if isinstance(signing_party_value, str):
+                        signing_party_value = re.sub(r",$", "", signing_party_value)
+                        self.normalization_count += 1
+
+                        function = None
+
+                        if "funkce" in signing_party:
+                            if isinstance(signing_party["funkce"], str):
+                                function = signing_party["funkce"]
+                            else:
+                                issue_count += 1
+                                contract.notes += f"Špatně zadaná funkce zástupce smluvní strany: {signing_party['funkce']}\n"
+                                issues.append(
+                                    self.use_issue(
+                                        contract,
+                                        "Špatně zadaná funkce zástupce smluvní strany",
+                                    )
+                                )
+
+                                if self.verbosity >= 2:
+                                    self.stderr.write(
+                                        self.style.NOTICE(
+                                            f"Contract {slug} has an invalid signing party "
+                                            f"representative function: {signing_party['funkce']}."
+                                        )
+                                    )
+
+                        representatives.append(
+                            representative_model(name=signing_party_value)
+                        )
+                    else:
+                        for representative_name in signing_party_value:
+                            if not isinstance(representative_name, str):
+                                issue_count += 1
+                                contract.notes += f"Špatně zadaný jeden ze zástupců smluvní strany: {representative_name}\n"
+                                issues.append(
+                                    self.use_issue(
+                                        contract,
+                                        "Špatně zadaný zástupce smluvní strany",
+                                    )
+                                )
+
+                                if self.verbosity >= 2:
+                                    self.stderr.write(
+                                        self.style.NOTICE(
+                                            f"Contract {slug} has an invalid signing party "
+                                            f"representative list item: {representative_name}."
+                                        )
+                                    )
+
+                                continue
+
+                            representative_name = re.sub(r",$", "", representative_name)
+                            self.normalization_count += 1
+
+                            representatives.append(
+                                representative_model(name=signing_party_value)
+                            )
+                case "orgán":
+                    if not isinstance(signing_party_value, str):
+                        issue_count += 1
+                        contract.notes += f"Špatně zadaný orgán smluvní strany: {signing_party_value}\n"
+                        issues.append(
+                            self.use_issue(
+                                contract, "Špatně zadaný orgán smluvní strany"
+                            )
+                        )
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Contract {slug} has an invalid signing party "
+                                    f"department: {signing_party_value}."
+                                )
+                            )
+
+                        continue
+
+                    if signing_party_value in (
+                        "Česká Pirátská strana",
+                        "Česká pirátská strana",
+                    ):
+                        # Irrelevant
+                        continue
+
+                    instance.department = self.normalize_department(signing_party_value)
+                    self.normalization_count += 1
+
+        if model is Signee:
+            if (
+                "s.r.o" in instance.name
+                or "s. r. o." in instance.name
+                or "a.s." in instance.name
+                or "a. s." in instance.name
+                or "o.s." in instance.name
+                or "o. s." in instance.name
+                or "z.s." in instance.name
+                or "z. s." in instance.name
+                or "1" in instance.name
+                or "2" in instance.name
+                or "3" in instance.name
+                or "4" in instance.name
+                or "5" in instance.name
+                or "6" in instance.name
+                or "7" in instance.name
+                or "8" in instance.name
+                or "9" in instance.name
+                or "0" in instance.name
+                or len(instance.name.split(" ")) not in (2, 3)
+            ):
+                instance.entity_type = instance.EntityTypes.LEGAL_ENTITY
+            else:
+                if instance.ico_number is None:
+                    instance.entity_type = instance.EntityTypes.NATURAL_PERSON
+                else:
+                    instance.entity_type = instance.EntityTypes.BUSINESS_NATURAL_PERSON
+
+        # Do our best to merge signing parties together.
+        existing_instances = model.objects.filter(
+            (
+                models.Q(name=instance.name)
+                & (
+                    models.Q(department=instance.department)
+                    if instance.department is not None
+                    else models.Q(department__isnull=True)
+                )
+                & (
+                    (
+                        models.Q(
+                            address_street_with_number=instance.address_street_with_number
+                        )
+                        if instance.address_street_with_number is not None
+                        else models.Q(address_street_with_number__isnull=True)
+                    )
+                    | (
+                        models.Q(date_of_birth=instance.date_of_birth)
+                        if model is Signee and instance.date_of_birth is not None
+                        else (
+                            models.Q(date_of_birth__isnull=True)
+                            if model is Signee
+                            else models.Value(False)
+                        )
+                    )
+                )
+            )
+            | (
+                (
+                    models.Q(ico_number=instance.ico_number)
+                    & (
+                        models.Q(department=instance.department)
+                        if instance.department is not None
+                        else models.Q(department__isnull=True)
+                    )
+                )
+                if instance.ico_number is not None
+                else models.Value(False)
+            )
+        ).all()
+
+        if len(existing_instances) != 0:
+            for position, existing_instance in enumerate(existing_instances):
+                if (
+                    existing_instance.ico_number is None
+                    and instance.ico_number is not None
+                ):
+                    existing_instance.ico_number = instance.ico_number
+                    existing_instance.save()
+                    instance = existing_instance
+                    break
+                elif (
+                    existing_instance.ico_number == instance.ico_number
+                    or instance.ico_number is None
+                ):
+                    instance = existing_instance
+                    break
+                elif position == len(existing_instances) - 1:
+                    instance.save()
+        else:
+            instance.save()
+
+        return instance, issues, representatives, is_contractee, issue_count
+
+    def assign_contract_data(
         self,
         contract: Contract,
         metadata: dict,
         slug: str,
+        contract_root: str,
     ) -> None:
         filing_area = None
         types = []
+        signees = []
+        contractees = []
+        files = []
+        issues = []
         is_already_imported = False
         observed_issues_count = 0
 
@@ -275,6 +782,9 @@ class Command(BaseCommand):
                     elif value is not None:
                         observed_issues_count += 1
                         contract.notes += f"Špatně zadaný začátek platnosti: {value}\n"
+                        issues.append(
+                            self.use_issue(contract, "Špatně zadaný začátek platnosti")
+                        )
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -291,6 +801,9 @@ class Command(BaseCommand):
                     ):
                         observed_issues_count += 1
                         contract.notes += f"Špatně zadaný konec platnosti: {value}\n"
+                        issues.append(
+                            self.use_issue(contract, "Špatně zadaný konec platnosti")
+                        )
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -313,6 +826,7 @@ class Command(BaseCommand):
                 case "použité smluvní typy":
                     if isinstance(value, str):
                         value = self.normalize_type(value)
+                        self.normalization_count += 1
 
                         try:
                             type_instance = ContractType.objects.get(name=value)
@@ -325,6 +839,7 @@ class Command(BaseCommand):
                     elif not isinstance(value, list):
                         observed_issues_count += 1
                         contract.notes += f"Špatně zadané typy: {value}\n"
+                        issues.append(self.use_issue(contract, "Špatně zadané typy"))
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -338,7 +853,8 @@ class Command(BaseCommand):
                     for type_name in value:
                         if not isinstance(type_name, str):
                             observed_issues_count += 1
-                            contract.notes += f"Nezaevidovaný typ: {type_name}\n"
+                            contract.notes += f"Špatně zadaný typ: {type_name}\n"
+                            issues.append(self.use_issue(contract, "Špatně zadaný typ"))
 
                             if self.verbosity >= 2:
                                 self.stderr.write(
@@ -350,6 +866,7 @@ class Command(BaseCommand):
                             continue
 
                         type_name = self.normalize_type(type_name)
+                        self.normalization_count += 1
 
                         try:
                             type_instance = ContractType.objects.get(name=type_name)
@@ -379,6 +896,7 @@ class Command(BaseCommand):
                     else:
                         observed_issues_count += 1
                         contract.notes += f"Neznámý stav: {value}\n"
+                        issues.append(self.use_issue(contract, "Neznámý právní stav"))
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -399,6 +917,9 @@ class Command(BaseCommand):
                             contract.notes += (
                                 f"Původní, špatně zadané náklady: {value}\n"
                             )
+                            issues.append(
+                                self.use_issue(contract, "Špatně zadané náklady")
+                            )
 
                             if self.verbosity >= 2:
                                 self.stderr.write(
@@ -426,6 +947,9 @@ class Command(BaseCommand):
                         ):
                             observed_issues_count += 1
                             contract.notes += f"Původní, neropoznané náklady: {value}\n"
+                            issues.append(
+                                self.use_issue(contract, "Špatně zadané náklady")
+                            )
 
                             if self.verbosity >= 2:
                                 self.stderr.write(
@@ -441,6 +965,7 @@ class Command(BaseCommand):
                     elif value not in (None, "0"):
                         observed_issues_count += 1
                         contract.notes += f"Původní, neropoznané náklady: {value}\n"
+                        issues.append(self.use_issue(contract, "Špatně zadané náklady"))
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -451,6 +976,7 @@ class Command(BaseCommand):
                 case "místo uložení":
                     if isinstance(value, str):
                         value = self.normalize_filing_area(value)
+                        self.normalization_count += 1
 
                     try:
                         contract.paper_form_state = contract.PaperFormStates.STORED
@@ -464,6 +990,9 @@ class Command(BaseCommand):
                         else:
                             observed_issues_count += 1
                             contract.notes += f"Špatně zadaná spisovna: {value}\n"
+                            issues.append(
+                                self.use_issue(contract, "Špatně zadaná spisovna")
+                            )
 
                             if self.verbosity >= 2:
                                 self.stderr.write(
@@ -471,14 +1000,164 @@ class Command(BaseCommand):
                                         f"Contract {slug} has an invalid filing area: {value}."
                                     )
                                 )
+                case "smluvní strany":
+                    if not isinstance(value, list):
+                        observed_issues_count += 1
+                        contract.notes += (
+                            f"Špatně zadané smluvní strany, nejsou seznam: {value}\n"
+                        )
+                        issues.append(
+                            self.use_issue(contract, "Špatně zadaný smluvní strany")
+                        )
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Signing parties on {slug} are not a list: {value}."
+                                )
+                            )
+
+                        continue
+
+                    for signing_party in value:
+                        if not isinstance(signing_party, dict):
+                            observed_issues_count += 1
+                            contract.notes += (
+                                f"Špatně zadaná smluvní strana: {signing_party}\n"
+                            )
+                            issues.append(
+                                self.use_issue(contract, "Špatně zadaná smluvní strana")
+                            )
+
+                            if self.verbosity >= 2:
+                                self.stderr.write(
+                                    self.style.NOTICE(
+                                        f"Signing party on {slug} is not a dictionary: {value}."
+                                    )
+                                )
+
+                            continue
+
+                        (
+                            instance,
+                            signing_party_issues,
+                            representatives,
+                            is_contractee,
+                            signing_party_issue_count,
+                        ) = self.assign_signing_party_metadata(
+                            slug, contract, signing_party
+                        )
+
+                        issues += signing_party_issues
+                        observed_issues_count += signing_party_issue_count
+
+                        # Store representatives in relation to the instance, hacky but good enough
+                        instance._representatives = representatives
+
+                        if is_contractee:
+                            contractees.append(instance)
+                        else:
+                            signees.append(instance)
+                case "soubory":
+                    if not isinstance(value, list):
+                        observed_issues_count += 1
+                        contract.notes += f"Špatně zadané soubory.\n"
+                        issues.append(self.use_issue(contract, "Špatně zadané soubory"))
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Files for {slug} are not a list: {value}."
+                                )
+                            )
+
+                        continue
+
+                    for file_data in value:
+                        if not isinstance(file_data, dict):
+                            observed_issues_count += 1
+                            contract.notes += (
+                                f"Špatně zadané informace o souboru: {file_data}.\n"
+                            )
+                            issues.append(
+                                self.use_issue(
+                                    contract, "Špatně zadané informace o souboru"
+                                )
+                            )
+
+                            if self.verbosity >= 2:
+                                self.stderr.write(
+                                    self.style.NOTICE(
+                                        f"File data in {slug} is not a dict: {file_data}."
+                                    )
+                                )
 
                             continue
 
+                        for file_key, file_value in file_data.items():
+                            file_key = file_key.strip()
+
+                            if file_key.lower() in ("název", "náhled", "náhlad"):
+                                continue
+
+                            if not isinstance(file_value, str):
+                                observed_issues_count += 1
+                                contract.notes += f"Špatně zadaný název souboru {file_key}: {file_value}.\n"
+                                issues.append(
+                                    self.use_issue(contract, "Neplatný název souboru")
+                                )
+
+                                if self.verbosity >= 2:
+                                    self.stderr.write(
+                                        self.style.NOTICE(
+                                            f"Filename in {slug} for file {file_key} invalid: {file_value}."
+                                        )
+                                    )
+
+                                continue
+
+                            file_path = os.path.join(contract_root, file_value)
+
+                            if not os.path.isfile(file_path):
+                                observed_issues_count += 1
+                                contract.notes += (
+                                    f"Neexistující soubor: {file_value}.\n"
+                                )
+                                issues.append(
+                                    self.use_issue(contract, "Neexistující soubor")
+                                )
+
+                                if self.verbosity >= 2:
+                                    self.stderr.write(
+                                        self.style.NOTICE(
+                                            f"Filename in {slug} does not correspond to a file: {file_value}."
+                                        )
+                                    )
+
+                                continue
+
+                            with open(file_path, "rb") as open_file:
+                                self.normalization_count += 1
+                                file = ContractFile(
+                                    contract=contract,
+                                    name=self.normalize_filename(file_key),
+                                    is_public=True,
+                                )
+
+                                file.file.save(
+                                    file_value,
+                                    File(open_file),
+                                    save=False,
+                                )
+
+                                files.append(file)
+
         if not is_already_imported:
             if contract.name in (None, "") or (
                 isinstance(contract.name, str)
                 and re.sub(r"/\s\s+/", "", contract.name) == ""
             ):
+                self.normalization_count += 1
                 contract.name = slug
 
             if contract.valid_start_date is None:
@@ -499,8 +1178,49 @@ class Command(BaseCommand):
             # Save primary key first
             contract.save()
 
+            for contractee in contractees:
+                contractee.save()
+
+                signature = ContracteeSignature(
+                    contract=contract,
+                    contractee=contractee,
+                    date=contract.valid_start_date,
+                )
+
+                signature.save()
+
+                contract.contractee_signatures.add(signature)
+
+                for representative in contractee._representatives:
+                    representative.signature = signature
+                    representative.save()
+
+            for signee in signees:
+                signee.save()
+
+                signature = SigneeSignature(
+                    contract=contract,
+                    signee=signee,
+                    date=contract.valid_start_date,
+                )
+
+                signature.save()
+
+                contract.signee_signatures.add(signature)
+
+                for representative in signee._representatives:
+                    representative.signature = signature
+                    representative.save()
+
+            for file in files:
+                file.save()
+
+            for issue in issues:
+                issue.save()
+
             contract.filing_area = filing_area
             contract.types.set(types)
+            contract.issues.set(issues)
             contract.save()
         else:
             self.already_imported_count += 1
@@ -535,10 +1255,11 @@ class Command(BaseCommand):
 
                         return
 
-                    self.assign_contract_metadata(
+                    self.assign_contract_data(
                         contract,
                         metadata,
                         os.path.basename(contract_root),
+                        contract_root,
                     )
                 elif file_.endswith(".pdf"):
                     # TODO
@@ -611,7 +1332,8 @@ class Command(BaseCommand):
                     f"Saved a total of {self.normal_import_count + self.partial_import_count} contracts.\n"
                     f"    {self.partial_import_count} contained a total of {self.issue_count} issues.\n"
                     f"    {self.already_imported_count} were already saved previously and skipped.\n"
-                    f"    {self.fatal_error_count} potential contracts were unparseable."
+                    f"    {self.fatal_error_count} potential contracts were unparseable.\n"
+                    f"    {self.normalization_count} data points were normalized."
                 )
             )
 
@@ -623,7 +1345,7 @@ class Command(BaseCommand):
             (options["directory"] if options["directory"] is not None else "git"),
         )
 
-        if os.path.exists(git_dir):
+        if os.path.exists(git_dir) and not options["delete"]:
             if not options["existing"]:
                 if self.verbosity >= 1:
                     self.stderr.write(
@@ -640,6 +1362,12 @@ class Command(BaseCommand):
                 if self.verbosity >= 2:
                     self.stdout.write("Using existing git storage directory.")
         else:
+            if options["delete"] and os.path.exists(git_dir):
+                if self.verbosity >= 2:
+                    self.stdout.write("Deleting old git storage directory.")
+
+                shutil.rmtree(git_dir)
+
             if self.verbosity >= 2:
                 self.stdout.write("Cloning repository.")
 
@@ -653,9 +1381,17 @@ class Command(BaseCommand):
                 self.stdout.write(self.style.SUCCESS("Finished cloning repository."))
 
         if options["purge"]:
-            Contract.objects.filter().delete()
-            ContractType.objects.filter().delete()
-            ContractFilingArea.objects.filter().delete()
+            for model in (
+                Contract,
+                ContractType,
+                ContractFilingArea,
+                ContractFile,
+                Contractee,
+                ContracteeSignature,
+                Signee,
+                SigneeSignature,
+            ):
+                model.objects.filter().delete()
 
             if self.verbosity >= 1:
                 self.stdout.write(self.style.SUCCESS("Deleted all previous records."))
diff --git a/contracts/migrations/0053_alter_contractfile_file.py b/contracts/migrations/0053_alter_contractfile_file.py
index aa0900d944457b4e00028d0ee4a751df7f62d332..b5fefd1c6fa3aba07b7cedbbd7d815fee1496ba0 100644
--- a/contracts/migrations/0053_alter_contractfile_file.py
+++ b/contracts/migrations/0053_alter_contractfile_file.py
@@ -1,19 +1,22 @@
 # Generated by Django 4.1.4 on 2023-04-21 12:10
 
-import contracts.models
 from django.db import migrations
 
+import contracts.models
+
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('contracts', '0052_remove_contract_legal_state_contract_is_valid'),
+        ("contracts", "0052_remove_contract_legal_state_contract_is_valid"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='contractfile',
-            name='file',
-            field=contracts.models.ContractFileField(upload_to=contracts.models.get_contract_file_loaction, verbose_name='Soubor'),
+            model_name="contractfile",
+            name="file",
+            field=contracts.models.ContractFileField(
+                upload_to=contracts.models.get_contract_file_loaction,
+                verbose_name="Soubor",
+            ),
         ),
     ]
diff --git a/contracts/migrations/0054_alter_signee_address_zip_alter_signee_ico_number.py b/contracts/migrations/0054_alter_signee_address_zip_alter_signee_ico_number.py
new file mode 100644
index 0000000000000000000000000000000000000000..52d1ce4ff5182c5a11d9e6b9df574bb24679e659
--- /dev/null
+++ b/contracts/migrations/0054_alter_signee_address_zip_alter_signee_ico_number.py
@@ -0,0 +1,34 @@
+# Generated by Django 4.1.4 on 2023-04-21 22:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("contracts", "0053_alter_contractfile_file"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="signee",
+            name="address_zip",
+            field=models.CharField(
+                blank=True,
+                help_text="Veřejné pouze, když typ není nastaven na fyzickou osobu.",
+                max_length=256,
+                null=True,
+                verbose_name="PSČ",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="signee",
+            name="ico_number",
+            field=models.CharField(
+                blank=True,
+                help_text="U právnických a podnikajících fyzických osob musí být vyplněno. Vyplněním můžeš automaticky načíst data z ARES.",
+                max_length=256,
+                null=True,
+                verbose_name="IČO",
+            ),
+        ),
+    ]
diff --git a/contracts/migrations/0055_alter_contractee_address_zip_and_more.py b/contracts/migrations/0055_alter_contractee_address_zip_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..1bbcaaead5596e9a3ed11cb27999276ad4daa13c
--- /dev/null
+++ b/contracts/migrations/0055_alter_contractee_address_zip_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 4.1.4 on 2023-04-21 22:52
+
+from django.db import migrations, models
+
+import contracts.models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("contracts", "0054_alter_signee_address_zip_alter_signee_ico_number"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="contractee",
+            name="address_zip",
+            field=models.CharField(
+                default=contracts.models.get_default_contractee_zip,
+                max_length=256,
+                verbose_name="PSČ",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="contractee",
+            name="ico_number",
+            field=models.CharField(
+                blank=True,
+                default=contracts.models.get_default_contractee_ico_number,
+                max_length=256,
+                null=True,
+                verbose_name="IČO",
+            ),
+        ),
+    ]
diff --git a/contracts/migrations/0056_rename_contractee_signature_contracteesignaturerepresentative_signature_and_more.py b/contracts/migrations/0056_rename_contractee_signature_contracteesignaturerepresentative_signature_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..c80d53df487dc8a2b924de82067876fcac22c760
--- /dev/null
+++ b/contracts/migrations/0056_rename_contractee_signature_contracteesignaturerepresentative_signature_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.1.4 on 2023-04-22 22:13
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("contracts", "0055_alter_contractee_address_zip_and_more"),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name="contracteesignaturerepresentative",
+            old_name="contractee_signature",
+            new_name="signature",
+        ),
+        migrations.RenameField(
+            model_name="signeesignaturerepresentative",
+            old_name="signee_signature",
+            new_name="signature",
+        ),
+    ]
diff --git a/contracts/migrations/0057_alter_contract_options_alter_contractee_options_and_more.py b/contracts/migrations/0057_alter_contract_options_alter_contractee_options_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..37e8323528e972094deedab264181334dc7e38b4
--- /dev/null
+++ b/contracts/migrations/0057_alter_contract_options_alter_contractee_options_and_more.py
@@ -0,0 +1,80 @@
+# Generated by Django 4.1.4 on 2023-04-23 10:28
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        (
+            "contracts",
+            "0056_rename_contractee_signature_contracteesignaturerepresentative_signature_and_more",
+        ),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name="contract",
+            options={
+                "ordering": ("-created_on", "-updated_on", "-name"),
+                "permissions": [
+                    ("approve", "Schválit / zrušit schválení"),
+                    ("view_confidential", "Zobrazit tajné informace"),
+                    ("edit_when_approved", "Upravit schválené"),
+                    ("delete_when_approved", "Odstranit schválené"),
+                    ("edit_others", "Upravit cizí"),
+                    ("delete_others", "Odstranit cizí"),
+                    ("can_edit_contract_settings", "Can edit Smlouva settings"),
+                ],
+                "verbose_name": "Smlouva",
+                "verbose_name_plural": "Smlouvy",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contractee",
+            options={
+                "ordering": ["-name", "-department"],
+                "permissions": [
+                    ("edit_others", "Upravit cizí"),
+                    ("delete_others", "Odstranit cizí"),
+                ],
+                "verbose_name": "Naše smluvní strana",
+                "verbose_name_plural": "Naše smluvní strany",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contractfilingarea",
+            options={
+                "ordering": ["-name"],
+                "verbose_name": "Spisovna",
+                "verbose_name_plural": "Spisovny",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contractissue",
+            options={
+                "ordering": ["-name"],
+                "verbose_name": "Problém se smlouvou",
+                "verbose_name_plural": "Problémy se smlouvami",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contracttype",
+            options={
+                "ordering": ["-name"],
+                "verbose_name": "Typ smlouvy",
+                "verbose_name_plural": "Typy smluv",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="signee",
+            options={
+                "ordering": ["-name", "-department"],
+                "permissions": [
+                    ("edit_others", "Upravit cizí"),
+                    ("delete_others", "Odstranit cizí"),
+                ],
+                "verbose_name": "Jiná smluvní strana",
+                "verbose_name_plural": "Ostatní smluvní strany",
+            },
+        ),
+    ]
diff --git a/contracts/migrations/0058_alter_contract_options_alter_contractee_options_and_more.py b/contracts/migrations/0058_alter_contract_options_alter_contractee_options_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..52f5e1f0dbb24bfcfb4f5b3b5d02387a6be89e56
--- /dev/null
+++ b/contracts/migrations/0058_alter_contract_options_alter_contractee_options_and_more.py
@@ -0,0 +1,77 @@
+# Generated by Django 4.1.4 on 2023-04-23 10:29
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("contracts", "0057_alter_contract_options_alter_contractee_options_and_more"),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name="contract",
+            options={
+                "ordering": ("-created_on", "-updated_on", "name"),
+                "permissions": [
+                    ("approve", "Schválit / zrušit schválení"),
+                    ("view_confidential", "Zobrazit tajné informace"),
+                    ("edit_when_approved", "Upravit schválené"),
+                    ("delete_when_approved", "Odstranit schválené"),
+                    ("edit_others", "Upravit cizí"),
+                    ("delete_others", "Odstranit cizí"),
+                    ("can_edit_contract_settings", "Can edit Smlouva settings"),
+                ],
+                "verbose_name": "Smlouva",
+                "verbose_name_plural": "Smlouvy",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contractee",
+            options={
+                "ordering": ["name", "department"],
+                "permissions": [
+                    ("edit_others", "Upravit cizí"),
+                    ("delete_others", "Odstranit cizí"),
+                ],
+                "verbose_name": "Naše smluvní strana",
+                "verbose_name_plural": "Naše smluvní strany",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contractfilingarea",
+            options={
+                "ordering": ["name"],
+                "verbose_name": "Spisovna",
+                "verbose_name_plural": "Spisovny",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contractissue",
+            options={
+                "ordering": ["name"],
+                "verbose_name": "Problém se smlouvou",
+                "verbose_name_plural": "Problémy se smlouvami",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="contracttype",
+            options={
+                "ordering": ["name"],
+                "verbose_name": "Typ smlouvy",
+                "verbose_name_plural": "Typy smluv",
+            },
+        ),
+        migrations.AlterModelOptions(
+            name="signee",
+            options={
+                "ordering": ["name", "department"],
+                "permissions": [
+                    ("edit_others", "Upravit cizí"),
+                    ("delete_others", "Odstranit cizí"),
+                ],
+                "verbose_name": "Jiná smluvní strana",
+                "verbose_name_plural": "Ostatní smluvní strany",
+            },
+        ),
+    ]
diff --git a/contracts/migrations/0059_alter_contract_is_valid.py b/contracts/migrations/0059_alter_contract_is_valid.py
new file mode 100644
index 0000000000000000000000000000000000000000..df29dad1e83e8eb129a54642ba1ab5af5a3fbd6b
--- /dev/null
+++ b/contracts/migrations/0059_alter_contract_is_valid.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1.4 on 2023-05-01 20:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0058_alter_contract_options_alter_contractee_options_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='contract',
+            name='is_valid',
+            field=models.BooleanField(default=False, help_text='Právní vztah vyplývající ze smlouvy je aktuálně účinný a platný', verbose_name='Je právně platná'),
+        ),
+    ]
diff --git a/contracts/models.py b/contracts/models.py
index 72d01d2a5649dcb0ff475234d4d607ae49870e3e..5e9719e79202e770c039ae56195f6491da9d5ba6 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -8,8 +8,8 @@ from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.core.validators import RegexValidator, URLValidator
 from django.db import models
-from django.db.models.signals import post_save
 from django.db.models.fields.files import FieldFile
+from django.db.models.signals import post_save
 from django.dispatch import receiver
 from django.urls import reverse
 from django.utils.safestring import mark_safe
@@ -156,7 +156,7 @@ class Signee(
     )
 
     address_zip = models.CharField(
-        max_length=16,
+        max_length=256,
         blank=True,
         null=True,
         verbose_name="PSČ",
@@ -172,7 +172,7 @@ class Signee(
     )
 
     ico_number = models.CharField(
-        max_length=16,
+        max_length=256,
         blank=True,
         null=True,
         verbose_name="IČO",
@@ -274,6 +274,8 @@ class Signee(
         verbose_name = "Jiná smluvní strana"
         verbose_name_plural = "Ostatní smluvní strany"
 
+        ordering = ["name", "department"]
+
         permissions = OwnPermissionsMixin.Meta.permissions
 
 
@@ -323,7 +325,7 @@ class Contractee(
     )
 
     address_zip = models.CharField(
-        max_length=16,
+        max_length=256,
         default=get_default_contractee_zip,
         verbose_name="PSČ",
     )
@@ -335,7 +337,7 @@ class Contractee(
     )
 
     ico_number = models.CharField(
-        max_length=16,
+        max_length=256,
         blank=True,
         null=True,
         default=get_default_contractee_ico_number,
@@ -367,6 +369,8 @@ class Contractee(
         verbose_name = "Naše smluvní strana"
         verbose_name_plural = "Naše smluvní strany"
 
+        ordering = ["name", "department"]
+
         permissions = OwnPermissionsMixin.Meta.permissions
 
 
@@ -383,6 +387,8 @@ class ContractType(ContractCountMixin, NameStrMixin, models.Model):
     class Meta:
         app_label = "contracts"
 
+        ordering = ["name"]
+
         verbose_name = "Typ smlouvy"
         verbose_name_plural = "Typy smluv"
 
@@ -400,6 +406,8 @@ class ContractIssue(ContractCountMixin, NameStrMixin, models.Model):
     class Meta:
         app_label = "contracts"
 
+        ordering = ["name"]
+
         verbose_name = "Problém se smlouvou"
         verbose_name_plural = "Problémy se smlouvami"
 
@@ -422,6 +430,8 @@ class ContractFilingArea(ContractCountMixin, NameStrMixin, models.Model):
     class Meta:
         app_label = "contracts"
 
+        ordering = ["name"]
+
         verbose_name = "Spisovna"
         verbose_name_plural = "Spisovny"
 
@@ -554,6 +564,7 @@ class Contract(NameStrMixin, models.Model):
     is_valid = models.BooleanField(
         default=False,
         verbose_name="Je právně platná",
+        help_text="Právní vztah vyplývající ze smlouvy je aktuálně účinný a platný",
     )
 
     is_public = models.BooleanField(
@@ -789,6 +800,12 @@ class Contract(NameStrMixin, models.Model):
         verbose_name = "Smlouva"
         verbose_name_plural = "Smlouvy"
 
+        ordering = (
+            "-created_on",
+            "-updated_on",
+            "name",
+        )
+
         permissions = [
             ("approve", "Schválit / zrušit schválení"),
             ("view_confidential", "Zobrazit tajné informace"),
@@ -827,7 +844,9 @@ def get_contract_file_loaction(instance, filename):
 class ContractFileProxy(FieldFile):
     @property
     def url(self) -> str:
-        return reverse("contracts:download_contract_file", args=(str(self.instance.id),))
+        return reverse(
+            "contracts:download_contract_file", args=(str(self.instance.id),)
+        )
 
 
 class ContractFileField(models.FileField):
@@ -947,7 +966,7 @@ class SigneeSignature(models.Model):
 
 
 class ContracteeSignatureRepresentative(RepresentativeMixin, models.Model):
-    contractee_signature = models.ForeignKey(
+    signature = models.ForeignKey(
         ContracteeSignature,
         on_delete=models.CASCADE,
         related_name="representatives",
@@ -972,7 +991,7 @@ class ContracteeSignatureRepresentative(RepresentativeMixin, models.Model):
 
 
 class SigneeSignatureRepresentative(RepresentativeMixin, models.Model):
-    signee_signature = models.ForeignKey(
+    signature = models.ForeignKey(
         SigneeSignature,
         on_delete=models.CASCADE,
         related_name="representatives",
diff --git a/package-lock.json b/package-lock.json
index 555756a82080856216af658fc1ac4af15cb36bba..b57b2e441884312be7b106c23e62d1eb89ff4d60 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -515,9 +515,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001468",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz",
-      "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==",
+      "version": "1.0.30001481",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz",
+      "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==",
       "funding": [
         {
           "type": "opencollective",
@@ -526,6 +526,10 @@
         {
           "type": "tidelift",
           "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
         }
       ]
     },
@@ -2568,9 +2572,9 @@
       "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
     },
     "caniuse-lite": {
-      "version": "1.0.30001468",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz",
-      "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A=="
+      "version": "1.0.30001481",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz",
+      "integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ=="
     },
     "chokidar": {
       "version": "3.5.3",
diff --git a/registry/settings/base.py b/registry/settings/base.py
index 15f0ca7001cdb133c88efd3666e6fdba61f893ea..c2f29bd217dd4272ac6a7fdeb5727ef3a17861a3 100644
--- a/registry/settings/base.py
+++ b/registry/settings/base.py
@@ -53,6 +53,7 @@ INSTALLED_APPS = [
     "django.contrib.sessions",
     "django.contrib.messages",
     "django.contrib.staticfiles",
+    "admin_auto_filters",
     "dbsettings",
     "nested_admin",
     "rangefilter",
diff --git a/requirements/base.txt b/requirements/base.txt
index c8f33c620f3939b2097253df401d14bfc05fd049..e4fe2396bd4a6f5a08f1182a3859258f20f97548 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,5 +1,6 @@
 clamd==1.0.2
 django==4.1.4
+django-admin-autocomplete-filter==0.7.1
 django-admin-index==2.0.2
 django-admin-interface==0.24.2
 django-admin-rangefilter==0.9.0
@@ -17,5 +18,6 @@ django-http-exceptions==1.4.0
 django-guardian==2.4.0
 GitPython==3.1.31
 Markdown==3.4.3
+postal==1.1.10
 PyJWT==2.6.0
 PyYAML==6.0
diff --git a/shared/templates/shared/includes/base.html b/shared/templates/shared/includes/base.html
index 2755e32994e14b2e262d52630180fec8ad7c6d8e..5520d1980abf67c68c5d7bffbbba687e26ca588a 100644
--- a/shared/templates/shared/includes/base.html
+++ b/shared/templates/shared/includes/base.html
@@ -60,7 +60,7 @@
                         <div class="container container--default navbar__content" :class="{'navbar__content--initialized': true}">
                             <div class="navbar__brand my-4 flex items-center lg:pr-8 lg:my-0">
                                 <a href="/">
-                                    <img src="https://styleguide.pirati.cz/2.12.x/images/logo-round-white.svg" class="w-8" />
+                                    <img src="https://styleguide.pirati.cz/2.12.x/images/logo-round-white.svg" class="w-8">
                                 </a>
                                 <a href="/" class="pl-4 font-bold text-xl hover:no-underline lg:border-r lg:border-grey-300 lg:pr-8">Registr smluv</a>
                             </div>
@@ -190,8 +190,8 @@
                             </a>
                             <a href="https://nalodeni.pirati.cz" class="btn btn--icon btn--blue-300 btn--hoveractive text-lg btn--fullwidth sm:btn--autowidth">
                                 <div class="btn__body-wrap">
-                                    <div class="btn__body ">Naloď se</div>
-                                    <div class="btn__icon "><i class="ico--anchor"></i></div>
+                                    <div class="btn__body">Naloď se</div>
+                                    <div class="btn__icon"><i class="ico--anchor"></i></div>
                                 </div>
                             </a>
                         </div>
diff --git a/static_src/admin/contract_file_form.js b/static_src/admin/contract_file_form.js
index 38f84779a4fa84d832f7e7d297507c70e1c48f06..b5210e1ca542a749ead5f32da4f938ceac647b3b 100644
--- a/static_src/admin/contract_file_form.js
+++ b/static_src/admin/contract_file_form.js
@@ -6,6 +6,7 @@ $(window).ready(
             `<datalist id="file-types">
     <option value="Původní verze">
     <option value="Anonymizovaná verze">
+    <option value="Upravitelná verze">
 </datalist>`
         );
     }
diff --git a/users/models.py b/users/models.py
index e9332ea84b61f7e978f30b131fd6ecffbcc813c6..64dee9b879678b89f0304418b428213490a116b4 100644
--- a/users/models.py
+++ b/users/models.py
@@ -33,7 +33,7 @@ class User(pirates_models.AbstractUser):
 
     @property
     def can_create_contracts(self) -> bool:
-        return self.has_perm("contracts.add")
+        return self.has_perm("contracts.add_contract")
 
     @property
     def can_view_confidential(self) -> bool: