Hi,
I would like to solicit some feedback regarding some existing tight coupling between django.contrib.auth and django.contrib admin.
The existing, and nominally swappable user model that ships with django assumes, for the out-of-the-box application benefit of django.contrib.admin, that there are 3 differentiable fields that should ship with any swappable user model that wishes to be swappable:
is_staff
is_superuser
and
is_active
I would like to propose that at least the "is_staff" and the "is_superuser" attributes that are part of the default user model become fallbacks to a more generic permissions (maybe even swappable permissions???) approach.
What I am envisioning is that whereever there are checks for is_superuser, is_staff and is_active that they be wrapped in a way that if the custom user model did not offer these attributes, that perhaps some "pluggable permissioning" take their place. I enumerate the existing admin dependencies in this post, the is_superuser is relevant prolly in a different thread as it only touches the default user model (and not admin directly)
For example: from django/contrib/admin/views/decorators.py
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test
def staff_member_required(view_func=None, redirect_field_name=REDIRECT_FIELD_NAME,
login_url='admin:login'):
"""
Decorator for views that checks that the user is logged in and is a staff
member, redirecting to the login page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_active and u.is_staff,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if view_func:
return actual_decorator(view_func)
return actual_decorator
itself could wrap u.is_active and u.is_staff.
or, IMO, better yet, the actual decorator could be overriden by the custom user model:
e.g. the user model could:
def user_is_staff_test(self):
if self.has_permissions(['list of perms']):
return True
else:
return False
and
def user_is_superuser_test(self):
if self.has_permissions(['list of other perms']):
return True
else:
return False
and then existing decorator could be modifed to to fetch the user_passes_test method and revert to a default if it doesn't exist.
Similar mods would be required for the Admin Site class django/contrib/admin/sites.py
def has_permission(self, request):
"""
Return True if the given HttpRequest has permission to view
*at least one* page in the admin site.
"""
return request.user.is_active and request.user.is_staff
And the AdminAuthenticationForm (django/contrib/admin/forms.py):
class AdminAuthenticationForm(AuthenticationForm):
"""
A custom authentication form used in the admin app.
"""
error_messages = {
'invalid_login': _(
"Please enter the correct %(username)s and password for a staff "
"account. Note that both fields may be case-sensitive."
),
}
required_css_class = 'required'
def confirm_login_allowed(self, user):
if not user.is_active or not user.is_staff:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name}
)
I would love to hear people's thoughts on this. I am targeting an architecture where the user model as simply two meaningful columns (I don't care too much about the surrogate "id" PK), email & password.
The other attributes would be stored elsewhere. I can see carrying a convenience field of "is_active" in this table but that too could be managed in other ways (user inactivation could be done by migrating a db row to an "inactive users" table, e.g.
The way I think about last login is to provide either a user_login_history table or a more generic event table where one of the possible events would be 'user_login'.
I appreciate others' thoughts. And special thanks to the django folks who just, with release 2.02, made the last_login attribute of a custom user model no longer necessary! I had been riding with a fork of auth_user to get around that for a bit.
steve