diff --git a/contracts/admin.py b/contracts/admin.py
index 662e94a3e7cf0ad2147c4f86976d75d6728707ef..553feb8addfdad5d59833a5e04bc4aeb155bf92d 100644
--- a/contracts/admin.py
+++ b/contracts/admin.py
@@ -1,4 +1,5 @@
 import copy
+import typing
 
 from django.contrib import admin
 from django.contrib.auth.models import Permission
@@ -29,54 +30,83 @@ class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
         return False
 
 
-class OwnPermissionsMixin:
-    def has_change_permission(self, request, obj=None) -> bool:
-        if (
-            obj is not None
-            and obj.created_by != request.user
-            and not request.user.has_perm("contracts.edit_others", obj)
-        ):
-            return False
+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,
+) -> 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))
+            ):
+                return False
 
-        return super().has_change_permission(request, obj)
+            return super().has_change_permission(request, obj)
 
-    def has_delete_permission(self, request, obj=None) -> bool:
-        if (
-            obj is not None
-            and obj.created_by != request.user
-            and not request.user.has_perm("contracts.delete_others", obj)
-        ):
-            return False
+        def has_delete_permission(self, request, obj=None) -> bool:
+            if (
+                obj is not None
+                and obj_conditional(request, obj)
+                and not request.user.has_perm(delete_permission, delete_attr_func(obj))
+            ):
+                return False
 
-        return super().has_change_permission(request, obj)
+            return super().has_change_permission(request, obj)
 
+    return Mixin
 
-class ParentContractOwnPermissionsMixin:
-    def has_change_permission(self, request, obj=None) -> bool:
-        if (
-            obj is not None
-            and obj.contract.created_by != request.user
-            and not request.user.has_perm("contracts.edit_others", obj.contract)
-        ):
-            return False
 
-        return super().has_change_permission(request, obj)
+get_obj_contract = lambda obj: obj.contract
 
-    def has_delete_permission(self, request, obj=None) -> bool:
-        if (
-            obj is not None
-            and obj.contract.created_by != request.user
-            and not request.user.has_perm("contracts.delete_others", obj.contract)
-        ):
-            return False
 
-        return super().has_change_permission(request, obj)
+OwnPermissionsMixin = permissions_mixin_factory(
+    "contracts.edit_others",
+    "contracts.delete_others",
+    lambda request, obj: obj.created_by != request.user,
+)
+
+
+def own_permissions_mixin_save_model(self, request, obj, form, change):
+    if obj.created_by is None:
+        obj.created_by = request.user
+
+    return super().save_model(request, obj, form, change)
+
+
+OwnPermissionsMixin.save_model = own_permissions_mixin_save_model
+
+
+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,
+)
+
+
+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,
+)
 
 
 # BEGIN Contracts
 
 
-class ContractFileAdmin(IndexHiddenModelAdmin):
+class ContractFileAdmin(
+    IndexHiddenModelAdmin,
+    ParentContractApprovedPermissionsMixin,
+    ParentContractOwnPermissionsMixin,
+):
     form = ContractFileAdminForm
 
 
@@ -117,9 +147,14 @@ class ContractIntentInline(NestedTabularInline):
 
 class ContractAdmin(
     OwnPermissionsMixin,
+    permissions_mixin_factory(
+        "contracts.edit_when_approved",
+        "contracts.delete_when_approved",
+        lambda request, obj: obj.is_approved,
+    ),
     MarkdownxGuardedModelAdmin,
     FieldsetsInlineMixin,
-    NestedModelAdmin
+    NestedModelAdmin,
 ):
     form = ContractAdminForm
 
