diff --git a/contracts/models.py b/contracts/models.py
index 9fe9d08fc1a3e8b33bf0af53e02b0c59428b7de9..e2589a9522fbf0bef8fc414d33457e30057237c9 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -11,6 +11,32 @@ from shared.models import NameStrMixin
 from users.models import User
 
 
+class ContractCountMixin(models.Model):
+    def get_contract_count(self, user) -> None:
+        filter = {"is_approved": True}
+
+        if not user.has_perm("contract.view_confidential"):
+            filter["is_public"] = True
+
+        return self.contracts.filter(**filter).count()
+
+    class Meta:
+        abstract = True
+
+
+class SignatureCountMixin(models.Model):
+    def get_signature_count(self, user) -> None:
+        filter = {"contract__is_approved": True}
+
+        if not user.has_perm("contract.view_confidential"):
+            filter["contract__is_approved"] = True
+
+        return self.signatures.filter(**filter).count()
+
+    class Meta:
+        abstract = True
+
+
 class RepresentativeMixin:
     @property
     def name(self):
@@ -29,7 +55,7 @@ class RepresentativeMixin:
         return result
 
 
-class Signee(models.Model):
+class Signee(SignatureCountMixin, models.Model):
     name = models.CharField(
         max_length=256,
         verbose_name="Jméno",
@@ -100,12 +126,6 @@ class Signee(models.Model):
         verbose_name="Role",
     )
 
-    class Meta:
-        app_label = "contracts"
-
-        verbose_name = "Jiná smluvní strana"
-        verbose_name_plural = "Ostatní smluvní strany"
-
     @property
     def url(self) -> str:
         return reverse("contracts:view_signee", args=(self.id,))
@@ -131,8 +151,14 @@ class Signee(models.Model):
 
         return result
 
+    class Meta:
+        app_label = "contracts"
+
+        verbose_name = "Jiná smluvní strana"
+        verbose_name_plural = "Ostatní smluvní strany"
 
