¿Cómo puedo evitar una escalada en el permiso de Django administrador cuando la concesión de permiso “usuario cambio”?
-
21-09-2019 - |
Pregunta
Tengo un sitio Django con una gran base de clientes. Me gustaría dar nuestro departamento de servicio al cliente la posibilidad de modificar las cuentas de usuario normales, haciendo cosas como el cambio de contraseñas, direcciones de correo electrónico, etc. Sin embargo, si alguien Concedo la incorporada en el permiso auth | user | Can change user
, adquieren la capacidad para establecer el indicador is_superuser
por ningún motivo, incluido el propio. (!!!)
¿Cuál es la mejor manera de eliminar esta opción para el personal no superusuario? Estoy seguro de que implica la subclasificación django.contrib.auth.forms.UserChangeForm
y enganchándolo en mi objeto UserAdmin
ya de por encargo ... de alguna manera. Pero no puedo encontrar ninguna documentación sobre cómo hacer esto, y yo todavía no comprenden la parte interna lo suficientemente bien.
Solución
se obtiene la capacidad para establecer el indicador is_superuser en cualquier cuenta, incluido el propio. (!!!)
No sólo esto, sino que también adquieren la capacidad de darse los permisos de uno en uno, mismo efecto ...
Estoy seguro de que se trata de subclases django.contrib.auth.forms.UserChangeForm
Bueno, no necesariamente. La forma que se ve en la página de cambio de administración de Django es dinámica creada por la aplicación de administración, y en base a UserChangeForm
, pero esta clase apenas añade la validación de expresiones regulares al campo username
.
y enganchándolo en mi ya de por encargo objeto UserAdmin ...
A UserAdmin
costumbre es el camino a seguir aquí. Básicamente, desea cambiar la propiedad fieldsets
a algo así:
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',)}),
)
Sin embargo, el problema aquí es que esta restricción se aplicará a todos los usuarios. Si esto no es lo que quiere, se puede por ejemplo change_view
anulación para comportarse de forma diferente en función de la autorización de los usuarios. Fragmento de código:
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)
Otros consejos
La continuación parte de la respuesta aceptada tiene una condición de carrera en la que si dos usuarios intentan acceder a personal de la forma de administración, al mismo tiempo, uno de ellos puede obtener el formulario de superusuario.
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
Para evitar esta condición de carrera (y en mi opinión mejorar la calidad general de la solución), podemos reemplazar los métodos get_fieldsets()
y get_readonly_fields()
directamente:
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)
Muchas gracias a Clément. Lo que se me ocurrió cuando se hace lo mismo para mi sitio es que necesitaba, además, para que todos los campos de sólo lectura para los usuarios que otros que sí. Así basándose en la respuesta de Clemente I addeed campos de sólo lectura y escondite campo de contraseña cuando se ve no se 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)
código completo de Django 1.1 (limitado a la información de usuario básica para el personal (no superusuarios))
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)
Este enfoque fue elaborado a partir de varios consejos útiles en la web. En este caso estamos modificando UserAdmin de manera que, para el personal no superusuario con el usuario agrega / permiso de modificación, los únicos permisos y grupos que pueden conceder otro usuario son los que el miembro del personal que ya tiene.
(por 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)
Esto debe hacerse del mismo modo para GroupAdmin si un usuario se le da permiso para grupos de cambio.