Django Redirect If Authenticated Mixin - python-3.x

i want to create a mixin to be redirect user to a specified page if they're already authenticated. i want to be able to use this mixin in different parts of the application without having to rewrite the logic over and over again.
i get a accounts.views.view didn't return an HttpResponse object. It returned None instead. error if the user is not authenticated but it works if user is authenticated. accounts is the app_name
here's my code in mixin.py
class RedirectIfAuthenticatedMixin:
"""
RedirectIfAuthenticatedMixin: redirect authenticated user to different page via redirect_to parameter
"""
redirect_to = None
def get(self, request):
"""
Get request handler to check if user is already authenticated
then redirect user to specified url with redirect_to
"""
if request.user.is_authenticated:
return HttpResponseRedirect(self.get_redirect_url())
# return ??? <- WHAT TO WRITE HERE TO ALLOW REQUEST TO CONTINUE EXECUTION
def get_redirect_url(self):
"""
Get the specified redirect_to url
"""
if not self.redirect_to:
raise ImproperlyConfigured('no url to redirect to. please specify a redirect url')
return str(self.redirect_to)
it works when i add this to the view itself
class RegisterView(RedirectIfAuthenticatedMixin, FormView):
"""
RegisterView: form view to handle user registration
"""
template_name = 'registration/register.html'
success_url = reverse_lazy('accounts:activation-sent')
form_class = RegistrationForm
def form_valid(self, form):
"""
Method to handle form submission and validation
"""
# save user information
user = form.save(commit = False)
user.email = form.cleaned_data['email']
user.set_password(form.cleaned_data['password'])
user.is_active = False
user.save()
# email configuration/compose to send to user
current_site = get_current_site(self.request)
subject = 'Activate Your Padumba Account'
message = render_to_string('registration/account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
# send the account confirmation email to the user
user.email_user(subject = subject, message = message)
# send a flash message to the user
messages.success(self.request, ('Check Your Email For Account Activation Link'))
return super().form_valid(form)
def get(self, request, *args, **kwargs):
if request.user.is_authenticated:
return HttpResponseRedirect(reverse_lazy('accounts:index'))
return super(RegisterView, self).get(request, *args, **kwargs)

The logic will not be evaluated if there is an override of the get method for that view. You probably better override the dispatch method. This will also prevent making POST, PUT, PATCH, DELETE, etc. requests to the view:
class RedirectIfAuthenticatedMixin:
redirect_to = None
def get_redirect_url(self):
if not self.redirect_to:
raise ImproperlyConfigured('no url to redirect to. please specify a redirect url')
return str(self.redirect_to)
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated:
return HttpResponseRedirect(self.get_redirect_url())
return super().dispatch(request, *args, **kwargs)
But you can actually make this a special case of the UserPassesTestMixin mixin [Django-doc]:
from django.contrib.auth.mixins import UserPassesTestMixin
class RedirectIfAuthenticatedMixin(UserPassesTestMixin):
redirect_to = None
def get_redirect_url(self):
if not self.redirect_to:
raise ImproperlyConfigured('no url to redirect to. please specify a redirect url')
return str(self.redirect_to)
def handle_no_permission(self):
return HttpResponseRedirect(self.get_redirect_url())
def test_func(self):
return not self.request.user.is_authenticated

#coder-six
I think implementation of such class will require you handle the main request
in implementing class i.e. implement also get method in the child class and
then from there call the get method of the of your mixin class. To make things easier you can change the RedirectIfAuthenticatedMixin get method to redirect_if_authenticated.
You will have to then call this method in every child class where you want this functionality.
Example:
class RedirectIfAuthenticatedMixin:
"""
RedirectIfAuthenticatedMixin: redirect authenticated user to different page via redirect_to parameter
"""
redirect_to = None
def redirect_if_authenticated(self, request):
"""
Get request handler to check if user is already authenticated
then redirect user to specified url with redirect_to
"""
if request.user.is_authenticated:
return HttpResponseRedirect(self.get_redirect_url())
#other methods of class here ....
class RegisterView(FormView, RedirectIfAuthenticatedMixin):
"""
RegisterView: form view to handle user registration
"""
def form_valid(self, form):
"""
Method to handle form submission and validation
"""
pass
# THIS WORKS,...
def get(self, request, *args, **kwargs):
self.redirect_if_authenticated(request)
return #continue your request here if not authenticated
But I also think using a decorator might make things eaisier for you.
Decorator:
def redirect_on_authenticated(func):
def wrapper(self, request, *args, **kwargs):
if request.user.is_authenticated:
return HttpResponseRedirect(self.get_redirect_url())
else:
return func(self, request, *args, **kwargs)
return wrapper
class RegisterView(FormView, RedirectIfAuthenticatedMixin):
"""
RegisterView: form view to handle user registration
"""
def form_valid(self, form):
"""
Method to handle form submission and validation
"""
pass
#redirect_on_authenticated
def get(self, request, *args, **kwargs):
self.redirect_if_authenticated(request)
return #continue your request here if not authenticated

Related

How do I apply Django middleware everywhere except for a single path?

I'm using Python 3.9 with Django 3. I have defined this middleware ...
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'directory.middleware.extend_token_response.ExtendTokenResponse'
]
However, I don't want the middleware to apply to a certain URL. I have hard-coded this in the middleware like so
class ExtendTokenResponse:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
response = self.get_response(request)
if request.path != '/' + LOGOUT_PATH:
# Code to be executed for each request before
# the view (and later middleware) are called.
is_expired = True
try:
token = request.auth
print("req path: %s" % request.path)
is_expired = is_token_expired(token) if token else True
except Exception as err:
print(err)
if not is_expired:
but this seems a little sloppy and I would think the middleware comes with somethign out of the box to configure that this wouldn't need to be applied to my "/logout" path. Is there a more elegant way to configure this?
Edit: In response to Bernhard Vallant's answer, I changed my middleware to the below
def token_response_exempt(view_func):
# Set an attribute on the function to mark it as exempt
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.token_response_exempt = True
return wraps(view_func)(wrapped_view)
class ExtendTokenResponse:
def init(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def process_view(self, request, view_func, view_args, view_kwargs):
print("in process view method ...\n")
if getattr(view_func, "token_response_exempt", False):
print("returning none ...\n")
return None
# Code to be executed for each request before
# the view (and later middleware) are called.
is_expired = True
try:
token = request.auth
print("req path: %s" % request.path)
is_expired = is_token_expired(token) if token else True
except Exception as err:
print(err)
if not is_expired:
token.delete()
new_token = Token.objects.create(user = token.user)
# Code to be executed for each request/response after
# the view is called.
print("setting new token to %s" % new_token)
request.token = new_token
def __call__(self, request):
response = self.get_response(request)
print("---- in call method ----\n")
if getattr(request, "token", None) is not None:
print("setting refresh token header = %s" % request.token)
response['Refresh-Token'] = request.token
return response
but any call to an endpoint, e.g.,
curl --header "Content-type: application/json" --data "$req" --request POST "http://localhost:8000/login"
results in no token being retrieved from the reqeust. "request.auth" generates the error
'WSGIRequest' object has no attribute 'auth'
Django itself doesn't provide a solution for this. Probably hardcoding/defining paths in your settings/middleware is fine as long it is a middleware that primarly exists for one specific project.
However if you want to mark certain views to exclude them from being processed you could use decorators in the same way Django does with the csrf_exempt decorator.
from functools import wraps
def token_response_exempt(view_func):
# Set an attribute on the function to mark it as exempt
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.token_response_exempt = True
return wraps(view_func)(wrapped_view)
# your middleware
class ExtendTokenResponse:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if getattr(request, "token", None) is not None:
response['Refresh-Token'] = request.token
return response
def process_view(self, request, view_func, view_args, view_kwargs):
if getattr(view_func, "token_response_exempt", False):
return None
# do your token generation here
request.token = token
And then you can use decorator like the following:
# urls.py
urlpatterns = [
path('logout/', token_response_exempt(LogOutView.as_view())),
]
About your case, I have 2 recommendations below:
Method 1: use process_view and define a list func will be excluded with structure "app.module.func" and check to skip in process_view
# In settings.py
EXCLUDE_FROM_MY_MIDDLEWARE =set({'custom_app.views.About'})
# In middlewares.py
class ExtendTokenResponse:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
logger.info(f'request hit request {request}')
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
def process_view(self, request, view_func, view_args, view_kwargs):
view_function = '.'.join((view_func.__module__, view_func.__name__))
exclusion_set=getattr(settings,'EXCLUDE_FROM_MY_MIDDLEWARE',set() )
if view_function in exclusion_set:
return None
Method 2: Use decorator_from_middleware and apply middleware to each function needed it.
from django.utils.decorators import decorator_from_middleware
# with function view
#decorator_from_middleware(ExtendTokenResponse)
def view_function(request):
...
#with class view
class SimpleMiddlewareMixin:
#decorator_from_middleware(ExtendTokenResponse)
def dispatch(*args, **kwargs):
return super().dispatch(*args, **kwargs)
class MyClassBasedView(SimpleMiddlewareMixin, ListView):

Send email after returning response to user in django

I want to send verification email to new users. But because of slow connection it takes a long time to send an email and user will see a CONNECTION TIMED OUT error. I want to return the response to user and after that send the email. I already tried StreamingHttpResponse and using yield. But it returned a creepy response instead of the response I wanted.
I prefer to do it using function view but there is no problem if I HAVE to use class view. What should I do?
from django.shortcuts import render
from django.http import HttpRequest
def sign_up (request: HttpResponse):
method = request.method
if method == 'GET':
# checking cookies
return render(request, "template name", context)
if method == 'POST':
# validating data
if unvalid_data:
return render(request, "template name", context)
# saving data
return render(request, "template name", context)
# sending email
Thanks to NeuronQ in this question I got this Idea and it's working:
# myclass.py
from django.http import HttpResponse
__all__ = ['do_after_render']
class do_after_response (HttpResponse):
def __init__(self, do_after, content=b'', do_after_args=[], *args, **kwargs):
self.do_after = do_after
self.do_after_args = do_after_args
super().__init__(content=content, *args, **kwargs)
def close(self):
super().close()
self.do_after(*self.do_after_args)
def do_after_render (do_after, request, template_name, context=None, do_after_args=[], content_type=None, status=None, using=None):
content = render_to_string(template_name, context, request, using=using)
return do_after_response(do_after=do_after, content=content, do_after_args=do_after_args, content_type=content_type, status=status)
# views.py
from .myclass import *
from django.shortcuts import render
from django.http import HttpRequest
def send_mail (some_args, some_other_args):
# sending email
def sign_up (request: HttpResponse):
method = request.method
if method == 'GET':
# checking cookies
return render(request, "template name", context)
if method == 'POST':
# validating data
if unvalid_data:
return render(request, "template name", context)
# saving data
do_after_args = [some_args, some_other_args]
return do_after_render(send_mail, request, "template name", context, do_after_args)

How to push post parameter into scrapy-redis

I have a post request like
def start_requests(self):
yield FormRequest(url,formdata={'id': "parameter from redis"})
Can I use redis-cli lpush to save post parameter and that my crawler run it?
By default the scrapy-redis queue working only with url as messages.
One message = one url. But you can modify this behavior.
For example you can use some object for your messages/requests:
class ScheduledRequest:
def __init__(self, url, method, body)
self.url = url
self.method = method
self.body = body
Pass it to queue as json encoded dic:
redis.lpush(
queue_key,
json.dumps(
ScheduledRequest(
url='http://google.com',
method='POST',
body='some body data ...'
).__dict__
)
)
And rewrite the make_request_from_data and schedule_next_requests methods:
class MySpiderBase(RedisCrawlSpider, scrapy.Spider):
def __init__(self, *args, **kwargs):
super(MySpiderBase, self).__init__(*args, **kwargs)
def make_request_from_data(self, data):
scheduled = ScheduledRequest(
**json.loads(
bytes_to_str(data, self.redis_encoding)
)
)
# here you can use and FormRequest
return scrapy.Request(url=scheduled.url, method=scheduled.method, body=scheduled.body)
def schedule_next_requests(self):
for request in self.next_requests():
self.crawler.engine.crawl(request, spider=self)
def parse(self, response):
pass

Why isn't my current_user authenticated in flask-login?

My goal is to make my home view (/) a login page. Once the user logs in, a different page is render depending on its role. When I login (/auth), I see that the username and password are correctly entered. It then attempts to render /, where it tells me that my user is not authenticated and renders /login. Here are the views that describe this:
Views
#app.route("/login")
def login():
return flask.render_template('login.html')
#app.route("/", methods=["GET"])
def home():
if current_user.is_authenticated:
if current_user.is_admin():
return flask.render_template('admin_index.html')
return flask.render_template('user_index.html')
logger.info("Not authenticated. Going back to login.")
return flask.render_template('login.html')
#app.route("/auth", methods=["POST"])
def auth():
username = request.form['username']
password = request.form['password']
user = db.session.query(User).filter(User.username == username).first()
logger.info(user)
logger.info("{0}: {1}".format(username, password))
print("user exists? {0}".format(str(user != None)))
print("password is correct? " + str(user.check_password(password)))
if user and user.check_password(password):
user.is_authenticated = True
login_user(user)
return flask.redirect(url_for('home'))
return flask.redirect(url_for('login'))
The problem is that flask-login's current_user.is_authenticated is always returning False after I attempt to login. My created user is correctly created and committed to the database. Below is my User model with the necessary methods as per flask-login:
User model
class User(db.Model):
"""
A user. More later.
"""
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(128), unique=True)
hashed_password = db.Column(db.String(160))
admin = db.Column(db.Boolean)
def __init__(self, username, password="changeme123", admin=False):
self.username = username
self.set_password(password)
self.admin = admin
self.is_authenticated = False
def is_active(self):
return True
def is_authenticated(self):
return self.is_authenticated
def is_anonymous(self):
return False
def is_admin(self):
return self.admin
def get_id(self):
return self.id
def __repr__(self):
return '<User {0}>'.format(self.username)
def set_password(self, password):
self.hashed_password = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.hashed_password, password)
Here is the load_user function:
load_user
#login_manager.user_loader
def load_user(user_id):
try:
return User.query.get(User.id==user_id)
except:
return None
Why is current_user.is_authenticated returning False? I presumed that login_user(user) would make current_user == user, i.e., the one who is being authenticated in /auth, but it seems this is not the case.
You have a method named User.is_authenticated. Inside User.__init__, though, you set an attribute with the same name.
self.is_authenticated = False
This overrides the method. Then, whenever you check current_user.is_authenticated, you are accessing the attribute that's always false.
You should remove the assignment from __init__ and change is_authenticated to the following:
def is_authenticated(self):
return True
If you need it to be dynamic for some reason, rename the attribute so it doesn't shadow the method.
def is_authenticated(self):
return self._authenticated
Another problem is with your load_user function.
Instead of filtering for User.id==user_id, you are getting it. The user wasn't being returned because load_user is returning User.query.get(True) instead of User.query.get(user_id).
If you make the following change, it will work:
#login_manager.user_loader
def load_user(user_id):
try:
return User.query.get(user_id)
except:
return None
You don't have to create is_authenticated field in Database
Flask-Login will take care of that

Django rest framework. If user exist return True, False if not

Need to preform simple check: If user exist return True, False if not.I am trying to solve thise problem using custom json answers, not sure it is the right way to deal with it. Please help me .
My serializer:
User = get_user_model()
Class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (User.USERNAME_FIELD, )
My views:
#api_view(['GET'])
def UserViewSet(self, request):
if request.method == 'GET':
try:
user = User.objects.get(username=User.USERNAME_FIELD)
if User.DoesNotExist:
json = {}
json['message']= 'False'
return Response(json)
else:
json = {}
json['message']= 'True'
return Response(json)
Instead of using UserViewSet and UserSerializer, you can just use APIView for that.
class UserExistsView(APIView):
def get(self, request, *args, **kwargs):
# use this if username is in url kwargs
username = self.kwargs.get('username')
# use this if username is being sent as a query parameter
username = self.request.query_params.get('username')
try:
user = User.objects.get(username=username) # retrieve the user using username
except User.DoesNotExist:
return Response(data={'message':False}) # return false as user does not exist
else:
return Response(data={'message':True}) # Otherwise, return True

Resources