58: Flask Login and Registration with Sessions - MantsSk/CA_PTUA14 GitHub Wiki
Today we will be making a simple login/registration system for our website. To start with this lesson, clone (or copy) this starting code. It has some routes, templates, and styling prepared so you can focus on the main task: https://github.com/MantsSk/CA_PTUA10/tree/master/58Pamoka%20-%20FlaskLoginSessions/starting_code
We'll use Flask's session management to handle user sessions.
Make sure to have all the necessary imports:
from flask import Flask, render_template, request, redirect, url_for, session
from flask_sqlalchemy import SQLAlchemy
Some new imports that you might notice:
-
redirect: redirect is a function provided by Flask that generates an HTTP redirect response to a specified URL. It is commonly used to redirect users to another page after performing a certain action, such as form submission or login.
-
session: session is a dictionary-like object provided by Flask that allows you to store user-specific information across multiple requests. It uses cookies to store data on the client side and provides a simple way to implement user sessions in Flask applications.
First of all, let's initialize SQLAlchemy to create a User class, with its help we will store the User data:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' # Change the database URI as per your needs
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
Also add database creation:
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
Currently if we hit "Register" button, nothing happens. Lets implement the register route:
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user:
return render_template('register.html', message='Username already exists')
new_user = User(username=username, password=password)
db.session.add(new_user)
db.session.commit()
session['username'] = username
return redirect(url_for('login'))
return render_template('register.html')
- This route handles both GET and POST requests. When a user visits the /register URL, they either see the registration template (GET request) or submit the form to register a new user (POST request).
- Inside the POST request block we extract the username and password from the form data submitted by the user (look at the register.html template)
- We then query the database to check if a user with the same username already exists. If a user is found, we render the registration form again with an error message indicating that the username already exists.
- If the username is unique, we create a new User object with the provided username and password. We add this new user to the database session and commit the transaction to persist the changes to the database.
- After successfully registering the user, we store their username in the session using
session['username'] = username
. Storing the username in the session allows us to keep track of the logged-in user across requests. - Finally, we redirect the user to the login page (redirect(url_for('login'))) so they can log in with their newly registered credentials.
However, for us to log in, we have to implement that as well:
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and user.password == password:
session['username'] = username
return redirect(url_for('home'))
else:
return render_template('login.html', message='Invalid username or password')
return render_template('login.html')
- Similar to the register route, the login route handles both GET and POST requests.
- When a user submits the login form (POST request), we retrieve the entered username and password from the form data. (look at the login.html)
- We then query the database to find a user with the provided username. If a user is found and the password matches the one stored in the database, we consider the login successful.
- If the login is successful, we store the username in the session using session['username'] = username. This indicates that the user is logged in.
- After successful login, we redirect the user to the homepage (redirect(url_for('home'))).
- If the username or password is incorrect, or if no user with the provided username exists, we render the login form template again (render_template('login.html')) with an error message indicating invalid credentials.
So now, you should see that we can log in. However, there is nothing that indicates that we logged in. If you take a look at our provided template, the code for showing the user name is already there:
<div class="navbar">
<a class="{% if active == 'home' %}active{% endif %}" href="{{ url_for('home') }}">Home</a>
{% if username %}
<a href="{{ url_for('logout') }}">Logout</a>
<span style="float:right; color:white; padding-right: 20px;">Hello, {{ username }}!</span>
{% else %}
<a href="{{ url_for('register') }}">Register</a>
<a href="{{ url_for('login') }}">Login</a>
{% endif %}
</div>
More specifically:
{% if username %}
Do you remember this line of code and what does it do? It is a conditional statement in the Jinja2 templating language, which is used within Flask templates. It checks if the variable username is defined or not. If the username is defined (i.e., if the user is logged in), the subsequent code block will be executed:
<a href="{{ url_for('logout') }}">Logout</a>
<span style="float:right; color:white; padding-right: 20px;">Hello, {{ username }}!</span>
Meaning it will show a logout button and "Hello your username message.
Why our template doesn't show anything right now? Well, because we don't pass username in our home route :) Let's modify our index route to provide the username value:
@app.route('/')
def home():
if 'username' in session:
return render_template('index.html', username=session['username'])
else:
return render_template('index.html')
if 'username' in session:
:
This condition checks if the 'username' key is present in the session object. If the user is logged in (i.e., their username is stored in the session), this condition evaluates to True.
return render_template('index.html', username=session['username']):
:
If the user is logged in, this line renders the index.html template and passes the username stored in the session as a variable called username. This allows the template to display personalized content, such as a welcome message.
And now we have our login/registration system set up :) Obviously it's still not safe, we don't has the passwords in the database and etc. But the prototype is already there for us.
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('home'))
Specifically this line:
session.pop('username', None)
This line removes the 'username' key from the session dictionary. If the key doesn't exist, it returns None.
And that is all for today. Yes, this is only a prototype and many safety features are not there, but you built a prototype that we are going to expand.
This exercise entails building a Flask application for a blog platform. The application will feature functionalities for user authentication (login, register, and logout), as well as CRUD operations for managing blog posts (create, view, edit, and delete posts). Your objective is to implement and fix all necessary routes and HTML templates to make the application fully work.
Issues to fix:
- Whatever I do, I always get errors that User and Post does not exist. Can you please resolve this?
- I fail to register two accounts, only one is possible. Please fix this!
- It seems like I login, but actually I don't. Even though I submit the login form without any errors - I still fail to do it. Resolve it.
- Whenever I try to create post - it just does not get created! Solve the issue.
- Currently, user is unable to delete his posts. Add buttons to each of his posts that deletes them.
- Something wrong with post preview (when pressing on a post). Probably something is unfinished. Please finish implementing code and resolve the problem.
- I would love a welcoming message with my username in the home page near the log out button. Please add it.
- I should be able to log out. Currently I fail to do so. Please finish logout implementation.
- I can not find a way to edit the post. Please make it possible to do so!
Use this starting code: https://github.com/MantsSk/CA_PTUA10/tree/master/58Pamoka%20-%20FlaskLoginSessions/uzduotis