From ee6873d50d417a35cc8a42d62fb526068585cbbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org> Date: Fri, 24 Mar 2023 16:21:19 +0100 Subject: [PATCH] permission based counters --- contracts/models.py | 94 ++++++++++++------- .../contracts/view_contract_filing_areas.html | 3 +- .../contracts/view_contract_issues.html | 3 +- .../contracts/view_contract_types.html | 3 +- .../templates/contracts/view_contractees.html | 3 +- .../templates/contracts/view_signees.html | 3 +- contracts/templatetags/counters.py | 13 +++ contracts/templatetags/subtract.py | 2 +- oidc/auth.py | 2 +- users/models.py | 58 ++++++------ 10 files changed, 114 insertions(+), 70 deletions(-) create mode 100644 contracts/templatetags/counters.py diff --git a/contracts/models.py b/contracts/models.py index 9fe9d08..e2589a9 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 4ee14b8..0bd344f 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 bbabe02..b323c33 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 6a674b6..dd165b3 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 6c33a7b..c8c51cb 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 758e132..2474256 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 0000000..2757785 --- /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 baf7853..d39e880 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 78db056..07cea60 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 2d39275..d12e77b 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" -- GitLab