@@ -208,7 +243,7 @@ class ContractAdmin(
         if (
             obj is None  # Creating confidential data, creator will be request.user
             or obj.created_by == request.user
-            or request.user.has_perm("view_confidential", self)
+            or request.user.has_perm("view_confidential", obj)
         ):
             fieldsets_with_inlines[0][1]["fields"].insert(
                 fieldsets_with_inlines[0][1]["fields"].index("is_public") + 1,
@@ -237,10 +272,7 @@ class ContractAdmin(
 
         return queryset
 
-    def save_model(self, request, obj, form, change) -> None:
-        if obj.created_by is None:
-            obj.created_by = request.user
-
+    def save_model(self, request, obj, form, change):
         if obj.valid_start_date is None:
             last_signature_date = None
 
@@ -257,13 +289,13 @@ class ContractAdmin(
 
             obj.valid_start_date = last_signature_date
 
-        super().save_model(request, obj, form, change)
+        return super().save_model(request, obj, form, change)
 
     def has_change_permission(self, request, obj=None):
         if (
             obj is not None
             and obj.is_approved
-            and not request.user.has_perm("contracts.edit_when_approved", self)
+            and not request.user.has_perm("contracts.edit_when_approved", obj)
         ):
             return False
 
@@ -273,7 +305,7 @@ class ContractAdmin(
         if (
             obj is not None
             and obj.is_approved
-            and not request.user.has_perm("contracts.delete_when_approved", self)
+            and not request.user.has_perm("contracts.delete_when_approved", obj)
         ):
             return False
 
@@ -326,6 +358,7 @@ class ContractFilingAreaAdmin(MarkdownxGuardedModelAdmin):
 
 class ContracteeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
     model = Contractee
+
     search_fields = (
         "name",
         "department",
@@ -395,19 +428,72 @@ 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
+
+
+class SigneeSignatureRepresentativeAdmin(
+    IndexHiddenModelAdmin,
+    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,
+    ),
+     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,
+    ),
+):
+    pass
+
+
+class ContracteeSignatureRepresentativeAdmin(
+    IndexHiddenModelAdmin,
+    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,
+    ),
+     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,
+    ),
+):
+    pass
+
+
 # END Signing parties
 
 
+class ContractSubmodelAdmin(
+    IndexHiddenModelAdmin,
+    ParentContractApprovedPermissionsMixin,
+    ParentContractOwnPermissionsMixin,
+):
+    pass
+
+
 admin.site.register(Permission)
 
 for model in (
     SigneeSignature,
     ContracteeSignature,
-    ContracteeSignatureRepresentative,
-    SigneeSignatureRepresentative,
     ContractIntent,
 ):
-    admin.site.register(model, IndexHiddenModelAdmin)
+    admin.site.register(model, ContractSubmodelAdmin)
+
+admin.site.register(SigneeSignatureRepresentative, SigneeSignatureRepresentativeAdmin)
+admin.site.register(ContracteeSignatureRepresentative, ContracteeSignatureRepresentativeAdmin)
 
 admin.site.register(ContractType, ContractTypeAdmin)
 admin.site.register(ContractIssue, ContractIssueAdmin)
diff --git a/contracts/migrations/0018_contractee_created_by_signee_created_by.py b/contracts/migrations/0018_contractee_created_by_signee_created_by.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d64aea99428bb2a404c3476f0e824cde421013d
--- /dev/null
+++ b/contracts/migrations/0018_contractee_created_by_signee_created_by.py
@@ -0,0 +1,26 @@
+# Generated by Django 4.1.4 on 2023-03-26 18:45
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('contracts', '0017_alter_contract_options'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='contractee',
+            name='created_by',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Vytvořeno uživatelem'),
+        ),
+        migrations.AddField(
+            model_name='signee',
+            name='created_by',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Vytvořeno uživatelem'),
+        ),
+    ]
diff --git a/contracts/models.py b/contracts/models.py
index b031e146996e866e76734da8e1566f22e618e723..d24b703aa15f6fdb7ecb2ced7d6ee63ceda72245 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -47,6 +47,20 @@ class SignatureCountMixin(models.Model):
         abstract = True
 
 
+class CreatedByMixin(models.Model):
+    created_by = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.SET_NULL,
+        blank=True,
+        null=True,
+        related_name="+",
+        verbose_name="Vytvořeno uživatelem",
+    )  # WARNING: exclude in admin
+
+    class Meta:
+        abstract = True
+
+
 class RepresentativeMixin:
     @property
     def name(self):
@@ -65,7 +79,7 @@ class RepresentativeMixin:
         return result
 
 
-class Signee(OwnPermissionsMixin, SignatureCountMixin, models.Model):
+class Signee(CreatedByMixin, OwnPermissionsMixin, SignatureCountMixin, models.Model):
     name = models.CharField(
         max_length=256,
         verbose_name="Jméno",
@@ -170,7 +184,7 @@ class Signee(OwnPermissionsMixin, SignatureCountMixin, models.Model):
         permissions = OwnPermissionsMixin.Meta.permissions
 
 
-class Contractee(OwnPermissionsMixin, SignatureCountMixin, models.Model):
+class Contractee(CreatedByMixin, OwnPermissionsMixin, SignatureCountMixin, models.Model):
     name = models.CharField(
         max_length=256,
         default=settings.DEFAULT_CONTRACTEE_NAME,