diff --git a/README.md b/README.md
index 17cd5641e4be239f4c3dd597b77f82c5beace004..bf6dd22f54d6ee7792ade1af5da5647796018c2b 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ V produkci je potřeba:
 | proměnná | popis |
 | --- | --- |
 | `ALLOWED_HOSTS` | Seznam domén, skrz které se na server lze připojovat. [Více info](https://docs.djangoproject.com/en/4.1/ref/settings/#allowed-hosts) |
+| `SENTRY_DSN` | Pokud je zadáno, pády se reportují do Sentry. |
 
 ## Vývoj
 
diff --git a/contracts/admin.py b/contracts/admin.py
index 1ea01de6d9c1ec44cfc51df2e4e77a15f0719785..22e0dd8bafbf988fa3ed1fa71a8a037306759157 100644
--- a/contracts/admin.py
+++ b/contracts/admin.py
@@ -42,7 +42,7 @@ class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
 def permissions_mixin_factory(
     change_permission: str,
     delete_permission: str,
-    obj_conditional: typing.Callable = lambda request, obj: True
+    obj_conditional: typing.Callable = lambda request, obj: True,
 ) -> object:
     class Mixin:
         def has_change_permission(self, request, obj=None) -> bool:
@@ -95,7 +95,8 @@ ParentContractApprovedPermissionsMixin = permissions_mixin_factory(
 ParentContractOwnPermissionsMixin = permissions_mixin_factory(
     "contracts.edit_others",
     "contracts.delete_others",
-    obj_conditional=lambda request, obj: get_obj_contract(obj).created_by != request.user,
+    obj_conditional=lambda request, obj: get_obj_contract(obj).created_by
+    != request.user,
 )
 
 
@@ -500,7 +501,8 @@ class SigneeSignatureRepresentativeAdmin(
     permissions_mixin_factory(
         "contracts.edit_others",
         "contracts.delete_others",
-        obj_conditional=lambda request, obj: get_obj_contractee_contract(obj).created_by != request.user,
+        obj_conditional=lambda request, obj: get_obj_contractee_contract(obj).created_by
+        != request.user,
     ),
 ):
     pass
@@ -511,12 +513,15 @@ class ContracteeSignatureRepresentativeAdmin(
     permissions_mixin_factory(
         "contracts.edit_when_approved",
         "contracts.delete_when_approved",
-        obj_conditional=lambda request, obj: get_obj_contractee_contract(obj).is_approved,
+        obj_conditional=lambda request, obj: get_obj_contractee_contract(
+            obj
+        ).is_approved,
     ),
     permissions_mixin_factory(
         "contracts.edit_others",
         "contracts.delete_others",
-        obj_conditional=lambda request, obj: get_obj_contractee_contract(obj).created_by != request.user,
+        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 d2fafd01efce80a7967a22edb1984e9eb8b7b81a..312f62523f4e3f7efd5d8020d72702f44fe87081 100644
--- a/contracts/management/commands/import_old_contracts.py
+++ b/contracts/management/commands/import_old_contracts.py
@@ -1,8 +1,8 @@
 import io
 import os
 import re
-import string
 import shutil
+import string
 from datetime import date, datetime
 
 import yaml
@@ -588,7 +588,9 @@ class Command(BaseCommand):
                                 issue_count += 1
                                 contract.notes += f"Špatně zadaná funkce zástupce smluvní strany: {signing_party['funkce']}\n"
                                 issues.append(
-                                    self.use_issue("Špatně zadaná funkce zástupce smluvní strany")
+                                    self.use_issue(
+                                        "Špatně zadaná funkce zástupce smluvní strany"
+                                    )
                                 )
 
                                 if self.verbosity >= 2:
@@ -608,7 +610,9 @@ class Command(BaseCommand):
                                 issue_count += 1
                                 contract.notes += f"Špatně zadaný jeden ze zástupců smluvní strany: {representative_name}\n"
                                 issues.append(
-                                    self.use_issue("Špatně zadaný zástupce smluvní strany")
+                                    self.use_issue(
+                                        "Špatně zadaný zástupce smluvní strany"
+                                    )
                                 )
 
                                 if self.verbosity >= 2:
@@ -779,9 +783,7 @@ 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("Špatně zadaný začátek platnosti")
-                        )
+                        issues.append(self.use_issue("Špatně zadaný začátek platnosti"))
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -798,9 +800,7 @@ class Command(BaseCommand):
                     ):
                         observed_issues_count += 1
                         contract.notes += f"Špatně zadaný konec platnosti: {value}\n"
-                        issues.append(
-                            self.use_issue("Špatně zadaný konec platnosti")
-                        )
+                        issues.append(self.use_issue("Špatně zadaný konec platnosti"))
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -914,9 +914,7 @@ class Command(BaseCommand):
                             contract.notes += (
                                 f"Původní, špatně zadané náklady: {value}\n"
                             )
-                            issues.append(
-                                self.use_issue("Špatně zadané náklady")
-                            )
+                            issues.append(self.use_issue("Špatně zadané náklady"))
 
                             if self.verbosity >= 2:
                                 self.stderr.write(
@@ -944,9 +942,7 @@ class Command(BaseCommand):
                         ):
                             observed_issues_count += 1
                             contract.notes += f"Původní, neropoznané náklady: {value}\n"
-                            issues.append(
-                                self.use_issue("Špatně zadané náklady")
-                            )
+                            issues.append(self.use_issue("Špatně zadané náklady"))
 
                             if self.verbosity >= 2:
                                 self.stderr.write(
@@ -987,9 +983,7 @@ class Command(BaseCommand):
                         else:
                             observed_issues_count += 1
                             contract.notes += f"Špatně zadaná spisovna: {value}\n"
-                            issues.append(
-                                self.use_issue("Špatně zadaná spisovna")
-                            )
+                            issues.append(self.use_issue("Špatně zadaná spisovna"))
 
                             if self.verbosity >= 2:
                                 self.stderr.write(
@@ -1003,9 +997,7 @@ class Command(BaseCommand):
                         contract.notes += (
                             f"Špatně zadané smluvní strany, nejsou seznam: {value}\n"
                         )
-                        issues.append(
-                            self.use_issue("Špatně zadaný smluvní strany")
-                        )
+                        issues.append(self.use_issue("Špatně zadaný smluvní strany"))
 
                         if self.verbosity >= 2:
                             self.stderr.write(
@@ -1124,9 +1116,7 @@ class Command(BaseCommand):
                                 contract.notes += (
                                     f"Neexistující soubor: {file_value}.\n"
                                 )
-                                issues.append(
-                                    self.use_issue("Neexistující soubor")
-                                )
+                                issues.append(self.use_issue("Neexistující soubor"))
 
                                 if self.verbosity >= 2:
                                     self.stderr.write(
diff --git a/contracts/migrations/0059_alter_contract_is_valid.py b/contracts/migrations/0059_alter_contract_is_valid.py
index df29dad1e83e8eb129a54642ba1ab5af5a3fbd6b..3d94f034352cc535066f134e4df2a306470ae868 100644
--- a/contracts/migrations/0059_alter_contract_is_valid.py
+++ b/contracts/migrations/0059_alter_contract_is_valid.py
@@ -4,15 +4,18 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('contracts', '0058_alter_contract_options_alter_contractee_options_and_more'),
+        ("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á'),
+            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/migrations/0060_alter_contractfile_name.py b/contracts/migrations/0060_alter_contractfile_name.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac14b7d0d0ed106ad523edea9eeaab057d361414
--- /dev/null
+++ b/contracts/migrations/0060_alter_contractfile_name.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.1.4 on 2023-05-03 11:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("contracts", "0059_alter_contract_is_valid"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="contractfile",
+            name="name",
+            field=models.CharField(
+                default="Neznámé jméno", max_length=256, verbose_name="Jméno"
+            ),
+            preserve_default=False,
+        ),
+    ]
diff --git a/contracts/migrations/0061_alter_contract_id_number.py b/contracts/migrations/0061_alter_contract_id_number.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae3ac80d17e9fb51fd21ec788eae39343269c8c8
--- /dev/null
+++ b/contracts/migrations/0061_alter_contract_id_number.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.1.4 on 2023-05-23 13:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("contracts", "0060_alter_contractfile_name"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="contract",
+            name="id_number",
+            field=models.CharField(
+                blank=True,
+                help_text="<strong>Není IČO!</strong> Používá se pro identifikaci smluv s velkými organizacemi. Např. <code>MF-8687/2022/15-3</code>.",
+                max_length=256,
+                null=True,
+                verbose_name="Identifikační číslo",
+            ),
+        ),
+    ]
diff --git a/contracts/models.py b/contracts/models.py
index 5e9719e79202e770c039ae56195f6491da9d5ba6..e717261023e8ecbae4ecdf699444289b14bf8782 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -102,7 +102,7 @@ class RepresentativeMixin:
         raise NotImplementedError
 
     def __str__(self) -> str:
-        result = self.name
+        result = str(self.name)
 
         if self.function is not None:
             result += f", {self.function}"
@@ -255,7 +255,7 @@ class Signee(
         return super().clean()
 
     def __str__(self) -> str:
-        result = self.name
+        result = str(self.name)
 
         if self.ico_number is not None:
             result += f" ({self.ico_number})"
@@ -356,7 +356,7 @@ class Contractee(
         return reverse("contracts:view_contractee", args=(self.id,))
 
     def __str__(self) -> str:
-        result = self.name
+        result = str(self.name)
 
         if self.department is not None:
             result += f", {self.department}"
@@ -511,6 +511,10 @@ class Contract(NameStrMixin, models.Model):
         blank=True,
         null=True,
         verbose_name="Identifikační číslo",
+        help_text=mark_safe(
+            "<strong>Není IČO!</strong> Používá se pro identifikaci smluv "
+            "s velkými organizacemi. Např. <code>MF-8687/2022/15-3</code>."
+        ),
     )
 
     types = models.ManyToManyField(
@@ -856,8 +860,6 @@ class ContractFileField(models.FileField):
 class ContractFile(NameStrMixin, models.Model):
     name = models.CharField(
         max_length=256,
-        blank=True,
-        null=True,
         verbose_name="Jméno",
     )
 
diff --git a/registry/settings/base.py b/registry/settings/base.py
index c2f29bd217dd4272ac6a7fdeb5727ef3a17861a3..8755276a36ebacca4e2e513a808b93bda53978f9 100644
--- a/registry/settings/base.py
+++ b/registry/settings/base.py
@@ -15,6 +15,9 @@ import pathlib
 
 import dj_database_url
 import environ
+import sentry_sdk
+
+from sentry_sdk.integrations.django import DjangoIntegration
 
 # Build paths inside the project like this: BASE_DIR / 'subdir'.
 BASE_DIR = pathlib.Path(__file__).parents[2]
@@ -229,6 +232,17 @@ CLAMD_TCP_SOCKET = env.int("CLAMD_TCP_SOCKET")
 CLAMD_TCP_ADDR = env.str("CLAMD_TCP_ADDR")
 
 
+# Sentry
+
+SENTRY_DSN = env.str("SENTRY_DSN", default="")
+
+if SENTRY_DSN != "":
+    sentry_sdk.init(
+        dsn=SENTRY_DSN,
+        integrations=[DjangoIntegration()],
+        send_default_pii=True,
+    )
+
 ## App-specific
 
 DEFAULT_CONTRACTEE_NAME = env.str("DEFAULT_CONTRACTEE_NAME")
diff --git a/registry/templates/admin/index.html b/registry/templates/admin/index.html
index 834866cf238c91b8f6a64496c0e198221679e69c..f96c70270891fdaecba7b90c9f114447e32223e3 100644
--- a/registry/templates/admin/index.html
+++ b/registry/templates/admin/index.html
@@ -34,7 +34,7 @@
 
 {{ block.super }}
 
-<div style="width:100%;float:left;padding-top:0.5rem;margin-top:0.5rem;border-top:1px solid var(--hairline-color)"> 
+<div style="width:100%;float:left;padding-top:0.5rem;margin-top:0.5rem;border-top:1px solid var(--hairline-color)">
     <h2>Tvá oprávnění</h2>
 
     <ul>
diff --git a/requirements/production.txt b/requirements/production.txt
index ce5169e4440b67843ee5d28199ed63e0d8323cfc..efb3dddbc10dc64194f8c49333b06d1378f132b0 100644
--- a/requirements/production.txt
+++ b/requirements/production.txt
@@ -1,2 +1,3 @@
 gunicorn==20.1.0
 whitenoise==6.3.0
+sentry-sdk==1.24.0
diff --git a/shared/models.py b/shared/models.py
index ee10b8b8e3b8e01efdbdf4e251b94b37c35863a2..b9f1d20b03e5db0dc6ab6b01d473b8ae1fb537d6 100644
--- a/shared/models.py
+++ b/shared/models.py
@@ -1,5 +1,7 @@
 class NameStrMixin:
-    name = ""
+    @property
+    def name(self):
+        raise NotImplementedError
 
     def __str__(self) -> str:
-        return self.name
+        return str(self.name)
diff --git a/users/models.py b/users/models.py
index aa19469e93ee0a716fe8f6a06512c945cdced777..83f21bfc393d3c21aa0790ff0854f06b246fa5e4 100644
--- a/users/models.py
+++ b/users/models.py
@@ -90,15 +90,12 @@ class User(pirates_models.AbstractUser):
     def get_all_permissions_ordered(self, obj=None) -> list:
         if not self.is_superuser:
             permissions = (
-                Permission.
-                objects.
-                filter(
-                    models.Q(group__user=self) |
-                    models.Q(user=self)
-                ).
-                order_by("content_type__app_label").
-                distinct().
-                all()
+                Permission.objects.filter(
+                    models.Q(group__user=self) | models.Q(user=self)
+                )
+                .order_by("content_type__app_label")
+                .distinct()
+                .all()
             )
         else:
             permissions = Permission.objects.order_by("content_type__app_label").all()