I'm a little bit confused on how Django's views work. I thought it worked this way:
User pressed a button on a html page
The action is linked to a view, so it goes to the function as defined in the url.py, and then it does some logic.
However below I have an index.html page that will redirect the user to a login page if the user is NOT logged in:
def index(request):
if not request.user.is_authenticated():
return redirect('/login.html')
else:
result = Hello_World.delay()
somethingDownByCelery = result.get(timeout=2)
context = {'somethingDownByCelery': somethingDownByCelery, 'userName': request.user.username}
return render(request, 'webapp/index.html', context)
Then I have a login.html, I have a logger that records users behaviors on each of the webpage.
def loginUser(request):
logger = logging.getLogger('views.logger.login')
try:
username = request.POST['username'];
logger.info("User:" + username + " in Login Page");
except:
logger.error("Cannot Identify User");
type = ""
try:
type = request.POST['submit']
logger.info("User:" + username + " requests:" + type);
except MultiValueDictKeyError:
logger.error("Cannot Identify User's Request");
try:
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('index.html')
else:
return redirect('disabled.html')
else:
condition = "Invalid Login"
context = {'condition': condition}
return render(request, 'webapp/login.html', context)
except MultiValueDictKeyError:
context = None
return render(request, 'webapp/login.html', context)
The problem is that when the webpage is refreshed, or redirected to, it will get two logger.error in the two exceptions when I'm trying to request POST with username and submit, because I thought the behavior was 1(press the button in the webpage) then 2(run the function in views).
However, somehow it goes through the whole function first then generate a webpage, which is a 3 step procedure?
When Django executes a redirect, it executes the code for that view first, before rendering the actual page. Your code is executing loginUser(), and is triggering exceptions in both the first and second try blocks, which causes your logger statements.
So assuming you're coming from index and are not authenticated, the process goes something like:
index()
redirect('/login.html') [this will call whatever view is mapped to that url; you may want to consider using the url resolution django offers]
loginUser()
return render(request, 'webapp/login.html', context)
create and return the html to the user
Related
I am using Django defender, watch_login() decorator to add brute-force prevention to my custom login view:
def user_login(request):
if request.user.is_authenticated:
if request.user.is_active:
return redirect('home:home')
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('home:home')
else:
return HttpResponse('Account is disabled')
else:
messages.error(request,'Sorry, the username or password you entered is not correct')
return redirect('main:user_login')
return render(request, 'main/user_login.html')
However, when I wrap the function with the decorator in my urls.py file I get the error:
'function' object has no attribute 'get'.
I have tried using the decorator in views.py with no arguments by simply doing #watch_login(). However, that does not seem to be working. How can I add this wrapper to my function in the urls.py file?
I have also tried this:Wrapping/decorating a function in urls.py vs in views.py
And I still get the same error ('function' object has no attribute 'get')
EDIT
I have also tried HttpResponseRedirect using the following method yet the user does not get blocked after reaching maximum login limits:
When user submit wrong credentials I do:
url = reverse('main:user_login')
return HttpResponseRedirect(url, status=302)
And as my decorator I have:
#watch_login(status_code=302)
Which still seems not to be working. And when I do cleanup_django_defender I can see there are no Attempts stored in my database.
EDIT
I had to render the template again on incorrect login rather than redirecting since it will be redirected with a new HttpStatus and watch_login() will never return true.
So now on a failed login attempt I simply have:
return render(request, 'main/user_login.html')
However, the issue with this approach is that the POST request parameters will not reset and as per Django documentation it is best practice to redirect after for submission
From #watch_login() docstring:
To make it work on normal functions just pass the status code that
should indicate a failure and/or a string that will be checked within
the response body.
Which means your user_login() function should return a specific status code (and/or a message) on error.
Example from tests 1:
#watch_login(status_code=401)
def fake_api_401_login_view_without_msg(request):
""" Fake the api login with 401 """
return HttpResponse(status=401)
Example from tests 2:
#watch_login(status_code=401, msg="Invalid credentials")
def fake_api_401_login_view_without_msg(request):
""" Fake the api login with 401 """
return HttpResponse("Sorry, Invalid credentials", status=401)
EDIT
If you redirect on failed login this condition from #watch_login() will never evaluate to true (because response.status_code == 302).
if status_code == 302: # standard Django login view
login_unsuccessful = (
response
and not response.has_header("location")
and response.status_code != status_code
)
I want to accomplish the following, email verification with a verification code, not link
user signs up in a sign up form
user email is passed as context to a verification code entry form
user is sent a verification code
user enters the verification code in the verification code entry form
verification code is confirmed on the backend with the email sent from the first form context
So what I'm trying to do is that when the user accesses example.com/signup and fill out the form, I then render the verification_code_entry_form, and then they enter their verification code there. Finally once they submit that, they are redirected to login. However the difference between render and redirect is tripping me up.
in urls.py
path('signup', views.signup_page, name='signup'),
path('confirm_account', views.confirm_account, name="confirm_account"),
path('send_verification_code', views.send_verification_code, name="send_verification_code"),
in views.py
def signup_page(request):
form = CreateUserForm(request.POST or None)
context = {
'form': form
}
if form.is_valid():
new_user = form.save(commit=False)
password = form.cleaned_data.get('password')
email = form.cleaned_data.get('email')
try:
...backend authentication client work goes here
except Exception as e:
...catch exceptions here
messages.error(
request,
f'An unexpected error has occured.'
)
return render(request, 'accounts/signup.html', context)
else:
# Create user and verifications
new_user.save()
# Save the email in the context to use in the verification form
context['email'] = email
form = AccountConfirmationForm(request.POST or None)
context['form'] = form
messages.success(
request,
f'Account succesfully created for {email}. Please verify your e-mail, then login.'
)
# Redirect (render?) to verification code formation
return render(request, 'confirm_account.html', context)
return render(request, 'signup.html', context)
def confirm_account_verification_code(request):
if not request.get('email'):
"""
This represents a failure of the client to properly navigate to the verification code from signup or login
So we should prompt them to reset their verification code and re-render with context
"""
messages.error(request, f"An error occurred when processing your verification code request. Please try again")
return redirect(request, "resend_verification_code")
email = request.get('email')
form = AccountConfirmationForm(request.POST or None)
context = {
'email': email,
'form': form,
}
if form.is_valid():
try:
verification_code = form.cleaned_data.get('verification_code')
...submit to backend
except Exception as e:
...handle exceptions
messages.error(
request,
f'An error occurred trying to confirm your account'
)
return render(request, "confirm_account.html", context)
else:
return redirect(reverse('signup_success') + '?first_time=yes')
return render(request, "confirm_account.html", context)
Unfortunately my expected behavior isn't happening, though I acknowledge this doesn't seem quite right. Does django actually use the view function confirm_account_verification_code() when the return render(request, 'confirm_account.html', context) is called from the sign up form? I'm not entirely sure what I'm doing wrong here.
I have a view that handles the password change for a user
#csrf_exempt
def change_password(request):
if request.method == "POST":
user_request = json.loads(request.body.decode('utf-8'))
try:
u = User.objects.get(username=user_request.get("user"))
u.set_password(user_request.get("password"))
u.save()
except Exception as e:
print(e)
return HttpResponse("Failure")
return HttpResponseRedirect('/')
This does change the password for the user since next time if I have to login, I have to supply the new password.But it doesn't redirect the user immediately to the home page (which in turn will redirect to login page since the user is logged out).
I have a similar view for login given below
#csrf_exempt
def login_view(request):
if request.POST:
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return HttpResponseRedirect('/')
else:
return render(request, "login.html")
else:
return render(request, "login.html")
But it does redirect the user to the home page if the authentication is successful.
Then why doesn't any redirection happens when the user changes their password?I instead get a 302 status code.
"302 Found" is a re-direct. It includes a "Location" header in the HTTP response, telling the browser where to go instead.
In django is it the only way to have only one view for one whole page/url. And whatever functions(upload/post/update/log-in) that page contains just needs to pass inside that view. I found this is the only way as i can only return one url with one view.
I am wondering if there has any way where i can make different view(may be classed base or normal) for each function and at last add all of them on one single view(that view return that url also). If it is possible than how ? Because having all the functions of a url inside one view is looking weird and messy to me.
##################
def logInRegisterUser(request):
###################login##################
loginForm = UserLoginForm(request.POST or None)
if loginForm.is_valid() and 'log-in' in request.POST:
username = loginForm.cleaned_data.get("username")
password = loginForm.cleaned_data.get("password")
user = authenticate(username = username, password = password)
# if not user or not user.check_password(password):
# raise validation error
login(request, user)
print(request.user.is_authenticated())
###################registration###################
registrationForm = RegistrationForm(request.POST or None)
if registrationForm.is_valid() and 'sign-up' in request.POST:
user2 = registrationForm.save(commit = False)
password2 = registrationForm.cleaned_data.get('password')
user2.set_password(password2)
user2.save()
new_user = authenticate(username = user2.username, password = password2)
login(request, new_user)
###################log-out###################
###################search-post###################
####################voting-post##################
context = {
"loginForm":loginForm,
"registrationForm":registrationForm,
"re":request.POST
}
###################return###################
return render(request,"enter.html",context)
you can merge the response from another class. You can merge multiple class response into to a single view.
Django - Having two views in same url
Im new to Django and I know that to redirect after login I have to set the parameter 'page'. But this only works when the login is successful. How can i do the same thing when some error occurs??
Ps: Im currently also using django-registration with simple backend
I think it's what you are looking for:
# Login
def connection(request):
# Redirect to dashboard if the user is log
if request.user.is_authenticated():
return redirect('YourProject.views.home')
# Control if a POST request has been sent.
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None: #Verify form's content existence
if user.is_active: #Verify validity
login(request, user)
return redirect('/index') #It's ok, so go to index
else:
return redirect('/an_url/') #call the login view
return render(request, 'login.html', locals()) #You can remove local() it is for user's data viewing..