105👍
Middleware may be your best bet. I’ve used this piece of code in the past, modified from a snippet found elsewhere:
import re
from django.conf import settings
from django.contrib.auth.decorators import login_required
class RequireLoginMiddleware(object):
"""
Middleware component that wraps the login_required decorator around
matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and
define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your
settings.py. For example:
------
LOGIN_REQUIRED_URLS = (
r'/topsecret/(.*)$',
)
LOGIN_REQUIRED_URLS_EXCEPTIONS = (
r'/topsecret/login(.*)$',
r'/topsecret/logout(.*)$',
)
------
LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must
be a valid regex.
LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly
define any exceptions (like login and logout URLs).
"""
def __init__(self):
self.required = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS)
self.exceptions = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)
def process_view(self, request, view_func, view_args, view_kwargs):
# No need to process URLs if user already logged in
if request.user.is_authenticated():
return None
# An exception match should immediately return None
for url in self.exceptions:
if url.match(request.path):
return None
# Requests matching a restricted URL pattern are returned
# wrapped with the login_required decorator
for url in self.required:
if url.match(request.path):
return login_required(view_func)(request, *view_args, **view_kwargs)
# Explicitly return None for all non-matching requests
return None
Then in settings.py, list the base URLs you want to protect:
LOGIN_REQUIRED_URLS = (
r'/private_stuff/(.*)$',
r'/login_required/(.*)$',
)
As long as your site follows URL conventions for the pages requiring authentication, this model will work. If this isn’t a one-to-one fit, you may choose to modify the middleware to suit your circumstances more closely.
What I like about this approach – besides removing the necessity of littering the codebase with @login_required
decorators – is that if the authentication scheme changes, you have one place to go to make global changes.
32👍
There is an alternative to putting a decorator on each view function. You can also put the login_required()
decorator in the urls.py
file.
While this is still a manual task, at least you have it all in one place, which makes it easier to audit.
e.g.,
from my_views import home_view urlpatterns = patterns('', # "Home": (r'^$', login_required(home_view), dict(template_name='my_site/home.html', items_per_page=20)), )
Note that view functions are named and imported directly, not as strings.
Also note that this works with any callable view object, including classes.
- [Django]-Why does DEBUG=False setting make my django Static Files Access fail?
- [Django]-How to test auto_now_add in django
- [Django]-Whats the difference between using {{STATIC_URL}} and {% static %}
5👍
In Django 2.1, we can decorate all methods in a class with:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
UPDATE:
I have also found the following to work:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
class ProtectedView(LoginRequiredMixin, TemplateView):
template_name = 'secret.html'
and set LOGIN_URL = '/accounts/login/'
in your settings.py
- [Django]-CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False
- [Django]-Django-allauth: Linking multiple social accounts to a single user
- [Django]-Django index page best/most common practice
3👍
Here is a middleware solution for django 1.10+
The middlewares in have to be written in a new way in django 1.10+.
Code
import re
from django.conf import settings
from django.contrib.auth.decorators import login_required
class RequireLoginMiddleware(object):
def __init__(self, get_response):
# One-time configuration and initialization.
self.get_response = get_response
self.required = tuple(re.compile(url)
for url in settings.LOGIN_REQUIRED_URLS)
self.exceptions = tuple(re.compile(url)
for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
# No need to process URLs if user already logged in
if request.user.is_authenticated:
return None
# An exception match should immediately return None
for url in self.exceptions:
if url.match(request.path):
return None
# Requests matching a restricted URL pattern are returned
# wrapped with the login_required decorator
for url in self.required:
if url.match(request.path):
return login_required(view_func)(request, *view_args, **view_kwargs)
# Explicitly return None for all non-matching requests
return None
Installation
- Copy the code into your project folder, and save as middleware.py
-
Add to MIDDLEWARE
MIDDLEWARE = [
…
‘.middleware.RequireLoginMiddleware’, # Require login
] - Add to your settings.py:
LOGIN_REQUIRED_URLS = (
r'(.*)',
)
LOGIN_REQUIRED_URLS_EXCEPTIONS = (
r'/admin(.*)$',
)
LOGIN_URL = '/admin'
Sources:
-
This answer by Daniel Naab
-
Django Middleware tutorial by Max Goodridge
- [Django]-Error when using django.template
- [Django]-Testing nginx without domain name
- [Django]-Django models: mutual references between two classes and impossibility to use forward declaration in python
2👍
It’s hard to change the built-in assumptions in Django without reworking the way url’s are handed off to view functions.
Instead of mucking about in Django internals, here’s an audit you can use. Simply check each view function.
import os
import re
def view_modules( root ):
for path, dirs, files in os.walk( root ):
for d in dirs[:]:
if d.startswith("."):
dirs.remove(d)
for f in files:
name, ext = os.path.splitext(f)
if ext == ".py":
if name == "views":
yield os.path.join( path, f )
def def_lines( root ):
def_pat= re.compile( "\n(\S.*)\n+(^def\s+.*:$)", re.MULTILINE )
for v in view_modules( root ):
with open(v,"r") as source:
text= source.read()
for p in def_pat.findall( text ):
yield p
def report( root ):
for decorator, definition in def_lines( root ):
print decorator, definition
Run this and examine the output for def
s without appropriate decorators.
- [Django]-How does django handle multiple memcached servers?
- [Django]-Django models: default value for column
- [Django]-How can I get the full/absolute URL (with domain) in Django?
2👍
As of Django 3+, you can change the default like the followings:
Step 1: Create a new file anything.py in your yourapp directory and write the following:
import re
from django.conf import settings
from django.contrib.auth.decorators import login_required
//for registering a class as middleware you at least __init__() and __call__()
//for this case we additionally need process_view() which will be automatically called by Django before rendering a view/template
class ClassName(object):
//need for one time initialization, here response is a function which will be called to get response from view/template
def __init__(self, response):
self.get_response = response
self.required = tuple(re.compile(url) for url in settings.AUTH_URLS)
self.exceptions = tuple(re.compile(url)for url in settings.NO_AUTH_URLS)
def __call__(self, request):
//any code written here will be called before requesting response
response = self.get_response(request)
//any code written here will be called after response
return response
//this is called before requesting response
def process_view(self, request, view_func, view_args, view_kwargs):
//if authenticated return no exception
if request.user.is_authenticated:
return None
//default case, no exception
return login_required(view_func)(request, *view_args, **view_kwargs)
Step 2: Add this anything.py to Middleware[] in project/settings.py like followings
MIDDLEWARE = [
// your previous middleware
'yourapp.anything.ClassName',
]
- [Django]-Is it bad to have my virtualenv directory inside my git repository?
- [Django]-How do I POST with jQuery/Ajax in Django?
- [Django]-Celery discover tasks in files with other filenames
1👍
Inspired by Ber’s answer I wrote a little snippet that replaces the patterns
function, by wrapping all of the URL callbacks with the login_required
decorator. This works in Django 1.6.
def login_required_patterns(*args, **kw):
for pattern in patterns(*args, **kw):
# This is a property that should return a callable, even if a string view name is given.
callback = pattern.callback
# No property setter is provided, so this will have to do.
pattern._callback = login_required(callback)
yield pattern
Using it works like this (the call to list
is required because of the yield
).
urlpatterns = list(login_required_patterns('', url(r'^$', home_view)))
- [Django]-Substring in a django template?
- [Django]-How do I install psycopg2 for Python 3.x?
- [Django]-How to use subquery in django?
1👍
Would be possible to have a single starting point for all the urls
in a sort of include and that decorate it using this packages https://github.com/vorujack/decorate_url.
- [Django]-In a django model custom save() method, how should you identify a new object?
- [Django]-Django filter queryset __in for *every* item in list
- [Django]-Django error – matching query does not exist
1👍
There’s an app that provides a plug-and-play solution to this:
https://github.com/mgrouchy/django-stronghold
pip install django-stronghold
# settings.py
INSTALLED_APPS = (
#...
'stronghold',
)
MIDDLEWARE_CLASSES = (
#...
'stronghold.middleware.LoginRequiredMiddleware',
)
- [Django]-Django model one foreign key to many tables
- [Django]-Django: FloatField or DecimalField for Currency?
- [Django]-Django: Why do some model fields clash with each other?
1👍
This is the answer for Newer versions of Django. It works pretty well!
https://pypi.org/project/django-login-required-middleware/
works with:
- Python: 3.6, 3.7, 3.8
- Django: 1.11, 2.0, 2.1, 2.2, 3.x
- [Django]-Django: manage.py does not print stack trace for errors
- [Django]-Iterating over related objects in Django: loop over query set or use one-liner select_related (or prefetch_related)
- [Django]-How to obtain and/or save the queryset criteria to the DB?
0👍
You can’t really win this. You simply must make a declaration of the authorization requirements. Where else would you put this declaration except right by the view function?
Consider replacing your view functions with callable objects.
class LoginViewFunction( object ):
def __call__( self, request, *args, **kw ):
p1 = self.login( request, *args, **kw )
if p1 is not None:
return p1
return self.view( request, *args, **kw )
def login( self, request )
if not request.user.is_authenticated():
return HttpResponseRedirect('/login/?next=%s' % request.path)
def view( self, request, *args, **kw ):
raise NotImplementedError
You then make your view functions subclasses of LoginViewFunction
.
class MyRealView( LoginViewFunction ):
def view( self, request, *args, **kw ):
.... the real work ...
my_real_view = MyRealView()
It doesn’t save any lines of code. And it doesn’t help the “we forgot” problem. All you can do is examine the code to be sure that the view functions are objects. Of the right class.
But even then, you’ll never really know that every view function is correct without a unit test suite.
- [Django]-IOS app with Django
- [Django]-Retrieving a Foreign Key value with django-rest-framework serializers
- [Django]-No module named MySQLdb