Skip to content
Snippets Groups Projects
admin.py 13.94 KiB
import copy
import typing

from django.contrib import admin
from django.contrib.auth.models import Permission
from django.utils.html import format_html
from import_export import resources
from nested_admin import NestedModelAdmin, NestedStackedInline, NestedTabularInline
from rangefilter.filters import DateRangeFilter

from shared.admin import FieldsetInlineOrder, MarkdownxGuardedModelAdmin

from .forms import ContractAdminForm, ContractFileAdminForm, SigneeAdminForm
from .models import (
    Contract,
    Contractee,
    ContracteeSignature,
    ContracteeSignatureRepresentative,
    ContractFile,
    ContractFilingArea,
    ContractIntent,
    ContractIssue,
    ContractType,
    Signee,
    SigneeSignature,
    SigneeSignatureRepresentative,
)


class ContractResource(resources.ModelResource):
    class Meta:
        model = Contract


class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
    def has_module_permission(self, request):
        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)

        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 Mixin


get_obj_contract = lambda obj: obj.contract


class 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)


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,
    ParentContractApprovedPermissionsMixin,
    ParentContractOwnPermissionsMixin,
):
    form = ContractFileAdminForm


class ContracteeSignatureRepresentativeInline(NestedStackedInline):
    model = ContracteeSignatureRepresentative
    extra = 0


class ContracteeSignatureInline(NestedStackedInline):
    model = ContracteeSignature
    autocomplete_fields = ("contractee",)
    inlines = (ContracteeSignatureRepresentativeInline,)
    extra = 0


class SigneeSignatureRepresentativeInline(NestedStackedInline):
    model = SigneeSignatureRepresentative
    extra = 0


class SigneeSignatureInline(NestedStackedInline):
    model = SigneeSignature
    autocomplete_fields = ("signee",)
    inlines = (SigneeSignatureRepresentativeInline,)
    extra = 0


class ContractFileInline(NestedTabularInline):
    model = ContractFile
    form = ContractFileAdminForm
    extra = 0


class ContractIntentInline(NestedTabularInline):
    model = ContractIntent
    extra = 0


class ContractAdmin(
    OwnPermissionsMixin,
    permissions_mixin_factory(
        "contracts.edit_when_approved",
        "contracts.delete_when_approved",
        lambda request, obj: obj.is_approved,
    ),
    MarkdownxGuardedModelAdmin,
    NestedModelAdmin,
):
    form = ContractAdminForm

    readonly_fields = ("created_by",)
    autocomplete_fields = (
        "types",
        "filing_area",
        "issues",
    )

    inlines = (
        ContractFileInline,
        ContracteeSignatureInline,
        SigneeSignatureInline,
        ContractIntentInline,
    )

    def get_form(self, request, *args, **kwargs) -> ContractAdminForm:
        form = super().get_form(request, *args, **kwargs)
        form.current_user = request.user

        return form

    def get_fieldsets(self, request, obj=None) -> list:
        fieldsets = [
            (
                "Základní informace",
                {
                    "fields": [
                        "name",
                        "id_number",
                        "types",
                        "summary",
                        "is_public",
                        "legal_state",
                        "primary_contract",
                    ]
                },
            ),
            (
                "Data",
                {
                    "fields": [
                        "valid_start_date",
                        "valid_end_date",
                    ]
                },
            ),
            (
                "Náklady",
                {
                    "fields": [
                        "cost_amount",
                        "cost_unit",
                    ]
                },
            ),
            (
                "Odkazy",
                {
                    "fields": [
                        "tender_url",
                        "agreement_url",
                    ]
                },
            ),
            (
                "Fyzický dokument",
                {
                    "fields": [
                        "paper_form_state",
                        "filing_area",
                    ]
                },
            ),
            (
                "Doplňující informace",
                {
                    "fields": [
                        "issues",
                        "notes",
                    ]
                },
            ),
        ]

        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", obj)
        ):
            fieldsets[0][1]["fields"].insert(
                fieldsets[0][1]["fields"].index("is_public") + 1,
                "publishing_rejection_comment",
            )

        if request.user.has_perm("approve", self):
            fieldsets.insert(
                5,
                ("Schválení", {"fields": ["is_approved"]}),
            )

        return fieldsets

    def get_fieldsets_and_inlines_order(self, context) -> list:
        order = [
            FieldsetInlineOrder.FIELDSET,
            FieldsetInlineOrder.FIELDSET,
            FieldsetInlineOrder.FIELDSET,
            FieldsetInlineOrder.INLINE,
            FieldsetInlineOrder.INLINE,
            FieldsetInlineOrder.INLINE,
            FieldsetInlineOrder.FIELDSET,
            FieldsetInlineOrder.INLINE,
            FieldsetInlineOrder.FIELDSET,
            FieldsetInlineOrder.FIELDSET,
        ]

        if context["user"].has_perm("approve", self):
            order.insert(10, FieldsetInlineOrder.FIELDSET)

        return order

    def get_queryset(self, request):
        queryset = super().get_queryset(request)

        if not request.user.has_perm("contracts.view_confidential"):
            queryset = queryset.filter(is_public=True)

        if not request.user.has_perm("contracts.approve"):
            queryset = queryset.filter(is_approved=True)

        return queryset

    def save_model(self, request, obj, form, change):
        if obj.valid_start_date is None:
            last_signature_date = None

            for signature_set in (
                obj.contractee_signatures.all(),
                obj.signee_signatures.all(),
            ):
                for signature in signature_set:
                    if (
                        last_signature_date is None
                        or last_signature_date < signature.date
                    ):
                        last_signature_date = signature.date

            obj.valid_start_date = last_signature_date

        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", obj)
        ):
            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", obj)
        ):
            return False

        return super().has_change_permission(request, obj)

    list_filter = (
        "types",
        "is_approved",
        "legal_state",
        "is_public",
        "paper_form_state",
        "issues",
        ("all_parties_sign_date", DateRangeFilter),
        ("valid_start_date", DateRangeFilter),
        ("valid_end_date", DateRangeFilter),
    )

    list_display = (
        "name",
        "is_approved",
        "is_public",
    )


