Passing dynamically created json url to ajax and display to html page - python

I am currently working on a system where the login checking of username and password is checked by a python function. If the login details are correct, it will be redirected to a profile page (which i named dashboard). My problem is that my dahsboard/profile route reutrns a json if it is a POST and has also correct login details. I want this json data to be displayed in the html file. I managed to do it but I have used the variables in my jinja template. Although I have accomplished my goal (display the credentials in the html page), I would want it to be handled by ajax. How do I accomplish that?
Below are the codes I have tried so far (passing the data to the jinja variables)
#app.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
if request.method == 'GET':
#get the username passed along with the redirect
data1= getdatafromdb('getdata1',(request.args.get('uname'),))[0][0]
data2= getdatafromdb('getdata2',(code,))[0]
if 'Error' in str(data2):
return jsonify({'status': 'error', 'message': data2[0][0]})
return render_template('dashboard.html', firstname=data2[1],
middleinitial=data2[2],
lastname=data2[3],
contact=data2[4],
code=data2[5],
affiliation=data2[6],
city=data2[7])
elif request.method == 'POST':
return True
return render_template('dashboard.html')

Currently, it appears that you are running your validation process in your /dashboard route, which is not correct if you wish to redirect your user to that very page once their credentials are validated. Instead, you need to create your separate login method with ajax. First, from the / (home) route, render the template that contains the input boxes with ajax:
home.html:
<html>
<body>
<input type='text' name='username' id='username'>
<div class='username_failed'></div>
<input type='password' name='password' id='password'>
<div class='password_failed'></div>
<button type='button' class='login'>Login</button>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('.login').click(function() {
var username = $('#username').val();
var password = $('#password').val();
$.ajax({
url: "/login",
type: "get",
data: {username: username, password:password},
success: function(response) {
if (!response.status){
$('.'+response.issue+'_failed').html('<p>'+response.message+'</p>')
}
else{
window.location.replace('/dashboard'); //redirect to dashboard
}
},
error: function(xhr) {
//Do Something to handle error
}
});
});
});
</script>
</html>
Then, the login route will valid the input dynamically from the ajax in home.html. Previously, you need to create a function to validate the username and password. A possibility is to first check if they are empty, and then query the database:
import typing
def check_if_valid(username:str, password:str) -> typing.Dict[str, typing.Any]:
if not username or not password:
return {'status':False, 'issue':'username' if not username else 'password', 'message':f'{[username, password][username]} cannot be empty'}
_username = check_valid_username_from_db(username)
_password = check_valid_password_from_db(username, password)
if not _username:
return {'status':False, 'issue':'username', 'message':'Invalid username'}
if not _password:
return {'status':False, 'issue':'password', 'message':'Invalid username or password'}
return {'status':True}
#app.route('/login')
def login():
username = flask.requests.args.get('username')
password = flask.requests.args.get('password')
_r = check_if_valid(username, password)
if _r.status:
data2= getdatafromdb('getdata2',(code,))[0]
for i, a in enumerate(['firstname', 'middleinitial', 'lastname', 'contact', 'code', 'affiliation', 'city']):
flask.session[a] = data2[i]
flask.session['user_validated'] = _r.status
return flask.jsonify(_r)
Now, all your user data, if the user was successfully validated, will be stored as part of the session. Now, you can create your dashboard page, first with the html for the dashboard:
dashboard.html:
<html>
<body>
<h1>Welcome, {{firstname}}</h1>
<h4>Your data:</h4>
{%for d in data%}
<span>{{d.title}}: {{d.value}}</span>
{%endfor%}
</body>
</html>
Then, create the dashboard route with user validator:
def isloggedin(f):
def wrapper(*args):
if not flask.session['user_validated']:
return '<p>Not logged in</p>'
return f(*args)
return wrapper
#app.route('/dashboard', methods=['GET'])
#isloggedin
def dashboard():
from collections import namedtuple
headers = ['firstname', 'middleinitial', 'lastname', 'contact', 'code', 'affiliation', 'city']
data = namedtuple('data', ['title', 'value'])
return flask.render_template('dashboard.html', firstname = flask.session['firstname'], data = [data(a, flask.session[a]) for a in headers[1:]])
Lastly, link all together with the home route:
#app.route('/', methods=['GET'])
def home():
return flask.render_template('home.html')

Related

Launching a Stripe checkout pop-up after validating a WTForm

