diff --git a/contracts/admin.py b/contracts/admin.py
index c1856b9ca8159289cd8ffc16564f99c1b64ce721..662e94a3e7cf0ad2147c4f86976d75d6728707ef 100644
--- a/contracts/admin.py
+++ b/contracts/admin.py
@@ -5,26 +5,18 @@ from django.contrib.auth.models import Permission
 from django.utils.html import format_html
 from fieldsets_with_inlines import FieldsetsInlineMixin
 from import_export import resources
-from nested_admin import NestedModelAdmin, NestedStackedInline, NestedTabularInline
+from nested_admin import (NestedModelAdmin, NestedStackedInline,
+                          NestedTabularInline)
 from rangefilter.filters import DateRangeFilter
 
 from shared.admin import MarkdownxGuardedModelAdmin
 
 from .forms import ContractAdminForm, ContractFileAdminForm, SigneeAdminForm
-from .models import (
-    Contract,
-    Contractee,
-    ContracteeSignature,
-    ContracteeSignatureRepresentative,
-    ContractFile,
-    ContractFilingArea,
-    ContractIntent,
-    ContractIssue,
-    ContractType,
-    Signee,
-    SigneeSignature,
-    SigneeSignatureRepresentative,
-)
+from .models import (Contract, Contractee, ContracteeSignature,
+                     ContracteeSignatureRepresentative, ContractFile,
+                     ContractFilingArea, ContractIntent, ContractIssue,
+                     ContractType, Signee, SigneeSignature,
+                     SigneeSignatureRepresentative)
 
 
 class ContractResource(resources.ModelResource):
@@ -37,6 +29,50 @@ 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
+
+        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
+
+        return super().has_change_permission(request, obj)
+
+
+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)
+
+    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)
+
+
 # BEGIN Contracts
 
 
@@ -79,7 +115,12 @@ class ContractIntentInline(NestedTabularInline):
     extra = 0
 
 
-class ContractAdmin(MarkdownxGuardedModelAdmin, FieldsetsInlineMixin, NestedModelAdmin):
+class ContractAdmin(
+    OwnPermissionsMixin,
+    MarkdownxGuardedModelAdmin,
+    FieldsetsInlineMixin,
+    NestedModelAdmin
+):
     form = ContractAdminForm
 
     readonly_fields = ("created_by",)
@@ -99,7 +140,6 @@ class ContractAdmin(MarkdownxGuardedModelAdmin, FieldsetsInlineMixin, NestedMode
                     "types",
                     "summary",
                     "is_public",
-                    "publishing_rejection_comment",
                     "legal_state",
                     "primary_contract",
                 ]
