--
Ticket URL: <https://code.djangoproject.com/ticket/31405>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* stage: Unreviewed => Accepted
Comment:
I'll Accept based on the mailing list discussion. Thanks.
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:1>
* owner: nobody => Mehmet İnce
* status: new => assigned
* has_patch: 0 => 1
Comment:
https://github.com/django/django/pull/12632
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:2>
* owner: Mehmet İnce => mdisec
* needs_better_patch: 0 => 1
* needs_tests: 0 => 1
* needs_docs: 0 => 1
Comment:
Thanks Mehmet.
[https://github.com/django/django/pull/12632#pullrequestreview-444596884
Comments on PR] — Please uncheck flags when address to put it back in the
review queue.
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:3>
* needs_better_patch: 1 => 0
* has_patch: 1 => 0
* needs_tests: 1 => 0
Comment:
Replying to [comment:3 Carlton Gibson]:
> Thanks Mehmet.
[https://github.com/django/django/pull/12632#pullrequestreview-444596884
Comments on PR] — Please uncheck flags when address to put it back in the
review queue.
Thansk for the review Carlton. I believe that I solved the issues you
pointed.
There were nice people from the mailing list who are willing to help out
with docs. Once we are finished everything, I'll ping them for the docs :)
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:4>
* has_patch: 0 => 1
Comment:
Re-set the ''has patch'' flag removed by mistake.
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:5>
Comment (by Michael):
I am very interested in this new feature. Will it have a way to mark
function and class based views as no login requied?
Probably too late but heres some code from my solution:
A decorator to mark a view/function as no longer required:
{{{
from functools import wraps
def login_not_required(obj):
"""Adds the attrbiute login_not_required = True to the object
(func/class).
Use it as follows:
@login_not_required
class FooView(generic.View):
...
@login_not_required
def bar_view(request):
...
"""
@wraps(obj)
def decorator():
obj.login_not_required = True # For general pages
obj.permission_classes = [] # For REST framework
return obj
return decorator()
}}}
Middleware:
{{{
# settings.py
NONE_AUTH_ACCOUNT_PATHS = [
....
'/accounts/password_reset/',
'/accounts/reset/',
]
# middleware.py
class RequireLoginCheck:
"""Middleware to require authentication on all views by default,
except when allowed.
URLS can be opened by adding them to NONE_AUTH_ACCOUNT_PATHS, or by
adding
the @login_not_required decorator.
Must appear below the sessions middleware because the sessions
middleware
adds the user to the request, which is used by this middleware.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def _is_none_auth_path(self, path):
for none_auth_path in NONE_AUTH_ACCOUNT_PATHS:
if path.startswith(none_auth_path):
return True
return False
def _is_login_not_required(self, view_func):
with suppress(AttributeError):
# If a class with the @login_not_required decorator, will
return True
return view_func.view_class.login_not_required
with suppress(AttributeError):
# If a function with the @login_not_required decorator, will
return True
return view_func.login_not_required
return False
def _is_open_rest_view(self, view_func):
try:
klass = view_func.view_class
except AttributeError:
return False
if not issubclass(view_func.view_class, APIView):
return False
else:
auth_classes = getattr(klass, 'authentication_classes', None)
perm_classes = getattr(klass, 'permission_classes', None)
# if auth_classes and perm_classes are empty list/tuples, then
don't require login checks
no_login_required = (
auth_classes is not None
and not auth_classes
and perm_classes is not None
and not perm_classes
)
return no_login_required
def log_unauthorised_request(self, request, view_func, view_args,
view_kwargs):
get_response = lambda: HTTP_NO_RESPONSE
reason = CsrfViewMiddleware(get_response).process_view(request,
None, (), {})
s = ["base.auth.middleware.RequireLoginCheck"]
s.append(f"User: {request.user}")
s.append(f"Method: {request.method}")
s.append(f"URL: {request.path}")
s.append(f"IP: {get_ip(request)}")
s.append(f"Reason: {reason}")
s.append(f"Open URL (is_login_not_required):
{self._is_login_not_required(view_func)}")
s.append(f"is_none_auth_path:
{self._is_none_auth_path(request.path)}")
s.append(f"HEADERS: {request.headers}")
s.append(f"GET: {request.GET}")
s.append(f"POST: {request.POST}")
if LOGGING:
log_info(', '.join(s))
if settings.DEBUG and not request.path.startswith('static'):
print(', '.join(s))
def process_view(self, request, view_func, view_args, view_kwargs):
"""https://docs.djangoproject.com/en/stable/topics/http/middleware
/#other-middleware-hooks"""
if not (
request.user.is_authenticated
or self._is_login_not_required(view_func)
or self._is_open_rest_view(view_func)
or self._is_none_auth_path(request.path)
):
self.log_unauthorised_request(request, view_func, view_args,
view_kwargs)
if settings.LOGIN_URL != request.path:
# if next URL after login is the same login URL, then
cyclic loop
return redirect('%s?next=%s' % (settings.LOGIN_URL,
request.path))
else:
return redirect('%s?next=%s' % (settings.LOGIN_URL, '/'))
return None
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:6>
* owner: Mehmet INCE => Hisham Mahmood
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:7>
* needs_docs: 1 => 0
Comment:
[https://github.com/django/django/pull/17792 PR]
--
Ticket URL: <https://code.djangoproject.com/ticket/31405#comment:8>