class ContractTypeAdmin(MarkdownxGuardedModelAdmin):
    model = ContractType
    ordering = ("name",)
    search_fields = ("name",)


class ContractIssueAdmin(MarkdownxGuardedModelAdmin):
    model = ContractIssue
    ordering = ("name",)
    search_fields = ("name",)


class ContractFilingAreaAdmin(MarkdownxGuardedModelAdmin):
    model = ContractFilingArea
    ordering = ("name",)
    search_fields = (
        "name",
        "person_responsible",
    )


# END Contracts

# BEGIN Signing parties


class ContracteeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
    model = Contractee

    search_fields = (
        "name",
        "department",
        "role",
    )
    ordering = ("name",)


class SigneeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
    model = Signee

    search_fields = (
        "name",
        "department",
        "role",
    )
    ordering = ("name",)

    form = SigneeAdminForm

    readonly_fields = ("load_ares_data_button",)

    list_filter = ("entity_type",)
    list_display = ("name", "entity_type")

    def get_fields(self, request, obj=None):
        fields = [
            "name",
            "entity_type",
            "ico_number",
            "department",
            "role",
        ]

        if (
            obj is None  # Creating
            or obj.entity_has_public_address
            or request.user.has_perm("contracts.view_confidential", obj)
        ):
            entity_type_index = fields.index("entity_type") + 1

            fields[entity_type_index:entity_type_index] = [
                "address_street_with_number",
                "address_district",
                "address_zip",
                "address_country",
            ]

            fields.insert(
                fields.index("department"),
                "date_of_birth",
            )

        if obj is None or request.user.has_perm(  # Allowed to create
            "contracts.edit_signee", obj
        ):
            fields.insert(fields.index("ico_number"), "load_ares_data_button")

        return fields

    def load_ares_data_button(self, obj):
        return format_html(
            '<button type="button" id="load_ares_data">Načíst data</button>'
        )

    load_ares_data_button.allow_tags = True
    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,
    ContractIntent,
):
    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)
admin.site.register(ContractFile, ContractFileAdmin)
admin.site.register(ContractFilingArea, ContractFilingAreaAdmin)

admin.site.register(Contractee, ContracteeAdmin)
admin.site.register(Signee, SigneeAdmin)

admin.site.register(Contract, ContractAdmin)