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"