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"