I have a super simple flask form for a service that I'm trying to integrate with the Stripe API:
from flask_wtf import FlaskForm
import wtforms as wtf
class ServiceForm(FlaskForm):
name = wtf.StringField('The service name')
submit_ = wtf.SubmitField('Submit >')
I'm serving this form along with my stripe key as follows:
#app.route('/index', methods=['GET'])
def index() -> str:
form = ServiceForm()
render_template('index.html', form=form, key='some stripe key',
service='some service', service_price=2500)
and the index.html file looks like this:
<form id="service-form" action="{{ url_for('pay_for_service', service=service) }}" method="POST" novalidate>
{{ form.csrf_token }}
{{ form.name }}
{{ form.submit_(class_="some-button" id="service-form-submit-button") }}
<script
src="https://checkout.stripe.com/checkout.js"
class="stripe-button"
data-key="{{ key }}"
data-amount="{{ service_price }}"
data-locale="auto">
</script>
</form>
The action of this form points to the following route:
#app.route('/pay+for+service/<string:service>', methods=['POST'])
def pay_for_service(service: str) -> str:
form = ServiceForm()
if form.validate_on_submit():
# ... do some validation
# now launch the Stripe dialog box to input credit card details.
which I'll use to validate the form submission before the Stripe payment dialog box is launched.
Basically, I want the stripe-button in the script to be embedded in the form.submit_ button, which has a custom some-button class. Then I want the Stripe payment pop-up to show after I've validated the form. How do I do this please? I think it's pretty easy but been scratching my head for ages!
Thanks for any help, and stay safe :-)
It's most easily done with an ajax call. Set the button action to call "submit_form" which will be:
function submit_form(Elem) {
$.ajax({
type: 'POST',
url: "{{ url_for('pay_for_service', service=service) }}",
data: $("#service-form").serialize(),
success: function(data) {
if (data['status'] == 'success') {
// call stripe
} else {
alert(data['data']['message'])
};
}
});
}
You won't need two view functions, a single one will do:
#app.route('/index', methods=['GET'])
def index() -> str:
form = ServiceForm()
if form.validate_on_submit():
return json.dumps({'status': 'success', 'data': {'message': 'ok'}})
elif request.method == 'POST':
message = str(form.errors)
return json.dumps({'status': 'fail', 'data': {'message': message }})
render_template('index.html', form=form, key='some stripe key',
service='some service', service_price=2500)
This is not possible with the "simple" integration approach. It's possible to run your own validation and trigger the Legacy Checkout modal programmatically using the "custom" integration approach, as seen in this example.
var handler = StripeCheckout.configure({ ... })
// later ...
handler.open({...})
However, note that this is all using an old deprecated version of Checkout. You should strongly consider using the new Checkout. Among other significant advantages, you can to any validation you like server-side before creating the checkout session.

Returning either render_template, or jsonify from Flask as jQuery response

I am trying to create a login page, that after entering incorrect username or password displays an error in the paragraph [working], or redirects the user to the dashboard if the login was successful [not working].
The HTML for the login form looks as follows
<form id="login" action="/login" method="post">
<fieldset>
<legend>Login to the Metalworks</legend>
Username:<br>
<input type="text" name="username"><br>
Password:<br>
<input type="password" name="password"><br><br>
<input type="submit" value="Login">
</fieldset>
</form>
<br>
<p id="result" style="font-size:24px; font-weight:bold"></p>
then I have a JS script that sends a HTTP request to my webserver after hitting the submit button
$(document).ready(function() {
$("#login").ajaxForm({
url: "/login",
dataType: "json",
success: loginResult
});
});
function loginResult(response)
{
result = document.getElementById("result");
if (!response.success)
{
result.style.color = "red";
result.innerHTML = response.error;
}
}
so far all of this works, I get the username and password in my Flask application, where I compare it to the accounts in the DB and if an error occurs, I return a jsonify-ed object with error, otherwise I'd like to make a redirection to the dashboard.html, which doesn't work
#app.route("/")
def home():
return render_template("login.html", title="Metalworks Login");
#app.route("/login", methods = ["POST"])
def login():
if "username" not in request.form or len(request.form["username"]) == 0: return jsonify({"success":False, "error":"Username is not specified!"});
if "password" not in request.form or len(request.form["password"]) == 0: return jsonify({"success":False, "error":"Password is not specified!"});
username = request.form["username"];
password = request.form["password"];
cursor = accountsCollection.find({"username":username});
try:
account = cursor[0];
except:
return jsonify({"success":False, "error":"Could not find account {}!".format(username)});
if account["password"] != password:
return jsonify({"success":False, "error":"Incorrect password!"});
# this does nothing
return render_template("dashboard.html", title="Metalworks Dashboard");
1, any ideas on how to properly redirect after successful login?
2, what is the proper way of handling the session, setting timeout etc?
1.
You can use redirect to redirect user to other routes.
For example:
from flask import Flask, render_template, request, redirect, jsonify
app = Flask(__name__)
#app.route("/")
def home():
return render_template("login.html", title="Metalworks Login")
#app.route("/dashboard")
def dashboard():
return render_template("dashboard.html", title="Metalworks Login")
#app.route("/login", methods = ["POST"])
def login():
if "username" not in request.form or len(request.form["username"]) == 0: return jsonify({"success":False, "error":"Username is not specified!"})
if "password" not in request.form or len(request.form["password"]) == 0: return jsonify({"success":False, "error":"Password is not specified!"})
username = request.form["username"]
password = request.form["password"]
account = {"username": "admin", "password": "admin"}
if account["username"] != username or account["password"] != password:
return jsonify({"success":False, "error":"Incorrect password!"})
return redirect("/dashboard")
app.run(debug=True)
dashboard.html:
<h1>Dashboard</h1>
Output after inserting admin as username and password:
2.
I would suggest to try Flask-Login for login, logout and login_required functionalities.
From official documentation: login-example using Flask-Login

