Comment puis-je éviter l'escalade de l'autorisation dans l'admin Django lors de l'octroi autorisation « changement d'utilisateur »?

StackOverflow https://stackoverflow.com/questions/2297377

Question

J'ai un site django avec une grande base de clients. Je voudrais donner à notre service à la clientèle la possibilité de modifier les comptes d'utilisateurs normaux, faire des choses comme les mots de passe changeants, adresses e-mail, etc. Cependant, si je vous accordez à quelqu'un intégré dans l'autorisation de auth | user | Can change user, ils acquièrent la possibilité de définir le drapeau is_superuser sur un compte, y compris le leur. (!!!)

Quelle est la meilleure façon de supprimer cette option pour le personnel non-super-utilisateur? Je suis sûr que cela implique django.contrib.auth.forms.UserChangeForm de sous-classement et accrocher dans mon objet UserAdmin déjà sur mesure ... en quelque sorte. Mais je ne peux pas trouver de la documentation sur la façon de faire, et je ne comprends pas encore assez bien le fonctionnement interne.

Était-ce utile?

La solution

  

ils gagnent la possibilité de définir le drapeau is_superuser sur un compte, y compris le leur. (!!!)

Non seulement cela, ils gagnent également la possibilité de se donner toutes les autorisations un par un, même effet ...

  

Je suis sûr que cela implique subclassing django.contrib.auth.forms.UserChangeForm

Eh bien, pas nécessairement. La forme que vous voyez dans la page de changement de admin django est créé dynamiquement par l'application d'administration, et basée sur UserChangeForm, mais cette classe ajoute à peine la validation regex au champ username.

  

et l'accrocher dans mon objet UserAdmin déjà sur mesure ...

Un UserAdmin personnalisé est le chemin à parcourir ici. En gros, vous voulez changer la propriété fieldsets à quelque chose comme ça:

class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('username', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        # Removing the permission part
        # (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
        # Keeping the group parts? Ok, but they shouldn't be able to define
        # their own groups, up to you...
        (_('Groups'), {'fields': ('groups',)}),
    )

Mais le problème est que cette restriction s'appliquera à tous les utilisateurs. Si ce n'est pas ce que vous voulez, vous pouvez par exemple passer outre change_view à se comporter différemment selon l'autorisation des utilisateurs. Extrait de code:

