I'm trying to add RBAC to my existing flask application where I already have 2 models which describe User and Post model respectively. Here is my code:
# models.py
from datetime import datetime
from rpd_site import db, login_manager
from flask_login import UserMixin
# []
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# Main site account table
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
confirmed = db.Column(db.Boolean, nullable=False, default=0)
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.confirmed}')"
# Posts table
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.now) # current local time instead of .utcnow
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default_post.png')
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}', '{self.content[:15]}')"
When I tried add all missing code from here I faced with lots of errors. Especially I'm not sure if I should import UserMixin from flask_rbac or from flask_login.
Help me to understand how can I upgrade my DB with RBAC functionality.
This is a very broad question, I'll try to give you a minimum code so that you can achieve RBAC. Below example uses Flask-security.
from app import db
from flask_security import RoleMixin, UserMixin
# may to many association table between User and Role
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)
class Role(db.Model, RoleMixin):
__tablename__ = 'role'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
def __str__(self):
return self.name
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String(255))
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='joined'))
def __str__(self):
return self.email
You either migrate or create the DB based on above Models. The above will be sufficient for you to either perform back-end operation for RDBC or at view level.
You can then assign roles to each user easily using below link.
Flask-security create role,user and linking user_id to role_id
If you want to perform RBAC at view, follow below.
from flask_security import login_required, roles_accepted
#app.route('/a_restricted_view/')
#login_required
#roles_accepted('role_one', 'role_two')
def a_restricted_view():
return "I am only visible to users with role_one and role_two"
Related
I have two different roles setup on my Flask Python application, and I am using roles_required to force the user to be an admin to login or access the page. However, I am trying to work out how to use roles_required with two different roles...
I know there is a way to request a role and one of the two roles in a list. That looks like this:
roles_required('admin1', ['admin2'])
But what about role or other role?
Here is my problem:
My models:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(128), nullable=False, unique=True)
password = db.Column(db.String(192), nullable=True, unique=True)
roles = db.relationship('Role', secondary='user_roles', backref=db.backref('users', lazy='dynamic'))
has_roles = lambda self, *args: set(args).issubset({role.name for role in self.roles})
class Role(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50))
class UserRoles(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE'))
My route:
#mod_auth.route('/dashboard')
#login_required
# This line below doesn't seem to be working
#roles_required('admin1', 'admin2')
def dashboard():
return render_template('dashboard.html')
I'm trying to create to blog but I'm having issues creating database using sqlite as my database engine in which I created classes to define each table schema using an ORM with flask-sqlalchemy and also another thing I noticed was it created an empty blog.db file. I cant solve the issue im having right now. As of now this is my resort for help before using Firebase.
models.py
from blogent import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(50), nullable=False)
last_name = db.Column(db.String(50), nullable=False)
email = db.column(db.String(50), unique=True, nullable=False)
image = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(50), nullable=False)
post = db.relationship('Post', backref='author', lazy=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow())
updated_at = db.Column(db.DateTime, default=datetime.utcnow())
def __repr__(self):
return f"User('{self.email}', '{self.image}', '{self.created_at}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
post_name = db.Column(db.String(30), nullable=False)
post_title = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
comments = db.relationship('Comments', backref='posts', lazy=True)
user = db.relationship('User', backref='users', lazy=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow())
updated_at = db.Column(db.DateTime, default=datetime.utcnow())
def __repr__(self):
return f"Post('{self.post_name}', '{self.post_title}', '{self.created_at}')"
class Comments(db.Model):
id = db.Column(db.Integer, primary_key=True)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow())
updated_at = db.Column(db.DateTime, default=datetime.utcnow())
def __repr__(self):
return f"Comments('{self.content}', '{self.created_at}')"
init.py
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///blog.db"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO'] = True
app.config['SECRET_KEY'] = '48ea69eb3539fbe7e069deee6c74d46d'
db = SQLAlchemy(app)
#app.before_first_request
def create_tables():
db.create_all()
from blogent import routes
I am creating a server application that will utilize a remote database created by a Flask GUI. To keep things simple (and since it's primarily threaded calculations), I want to build my server application without utilizing a Flask framework. I will have a redis message server to keep the two processes in sync.
My challenge is that I want to use persistent storage wherever possible. So as part of this, my server application will need to import the Flask-SQLAlchemy database files to retrieve the parameters. The difficulty I am having is to write the import statement that will pull the SQLAlchemy files into pandas. Here is what I have so far:
config.py
import os
class Config:
SQLALCHEMY_DATABASE_URI_REMOTE = os.environ.get('RDQ_LOGIN') # remote docker database
SQLALCHEMY_DATABASE_URI_LOCAL = os.environ.get('RDU_LOGIN') # local file database
REDIS_IP = os.environ.get('REDIS_IP')
REDIS_PASSWORD = os.environ.get('REDIS_PASSWORD')
init.py
from src_code.config import Config
import redis
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base
import sqlalchemy as sa
engine_remote = create_engine(Config.SQLALCHEMY_DATABASE_URI_REMOTE, echo=True)
Base_remote = declarative_base()
Base_remote.metadata.create_all(engine_remote)
Session_remote = sessionmaker(bind=engine_remote)
Session_remote.configure(bind=engine_remote)
session_remote = Session_remote()
engine_local = create_engine(Config.SQLALCHEMY_DATABASE_URI_LOCAL, echo=True)
Base_local = declarative_base()
Base_local.metadata.create_all(engine_local)
Session_local = sessionmaker(bind=engine_local)
Session_local.configure(bind=engine_local)
session_local = Session_local()
redisChannel = redis.StrictRedis(host=Config.REDIS_IP, port=6379, password=Config.REDIS_PASSWORD,
decode_responses=True)
main.py
import pandas as pd
from src_code import session_local, session_remote
def start():
table_df = pd.read_sql(
'SELECT * from game',
con=session_remote
)
print(table_df)
if __name__ == "__main__":
start()
Unfortunately I am getting an error:
AttributeError: 'Session' object has no attribute 'cursor'
I don't need to be able to write back to the table (so read-only is sufficient). So I am using a simple example that would give me the ability to extract what I need from the panda (I am more confident to use pandas than SQL).
I do have the model statements that I could replicate in the server code if this would somehow facilitate the process:
from datetime import datetime
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
from src_code import db, login_manager
from flask_login import UserMixin
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
agent = db.Column(db.Integer, unique=False, nullable=False)
image_file = db.Column(db.String(20), unique=False, nullable=False,default='default.jpg')
password = db.Column(db.String(60), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
games_started = db.relationship('Game', backref='captained', lazy=True)
games_played = db.relationship('Player', backref='games', lazy=True)
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'], expires_sec)
return s.dumps({'user_id': self.id}).decode('utf-8')
#staticmethod
def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return None
return User.query.get(user_id)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.agent}', '{self.image_file}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False,default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
class Game(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(60), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
company_count = db.Column(db.Integer, nullable=False)
starting_year = db.Column(db.Integer, nullable=False)
time_limit = db.Column(db.Integer, nullable=False)
agent_decisions_visible = db.Column(db.Boolean, nullable=False)
client_count = db.Column(db.Integer, nullable=False)
pre_game_yrs = db.Column(db.Integer, nullable=False)
time_index = db.Column(db.Integer, nullable=False)
game_active = db.Column(db.Boolean, nullable=False)
player_capt = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
games_msgs = db.relationship('Messages', backref='messages', lazy=True)
def __repr__(self):
return f"Game('{self.title}', '{self.date_posted}')"
class GameRequests(db.Model):
id = db.Column(db.Integer, primary_key=True)
date_requested = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"GameRequest('{self.game_id}', '{self.user_id}', '{self.date_requested}'"
class GameDecisions(db.Model):
id = db.Column(db.Integer, primary_key=True)
date_requested = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
time_index = db.Column(db.Integer, nullable=False)
def __repr__(self):
return f"GameDecisions('{self.game_id}', '{self.user_id}', '{self.time_index}'"
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Player('{self.game_id}', '{self.user_id}', '{self.date_posted}'"
class Messages(db.Model):
id = db.Column(db.Integer, primary_key=True)
date_posted = db.Column(db.DateTime, nullable=False,default=datetime.utcnow)
type = db.Column(db.String(10), nullable=False)
msg_from = db.Column(db.String(20), nullable=False)
msg_to = db.Column(db.String(20), nullable=False)
message = db.Column(db.String(60), nullable=False)
game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False)
def __repr__(self):
return f"Messages('{self.game_id}', '{self.id}', '{self.message}', '{self.date_posted}')"
Thanks for any help you can offer!
Similar to your conclusion, here's how I read databases into pandas:
# Create your query.
# This can be as complex or simple as you'd like
query = session_remote.query(Game)
df = pd.read_sql(query.statement, session_remote.bind)
The key difference here is the utilization of the ORM to perform (or rather, write) the query itself.
Masking SQL behind an ORM has many advantages -- I strongly recommend against utilizing raw SQL in production backends.
It seems I was closer than I realized. The following code did what I needed:
SQLAlchemy ORM conversion to pandas DataFrame
table_df = pd.read_sql(
'SELECT * from game',
session_remote.bind
)
Here is my tables. I am using SQLite Database.
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False)
username = db.Column(db.String(30), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
date_joined = db.Column(db.DateTime, nullable=False,
default=datetime.utcnow)
posts = db.relationship('Post', backref='author',
lazy=True, passive_deletes=True)
def __repr__(self):
return f"User('{self.email}', '{self.username}', '{self.date_joined}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
body = db.Column(db.Text, nullable=False)
link = db.Column(db.String(300))
date_posted = db.Column(db.DateTime, nullable=False,
default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey(
'user.id', ondelete='CASCADE'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.body}', '{self.link}', '{self.date_posted}')"
This is the deletion of the "USER" route, it deletes the user but doesn't remove the posts created by the user.
#login_required
#app.route('/delete/account', methods=['GET', 'POST'])
def delete_account():
if current_user.is_authenticated:
db.session.query(User).filter_by(id=current_user.id).delete()
db.session.commit()
flash('Your account has been deleted!', 'success')
return redirect(url_for('home'))
return render_template('settings.html', title='Account Settings')
SQLite3 requires some coaxing to enforce foreign keys. You basically need to execute
PRAGMA foreign_keys=ON
on each new connection. I do something like the following
db = SQLAlchemy()
app = Flask(__name__)
... configure app
db.init_app(app)
if app.config['SQLALCHEMY_DATABASE_URI'].startswith('sqlite'):
from sqlalchemy import event # noqa
from sqlalchemy.engine import Engine #noqa
#event.listens_for(Engine, "connect")
def _sqlite_fk_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON;")
cursor.close()
This gist provides a variation.
You need to make a separate query to delete the posts.
db.session.query(Post).filter(Post.user_id == current_user.id).delete()
You can use attribute cascade in your User model to achieve that:
posts = db.relationship('Post', backref='author',
lazy=True, cascade='delete')
So I am working on a forum-like website for discussions using the python framework Flask. I set up a database using SQLite. I defined two tables (classes) and made primary keys for both. However, the primary keys they produce are the same. For example, for the first set of data, the primary key is 1 for both and I can't differentiate between the two sets of data. I can access the first table's data if I need to, but when I try to get the second table's data, it gives me the first tables data again. Is there any way to have two unique primary keys for both or do I have to set up two different databases.
from datetime import datetime
from flaskblog import db, login_manager
from flask_login import UserMixin
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
posts_addmath = db.relationship('PostAddmath', backref='author', lazy=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
class PostAddmath(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
These are the tables in models.py.
id_addmath = db.Column(db.Integer, primary_key=True)
I tried changing the 'id' variable in the 'PostAddmath' class but that didn't work.
#app.route("/post/<int:post_id>")
#login_required
def post_intmath(post_id):
post = Post.query.get_or_404(post_id)
return render_template('subjects/intmath/post_intmath.html', title=post.title, post=post)
#app.route("/post/<int:post_id>")
#login_required
def post_addmath(post_id):
post = PostAddmath.query.get_or_404(post_id)
return render_template('subjects/addmath/post_addmath.html', title=post.title, post=post)
This is how a access the data in my routes.py.
When I try to access the data using 'post.id' it gives me only the first tables results not the send tables.
If I have to set up a different database please let me know how to do so. Any help is greatly appreciated. Thank You.
You have the same route to both post_intmath and to post_addmath you have to change the routes for example the first route keep it as it is /post/<int:post_id> and the second route change it to /addpost/<int:post_id> for example.