Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
dj-kaos / models / admin.py
Size: Mime:
from __future__ import annotations

from dj_kaos.utils.admin import EditReadonlyAdminMixin, ExcludeFromFieldsetsMixin
from dj_kaos.utils.forms import unrequire_form
from dj_kaos.utils.perms import get_permission_descriptor
from django import forms
from django.contrib import admin
from django.contrib.admin.options import BaseModelAdmin
from django.shortcuts import get_object_or_404
from django_object_actions import DjangoObjectActions, BaseDjangoObjectActions

from dj_kaos.contrib.autocomplete_list_filter import AutocompleteListFilterAdminMixin


class BaseKaosModelAdmin(BaseModelAdmin):
    """
    Base model admin class used with models that support `model.config` (specifically `KaosModels`) to grab admin
    configuration `model.config`. It is the equivalent of `BaseModelAdmin` and inherits from it.

    Please keep in mind, for various reasons, this admin does not pull `fields` and `fieldsets` from the model config.
    You have to explicitly define these on your admin
    """

    extra_readonly_fields = ()

    def get_readonly_fields(self, request, obj=None):
        if self.readonly_fields != BaseModelAdmin.readonly_fields:
            return super().get_readonly_fields(request, obj)
        return (
            *super().get_readonly_fields(request, obj),
            *self.extra_readonly_fields,
            *self.model.config.get_readonly_fields(request, obj),
        )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        config = self.model.config

        if config.unrequired_fields and self.form is forms.ModelForm:
            self.form = unrequire_form(self.model, config.unrequired_fields)


class SimpleKaosModelAdmin(BaseKaosModelAdmin, admin.ModelAdmin):
    """
    Simple Admin class used with models that support `model.config` (specifically `KaosModels`) to grab admin
    configuration `model.config`.
    """

    def get_search_fields(self, request):
        if self.search_fields != admin.ModelAdmin.search_fields:
            return super().get_search_fields(request)
        return self.model.config.get_search_fields(request)

    def get_list_display(self, request):
        if self.list_display != admin.ModelAdmin.list_display:
            return super().get_list_display(request)
        return self.model.config.get_list_display(request)

    def get_list_filter(self, request):
        if self.list_filter != admin.ModelAdmin.list_filter:
            return super().get_list_filter(request)
        return self.model.config.get_list_filter(request)


def should_include_action(admin, user, obj, action_name):
    admin_method = getattr(admin, action_name)
    if not user.is_superuser and admin_method:
        allowed_permissions = getattr(admin_method, 'allowed_permissions', None)
        if allowed_permissions:
            for perm_action in allowed_permissions:
                if not user.has_perm(get_permission_descriptor(obj._meta, perm_action)):
                    return False

    method = getattr(obj, action_name)
    if method:
        if not user.is_superuser and not method.check_permissions(user, obj):
            return False
        if method.exclude(obj):
            return False
        if not method.include(obj):
            return False
    return True


class ObjectActionsPermissionsMixin(BaseDjangoObjectActions):
    """
    Built on DjangoObjectActions Admin, it checks if the user has change permissions on the object in order to show the
    change actions
    """

    def get_change_actions(self, request, object_id, form_url, obj=None):
        change_actions = super(ObjectActionsPermissionsMixin, self).get_change_actions(request, object_id, form_url)
        obj = obj or self._get_change_action_object(object_id)

        return tuple(action for action in change_actions if should_include_action(self, request.user, obj, action))

    def _get_change_action_object(self, object_id=None):
        if object_id and getattr(self, '__obj', None) is None:
            self.__obj = get_object_or_404(self.model, pk=object_id)
        return self.__obj


class ObjectPermissionsAdminMixin(BaseModelAdmin):
    def has_change_permission(self, request, obj=None):
        opts = self.opts
        return request.user.has_perm(get_permission_descriptor(opts, 'change'), obj)

    def has_delete_permission(self, request, obj=None):
        opts = self.opts
        return request.user.has_perm(get_permission_descriptor(opts, 'delete'), obj)

    def has_view_permission(self, request, obj=None):
        opts = self.opts
        return request.user.has_perm(get_permission_descriptor(opts, 'view'), obj) or self.has_change_permission(
            request, obj)


class KaosModelAdmin(
    EditReadonlyAdminMixin,
    AutocompleteListFilterAdminMixin,
    ObjectActionsPermissionsMixin,
    DjangoObjectActions,
    ExcludeFromFieldsetsMixin,
    ObjectPermissionsAdminMixin,
    SimpleKaosModelAdmin
):
    """
    Admin class used with models that support `model.config` (specifically `KaosModels`) to grab admin
    configuration `model.config`.

    Please keep in mind, for various reasons, this admin does not pull `fields` and `fieldsets` from the model config.
    You have to explicitly define these on your admin

    Adds a few mixins over `SimpleKaosModelAdmin` that are used broadly and can be all provided through this class.

    Example:
        >>> # Make sure `MyModel` has `.config = MyModelConfig()`
        >>> @admin.register(MyModel)
        >>> class MyModelAdmin(KaosModelAdmin):
        >>>     # You don't need to define any fields besides fieldsets.
        >>>     fieldsets = MyModel.config.fieldsets
        >>>     search_fields = MyModel.config.search_fields  #  Sometimes you might need to define a field
        >>>     # explicitly so django doesn't complain. For example search_fields needs to be explicitly set if you are
        >>>     # using this model in an autocomplete_fields somewhere.
    """

    def get_change_actions(self, request, object_id, form_url, obj=None):
        obj = self._get_change_action_object(object_id)
        change_actions = self.model.config.get_change_actions(request, obj)
        return ObjectActionsPermissionsMixin.get_change_actions(self, request, object_id, change_actions, obj)

    def get_edit_readonly_fields(self, request, obj=None):
        if self.edit_readonly_fields != EditReadonlyAdminMixin.edit_readonly_fields:
            return super().get_edit_readonly_fields(request, obj)
        return self.model.config.get_edit_readonly_fields(request, obj)

    def get_list_filter_autocomplete(self, request):
        if self.list_filter_autocomplete != AutocompleteListFilterAdminMixin.list_filter_autocomplete:
            return super().get_list_filter_autocomplete(request)
        return self.model.config.get_list_filter_autocomplete(request)


__all__ = (
    'BaseKaosModelAdmin',
    'SimpleKaosModelAdmin',
    'ObjectActionsPermissionsMixin',
    'KaosModelAdmin',
)