I have two models, one is Identification which contains two IDs (first_id and second_id) and the second is User. The idea is that only authorised users will be given their first_id and second_id pair of values. They go to the site and login by entering the two id's plus a username and password (which they generate there and then).
I am trying to achieve two things here:
Pre-populate the Identification table with many (let's say 100) first_id/second_id values that will serve as the correct value pairs for logging in.
Set up the User class in such a way that only if the user enters a correct first_id/second_id pair in the login form can they log in (presumable this involves checking the form data with the Identification table somehow).
Here are the model classes:
class Identification(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_id= db.Column(db.Text, unique=True)
second_id= db.Column(db.Text, unique=True)
def __init__(self, first_id, second_id):
self.first_id= first_id
self.second_id= second_id
def __repr__(self):
return f"ID: {self.id}, first_id: {self.first_id}, second_id: {self.second_id}"
class User(db.Model, UserMixin):
__tablename__ = 'user'
first_id= db.relationship('Identification', backref = 'identificationFID', uselist=False)
second_id = db.relationship('Identification', backref = 'identificationSID', uselist=False)
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Text, unique=True, index=True)
password_hash = db.Column(db.Text(128))
identification_id = db.Column(db.Integer, db.ForeignKey('identification.id'), unique=True)
first_id = db.Column(db.Text, unique=True)
second_id = db.Column(db.Text, unique=True)
I would appreciate any help on this as I'm struggling and this is really above my understanding of python/Flask. Thanks all!
The answer above didn't work for me, because the create_tables() function since being part of the User class, requested that I pass an Instance of that class.
The solution I came up with, was to call the function after db.create_all(). This seemed like a good place to put the call, because of the #app.before_first_request decorator.
init.py
#app.before_first_request
def create_tables():
"""Create Tables and populate certain ones"""
db.create_all()
from app.models.init_defaults import init_defaults
init_defaults()
init_defaults.py
def init_defaults():
"""Pre-Populate Role Table"""
if Role.query.first() is None:
roles = ['Admin', 'Master', 'Apprentice']
for role in roles:
user_role = Role(access_level=role)
if role != 'Apprentice':
user_role.set_password('Passw0rd!')
db.session.add(user_role)
db.session.commit()
pass
Due to the decorator the function is now only called once per instance. Another solution I could imagine working, would be to use events:
https://dzone.com/articles/how-to-initialize-database-with-default-values-in
Note: This is a development solution not fit for production.
You can use mock data to populate these tables.
create a function in this py file where you can add objects to DB using ORM
and then call the function in __init__.py, which will populate data once your flask server starts.
Update:-
here is a code for your reference.
Model.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
class User(Base):
\__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
#Create getter setters
def create_tables():
Base.metadata.create_all(engine)
user = User()
user.id=1
user.name="ABC"
user.fullname="ABCDEF"
session.add(user)
# similarly create more user objects with mock data and add it using session
__init__.py
from model import User
User.create_tables()
Reference
Related
I have two model classes in separate files, created a One-To-Many relationship between them.
user.py:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(100), index=True, unique=True, nullable=False)
password = db.Column(db.String(128), nullable=False)
projects = db.relationship('Project', backref='owner', lazy='dynamic')
project.py:
class Project(db.Model):
__tablename__ = 'projects'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), index=True, unique=True, nullable=False)
owner_id = db.Column(db.Integer, db.ForeignKey('User.id'))
This is how I create my app:
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
db = SQLAlchemy(app)
#app.before_first_request
def create_tables():
db.create_all()
app.run(debug=True)
However when I create a request I get the following error:
sqlalchemy.exc.NoReferencedTableError:
Foreign key associated with column 'projects.owner_id' could not find
table 'User' with which to generate a foreign key to target column 'id'
I understand this is a duplicate of this question asked before, however I tried that and did not work:
#app.before_first_request
def create_tables():
from projectx.models.auth.user import User
from projectx.models.private_data.project import Project
db.create_all()
I also tried giving the same __table_args__ = {"schema": "testdb"} args to both models (I manually created the db with this name) and refer to db.ForeignKey('testdb.User.id) to no avail, the error is the same.
What am I missing here?
It seems I misunderstood some concepts about how this relationship abstraction works.
The foreign key reference is to the tablename and not the class name naturally.
I should have written db.ForeignKey('users.id').
I'm starting with FastAPI and SQLAlchemy, and I have a question about loading models in the correct order to satisfy SQLAlchemy models relationship. I've got two models, Profile and Account:
Profile:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from project.core.database import Base
class Profile(Base):
__tablename__ = "profiles"
id = Column(Integer, primary_key=True)
name = Column(String(10), nullable=False)
slug = Column(String(10), nullable=False)
order = Column(Integer, unique=True, nullable=False)
accounts = relationship(
"Account", foreign_keys="[Account.profile_id]", back_populates="profile"
)
Account:
from sqlalchemy import Column, Integer, String, LargeBinary, ForeignKey
from sqlalchemy.orm import relationship
from project.core.database import Base
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
email = Column(String(255), nullable=False)
password = Column(LargeBinary, nullable=False)
profile_id = Column(Integer, ForeignKey("profiles.id"))
profile = relationship(
"Profile", foreign_keys=[profile_id], back_populates="accounts"
)
And in my project.core.database file, I created a method to import the models, since I was having issues with models not being located when the relationship was attempted to be made.
def import_all():
import project.models.profile
import project.models.account
def init_db():
import_all()
My question is, is there a smarter way to load the models in the correct order? Because now I only have two models, but soon it can grow to dozens of models and I think this will become such a monster to manage.
I looked for resources and examples, but everything I found created the models in a single file.
Thanks in advance!
Let's take a look at full-stack-fastapi-postgresql, a boilerplate template created by the author of FastAPI:
base.py
from app.db.base_class import Base
from app.models.item import Item
from app.models.user import User
init_db.py
from app.db import base # noqa: F401 import db models
# make sure all SQL Alchemy models are imported (app.db.base) before initializing DB
# otherwise, SQL Alchemy might fail to initialize relationships properly
# for more details: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28
def init_db(db: Session) -> None:
...
So, as you can see it's a polucalar way to import models, here you can find additional discussion: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28
So I'm working on an webapp using Flask. I followed a naming convention in my data models but it seemed that this convention does not properly integrate well with Flask-extensions for specific field naming, quoting for instance, from Flask-Security extension
Models
Flask-Security assumes you’ll be using libraries such as SQLAlchemy,
MongoEngine, Peewee or PonyORM to define a data model that includes a
User and Role model. The fields on your models must follow a
particular convention depending on the functionality your app
requires. Aside from this, you’re free to add any additional fields to
your model(s) if you want. At the bare minimum your User and Role
model should include the following fields:
User
id
email
password
active
...
Now assume my user model is something like:
class User(UserMixin, db.Model):
'''This model represents all types of Users registered'''
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
user_email = db.Column(db.String(64), unique=True, index=True)
user_password_hash = db.Column(db.String(128))
If I have to change my model's field to what Flask-extension requires, that requires me to change in a lot of files, which is a tedious task to do.
What I thought of is something like this:
class User(UserMixin, db.Model):
'''This model represents all types of Users registered'''
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
id = self.user_id #For Flask-Extensions
user_email = db.Column(db.String(64), unique=True, index=True)
email = self.user_email #For Flask-Extensions
user_password_hash = db.Column(db.String(128))
password = self.user_password_hash #For Flask-Extensions
How bad is this solution and what alternatives I have?
I think you can use Synonyms.
I didn't check but I think this should works.
from sqlalchemy.orm import synonym
class User(UserMixin, db.Model):
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
id = synonym('user_id')
user_email = db.Column(db.String(64), unique=True, index=True)
email = synonym('user_email')
user_password_hash = db.Column(db.String(128))
password = synonym('user_password_hash')
that's more or less workable and something I've done. I'd recommend implementing it using a property:
#property
def id(self):
return self.user_id
If Flask-Security needs the property accessible at the class level as well, you can use SQLAlchemy's hybrid_property instead.
I want to save hash of name to hash_name column Also I use Flask-Admin to manage my data.
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode, unique=True, nullable=False)
hash_name = db.Column(db.Unicode, unique=True)
admin.add_view(ModelView(User, db.session))
Also I set default with uuid package for hash_name but this page in result had a problem .my uuid never changed . I refreshed but not changed
If you only use flask-admin's SQLAlchemy ModelViews for editing, then it's possible to do following:
class UserView(sqla.ModelView):
# Hide `hash_name` in list and form views
column_exclude_list = ('hash_name',)
form_excluded_columns = ('hash_name',)
# Generate new hash on `name` change
def on_model_change(self, form, model, is_created):
if len(model.name):
model.hash_name = generate_hash_name(model.name)
Otherwise use #mehdy's event approach.
I think you can use sqlalchemy's even listeners to manipulate your object before committing it to the database:
from sqlalchemy import event
...
#event.listens_for(User, "before_commit")
def gen_default(mapper, connection, instance):
instance.hash_name = hash_function(instance.name)
so before each commit it will be invoked and updates the hash_name attribute with the proper hash on name
So I am new to SQLAlchemy, and I would like after a new User object is created that a Permissions object is created by default. Reading the documentation it seems that after_insert mapper event is what I should be using. The problem is that the event listener is called before the object is actually committed.
Is there a way to have the Permissions object at least put in possibly a transaction or on the list of objects to be created after the user object is actually committed.
class Users():
__tablename__ = 'users'
user_id = Column(String(36), primary_key=True, nullable=False)
.......
class Permissions():
__tablename__ = 'permissions'
user_id = Column(String(36), ForeignKey('users.user_id'), primary_key=True,
nullable=False)
..........
#event.listens_for(Users, "after_insert)
def create_permissions(mapper, connection, user):
connection.execute(Permissions.__table__.insert(user_id=user.user_id))
IntegrityError: (IntegrityError) (1452, 'Cannot add or update a child row: a foreign key constraint fails
Ideally I would like to do this without a Database Trigger on the Users table.
You do not need to attach session to ORM object. Just use object_session function from sqlalchemy.orm module like following:
from sqlalchemy.orm import object_session
#event.listens_for(Users, 'after_insert')
def create_permissions(mapper, connection, user):
object_session(user).add(Permissions(user_id=user.user_id))
So by changing my class defintions around, and attaching the session object to each class I am able to add a new Permissions object at time after_insert is signaled. And the proper sequences of commits take place.
class Users():
__tablename__ = 'users'
user_id = Column(String(36), primary_key=True, nullable=False)
............
class Permissions():
__tablename__ = 'permissions'
user_id = Column(String(36), ForeignKey('users.user_id'), primary_key=True,
nullable=False)
............
#event.listens_for(Users, "after_insert")
def create_permissions(mapper, connection, user):
user.session.add(Permissions(user_id=user.user_id))