I would like to implement a 2nd admin site which provides a subset of feature of the primary admin site. That's possible and described in the Django docs
However, I would like to limit access on the primary admin site. Some users can access the 2ndary site but not the primary site.
In order to implement that feature, I would like these users not to be in the staff (is_staff=False) and rewrite the AdminSite.has_permission
class SecondaryAdminSite(AdminSite):
def has_permission(self, request):
if request.user.is_anonymous:
try:
username = request.POST['username']
password = request.POST['password']
except KeyError:
return False
try:
user = User.objects.get(username = username)
if user.check_password(password):
return user.has_perm('app.change_onlythistable')
else:
return False
except User.DoesNotExist:
return False
else:
return request.user.has_perm('app.change_onlythistable')
Unfortunately, this approach doesn't work. The user can login but can't see anything in the secondary admin site.
What's wrong with this approach?
Any idea how to implement this feature?
Thanks in advance
Here's what worked for me with Django >= 3.2.
Create a subclass of AdminSite
Override the has_permission() method to remove the is_staff check.
Override the login_form to use AuthenticationForm.
AdminSite uses AdminAuthenticationForm, which extends AuthenticationForm and adds a check for is_staff.
Code
# PROJECT/APP/admin.py
from django.contrib.admin import AdminSite
from django.contrib.admin.forms import AuthenticationForm
class MyAdminSite(AdminSite):
"""
App-specific admin site implementation
"""
login_form = AuthenticationForm
site_header = 'Todomon'
def has_permission(self, request):
"""
Checks if the current user has access.
"""
return request.user.is_active
site = MyAdminSite(name='myadmin')
I think that your approach should now be possible: http://code.djangoproject.com/ticket/14434 (closed 5 weeks ago)
However, the explicit "is_staff" check is still done in two places (apart from the staff_member_required decorator):
django.contrib.admin.forms.AdminAuthenticationForm.clean()
On top of "has_permission()" you'd need to provide your non-staff AdminSite with a "login_form" that doesn't do the is_staff check, so could just subclass and adjust clean() accordingly.
templates/admin/base.html
would need to be slightly customized.
The div with id "user-tools" is only shown for active staff members. I'm assuming that's done because the login form also uses this template, and someone could be logged in as an active non-staff member but still should'nt see those links.
What's wrong with this approach? Any idea how to implement this feature?
What's wrong with this approach is that permissions and groups can already provide you with what you need. There is no need to subclass AdminSite if all you need is to divide users.
This is probably why this feature is so poorly documented, IMHO
Related
I have made a custom user model using the AbstractUser , removed the username and replaced it with email and extended the model, tried creating superuser and it worked and also created some users by a signup form , logged in the admin interface and it worked however when tried to create a login form for the users it fails
I tried this but it didn't work
def LoginView(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
login(request,user)
return redirect('accounts:login')
else:
form = AuthenticationForm()
return render(request,'accounts/login.html', {'form':form})
then i tried this
class LoginView(FormView):
form_class = AuthenticationForm
template_name = 'login.html'
def form_valid(self, form):
email = form.cleaned_data['email']
password = form.cleaned_data['password']
user = authenticate(email=email, password=password)
# Check here if the user is an admin
if user is not None and user.is_active:
login(self.request, user)
return HttpResponseRedirect(self.success_url)
else:
return self.form_invalid(form)
Obviously i expect the user to be logged in
i think the code in this post is badly formatted. mainly it's my fault as i'm new to this platform
I've developed almost the same setup that you're describing (I didn't remove the username field, just stopped using it). If you haven't already seen it, Django's documentation at https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model is quite helpful.
There are a couple of important things that need to be set up correctly for this to work.
The USERNAME_FIELD on your model should be set to the name of your email field.
The AUTH_USER_MODEL needs to point to your custom user model.
class MyUser(AbstractUser):
USERNAME_FIELD = 'email'
AUTH_USER_MODEL = 'customauth.MyUser'
Since you've removed the username field altogether you might need to subclass django.contrib.auth.forms.AuthenticationForm and django.contrib.auth.views.LoginView to avoid breaking things, but Django should handle a different authentication field quite well.
If you do wind up needing to subclass the view, https://ccbv.co.uk/projects/Django/2.2/django.contrib.auth.views/LoginView/ is a great place to look over all the methods to see what's going on.
Edit - On Subclassing and it's necessity
What I was saying about possibly needing to subclass certain things was influenced by https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#writing-a-manager-for-a-custom-user-model. I wasn't sure if there were other parts of the authentication system that would need you to customize them because you removed the username field.
I've read through some of the source code for Django's authentication system. Here's the path that's being followed.
When the POST request is made to Django's authentication view the authentication form is validated. https://github.com/django/django/blob/2.2.2/django/contrib/auth/forms.py#L191
The authenticate function is called. This iterates through the backends set up and tries to authenticate on each of them. https://github.com/django/django/blob/2.2.2/django/contrib/auth/__init__.py#L62
Django's built-in authentication backend gets the user if it exists using the natural key. https://github.com/django/django/blob/2.2.2/django/contrib/auth/backends.py#L16
We can see in the base manager that the natural key used is the field named by USERNAME_FIELD. https://github.com/django/django/blob/2.2.2/django/contrib/auth/base_user.py#L43
If the form is valid, meaning that the user is authenticated properly, the user is then logged in. https://github.com/django/django/blob/2.2.2/django/contrib/auth/views.py#L88
My reaction is that it looks like Django should work out of the box for your use case. You shouldn't need to write a backend. Here's the extent of the code my gut says you should have to write.
from django.contrib.auth import views as auth_views
from django.shortcuts import resolve_url
class LoginView(auth_views.LoginView):
template_name = 'accounts/login.html'
def get_success_url(self):
return resolve_url('accounts:login')
I don't know if this could be of any use to somebody but I can confirm that Django can authenticate you well with its own Login view if you just replace the username with an email field on your custom user model (as long as you specify the USERNAME_FIELD on the custom user model and are indeed using it by declaring it in the settings).
As I was expecting this behavior I designed a custom HTML form with email/password inputs and used the same principles I would use with the original user model authentication. It was failing though and I understood it was because I wasn't adapting my form to the original Login view expectations or would have worked from the start.
Just make sure to remember that the form HTML input tag for the email address needs to have "type" set to "email" but "id" set to "id_username" and "name" to "username".
This means you can just replace username with an email field and authenticate normally. I did not even declare a Login view for my login form to work, the Django view automacally used from the core is just being called at /accounts/login and working on its own. I'm working on Django 3.2
I have added some URLs in my Django API for posting deleting and putting data and I don't know how to authenticate users first and give some of them to use these methods and ban some of them
As far as I know, you can use inbuilt decorator
#user_passes_test
then you can specify who can access your views just like below,
from django.contrib.auth.decorators import user_passes_test
def admin_user(user):
return user.is_superuser # write your logic here
#user_passes_test(admin_user)
def your_view(request):
--------
Have a look at the documentation for more clarification: https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.decorators.user_passes_test
Since you are using the tag django-rest-framework, I assume that your view is being created with Django REST Framework.
First, you should force users to be authenticated to use the API. Second, you need to define what types of permissions are needed to perform the actions.
You stated that Django Super Users should be able to perform these actions. Thus, you could create a custom permission to make sure that only a user that is a Django Super User will have permission:
from rest_framework.permissions import BasePermission
class IsSuperUser(BasePermission):
"""
Allows access only to admin users.
"""
def has_permission(self, request, view):
is_superuser = request.user and request.user.is_superuser
if not is_superuser and request.user:
# Your ban logic goes here
pass
return is_superuser
Then on your view, you can do:
from rest_framework.views import APIView
from your_app.permissions import IsSuperUser
class YourApiView(APIView):
permission_classes = [IsSuperUser]
If this is not enough information for you, I would suggest that you follow Django REST Framework's tutorial.
I am kind of new to django and I am concerned about can we use multiple models for authentication for different types of users for our application e.g for Customers use Customer model, for Suppliers use Supplier model and also keep default User registration model for administration use only? If so can you point me in the right direction, how it can be done? Which package should be used?
There is one way that I came around is by adding a foreign key to each model viz needed for authentication but that will involve joins in every query which could result in performance issues. I need to know if there is a better way. An also these custom models can benefit for all permissions stuff available in admin panel.
Expert opinion will be really appreciated.
There are a few different options to handle that.
Maybe check first the Django-docu.
I've you'ld like to customize it to authenticate your users with a mail-address, here is an example written by knbk and rahul-gupta on the ex-documentation:
Django's default authentication works on username and password fields. Email authentication backend will authenticate users based on email and password.
from django.contrib.auth import get_user_model
class EmailBackend(object):
"""
Custom Email Backend to perform authentication via email
"""
def authenticate(self, username=None, password=None):
user_model = get_user_model()
try:
user = user_model.objects.get(email=username)
if user.check_password(password): # check valid password
return user # return user to be authenticated
except user_model.DoesNotExist: # no matching user exists
return None
def get_user(self, user_id):
user_model = get_user_model()
try:
return user_model.objects.get(pk=user_id)
except user_model.DoesNotExist:
return None
Add this authentication backend to the AUTHENTICATION_BACKENDS setting.
# settings.py
AUTHENTICATION_BACKENDS = (
'my_app.backends.EmailBackend',
...
)
Is it possible to have is_staff selected by choosing a group? Let's say there are two groups: users, admins
When a new user is in the users group he is not staff, but if he is in the admins group he is staff.
There is an easy way to do this define the following in your user model
#property
def is_staff(self):
if self.is_staff == True or self.groups.filter(name="staff").exists()
Thus during admin login or any other time when you call from the user_object.is_staff You will be getting what you want on basis of groups too.
I managed to make it work by extending the UserAdmin class and in the get_form function I placed this with help of mascot6699's answer:
if obj.groups.filter(name="Administrator").exists():
obj.is_staff = True
else:
obj.is_staff = False
So whenever I place a user (with the admin menu) in the Administrator group it will check the is_staff option else it unchecks it.
The is_staff property is primarily used by the admin interface. If you want to have an admin interface that's dependent on group membership, you can override AdminSite.has_permission() instead:
class GroupBasedAdminSite(admin.AdminSite):
def has_permission(self, request):
return request.user.is_active and request.user.groups.filter(name = 'admins').exists()
# override default admin site
admin.site = GroupBasedAdminSite()
You can also use the official override feature, or have a dedicated GroupBasedAdminSite hosted on a different path, in case you want to support different types of "admins".
There are two place one should override to implement this behaviour
# inside any app's admin.py module
import types
from django.contrib import admin
from django.contrib.admin.forms import AdminAuthenticationForm
def has_permission(self, request):
return request.user.is_active and (
request.user.is_staff
or request.user.groups.filter(name="grp").exists()
)
class GrpAdminAuthenticationForm(AdminAuthenticationForm):
def confirm_login_allowed(self, user):
if user.groups.filter(name="grp").exists():
user.is_staff = True
super().confirm_login_allowed(user)
admin.site.login_form = GrpAdminAuthenticationForm
admin.site.has_permission = types.MethodType(has_permission, admin.site)
It will update the default admin.site object so one doesn't need to register to a custom object.
I have a small view:
def AccountHome(request):
return render(request, 'myapp/account/accounthome.html', {
})
In previous views, I've used:
if user is not None and user.is_active
to check if a user is already authenticated or not when using native form classes like: AuthenticationForm for example when logging in a user.
But on this view I am not using that, is there someway to validate whether a user is logged in or not without using this AuthenticationForm classagain? Thisviews purpose is to show the homescreen when logged in, so it seems non-intuitive to extend thatAuthenticationForm` class again.
Any help or thoughts?
Thanks
Use is_authenticated() method
Like this: if request.user.is_authenticated():
You can find the reference here: https://docs.djangoproject.com/en/dev/ref/contrib/auth/#methods