I'm making a web app in Flask and I have some code that could be reused, so I'll share it. We haven't mentioned which code provides what functionality here, so please check it out for yourself.
Install the extension to be used this time in advance. You don't need to specify the version because we will prepare all the latest ones.
requirements.txt
Flask
Flask-Login
Flask-Migrate
Flask-SQLAlchemy
Flask-WTF
SQLAlchemy
Werkzeug
WTForms
Jinja2
email-validator
pip install -r requirements.txt
Flask uses something called Blueprint
to split the functionality.
├───main.py # main app.py file to be called to start server for web app
├───requirements.txt # File of pip install statements for your app
├───migrations # folder created for migrations by calling
├───myproject # main project folder, sub-components will be in separate folders
│ │ data.sqlite
│ │ models.py
│ │ __init__.py
│ │
│ │───auth
│ │ │ forms.py
│ │ │ views.py
│ │ │
│ │ │───templates
│ │ │ └───auth
│ │ │ login.html
│ │ │ signup.html
│ │
│ ├───static # Where you store your CSS, JS, Images, Fonts, etc...
│ ├───templates
│ base.html
│ home.html
In this configuration, __init__.py
contains config
, but you can separate it as config.py
.
We have prepared a common layout for base.html
and reused it as{{% extends "base.html"%}}
using a template engine called jinja2
(layout. The name hmtl
is also often used).
I'm using SQLite
for SQL, but I can't deploy to Heroku
and it's not suitable for scale, so when using SQL, it's better to actually use MySQL
or PostgreSQL
. I think. Since we use ʻORM called
SQLAlchemyto handle the database, the same code can be used even if the SQL is different (setup is different).
data.sqlite`` migrations` is a file / folder created by yourself.
It is the request of Blueprint
to have a redundant structure like myproject \ auth \ templates \ auth
.
ʻAuth \ forms.py receives the sign-up and login forms, and ʻauth \ views.py
receives the data entered from the forms, processes them, and renders them.
main.py
and __ init__.py
Since main.py only makes calls, it looks like this:
main.py
from flask import render_template
from myproject import app
@app.route('/')
def index():
return render_template('home.html')
if __name__ == '__main__':
app.run(debug=True)
This file registers with config and blueprint, and does all the initialization.
__init__.py
import os
from flask import Flask, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
login_manager = LoginManager()
app = Flask(__name__, static_folder='static', template_folder='templates')
basedir = os.path.abspath(os.path.dirname(__file__))
#If you added the Heroku Postgres add-on, the environment variable DATABASE_In the URL
#This value is set because the URL to connect to the PostgreSQL database is set.
#When set(When running on Heroku)Use it,
#When not set(Debugging locally, etc.)Makes it use the local SQLite database.
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') or 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SECRET_KEY'] = 'mysecretkey'
app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
Migrate(app, db)
#Be sure to import here after finishing the definition of db
from myproject.auth.views import auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/authenticate')
login_manager.init_app(app)
login_manager.login_view = "auth.login"
The last line, login_manager.login_view =" auth.login "
, points to the login screen that is redirected when you try to access it, even though it has @ login_required
.
For example, if you try to go to \ logout
while you are not logged in, the login function defined in views.py
will be called (since the login screen is rendered by the return of the function, go to that screen. Masu).
The table is defined in models.py
. I keep my username, email address, and hashed password.
As an aside, pressing the "Forgot your password?" Button doesn't tell you your password because it doesn't remain in the database in the first place, so you can't tell it.
models.py
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from myproject import db, login_manager
@login_manager.user_loader
def load_user(user_id):
return User.query.get(user_id)
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
def __init__(self, email, username, password):
self.email = email
self.username = username
self.password_hash = generate_password_hash(password)
def check_password(self,password):
return check_password_hash(self.password_hash, password)
It is intended for general user sign-up. The user
Enter the.
First is the sign-up form.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired,Email,EqualTo
class SignupForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(),Email()])
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords Must Match!')])
pass_confirm = PasswordField('Confirm password', validators=[DataRequired()])
submit = SubmitField('Sign Up')
Next is internal processing. The user name and e-mail address must not overlap in the database, so if they match in the database, an error will be issued and you will be asked to enter them again.
If you sign up successfully, you will be taken to the login form.
views.py
from flask import (Blueprint, render_template,
redirect, url_for, request,
flash, abort)
from flask_login import login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from myproject import db, app
from myproject.models import User
from myproject.auth.forms import LoginForm, SignupForm
auth_blueprint = Blueprint('auth',
__name__,
template_folder='templates/auth')
@auth_blueprint.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
if User.query.filter_by(email=form.email.data).first():
flash('Email has been registered already!')
redirect(url_for('auth.signup'))
elif User.query.filter_by(username=form.username.data).first():
flash('Username has been registered already!')
redirect(url_for('auth.signup'))
else:
user = User(email=form.email.data,
username=form.username.data,
password=form.password.data)
db.session.add(user)
db.session.commit()
flash('Now You can login!')
return redirect(url_for('auth.login'))
return render_template('signup.html', form=form)
It is assumed that a general user will log in.
The user enters their email address and password. It is described in the same file as the sign-up form.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired,Email,EqualTo
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Log In')
class SignupForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(),Email()])
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords Must Match!')])
pass_confirm = PasswordField('Confirm password', validators=[DataRequired()])
submit = SubmitField('Sign Up')
Next is internal processing. Identify the user by email address. The password is compared with the user's password hash value using the check_password
function played in models.py
.
If you log in successfully, you can jump to any location.
By the way, I will also leave the logout process.
views.py
from flask import (Blueprint, render_template,
redirect, url_for, request,
flash, abort)
from flask_login import login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from myproject import db, app
from myproject.models import User
from myproject.auth.forms import LoginForm, SignupForm
auth_blueprint = Blueprint('auth',
__name__,
template_folder='templates/auth')
@auth_blueprint.route('/logout')
@login_required
def logout():
logout_user()
flash('You logged out!')
return redirect(url_for('index'))
@auth_blueprint.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is None:
flash('something wrong!')
redirect(url_for('auth.login'))
elif user.check_password(form.password.data):
login_user(user)
flash('Logged in successfully.')
# login => somewhere
return redirect(url_for('somewhere'))
else:
pass
return render_template('login.html', form=form)
@auth_blueprint.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
if User.query.filter_by(email=form.email.data).first():
flash('Email has been registered already!')
redirect(url_for('auth.signup'))
elif User.query.filter_by(username=form.username.data).first():
flash('Username has been registered already!')
redirect(url_for('auth.signup'))
else:
user = User(email=form.email.data,
username=form.username.data,
password=form.password.data)
db.session.add(user)
db.session.commit()
flash('Now You can login!')
return redirect(url_for('auth.login'))
return render_template('signup.html', form=form)
Flash () is received as follows.
example.html
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endif %}
{% endwith %}
You may want to change the display before and after the login status. For example, when you log in, you will see a logout button.
In this case, you can use current_user.is_authenticated
for conditional branching.
example.html
{% if current_user.is_authenticated %}
<a class="nav-item nav-link" href="#">{{ current_user.username }}</a>
<a class="nav-item nav-link" href="{{ url_for('task.scheduletoday') }}">Today's schedule</a>
<a class="nav-item nav-link" href="{{ url_for('task.alltasks')}}">Task list</a>
<a class="nav-item nav-link" href="{{ url_for('auth.logout') }}">Log out</a>
{% else %}
<a class="nav-item nav-link" href="{{ url_for('auth.login') }}">Login</a>
{% endif %}
You can do it like this.
If you try to access a location that requires login without logging in, you can limit it as long as @login_required
is written, but you will be able to access locations that do not require login while logged in. If this is possible, you will be in an uncertain situation where you can log in again even though you are logged in. In such a case
render_template('home.html')
not
return redirect(url_for('somewhere')) if current_user.is_authenticated else render_template('home.html')
You can do it like this.
Once you have defined a new table, you need to update it for it to take effect.
---------------------------------------
MACOS/Linux
$ export FLASK_APP=main.py
Windows
$ set FLASK_APP=main.py
flask db init
#Only the first so far
----------------------------------------
flask db migrate -m "message"
flask db upgrade
You can do it like this.
python main.py
I have summarized the code that I have written so far that can be reused. If you have a better way to write it, please let us know.