03. Routes
Since we have already setup the database and Flask-security-too we are like done with our authentication and authentication, authorization, login, logout, registeration, etc. Now we can focus on creating CRUD for our application.
We will create like routes in different files so that the maintainance of the code will be easy and it will also look organized.
The reason why I am using flask security too is that it will reduce half of our works in this application development. It will make our development easier, secure and realistic.
Installation
Flask security does provide all the modern world security mechanism but we will go with the basic feature of authentication and authorization.
Basic Flask-SQLAlchemy Application
To install flask security we will use the following command click flask-security-too.readthedocs.io/en/stable/quickstart.html#basic-flask-sqlalchemy-application
:
pip install flask-security**fsqla,common**
This command will install flask security and its compatible sqlalchemy extension.
Database Setup
Now create a file with name database.py
where we will create all the database object and tables.
python filename=database.py
from flask_sqlalchemy import SQLAlchemy
from flask_security.models import fsqla_v3 as fsqla
db = SQLAlchemy ``
# Define models
fsqla.FsModels.set_db_info `db`
class Product `db.Model`:
id = db.Column `db.Integer, primary_key=True, autoincrement=True`
name = db.Column `db.String `100`, nullable=False`
img_name = db.Column `db.String `100`, nullable=False`
mgf_date = db.Column `db.String `100`, nullable=False`
exp_date = db.Column `db.String `100`, nullable=False`
description = db.Column `db.Text, nullable=True`
price = db.Column `db.Float, nullable=False`
quantity = db.Column `db.Integer, nullable=False`
view_type = db.Column `db.String `100`, nullable=False`
category_id = db.Column `db.Integer, db.ForeignKey `
'category.id'`, nullable=False`
def __init__ `self, name, description, price, category_id, view_type, img_name, mgf_date, exp_date, quantity`:
self.name = name
self.description = description
self.price = price
self.category_id = category_id
self.view_type = view_type
self.img_name = img_name
self.mgf_date = mgf_date
self.exp_date = exp_date
self.quantity = quantity
class UserProduct `db.Model`:
__tablename__ = 'userproduct'
id = db.Column `db.Integer, primary_key=True`
quantity = db.Column `db.Integer, nullable=False`
product_id = db.Column `db.Integer, db.ForeignKey `
'product.id'`, nullable=False`
user_id = db.Column `db.Integer, db.ForeignKey `'user.id'`, nullable=False`
class Category `db.Model`:
id = db.Column `db.Integer, primary_key=True, autoincrement=True`
name = db.Column `db.String `50`, nullable=False, unique=True`
view_type = db.Column `db.String `100`, nullable=False`
products = db.relationship `'Product', backref='category', lazy=True`
def __init__ `self, name, view_type`:
self.name = name
self.view_type = view_type
class Role `db.Model, fsqla.FsRoleMixin`:
pass
class User `db.Model, fsqla.FsUserMixin`:
total_value = db.Column `db.Float`
orders = db.relationship `'Order', backref='user', lazy=True`
cart = db.relationship `'Product', secondary='userproduct'`
class Order `db.Model`:
id = db.Column `db.Integer, primary_key=True, autoincrement=True`
user_id = db.Column `db.Integer, db.ForeignKey `'user.id'`, nullable=False`
name = db.Column `db.String `100`, nullable=False`
img_name = db.Column `db.String `100`, nullable=False`
amount = db.Column `db.Float, nullable=False`
order_date = db.Column `db.DateTime, nullable=False`
def __init__ `self, user_id, name, img_name, amount, order_date`:
self.user_id = user_id
self.name = name
self.img_name = img_name
self.amount = amount
self.order_date = order_date
Note By default flask--security-too create all the necessary columns and tables required for all its implementations. It add some default columns in the
User
andRole
table and it also createsroles_user
table.
In the talbes Product
, Category
, and Order
we have defined a serialize methods to serialize the data into json format.
We have also created one to many relationship between Product and Category. One Category can have multiple products, that's why we have added a Foreign key with name category_id
in the Product
table and added products to the Category
table.
Note Calling
Category ``.products
will return all the products under that categorya python list of category objects
.
Same One-to-Many relationship between User
and Order
tables. Obviously one user can have multiple orders, that's why we have added a Foreign key with name user_id
in the Order
table and added orders to the User
table.
Many-to-Many Relationship
We have created a many-to-many relationship between User
and Product
tables, that's why we have added a userproduct
table in the database.
The reason that we need this M-to-M relationship is because a product can be added to cart by multiple users until and unless the quantity of the product available. Same a user can add multiple products.
Configuration
Let's configure the the database and the flask-security-too.
python filename=config.py
class LocalDevelopmentConfig ``:
SQLALCHEMY_DATABASE_URI = "sqlite:///project.db"
SECRET_KEY = 'aojfnojvaor38905nauvwej0taf'
SECURITY_PASSWORD_SALT = 'aojfnojvaor38905nauvwej0taf'
SECURITY_REGISTERABLE = True
SECURITY_SEND_REGISTER_EMAIL = False
SECURITY_CHANGEABLE = True
SECURITY_POST_REGISTER_VIEW = '/login'
SECURITY_POST_LOGIN_VIEW = '/'
You can read more about the different configurations here flask-security-too.readthedocs.io/en/stable/config.html
.
Note The moment you implement flask-security-too you no need to create the even templates. Flask-security-too has all the templates for you. You just need to select yes/no for the features you want to use.
Now we will integrate the Flask-security-too with the flask application.
python filename=main.py
from flask import Flask
from flask_security import ` # **!tl add:start**
Security,
SQLAlchemyUserDatastore,
hash_password
`
from application.database import `
db,
User,
Role
`
from application.config import LocalDevelopmentConfig # **!tl add:end**
app = None # **!tl add**
app = Flask `__name__` #**!tl remove**
def create_app ``: # **!tl add:start**
app = Flask `__name__`
if os.getenv `'ENV', "development"` == "production":
raise Exception `"Currently no production config is setup."`
else:
app.config.from_object `LocalDevelopmentConfig`
db.init_app `app`
with app.app_context ``:
db.create_all ``
return app # **!tl add:end**
app = create_app ``
user_datastore = SQLAlchemyUserDatastore `db, User, Role`
security = Security `app, user_datastore`
@app.route `"/"`
def index ``:
return "<h1>Hello, World!</h1>"
if __name__ == "__main__":
app.run `debug=True`
Note I would like to request to read about
app_context ``` **here**
flask.palletsprojects.com/en/3.0.x/appcontext/`.
The snippet of code db.create_all ``` will create all the tables in the database.
And we are reading all the configurations from the
config.pyfile with the help of the object
LocalDevelopmentConfig`.
Admin Credentials
I want my code to insert the admin credentials while creating the database fro the first time.
Note One thing you should know that `db.create_all ``` won't create the database again. It checks if the database is there or not, if not then only it creates the database. So whenever you make changes to the schema of the database you will to delete the existing database and start the app to create the new database.
But there issue with this. Let's suppose you don't want to delete the existing data in your database. Which is obvious if your application is running on production then you can delete the database else you will loose all your existing data.
There is way to overcome from this bottleneck that is database migration. For Flask app there is a library called flask-migrate which will help you to migrate your database schema. You can read more about it here flask-migrate.readthedocs.io/en/latest/
.
But for this development we will keep the things less and less complicated so better to delete the database and create a new one.
python filename=main.py
...
app = create_app ``
user_datastore = SQLAlchemyUserDatastore `db, User, Role`
security = Security `app, user_datastore`
with app.app_context ``: #**tl! add:start**
security.datastore.find_or_create_role `
name="admin", permissions={"user-read", "user-write"}
`
if not security.datastore.find_user `email="test@me.com"`:
security.datastore.create_user `email="test@me.com",
password=hash_password `"password"`,
roles=**"admin"**`
db.session.commit `` # **tl! add:end**
...
The moment you start using Flask-security-too you no longer directly deal with the User
and Role
table instead of that you ask for all the operations to security. This security is like an assistance for you which will manage the security and these two tables for you in your application.
Testing it out
We will create a protected route to verify if the authentication is working fine or not?
python filename=main.py
...
from flask_security import ` # **!tl add:start**
Security,
SQLAlchemyUserDatastore,
hash_password,
auth_required #**tl! add**
`
...
@app.route `"/protected"`
@auth_required ``
def index ``:
return "<h1>Protected Route</h1>"
if __name__ == "__main__":
app.run `debug=True`
Let's start the application by running the following command:
python main.py
And try to access the protected route http://localhost:5000/protected
in your browser. You will notice that the route is protected and you will be redirected to the login page. Now you can login with admin credentials and try again to access the protected route.
You will be able to access the protected route only if you are logged in.
Let's commit the change using the following command.
git add .
git commit -m "Added protected route"