Is there a ready way to use the Django admin page without any form of authentication? I know I can use this method, but that was for Django 1.3. Are there any changes that would let me do this more easily in Django 1.6?
My main motivation for this is that I want to have as few database tables as possible, and I am using this only locally, so there is no need for any sort of authentication (I'm only ever running the server on localhost anyways).
The accepted answer is already super simple however after messing around with this I found that in recent versions of Django (since admin.site.has_permission became a thing... >= 1.8?) you can do it without middleware.
In your project's urls.py:
from django.contrib import admin
class AccessUser:
has_module_perms = has_perm = __getattr__ = lambda s,*a,**kw: True
admin.site.has_permission = lambda r: setattr(r, 'user', AccessUser()) or True
# Register the admin views or call admin.autodiscover()
urlpatterns = [
# Your url configs then...
url(r'^admin/', admin.site.urls),
]
If you have AccessUser extend User you can leave out the __getattr__ portion which is a hacky way to return something when user.pk or similar is called.
Create a module auto_auth.py:
from django.contrib.auth.models import User
from django.utils.deprecation import MiddlewareMixin
class AutoAuthMiddleware(MiddlewareMixin):
def process_request(self, request):
request.user = User.objects.filter()[0]
Edit MIDDLEWARE in your settings.py:
Remove 'django.contrib.auth.middleware.AuthenticationMiddleware'
Add 'auto_auth.AutoAuthMiddleware'
You can change User.objects.filter()[0] to something else if you want a particular user.
In response to your comment: yes. To run the Django admin without users at all, try this:
class User:
is_superuser = True
is_active = True
is_staff = True
id = 1
def return_true(*args, **kwargs):
return True
User.has_module_perms = return_true
User.has_perm = return_true
class AutoAuthMiddleware(MiddlewareMixin):
def process_request(self, request):
request.user = User()
And remove 'django.contrib.auth' from INSTALLED_APPS
But if you use any apps that depend on the auth app, you're going to have a bad time.
The accepted answer adapted for Django version >= 1.10
/[yourapp]/middleware.py:
from django.contrib.auth.models import User
class AuthenticationMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.user = User.objects.filter()[0]
return self.get_response(request)
In [yourproject]/settings.py for the MIDDLEWARE list:
Comment or remove: 'django.contrib.auth.middleware.AuthenticationMiddleware',
Append: '[yourapp].middleware.AuthenticationMiddleware',
Probably obvious to most people but note that the solution still requires one user to exist. Create one manually python manage.py createsuperuser or automatically with a script:
How to automate createsuperuser on django?
Autocreate superuser with each schema django
Create django super user in a docker container without inputting password
Another Option allows access from anyone: get the first user to bypass authentication
# app/admin.py
from django.contrib.auth.models import User
anonymous_user = User.objects.all().first()
admin.site.has_permission = lambda r: setattr(r, 'user', anonymous_user) or True
For the newer versions of django >=2.1 you need to do something like this:
auto_auth.py
class User:
is_superuser = True
is_active = True
is_staff = True
id = 1
pk = 1
User.has_module_perms = True
User.has_perm = True
class Middleware(object):
def __init__(self, get_response):
self.response = get_response
def __call__(self, request):
request.user = User()
return self.response(request)
And also don't forget to modify your settings middleware and deactivate django.contrib.auth and add auto_auth
For all using Django 3.0,
comment out this code AUTH_USER_MODEL = 'customUser' in your settings.py and create a superuser (python manage.py createsuperuser) with the default user model first.
After creating the superuser then uncomment this code AUTH_USER_MODEL = 'customUser'.
This happened to me and that's what I did with Django 3.0
You should be good to go. Hope it helps
Related
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.
What is a good way to dynamically set SITE_ID in settings.py based on domain name in URL?
I have an app that will be running with too many domain names (city based) that a custom settings.py file for each domain is just not doable.
Is there a way to set it dynamically?
You can do a custom middleware which reads the request and sets the SITE_ID. I use this code on one of my sites:
class SiteMiddleware(object):
def process_request(self, request):
try:
current_site = Site.objects.get(domain=request.get_host())
except Site.DoesNotExist:
current_site = Site.objects.get(id=settings.DEFAULT_SITE_ID)
request.current_site = current_site
settings.SITE_ID = current_site.id
You should look at Django's documentation on middleware to find out how to add your own middleware. https://docs.djangoproject.com/en/dev/topics/http/middleware/
The middleware code form seems to have changed. The current (Django v3.1) form of the code (that seems to work):
from django.conf import settings
from django.contrib.sites.models import Site
class DynamicSiteDomainMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
try:
current_site = Site.objects.get(domain=request.get_host())
except Site.DoesNotExist:
current_site = Site.objects.get(id=settings.DEFAULT_SITE_ID)
request.current_site = current_site
settings.SITE_ID = current_site.id
response = self.get_response(request)
return response
This should work for Django v3.2.
This code is not very different compared to earlier answers, so I won't explain how this works.
However, I believe that writing custom middleware changed slightly in v3.2. The key difference is subclassing MiddleWareMixin. I put this code in middleware.py, and add it to the MIDDLEWARE list in settings.py.
The second change compared to above answers is the use of SITE_ID directly from settings.py instead of DEFAULT_SITE_ID, since I see no point in having both these set in settings.py.
# middleware.py
class DynamicSiteMiddleware(MiddlewareMixin):
"""
Make all domain names available through request.site
"""
def process_request(self, request):
try:
current_site = Site.objects.get(domain=request.get_host())
except Site.DoesNotExist:
current_site = Site.objects.get(id=settings.SITE_ID)
request.site = current_site
settings.SITE_ID = current_site.id
response = self.get_response(request)
return response
# settings.py
MIDDLEWARE = [
...
'library.middleware.DynamicSiteMiddleware',
]
I have an application written in Django and I have to extend it and include some other solution as an "app" in this application.
For example, my app to be integrated is named "my_new_app"
Now there is a backend authentication written for the main application and I cannot use it.
I have a MySQL DB to query from and the main app uses Cassandra and Redis mostly.
Is there any way I can use a separate authentication backend for the new app "my_new_app" and run both in the same domain?
You can have multiple authentication backends. Just set the AUTHENTICATION_BACKENDS in settings.py of your Django project to list the backend implementations you want to use. For example I often use a combination of OpenID authentication and the standard Django authentication, like this in my settings.py:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_openid_auth.auth.OpenIDBackend',
)
In this example Django will first try to authenticate using django.contrib.auth.backends.ModelBackend, which is the default backend of Django. If that fails, then it moves on to the next backend, django_openid_auth.auth.OpenIDBackend.
Note that your custom backends must be at a path visible by Django. In this example I have to add django_openid_auth to INSTALLED_APPS, otherwise Django won't be able to import it and use it as a backend.
Also read the relevant documentation, it's very nicely written, easy to understand:
https://docs.djangoproject.com/en/dev/topics/auth/customizing/
I've been through this problem before. This is the code I used.
This is the authentication backend at the api/backend.py
from django.contrib.auth.models import User
class EmailOrUsernameModelBackend(object):
def authenticate(self, username=None, password=None):
if '#' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
user = User.objects.get(**kwargs)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And this is my settings.py
AUTHENTICATION_BACKENDS = (
'api.backend.EmailOrUsernameModelBackend',
'django.contrib.auth.backends.ModelBackend',
)
This code will enable you to use email to authenticate the default Django user even in Django admin.
Using multiple backend authentications is as simple as pie. You just need to understand the workflow of Django apps.
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.Backend1',
'django_openid_auth.auth.Backend2',
)
For example you have the following two backends defined. Django will first go to the first backend and you just need to put some logic in that backend so that, if its not related to that backend it get forwarded to the other backend or returned without any results. In case of no results django will automatically shift the request from first backend to the second and if available third one.
I spend a lot of time on this and found out that it was not that complex.
Using Multiple AUTHENTICATION BACKENDS is very easy,
you just need to add this to settings.py
AUTHENTICATION_BACKENDS = (
'social_core.backends.open_id.OpenIdAuth',
'social_core.backends.google.GoogleOpenId',
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.google.GoogleOAuth',
'social_core.backends.facebook.FacebookOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
and this may create a problem at signup page so add a login argument in your signup view in views.py file like this
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
def signup_view(request):
if request.method=='POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user=form.save()
login(request, user, backend='django.contrib.auth.backends.ModelBackend')
return redirect('home')
Done a little safety and reassemble for code. cause provided solutions wasn't perfectly matching necessary.
my version for the previous: https://stackoverflow.com/a/17078818/14972653
with my addition and #M.Void comment:
This is the authentication backend at the api/backend.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.core.validators import validate_email
user_model = get_user_model()
class EmailOrUsernameModelBackend(ModelBackend):
"""EmailOrUsernameModelBackend - UsernameOrEmail custom authentication backend
with email or username validation on same field
provides case insensitive validation of username and email
BEWARE - if you allow users to register two usernames in differren cases like: Alex and alex - there would be error.
still uses ModelBackend so provides permissions and is_active validation
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(user_model.USERNAME_FIELD)
if username is None or password is None:
return
try:
validate_email(username)
except ValidationError as e:
kwargs = {'username__iexact': username} # remove __iexact to make it case sensitive
else:
kwargs = {'email__iexact': username} # remove __iexact to make it case sensitive
try:
user = user_model.objects.get(**kwargs)
except user_model.DoesNotExist:
return None
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def get_user(self, user_id):
try:
return user_model.objects.get(pk=user_id)
except user_model.DoesNotExist:
return None
and of course add it to your authentication backends:
AUTHENTICATION_BACKENDS = (
'api.backend.EmailOrUsernameModelBackend',
'django.contrib.auth.backends.ModelBackend',
)
django's User model has a last_login field, which is great if all the users were to log out each time they leave the site, but what if they don't?
How can I track when a user who never logged out and his activity on the site?
You need to have the last_activity field in the user profile (or custom user model). This field will be updated on every request. To achieve this you need to have custom middleware:
profiles/middleware.py:
from django.utils import timezone
from myproject.profiles.models import Profile
class UpdateLastActivityMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
assert hasattr(request, 'user'), 'The UpdateLastActivityMiddleware requires authentication middleware to be installed.'
if request.user.is_authenticated():
Profile.objects.filter(user__id=request.user.id) \
.update(last_activity=timezone.now())
Add this middleware in your setting file:
MIDDLEWARE_CLASSES = (
# other middlewares
'myproject.profiles.middleware.UpdateLastActivityMiddleware',
)
i think in 2022 this will satisfy what you need also i did this and worked for me but the answer didnt.
put LastActivityTraceMiddleware after AuthenticationMiddleware in settings.py.
from django.utils.timezone import now
from .... import MemberEntity
class LastActivityTraceMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
member: MemberEntity = request.user
if member.is_authenticated:
member.last_login = now()
member.save()
return response
I know the question is old ... and surely it has already been solved ...
but here is my contribution ...
In new versions of django you can use:
"Session Time" -> Used in configuration file. "settings.py"
If the user closes the browser, the session ends and must be logged in again ...
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
If the user does not close the browser, you can set a time limit for the session ...
SESSION_COOKIE_AGE = 60 * 60
For "SESSION_COOKIE_AGE" if I remember correctly it's defined in seconds.
You can see more here...
Recommended reading is also django's own documentation about sessions...
I want to be able to set an option in the user's settings that forces them to change their password upon the next login to the admin interface. Is this possible? How would it go about being implemented? I'm using the default auth model right now but not opposed to modifying or changing it. Thanks for any help.
I'm actually in the process of doing this myself. You need three components: a user profile (if not already in use on your site), a middleware component, and a pre_save signal.
My code for this is in an app named 'accounts'.
# myproject/accounts/models.py
from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
force_password_change = models.BooleanField(default=False)
def create_user_profile_signal(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
def password_change_signal(sender, instance, **kwargs):
try:
user = User.objects.get(username=instance.username)
if not user.password == instance.password:
profile = user.get_profile()
profile.force_password_change = False
profile.save()
except User.DoesNotExist:
pass
signals.pre_save.connect(password_change_signal, sender=User, dispatch_uid='accounts.models')
signals.post_save.connect(create_user_profile_signal, sender=User, dispatch_uid='accounts.models')
First, we create a UserProfile with a foreign key to User. The force_password_change boolean will, as its name describes, be set to true for a user whenever you want to force them to change their password. You could do anything here though. In my organization, we also chose to implement a mandatory change every 90 days, so I also have a DateTimeField that stores the last time a user changed their password. You then set that in the pre_save signal, password_changed_signal.
Second, we have the create_user_profile_signal. This is mostly added just for completeness. If you're just now adding user profiles into your project, you'll need a post_save signal that will create a UserProfile every time a User is created. This accomplishes that task.
Third, we have the password_changed_signal. This is a pre_save signal because at this point in the process the actual row in the User table hasn't be updated. Therefore, we can access both the previous password and the new password about to be saved. If the two don't match, that means the user has changed their password, and we can then reset the force_password_change boolean. This would be the point, also where you would take care of any other things you've added such as setting the DateTimeField previously mentioned.
The last two lines attach the two functions to their appropriate signals.
If you haven't already, you will also need to add the following line to your project's settings.py (changing the app label and model name to match your setup):
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
That covers the basics. Now we need a middleware component to check the status of our force_password_change flag (and any other necessary checks).
# myproject/accounts/middleware.py
from django.http import HttpResponseRedirect
import re
class PasswordChangeMiddleware:
def process_request(self, request):
if request.user.is_authenticated() and \
re.match(r'^/admin/?', request.path) and \
not re.match(r'^/admin/password_change/?', request.path):
profile = request.user.get_profile()
if profile.force_password_change:
return HttpResponseRedirect('/admin/password_change/')
This very simple middleware hooks into the process_request stage of the page loading process. It checks that 1) the user has already logged in, 2) they are trying to access some page in the admin, and 3) the page they are accessing is not the password change page itself (otherwise, you'd get an infinite loop of redirects). If all of these are true and the force_password_change flag has been set to True, then the user is redirected to the password change page. They will not be able to navigate anywhere else until they change their password (firing the pre_save signal discussed previously).
Finally, you just need to add this middleware to your project's settings.py (again, changing the import path as necessary):
MIDDLEWARE_CLASSES = (
# Other middleware here
'myproject.accounts.middleware.PasswordChangeMiddleware',
)
I have used Chris Pratt's solution, with a little change: instead of using a middleware, that'd be executed for every page with the consequent resource use, I figured I'd just intercept the login view.
In my urls.py I have added this to my urlpatterns:
url(r'^accounts/login/$', 'userbase.views.force_pwd_login'),
then I added the following to userbase.views:
def force_pwd_login(request, *args, **kwargs):
response = auth_views.login(request, *args, **kwargs)
if response.status_code == 302:
#We have a user
try:
if request.user.get_profile().force_password_change:
return redirect('django.contrib.auth.views.password_change')
except AttributeError: #No profile?
pass
return response
It seems to work flawlessly on Django 1.2, but I have no reason to believe 1.3+ should have problems with it.
This is the middleware I use with Django 1.11 :
# myproject/accounts/middleware.py
from django.http import HttpResponseRedirect
from django.urls import reverse
class PasswordChangeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
next = reverse('client:password-update')
if request.user.is_authenticated() and request.path != next:
if request.user.account.force_password_change:
return HttpResponseRedirect(next)
return response
Still adding it to the settings middleware list :
MIDDLEWARE_CLASSES = (
# Other middleware here
'myproject.accounts.middleware.PasswordChangeMiddleware',
)
I spent 2 days on this issue recently, and a new solution came out.
Hopefully it's useful.
Just as above said, a new user model created.
newuser/models.py
class Users(AbstractUser):
default_pwd_updated = models.NullBooleanField(default=None, editable=False)
pwd_update_time = models.DateTimeField(editable=False, null=True, default=None) # reserved column to support further interval password (such as 60 days) update policy
def set_password(self, raw_password):
if self.default_pwd_updated is None:
self.default_pwd_updated = False
elif not self.default_pwd_updated:
self.default_pwd_updated = True
self.pwd_update_time = timezone.now()
else:
self.pwd_update_time = timezone.now()
super().set_password(raw_password)
Set this model as the AUTH_USER_MODEL.
[project]/settings.py
AUTH_USER_MODEL = 'newuser.Users'
Now you just need to customize LoginView and some methods in AdminSite.
[project]/admin.py
from django.contrib.admin import AdminSite
from django.contrib.auth.views import LoginView
from django.utils.translation import gettext as _, gettext_lazy
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect
class NewLoginView(LoginView):
def get_redirect_url(self):
if self.request.method == "POST" and self.request.user.get_username()\
and not self.request.user.default_pwd_updated:
redirect_to = reverse("admin:password_change")
else:
redirect_to = self.request.POST.get(
self.redirect_field_name,
self.request.GET.get(self.redirect_field_name, '')
)
return redirect_to
class NewAdminSite(AdminSite):
site_header = site_title = gettext_lazy("Customized Admin Site")
def __init__(self, name="admin"):
super().__init__(name)
#never_cache
def login(self, request, extra_context=None):
"""
Display the login form for the given HttpRequest.
"""
if request.method == 'GET' and self.has_permission(request):
# Already logged-in, redirect to admin index
if request.user.get_username() and not request.user.default_pwd_updated:
# default password not changed, force to password_change view
path = reverse('admin:password_change', current_app=self.name)
else:
path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(path)
from django.contrib.auth.views import LoginView
from django.contrib.admin.forms import AdminAuthenticationForm
context = {
**self.each_context(request),
'title': _('Log in'),
'app_path': request.get_full_path(),
'username': request.user.get_username(),
}
if (REDIRECT_FIELD_NAME not in request.GET and
REDIRECT_FIELD_NAME not in request.POST):
context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name)
context.update(extra_context or {})
defaults = {
'extra_context': context,
'authentication_form': self.login_form or AdminAuthenticationForm,
'template_name': self.login_template or 'admin/login.html',
}
request.current_app = self.name
return NewLoginView.as_view(**defaults)(request) # use NewLoginView
#never_cache
def index(self, request, extra_context=None):
if request.user.get_username() and not request.user.default_pwd_updated:
# if default password not updated, force to password_change page
context = self.each_context(request)
context.update(extra_context or {})
return self.password_change(request, context)
return super().index(request, extra_context)
admin_site = NewAdminSite(name="admin")
NOTE: if you intend to use custom template for changing default password, you could override each_context method and then determine which template should be used up to the flag force_pwd_change.
[project]/admin.py
def using_default_password(self, request):
if self.has_permission(request) and request.user.get_username() and not request.user.default_pwd_updated:
return True
return False
def each_context(self, request):
context = super().each_context(request)
context["force_pwd_change"] = self.using_default_password(request)
return context
From a thread on the Django Users mailing list:
This isn't ideal, but it should work
(or prompt someone to propose
something better).
Add a one-to-one table for the user,
with a field containing the initial
password (encrypted, of course, so it
looks like the password in the
auth_user table).
When the user logs in, have the login
page check to see if the passwords
match. If they do, redirect to the
password change page instead of the
normal redirect page.
Checkout this simple package based on session (Tested with django 1.8). https://github.com/abdullatheef/django_force_reset_password
Create custom view in myapp.views.py
class PassWordReset(admin.AdminSite):
def login(self, request, extra_context=None):
if request.method == 'POST':
response = super(PassWordReset, self).login(request, extra_context=extra_context)
if response.status_code == 302 and request.user.is_authenticated():
if not "fpr" in request.session or request.session['fpr']:
request.session['fpr'] = True
return HttpResponseRedirect("/admin/password_change/")
return response
return super(PassWordReset, self).login(request, extra_context=extra_context)
def password_change(self, request, extra_context=None):
if request.method == 'POST':
response = super(PassWordReset, self).password_change(request, extra_context=extra_context)
if response.status_code == 302 and request.user.is_authenticated():
request.session['fpr'] = False
return response
return super(PassWordReset, self).password_change(request, extra_context=extra_context)
pfr_login = PassWordReset().login
pfr_password_change = PassWordReset().admin_view(PassWordReset().password_change, cacheable=True)
Then in project/urls.py
from myapp.views import pfr_password_change, pfr_login
urlpatterns = [
......
url(r'^admin/login/$', pfr_login),
url(r'^admin/password_change/$', pfr_password_change),
url(r'^admin/', admin.site.urls),
....
]
Then add this middleware myapp/middleware.py
class FPRCheck(object):
def process_request(self, request):
if request.user.is_authenticated() \
and re.match(r'^/admin/?', request.path) \
and (not "fpr" in request.session or ("fpr" in request.session and request.session['fpr'])) \
and not re.match(r"/admin/password_change|/admin/logout", request.path):
return HttpResponseRedirect("/admin/password_change/")
Order of middleware
MIDDLEWARE_CLASSES = [
....
'myapp.middleware.FPRCheck'
]
Note
This will not need any extra model.
Also work with any Session Engine.
No db query inside middleware.