Return result of postgresql query onto a html page

I am creating a book review website for CS50. I am having an issue displaying a query.The problem lies in the /books directory. The books.html contains a form with 3 inputs: isbn,title,author, and a submit button. Once I click the submit button it redirects to the login page. My goal is to output it to the user. If anyone could help me out, it would be really helpful.I get the error:
TypeError: 'ImmutableMultiDict' object is not callable
import os
from flask import Flask, session
from flask_session import Session
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from flask import Flask, render_template, request, redirect
app = Flask(__name__)
# Check for environment variable
if not os.getenv("DATABASE_URL"):
raise RuntimeError("DATABASE_URL is not set")
# Configure session to use filesystem
app.secret_key = 'key'
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
# Set up database
engine = create_engine(os.getenv("DATABASE_URL"))
db = scoped_session(sessionmaker(bind=engine))
#app.route("/")
def index():
return render_template('index.html', navbar=True)
#app.route("/register",methods=['GET','POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
cpassword = request.form.get('cpassword')
if not password == cpassword:
return render_template('error.html', message='Passwords do not match')
avail = db.execute('SELECT username FROM userdetails WHERE username=:username',
{'username': username}).fetchone()
if avail:
return render_template('error.html', message='Username Already Exists')
db.execute('INSERT INTO userdetails(username, password) VALUES(:username, :password)',
{'username': username, 'password': password})
db.commit()
session["username"] = username
return redirect('/')
else:
return render_template('register.html')
#app.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = db.execute('SELECT * FROM userdetails WHERE (username=:username AND password=:password)',
{'username': username, 'password': password}).fetchone()
if user is None:
return render_template('error.html', message='Entered credentials not valid!')
session["username"] = username
return redirect('books')
else:
return render_template('login.html', navbar=False)
#app.route("/books", methods=['GET', 'POST'])
def books():
isbn = request.form.get('isbn')
title = request.form.get('title')
author = request.form.get('author')
result = db.execute('SELECT * FROM books WHERE (isbn=:isbn AND title=:title AND author=:author)',
{'isbn': isbn, 'title': title, 'author': author}).fetchall()
return render_template('books.html')
#app.route("/result", methods=['GET', 'POST'])
def results():
if request.method == 'POST':
result = request.form()
return render_template("result.html",result = result)
Books.html
<html>
<head>
<title>Books</title>
</head>
<form method="POST" action="/result">
<input type="number" name="isbn" placeholder="isbn">
<input type="text" name="title" placeholder="title">
<input type="text" name="author" placeholder="author">
<input type="submit" value="Submit">
</form>
</html>
Result.html
<html>
<head>
</head>
<body>
{{result}}
</body>
</html>
One problem is this <form method="POST" action="/login"> in book.html. It is sending user back to the login page. From the spec, it seems the books route is from the search page (with "reviews" not yet implemented). The "book" page should allow user to enter a rating and review, and on submit presumably update the "reviews" table in the database (as suggested by the first Hint). It looks like the book page is not complete.
This line result = request.form() in results could be giving the TypeError since form is a MultiDict as per flask doc. Adding the () to the end looks like a callable function.
form
A MultiDict with the parsed form data from POST or PUT requests. Please keep in mind that file uploads will not end up here, but
instead in the files attribute.
The error message TypeError: 'ImmutableMultiDict' object is not callable should be giving a line number. Likely related to calling login with no username and passsword request form arguments.