class MyUserAdmin(UserAdmin):
    staff_fieldsets = (
        (None, {'fields': ('username', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        # No permissions
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
        (_('Groups'), {'fields': ('groups',)}),
    )

    def change_view(self, request, *args, **kwargs):
        # for non-superuser
        if not request.user.is_superuser:
            try:
                self.fieldsets = self.staff_fieldsets
                response = super(MyUserAdmin, self).change_view(request, *args, **kwargs)
            finally:
                # Reset fieldsets to its original value
                self.fieldsets = UserAdmin.fieldsets
            return response
        else:
            return super(MyUserAdmin, self).change_view(request, *args, **kwargs)

Autres conseils

La partie ci-dessous de la réponse acceptée est une condition de course où si deux utilisateurs du personnel tentent d'accéder au formulaire d'administration en même temps, l'un d'entre eux peuvent obtenir le formulaire de super-utilisateur.

try:
    self.readonly_fields = self.staff_self_readonly_fields
    response = super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs)
finally:
    # Reset fieldsets to its original value
    self.fieldsets = UserAdmin.fieldsets

Pour éviter cette condition de course (et à mon avis, d'améliorer la qualité globale de la solution), nous pouvons remplacer les méthodes de get_fieldsets() et get_readonly_fields() directement:

class UserAdmin(BaseUserAdmin):
    staff_fieldsets = (
        (None, {'fields': ('username')}),
        ('Personal info', {'fields': ('first_name', 'last_name', 'email')}),
        # No permissions
        ('Important dates', {'fields': ('last_login', 'date_joined')}),
    )
    staff_readonly_fields = ('username', 'first_name', 'last_name', 'email', 'last_login', 'date_joined')

    def get_fieldsets(self, request, obj=None):
        if not request.user.is_superuser:
            return self.staff_fieldsets
        else:
            return super(UserAdmin, self).get_fieldsets(request, obj)

    def get_readonly_fields(self, request, obj=None):
        if not request.user.is_superuser:
            return self.staff_readonly_fields
        else:
            return super(UserAdmin, self).get_readonly_fields(request, obj)

Un grand merci à Clément. Ce que je suis venu avec quand faire la même chose pour mon site est que je besoin en plus de faire tous les champs readonly pour les utilisateurs que vous autres que moi. Ainsi, se basant sur la réponse de Clément I addeed champs et readonly champ de mot de passe caché lors de la visualisation pas auto

class MyUserAdmin(UserAdmin):
    model = User
    staff_self_fieldsets = (
        (None, {'fields': ('username', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        # No permissions
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )

    staff_other_fieldsets = (
        (None, {'fields': ('username', )}),
        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        # No permissions
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )

    staff_self_readonly_fields = ('last_login', 'date_joined')

    def change_view(self, request, object_id, form_url='', extra_context=None, *args, **kwargs):
        # for non-superuser
        if not request.user.is_superuser:
            try:
                if int(object_id) != request.user.id:
                    self.readonly_fields = User._meta.get_all_field_names()
                    self.fieldsets = self.staff_other_fieldsets
                else:
                    self.readonly_fields = self.staff_self_readonly_fields
                    self.fieldsets = self.staff_self_fieldsets

                response = super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs)
            except:
                logger.error('Admin change view error. Returned all readonly fields')

                self.fieldsets = self.staff_other_fieldsets
                self.readonly_fields = ('first_name', 'last_name', 'email', 'username', 'password', 'last_login', 'date_joined')
                response = super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs)
            finally:
                # Reset fieldsets to its original value
                self.fieldsets = UserAdmin.fieldsets
                self.readonly_fields = UserAdmin.readonly_fields
            return response
        else:
            return super(MyUserAdmin, self).change_view(request, object_id, form_url, extra_context, *args, **kwargs)

Code complet pour django 1.1 (limité aux informations des utilisateurs de base pour le personnel (non superutilisateurs))

from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _


class MyUserAdmin(UserAdmin):
   my_fieldsets = (
       (None, {'fields': ('username', 'password')}),
       (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
   )

   def change_view(self, request, object_id, extra_context=None):
       # for non-superuser
       print 'test'
       if not request.user.is_superuser:
           self.fieldsets = self.my_fieldsets
           response = UserAdmin.change_view(self, request, object_id,
extra_context=None)
           return response
       else:
           return UserAdmin.change_view(self, request, object_id,
extra_context=None)


admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)

Cette approche a été mis en place de plusieurs conseils utiles sur le web. Dans ce cas, nous modifions UserAdmin de sorte que, pour le personnel non-utilisateur avec superuser ajouter / autorisation de modification, les seules autorisations et les groupes qu'ils peuvent accorder un autre utilisateur sont ceux que le membre du personnel a déjà.

(pour Django 1,11)

from django.contrib.auth.admin import UserAdmin, User
from django.contrib import admin

class RestrictedUserAdmin(UserAdmin):
    model = User

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(RestrictedUserAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        user = kwargs['request'].user
        if not user.is_superuser:
            if db_field.name == 'groups':
                field.queryset = field.queryset.filter(id__in=[i.id for i in user.groups.all()])
            if db_field.name == 'user_permissions':
                field.queryset = field.queryset.filter(id__in=[i.id for i in user.user_permissions.all()])
            if db_field.name == 'is_superuser':
                field.widget.attrs['disabled'] = True
        return field

admin.site.unregister(User)
admin.site.register(User, RestrictedUserAdmin)

Cela devrait également être fait pour GroupAdmin si un utilisateur reçoit l'autorisation de changer de groupe.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top