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,