@@ -165,6 +205,16 @@ class ContractAdmin(MarkdownxGuardedModelAdmin, FieldsetsInlineMixin, NestedMode
     def get_fieldsets(self, request, obj=None):
         fieldsets_with_inlines = copy.deepcopy(self.fieldsets_with_inlines)
 
+        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)
+        ):
+            fieldsets_with_inlines[0][1]["fields"].insert(
+                fieldsets_with_inlines[0][1]["fields"].index("is_public") + 1,
+                "publishing_rejection_comment",
+            )
+
         if request.user.is_superuser or request.user.has_perm("approve", self):
             fieldsets_with_inlines.insert(
                 8,
@@ -209,6 +259,26 @@ class ContractAdmin(MarkdownxGuardedModelAdmin, FieldsetsInlineMixin, NestedMode
 
         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)
+        ):
+            return False
+
+        return super().has_change_permission(request, obj)
+
+    def has_delete_permission(self, request, obj=None):
+        if (
+            obj is not None
+            and obj.is_approved
+            and not request.user.has_perm("contracts.delete_when_approved", self)
+        ):
+            return False
+
+        return super().has_change_permission(request, obj)
+
     list_filter = (
         "types",
         "is_approved",
@@ -254,7 +324,7 @@ class ContractFilingAreaAdmin(MarkdownxGuardedModelAdmin):
 # BEGIN Signing parties
 
 
-class ContracteeAdmin(MarkdownxGuardedModelAdmin):
+class ContracteeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
     model = Contractee
     search_fields = (
         "name",
@@ -264,7 +334,7 @@ class ContracteeAdmin(MarkdownxGuardedModelAdmin):
     ordering = ("name",)
 
 
-class SigneeAdmin(MarkdownxGuardedModelAdmin):
+class SigneeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
     model = Signee
 
     search_fields = (
@@ -295,7 +365,7 @@ class SigneeAdmin(MarkdownxGuardedModelAdmin):
             or obj.entity_has_public_address
             or request.user.has_perm("contracts.view_confidential", obj)
         ):
-            entity_type_index = fields.index("entity_type")
+            entity_type_index = fields.index("entity_type") + 1
 
             fields[entity_type_index:entity_type_index] = [
                 "address_street_with_number",
@@ -305,7 +375,7 @@ class SigneeAdmin(MarkdownxGuardedModelAdmin):
             ]
 
             fields.insert(
-                fields.index("department") - 1,
+                fields.index("department"),
                 "date_of_birth",
             )
 
diff --git a/contracts/migrations/0015_alter_contract_options.py b/contracts/migrations/0015_alter_contract_options.py
new file mode 100644
index 0000000000000000000000000000000000000000..6206469659d56c0b1be2fc3a13b3dbd3a91e921d
--- /dev/null
+++ b/contracts/migrations/0015_alter_contract_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.1.4 on 2023-03-24 20:29
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0014_alter_contracteesignaturerepresentative_options_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='contract',
+            options={'permissions': [('approve', 'Schválit / zrušit schválení'), ('view_confidential', 'Zobrazit tajné informace'), ('edit_others', 'Upravit cizí'), ('delete_others', 'Odstranit cizí')], 'verbose_name': 'Smlouva', 'verbose_name_plural': 'Smlouvy'},
+        ),
+    ]
diff --git a/contracts/migrations/0016_alter_contractee_options_alter_signee_options.py b/contracts/migrations/0016_alter_contractee_options_alter_signee_options.py
new file mode 100644
index 0000000000000000000000000000000000000000..702e85021fe7812ebf07c92bbc19767c7823bc62
--- /dev/null
+++ b/contracts/migrations/0016_alter_contractee_options_alter_signee_options.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.1.4 on 2023-03-24 20:47
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0015_alter_contract_options'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='contractee',
+            options={'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='signee',
+            options={'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/0017_alter_contract_options.py b/contracts/migrations/0017_alter_contract_options.py
new file mode 100644
index 0000000000000000000000000000000000000000..fce8fef88284af1611312774092f2d6d057ae09b
--- /dev/null
+++ b/contracts/migrations/0017_alter_contract_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.1.4 on 2023-03-24 21:13
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0016_alter_contractee_options_alter_signee_options'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='contract',
+            options={'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í')], 'verbose_name': 'Smlouva', 'verbose_name_plural': 'Smlouvy'},
+        ),
+    ]
diff --git a/contracts/models.py b/contracts/models.py
index 33d8c9fb46d1c53b9bbe95db94c8f0d38ccdc358..b031e146996e866e76734da8e1566f22e618e723 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -11,6 +11,16 @@ from shared.models import NameStrMixin
 from users.models import User
 
 
+class OwnPermissionsMixin(models.Model):
+    class Meta:
+        abstract = True
+
+        permissions = [
+            ("edit_others", "Upravit cizí"),
+            ("delete_others", "Odstranit cizí"),
+        ]
+
+
 class ContractCountMixin(models.Model):
     def get_contract_count(self, user) -> None:
         filter = {"is_approved": True}
@@ -55,7 +65,7 @@ class RepresentativeMixin:
         return result
 
 