Django AJAX search function

I'm trying to make a search function in my Django project using AJAX. But the functions in views.py don't seem to work properly. And maybe I have some other mistakes. Could you please tell me what I need to correct?
This is my AJAX file:
$(document).ready( function(){
$('#suggestion').keyup(function(){
var query;
query = $(this).val();
$.get('/friends_plans/suggest_users/', {suggestion: query}, function(data){
$('#user').html(data);
});
});
});
This is part of my template:
<div>
<ul class="nav nav-list">
<li class="nav-header">Find user</li>
<form>
<li><input class="search-query span10" type="text" name="suggestion" value=" " id="suggestion" /></li>
</form>
</ul>
</div>
<div id="user">
</div>
These ara functions from views.py:
def suggest_users(request):
users_list = []
starts_with = ''
if request.method == 'GET':
starts_with = request.GET['suggestion']
users_list = get_users_list(5, starts_with)
return render(request, 'friends_plans/list.html', {'users_list': users_list})
def get_users_list(max_results=0, starts_with=''):
users_list = []
if starts_with:
users_list = Person.objects.filter(username__istartswith=starts_with)
if max_results > 0:
if len(users_list) > 0:
users_list = users_list[:max_results]
return users_list
This is from urls.py:
url(r'^suggest_users/$', views.suggest_users, name='suggest_users')
The istartswith method doesn't work properly with the variable but does with the constant, I can't understand why. And suggest_users function doesn't return users_list to the object with id user ($('#user').html(data)), nothing appears on the page. But maybe there are some other mistakes.
Django's render function renders HTML after parsing it with Jinja. If you want to write a view that acts as an endpoint for an AJAX function, you do not want that view to return render.
Instead you should use return JsonResponse. JsonResponse accepts a dictionary as an argument. And it builds a proper JSON object for you. :) Which will then be picked up by your AJAX's success function.
Here's an example of how to use JsonResponse:
from django.http import JsonResponse
def some_endpoint(request, *args, **kwargs):
data = dict()
data["foo"] = "bar"
data["username"] = User.objects.get(id=request["id"]).username
return JsonResponse(data)
This will cause your view to return a JSON Object, which is what your AJAX function is looking for.
Second suggestion I would make would be to use jQuery's $.ajax() function rather than jQuery's shortcut .get() function. The advantage of this would be learning all the parameters that go along with AJAX calls.
Here's an example of jQuery's $.ajax() function.
$(document).ready( function(){
$('#suggestion').keyup(function(){
var query = $(this).val();
$.ajax(function(){
type: "GET",
url: "/friends_plans/suggest_users/",
data: {suggestion: query},
success: function(data){
console.log("SUCCESS");
console.log(data);
},
failure: function(data){
console.log("FAIL");
console.log(data);
},
});
});
});

Saving the HTML of a Flask web page generated from a form submit gives 400 output

Whenever I try to save the HTML of a web page generated in Flask from a form submission on my local test server, the saved page gives this output:
Bad Request
The browser (or proxy) sent a request that this server could not
understand.
I can save other pages of my app with no issue, it is only pages that are submitted through a form.
The snippet of the Flask url route in question, this is hit from a form on another url:
#app.route('/profile', methods=['GET', 'POST'])
def profile():
dist_type = request.form['district-select']
dist_num = int(request.form['district-number'])
json_data = {
'dist_type' : dist_type,
'dist_num' : dist_num
}
return render_template('profile.html', data = json_data)
The HTML template 'profile.html' is filled out with json_data:
<script type="text/javascript">
var data = {{ data|tojson|safe }}
</script>
<div class="distr-head" id="distr-type">{{data['dist_type']}}</div>
<div class="distr-head" id="distr-num">{{data['dist_num']}}</div>
When I Ctrl + S to save this filled template IN MY BROWSER, or link it to other sites, I cannot access the HTML and instead get a 400 Bad Request
request.form is not populated during a GET request, only during POST (and PUT, etc.). Trying to access a key on request.form when it doesn't exist raises a 400 error. You should guard the code that expects form data so that it only executes during a POST request.
#app.route('/profile', methods=['GET', 'POST'])
def profile():
data = None
if request.method == 'POST':
data = {
'dist_type': request.form['district-select'],
'dist_num': int(request.form['district-number'])
}
return render_template('profile.html', data=data)

Categories

Resources