kiwitcms/Kiwi

View on GitHub
tcms/kiwi_auth/admin.py

Summary

Maintainability
A
1 hr
Test Coverage
# -*- coding: utf-8 -*-

from django import forms
from django.contrib import admin, messages
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import GroupAdmin, UserAdmin, sensitive_post_parameters_m
from django.contrib.auth.models import Group, Permission
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _

from tcms.utils.user import delete_user

User = get_user_model()  # pylint: disable=invalid-name


def _modifying_myself(request, object_id):
    return request.user.pk == int(object_id)


class GroupAdminForm(forms.ModelForm):
    class Meta:
        model = Group
        fields = ["name", "permissions"]

    users = forms.ModelMultipleChoiceField(
        queryset=User.objects.all(),
        required=False,
        widget=FilteredSelectMultiple("users", False),
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["users"].label = _("Users")
        if self.instance.pk:
            self.fields["users"].initial = self.instance.user_set.all()

    def save(self, commit=True):
        instance = super().save(commit=commit)
        instance.save()

        self.instance.user_set.set(self.cleaned_data["users"])
        self.save_m2m()

        return instance


class KiwiUserAdmin(UserAdmin):
    list_display = UserAdmin.list_display + (
        "is_active",
        "is_superuser",
        "date_joined",
        "last_login",
    )
    ordering = ["-pk"]  # same as -date_joined

    def has_view_permission(self, request, obj=None):
        return _modifying_myself(
            request, getattr(obj, "pk", 0)
        ) or super().has_view_permission(request, obj)

    def has_change_permission(self, request, obj=None):
        return _modifying_myself(
            request, getattr(obj, "pk", 0)
        ) or super().has_change_permission(request, obj)

    def has_delete_permission(self, request, obj=None):
        return _modifying_myself(
            request, getattr(obj, "pk", 0)
        ) or super().has_delete_permission(request, obj)

    # pylint: disable=too-many-arguments
    def render_change_form(
        self, request, context, add=False, change=False, form_url="", obj=None
    ):
        if obj:
            if not context["adminform"].form._meta.help_texts:
                context["adminform"].form._meta.help_texts = {}

            label = _("Reset email address")
            url = reverse_lazy("reset-user-email", args=[obj.pk])
            context["adminform"].form._meta.help_texts[
                "email"
            ] = f"<a href='{url}'>{label}</a>"

        if not self.has_change_permission(request, obj):
            context.update(
                {
                    "show_save": False,
                    "show_save_and_continue": False,
                }
            )
        context.update(
            {
                "show_save_and_add_another": self.has_add_permission(request),
            }
        )
        return super().render_change_form(
            request, context, add=add, change=change, form_url=form_url, obj=obj
        )

    def get_readonly_fields(self, request, obj=None):
        # adding new user
        if not obj:
            return super().get_readonly_fields(request, obj)

        readonly_fields = [
            "username",
            "last_login",
            "date_joined",
            "email",
        ]

        # only other superusers can set the is_superuser flag
        if not request.user.is_superuser:
            readonly_fields.append("is_superuser")

        # if you have explicit change_user permission you can modify these fields
        # however users are not able to give themselves elevated permissions
        if not self.has_change_permission(request, None):
            readonly_fields.extend(
                [
                    "is_staff",
                    "is_active",
                    "groups",
                    "user_permissions",
                ]
            )

            # lastly users can't modify others unless they have the expolicit permission
            if not _modifying_myself(request, obj.pk):
                readonly_fields.extend(["first_name", "last_name", "email"])

        return readonly_fields

    def get_fieldsets(self, request, obj=None):
        # adding new account b/c we have permissions
        if not obj and self.has_add_permission(request):
            return super().get_fieldsets(request, obj)

        first_fieldset_fields = ("username",)
        if obj and _modifying_myself(request, obj.pk):
            first_fieldset_fields += ("password",)

        remaining_fieldsets = (
            (_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
            (
                _("Permissions"),
                {
                    "fields": (
                        "is_active",
                        "is_staff",
                        "is_superuser",
                        "groups",
                        "user_permissions",
                    )
                },
            ),
        )

        if request.user.is_superuser:
            field_sets = super().get_fieldsets(request, obj)
            if field_sets[0][0] is None and "password" in field_sets[0][1]["fields"]:
                remaining_fieldsets = field_sets[1:]

        return ((None, {"fields": first_fieldset_fields}),) + remaining_fieldsets

    @sensitive_post_parameters_m
    def user_change_password(
        self, request, id, form_url=""
    ):  # pylint: disable=redefined-builtin
        if _modifying_myself(request, id):
            return HttpResponseRedirect(reverse("admin:password_change"))

        raise PermissionDenied

    @admin.options.csrf_protect_m
    def delete_view(self, request, object_id, extra_context=None):
        user = User.objects.get(pk=object_id)
        # check whether the last superuser is being deleted
        if user.is_superuser and User.objects.filter(is_superuser=True).count() == 1:
            messages.add_message(
                request,
                messages.ERROR,
                _("This is the last superuser, it cannot be deleted!"),
            )
            return HttpResponseRedirect(
                reverse("admin:auth_user_change", args=[user.pk])
            )

        if not _modifying_myself(request, object_id):
            return super().delete_view(request, object_id, extra_context)

        # allow deletion of the user own account
        permission = Permission.objects.get(
            content_type__app_label="auth", codename="delete_user"
        )
        try:
            request.user.user_permissions.add(permission)
            return super().delete_view(request, object_id, extra_context)
        finally:
            request.user.user_permissions.remove(permission)

    def response_delete(self, request, obj_display, obj_id):
        result = super().response_delete(request, obj_display, obj_id)

        if not _modifying_myself(request, obj_id):
            return result

        # user doesn't exist anymore so go to the login page
        return HttpResponseRedirect(reverse("tcms-login"))

    def delete_model(self, request, obj):
        delete_user(obj)


class KiwiGroupAdmin(GroupAdmin):
    form = GroupAdminForm

    def has_delete_permission(self, request, obj=None):
        if obj and obj.name in ["Tester", "Administrator"]:
            return False
        return super().has_delete_permission(request, obj)

    def get_fields(self, request, obj=None):
        fields = super().get_fields(request, obj=obj)
        name_index = fields.index("name")

        # make sure Name is always the first field
        if name_index > 0:
            del fields[name_index]
            fields.insert(0, "name")

        return fields

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super().get_readonly_fields(request, obj)

        if obj and obj.name in ["Tester", "Administrator"]:
            readonly_fields += ("name",)

        return readonly_fields


# user admin extended functionality
admin.site.unregister(User)
admin.site.register(User, KiwiUserAdmin)
admin.site.unregister(Group)
admin.site.register(Group, KiwiGroupAdmin)