-class Contractee(models.Model):
+
+class Contractee(SignatureCountMixin, models.Model):
     name = models.CharField(
         max_length=256,
         default=settings.DEFAULT_CONTRACTEE_NAME,
@@ -189,12 +215,6 @@ class Contractee(models.Model):
     def url(self) -> str:
         return reverse("contracts:view_contractee", args=(self.id,))
 
-    class Meta:
-        app_label = "contracts"
-
-        verbose_name = "Naše smluvní strana"
-        verbose_name_plural = "Naše smluvní strany"
-
     def __str__(self) -> str:
         result = self.name
 
@@ -203,8 +223,14 @@ class Contractee(models.Model):
 
         return result
 
+    class Meta:
+        app_label = "contracts"
+
+        verbose_name = "Naše smluvní strana"
+        verbose_name_plural = "Naše smluvní strany"
+
 
-class ContractType(NameStrMixin, models.Model):
+class ContractType(ContractCountMixin, NameStrMixin, models.Model):
     name = models.CharField(
         max_length=32,
         verbose_name="Jméno",
@@ -221,7 +247,7 @@ class ContractType(NameStrMixin, models.Model):
         verbose_name_plural = "Typy smluv"
 
 
-class ContractIssue(NameStrMixin, models.Model):
+class ContractIssue(ContractCountMixin, NameStrMixin, models.Model):
     name = models.CharField(
         max_length=32,
         verbose_name="Jméno",
@@ -238,7 +264,7 @@ class ContractIssue(NameStrMixin, models.Model):
         verbose_name_plural = "Problémy se smlouvami"
 
 
-class ContractFilingArea(NameStrMixin, models.Model):
+class ContractFilingArea(ContractCountMixin, NameStrMixin, models.Model):
     name = models.CharField(
         max_length=32,
         verbose_name="Jméno",
@@ -448,17 +474,6 @@ class Contract(NameStrMixin, models.Model):
         help_text="Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat a pro tajné čtenáře.",
     )
 
-    class Meta:
-        app_label = "contracts"
-
-        verbose_name = "Smlouva"
-        verbose_name_plural = "Smlouvy"
-
-        permissions = (
-            ("approve", "Schválit / zrušit schválení"),
-            ("view_confidential", "Zobrazit tajné informace"),
-        )
-
     @property
     def primary_contract_url(self) -> typing.Union[None, str]:
         if self.primary_contract is None:
@@ -516,6 +531,17 @@ class Contract(NameStrMixin, models.Model):
 
         self.save()
 
+    class Meta:
+        app_label = "contracts"
+
+        verbose_name = "Smlouva"
+        verbose_name_plural = "Smlouvy"
+
+        permissions = (
+            ("approve", "Schválit / zrušit schválení"),
+            ("view_confidential", "Zobrazit tajné informace"),
+        )
+
 
 class ContractFile(NameStrMixin, models.Model):
     name = models.CharField(
@@ -575,15 +601,15 @@ class ContracteeSignature(models.Model):
         verbose_name="Datum podpisu",
     )
 
+    def __str__(self) -> str:
+        return f"{str(self.contractee)} - {self.date}"
+
     class Meta:
         app_label = "contracts"
 
         verbose_name = "Podpis naší smluvní strany"
         verbose_name_plural = "Podpisy našich smluvních stran"
 
-    def __str__(self) -> str:
-        return f"{str(self.contractee)} - {self.date}"
-
 
 class SigneeSignature(models.Model):
     signee = models.ForeignKey(
@@ -604,15 +630,15 @@ class SigneeSignature(models.Model):
         verbose_name="Datum podpisu",
     )
 
+    def __str__(self) -> str:
+        return f"{str(self.signee)} - {self.date}"
+
     class Meta:
         app_label = "contracts"
 
         verbose_name = "Podpis jiné smluvní strany"
         verbose_name_plural = "Podpisy jiných smluvních stran"
 
-    def __str__(self) -> str:
-        return f"{str(self.signee)} - {self.date}"
-
 
 class ContracteeSignatureRepresentative(RepresentativeMixin, models.Model):
     contractee_signature = models.ForeignKey(
diff --git a/contracts/templates/contracts/view_contract_filing_areas.html b/contracts/templates/contracts/view_contract_filing_areas.html
index 4ee14b85a7b4ae02d8c9755964f62448621d6d1d..0bd344f227d710d2b330bf0e7025bebb68e1045d 100644
--- a/contracts/templates/contracts/view_contract_filing_areas.html
+++ b/contracts/templates/contracts/view_contract_filing_areas.html
@@ -1,4 +1,5 @@
 {% extends "shared/includes/base.html" %}
+{% load counters %}
 
 {% block content %}
     <div class="flex gap-4 mb-10">
@@ -30,7 +31,7 @@
                         {{ filing_area.person_responsible }}
                     </td>
                     <td>
-                        {{ filing_area.contracts.count }}
+                        {{ filing_area|contract_count:user }}
                     </td>
                 </tr>
             {% endfor %}
diff --git a/contracts/templates/contracts/view_contract_issues.html b/contracts/templates/contracts/view_contract_issues.html
index bbabe0247b40d4eefa71bca8a464fbb93de9bd42..b323c3309950954859a5072d5903ab1d39ede65b 100644
--- a/contracts/templates/contracts/view_contract_issues.html
+++ b/contracts/templates/contracts/view_contract_issues.html
@@ -1,4 +1,5 @@
 {% extends "shared/includes/base.html" %}
+{% load counters %}
 
 {% block content %}
     <div class="flex gap-4 mb-10">
@@ -38,7 +39,7 @@
                         >{{ issue.name }}</a>
                     </td>
                     <td>
-                        {{ issue.contracts.count }}
+                        {{ issue|contract_count:user }}
                     </td>
                 </tr>
             {% endfor %}
diff --git a/contracts/templates/contracts/view_contract_types.html b/contracts/templates/contracts/view_contract_types.html
index 6a674b6042c4e3e7462a4346134fe03595558217..dd165b342981d9dfc51f99080c1fc3d85011b11d 100644
--- a/contracts/templates/contracts/view_contract_types.html
+++ b/contracts/templates/contracts/view_contract_types.html
@@ -1,4 +1,5 @@
 {% extends "shared/includes/base.html" %}
+{% load counters %}
 
 {% block content %}
     <div class="flex gap-4 mb-10">
@@ -26,7 +27,7 @@
                         >{{ type.name }}</a>
                     </td>
                     <td>
-                        {{ type.contracts.count }}
+                        {{ type|contract_count:user }}
                     </td>
                 </tr>
             {% endfor %}
diff --git a/contracts/templates/contracts/view_contractees.html b/contracts/templates/contracts/view_contractees.html
index 6c33a7b23ad0f9a4ef89e4362fa87389cf593e6a..c8c51cb1c7b62e9d93f62320dc6fdaed6de86b6a 100644
--- a/contracts/templates/contracts/view_contractees.html
+++ b/contracts/templates/contracts/view_contractees.html
@@ -1,4 +1,5 @@
 {% extends "shared/includes/base.html" %}
+{% load counters %}
 
 {% block content %}
     <div class="flex gap-4 mb-10">
@@ -34,7 +35,7 @@
                         </a>
                     </td>
                     <td>
-                        {{ contractee.signatures.count }}
+                        {{ contractee|signature_count:user }}
                     </td>
                 </tr>
             {% endfor %}
diff --git a/contracts/templates/contracts/view_signees.html b/contracts/templates/contracts/view_signees.html
index 758e132abeaac32cb2abc79f93c3803c2d9c6d32..2474256d9f3aca989eb3a49d13d2bd57528be1f7 100644
--- a/contracts/templates/contracts/view_signees.html
+++ b/contracts/templates/contracts/view_signees.html
@@ -1,4 +1,5 @@
 {% extends "shared/includes/base.html" %}
+{% load counters %}
 
 {% block content %}
     <div class="flex gap-4 mb-10">
@@ -34,7 +35,7 @@
                         </a>
                     </td>
                     <td>
-                        {{ signee.signatures.count }}
+                        {{ signee|signature_count:user }}
                     </td>
                 </tr>
             {% endfor %}
diff --git a/contracts/templatetags/counters.py b/contracts/templatetags/counters.py
new file mode 100644
index 0000000000000000000000000000000000000000..2757785bdbc274b7f35514eee6bbd5eb6b2df39b
--- /dev/null
+++ b/contracts/templatetags/counters.py
@@ -0,0 +1,13 @@
+from django import template
+
+register = template.Library()
+
+
+@register.filter
+def contract_count(model, user) -> int:
+    return model.get_contract_count(user)
+
+
+@register.filter
+def signature_count(model, user) -> int:
+    return model.get_signature_count(user)
diff --git a/contracts/templatetags/subtract.py b/contracts/templatetags/subtract.py
index baf78537c48e3ea9161a6739d009d81c831b8637..d39e880d9f290af55686c180f631d933d3252afb 100644
--- a/contracts/templatetags/subtract.py
+++ b/contracts/templatetags/subtract.py
@@ -3,6 +3,6 @@ from django import template
 register = template.Library()
 
 
-@register.filter(name="subtract")
+@register.filter
 def subtract(value, arg):
     return value - arg
diff --git a/oidc/auth.py b/oidc/auth.py
index 78db0561d5e9aa27cf1230f4f386588976ac3d68..07cea60a7ee8595ccd7f4ee2902823da600fde29 100644
--- a/oidc/auth.py
+++ b/oidc/auth.py
@@ -17,7 +17,7 @@ class RegistryOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend):
             user_groups = user.groups.all()
 
         for group in access_token["groups"]:
-            if group.startswith("_"):
+            if group.startswith("_"):  # Ignore internal Keycloak groups
                 continue
 
             group_name = f"sso_{group}"
diff --git a/users/models.py b/users/models.py
index 2d39275f6dd35d51bef50bc48add16205b8f24e0..d12e77b468e5029b6a7b2feaa1fc4be2ac2921bb 100644
--- a/users/models.py
+++ b/users/models.py
@@ -15,17 +15,26 @@ class User(pirates_models.AbstractUser):
         ),
     )
 
-    def set_unusable_password(self) -> None:
-        # Purely for compatibility with Guardian
-        pass
+    @property
+    def can_approve_contracts(self) -> bool:
+        return self.has_perm("contracts.approve")
 
-    def get_username(self) -> str:
-        first_name = self.first_name
+    @property
+    def can_create_contracts(self) -> bool:
+        return self.has_perm("contracts.add")
 
-        if len(first_name) != 0:
-            first_name += " "
+    @property
+    def can_view_confidential(self) -> bool:
+        return self.has_perm("contracts.view_confidential")
 
-        return f"{first_name}{self.last_name}"
+    @property
+    def contracts_to_approve_count(self) -> int:
+        if not self.can_approve_contracts:
+            return 0
+
+        from contracts.models import Contract
+
+        return Contract.objects.filter(is_approved=False).count()
 
     # https://docs.djangoproject.com/en/4.1/ref/models/instances/#customizing-model-loading
     @classmethod
@@ -53,6 +62,18 @@ class User(pirates_models.AbstractUser):
 
         return instance
 
+    def set_unusable_password(self) -> None:
+        # Purely for compatibility with Guardian
+        pass
+
+    def get_username(self) -> str:
+        first_name = self.first_name
+
+        if len(first_name) != 0:
+            first_name += " "
+
+        return f"{first_name}{self.last_name}"
+
     def save(self, *args, saved_by_auth: bool = False, **kwargs):
         if (
             not self._state.adding
@@ -70,27 +91,6 @@ class User(pirates_models.AbstractUser):
         self.is_staff_based_on_group = True
         self.is_staff = self.groups.filter(name=settings.DEFAULT_STAFF_GROUP).exists()
 
-    @property
-    def can_approve_contracts(self) -> bool:
-        return self.has_perm("contracts.approve")
-
-    @property
-    def can_create_contracts(self) -> bool:
-        return self.has_perm("contracts.add")
-
-    @property
-    def can_view_confidential(self) -> bool:
-        return self.has_perm("contracts.view_confidential")
-
-    @property
-    def contracts_to_approve_count(self) -> int:
-        if not self.can_approve_contracts:
-            return 0
-
-        from contracts.models import Contract
-
-        return Contract.objects.filter(is_approved=False).count()
-
     class Meta:
         app_label = "users"
         verbose_name = "Uživatel"