-class Signee(SignatureCountMixin, models.Model):
+class Signee(OwnPermissionsMixin, SignatureCountMixin, models.Model):
     name = models.CharField(
         max_length=256,
         verbose_name="Jméno",
@@ -157,8 +167,10 @@ class Signee(SignatureCountMixin, models.Model):
         verbose_name = "Jiná smluvní strana"
         verbose_name_plural = "Ostatní smluvní strany"
 
+        permissions = OwnPermissionsMixin.Meta.permissions
+
 
-class Contractee(SignatureCountMixin, models.Model):
+class Contractee(OwnPermissionsMixin, SignatureCountMixin, models.Model):
     name = models.CharField(
         max_length=256,
         default=settings.DEFAULT_CONTRACTEE_NAME,
@@ -229,6 +241,8 @@ class Contractee(SignatureCountMixin, models.Model):
         verbose_name = "Naše smluvní strana"
         verbose_name_plural = "Naše smluvní strany"
 
+        permissions = OwnPermissionsMixin.Meta.permissions
+
 
 class ContractType(ContractCountMixin, NameStrMixin, models.Model):
     name = models.CharField(
@@ -537,10 +551,12 @@ class Contract(NameStrMixin, models.Model):
         verbose_name = "Smlouva"
         verbose_name_plural = "Smlouvy"
 
-        permissions = (
+        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é"),
+        ] + OwnPermissionsMixin.Meta.permissions
 
 
 class ContractFile(NameStrMixin, models.Model):
diff --git a/static_src/admin/contract_form.js b/static_src/admin/contract_form.js
index 2750b17e967ce53baa28301999ff7e39656d1ca0..c55f6cf61030a10f070c4f9fef50fed56905053e 100644
--- a/static_src/admin/contract_form.js
+++ b/static_src/admin/contract_form.js
@@ -12,19 +12,19 @@ $(window).ready(
         css(
             "display",
             (
-                ($("#id_public_state").find(":selected").val() === "no") ?
+                (!$("#id_is_public").is(":checked")) ?
                 "block": "none"
             )
         );
 
-        $("#id_public_state").on(
+        $("#id_is_public").on(
             "change",
             event => {
                 $(".field-publishing_rejection_comment").
                 css(
                     "display",
                     (
-                        ($(event.target).val() === "no") ?
+                        (!$(event.target).is(":checked")) ?
                         "block" : "none"
                     )
                 );
diff --git a/users/models.py b/users/models.py
index d12e77b468e5029b6a7b2feaa1fc4be2ac2921bb..b5f19b07a4f43664a9652c8ac2cdb5ed14919767 100644
--- a/users/models.py
+++ b/users/models.py
@@ -1,15 +1,27 @@
 from django.conf import settings
-from django.contrib.auth.models import Group
+from django.contrib.auth.models import Group as AuthGroup
 from django.db import models
 from pirates import models as pirates_models
 
 
+class Group:
+    def save(self, *args, **kwargs):
+        for user in self.user_set.all():
+            if user.update_group_based_admin():
+                user.save()
+
+        return super().save(*args, **kwargs)
+
+    class Meta:
+        proxy = True
+
+
 class User(pirates_models.AbstractUser):
     is_staff_based_on_group = models.BooleanField(
         default=True,
         verbose_name="Administrační přístup dle členství ve skupině",
         help_text=(
-            'Určuje, zda bude "Administrační přístup" uživatele '
+            "Určuje, zda bude \"Administrační přístup\" uživatele "
             "definován dle členství ve skupinách, nebo podle "
             "speciálního nastavení zde."
         ),
@@ -84,12 +96,27 @@ class User(pirates_models.AbstractUser):
 
         return super().save(*args, **kwargs)
 
-    def update_group_based_admin(self) -> None:
+    def update_group_based_admin(self) -> bool:
+        """
+        Updates this user's `is_staff` attribute based on whether or not
+        one of their groups has default access to the admin interface.
+
+        Returns whether or not any changes have been made.
+        """
+
         if not self.is_staff_based_on_group:
-            return
+            return False
 
         self.is_staff_based_on_group = True
-        self.is_staff = self.groups.filter(name=settings.DEFAULT_STAFF_GROUP).exists()
+
+        is_staff = self.groups.filter(name=settings.DEFAULT_STAFF_GROUP).exists()
+        changes_made = False
+
+        if is_staff is not self.is_staff:
+            changes_made = True
+            self.is_staff = is_staff
+
+        return changes_made
 
     class Meta:
         app_label = "users"