I have an issue with foreign key in Flask.
My model is the following :
Model.py
class User(db.Model):
__tablename__ = "users"
__table_args__ = {'extend_existing': True}
user_id = db.Column(db.BigInteger, primary_key=True)
# EDIT
alerts = db.relationship('Alert', backref='user', lazy='dynamic')
def __init__(self, user_id):
self.user_id = user_id
class Alert(db.Model):
__tablename__ = 'alert'
__table_args__ = {'extend_existing': True}
alert_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.BigInteger, db.ForeignKey('users.user_id'), nullable=False)
name = db.Column(db.String(ALERT_NAME_MAX_SIZE), nullable=False)
def __init__(self, user_id, name):
self.user_id = user_id
self.name = name
I am able to add some user, for example
a = User(16)
b = User(17)
db.session.add(a)
db.session.add(b)
db.session.commit()
and some alerts :
c = Alert(16, 'test')
d = Alert(17, 'name_test')
db.session.add(c)
db.session.add(d)
db.session.commit()
I have two issues with the foreign key :
First of all, when I try to modify the user_id alert, I am able to do it even if the user_id is not in the database
alert = Alert.query.get(1)
alert.user_id = 1222 # not in the database
db.session.commit()
and I am able to create a alert with an user_id not in the Database:
r = Alert(16223, 'test')
db.session.add(r)
I don't understand why they is no relationship constraint.
Thx,
So I find how to do it with this stackoverflow question , I find how to force foreign Key Constraint.
I juste add this in __init__.py and change nothing to models.py
#event.listens_for(Engine, "connect")
def _set_sqlite_pragma(dbapi_connection, connection_record):
if isinstance(dbapi_connection, SQLite3Connection):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON;")
cursor.close()
There is mistake in your code for initialisation of Alert class. You should use backref variable (which is 'user') instead of user_id while initializing Alert. Following code should work.
class User(db.Model):
__tablename__ = "user"
__table_args__ = {'extend_existing': True}
user_id = db.Column(db.BigInteger, primary_key=True)
alerts = db.relationship('Alert', backref='user', lazy='dynamic')
def __init__(self, user_id):
self.user_id = user_id
class Alert(db.Model):
__tablename__ = 'alert'
alert_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.BigInteger, db.ForeignKey('user.user_id'), nullable=False)
name = db.Column(db.String(ALERT_NAME_MAX_SIZE), nullable=False)
def __init__(self, user, name):
self.user = user
self.name = name
It works as below:
>>> a = User(7)
>>> db.session.add(a)
>>> db.session.commit()
>>> b = Alert(a, 'test')
>>> db.session.add(b)
>>> db.session.commit()
>>> alert = Alert.query.get(1)
>>> alert.user_id
7
>>> alert.user
<app.User object at 0x1045cb910>
>>> alert.user.user_id
7
It does not allow you to assign variable like d = Alert(88, 'trdft')
I think you should read Flask SqlAlchemy's One-to-Many Relationships for more details.
If you are using SQLite, foreign key constraints are by default not enforced. See Enabling Foreign Key Support in the documentation for how to enable this.
Related
I am trying to build a model where there is the default values then there is the user defined values. So the default values would come from the spices table. Yes the spices table would contain default data. The user would define the composite spice and make modifications as desired for a specific recipe. If you think I am structuring this wrong please provide your expertise. I feel lost on how to do this.
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=True)
#... extra
#... extra
class Spice(db.Model):
__tablename__ = 'spices'
code = db.Column(db.String(5), primary_key=True) # this is the id code
name = db.Column(db.String(60))
origin = db.Column(db.String(15))
def __init__(self, code, name, origin):
self.code = code
self.name = name
self.origin = origin
class Seasoning(Spice):
__tablename__ = 'seasonings'
# Note that the below item should come from Recipe. How would I do this?
recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False)
class Recipe(db.Model):
__tablename__ = 'recipe'
user = db.relationship(User)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
description = db.Column(db.Text(), nullable=False)
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
ingredient = db.relationship('Ingredient', backref='recipe', lazy='dynamic', primaryjoin="Recipe.id == Seasoning.recipe_id")
def __init__(self, id, name, description, date):
self.id = id
self.name = name
self.description = description
self.date = date
in my views.py I have
...
seasoning = Seasoning(code=from.code.data, name=form.name.data, origin=form.origin,
recipe_id=recipe_id)
db.session.add(seasoning)
db.create_all()
db.session.commit()
...
When I run this I do get an error when I try to commit() to seasoning. How do I resolve this?
sqlalchemy.exc.OperationalError: (raised as a result of Query-invoked
autoflush; consider using a session.no_autoflush block if this flush
is occurring prematurely) (sqlite3.OperationalError) table spices has
no column named recipe_id
You need to describe recipe_id in your spices class
table spices has no column named recipe_id
I have two tables, Users and ChatSessions. ChatSessions has two fields, user_id and friend_id, both foreign keys to the Users table.
user_id always contains the user that initiated the chat session, friend_id is the other user. As a certain user can have chat sessions initiated by him, or his friends, he can have his id either as user_id or as friend_id, in various sessions.
Is it possible to define a relationship in the Users table, where i have access to all the chat_sessions of that user, no matter whether his id is in user_id or friend_id?
Something like this:
chat_sessions = db.relationship('chat_sessions',
primaryjoin="or_(User.id==ChatSession.user_id, User.id==ChatSession.friend_id)",
backref="user")
I receive the following error when I try to commit an entry to the Users table:
ERROR main.py:76 [10.0.2.2] Unhandled Exception [93e3f515-7dd6-4e8d-b096-8239313433f2]: relationship 'chat_sessions' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
The models:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(60), index=True, unique=True)
password = db.Column(db.String(255))
name = db.Column(db.String(100))
active = db.Column(db.Boolean(), nullable=False)
chat_sessions = db.relationship('chat_sessions',
primaryjoin="or_(User.id==ChatSession.user_id, User.id==ChatSession.friend_id)")
class ChatSession(db.Model):
__tablename__ = 'chat_sessions'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
friend_id = db.Column(db.Integer, db.ForeignKey('users.id'))
status = db.Column(db.String(50))
user = db.relationship('User', foreign_keys=[user_id])
friend = db.relationship('User', foreign_keys=[friend_id])
It's difficult to be certain without seeing the tables' code, but it might be sufficient to remove the backref argument.
Here's a pure SQLAlchemy implementation that seems to do what you want:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import orm
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
all_chats = orm.relationship('Chat',
primaryjoin="or_(User.id==Chat.user_id, User.id==Chat.friend_id)")
def __repr__(self):
return f'User(name={self.name})'
class Chat(Base):
__tablename__ = 'chats'
id = sa.Column(sa.Integer, primary_key=True)
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
friend_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
user = orm.relationship('User', foreign_keys=[user_id])
friend = orm.relationship('User', foreign_keys=[friend_id])
def __repr__(self):
return f'Chat(user={self.user.name}, friend={self.friend.name})'
engine = sa.create_engine('sqlite:///')
Base.metadata.create_all(bind=engine)
Session = orm.sessionmaker(bind=engine)
usernames = ['Alice', 'Bob', 'Carol']
session = Session()
users = [User(name=name) for name in usernames]
session.add_all(users)
session.flush()
a, b, c = users
session.add(Chat(user_id=a.id, friend_id=b.id))
session.add(Chat(user_id=a.id, friend_id=c.id))
session.add(Chat(user_id=c.id, friend_id=a.id))
session.commit()
session.close()
session = Session()
users = session.query(User)
for user in users:
for chat in user.all_chats:
print(user, chat)
print()
session.close()
This is the output:
User(name=Alice) Chat(user=Alice, friend=Bob)
User(name=Alice) Chat(user=Alice, friend=Carol)
User(name=Alice) Chat(user=Carol, friend=Alice)
User(name=Bob) Chat(user=Alice, friend=Bob)
User(name=Carol) Chat(user=Alice, friend=Carol)
User(name=Carol) Chat(user=Carol, friend=Alice)
So... I'm trying to commit some sql from flask app inside, and the model code is as follows:
class User(UserMixin, db.Model):
__tablename__ = '_users'
id = db.Column(db.Integer, primary_key=True)
user_email = db.Column(db.VARCHAR(50), nullable=False, unique=True)
user_reg_date = db.Column(db.TIMESTAMP, nullable=False)
last_login = db.Column(db.TIMESTAMP, nullable=True)
passwd = db.Column(db.VARCHAR(80), nullable=True)
social_id = db.Column(db.VARCHAR(80), nullable=True, unique=True)
def __init__(self, user_email, passwd, social_id):
self.user_email = user_email
self.user_reg_date = current_time()
self.passwd = passwd
self.social_id = social_id
class Player(db.Model):
__tablename__ = '_players'
id = db.Column(db.Integer, primary_key=True)
player_unique_id = db.Column(db.Integer, unique=True)
user_id = db.Column(db.Integer, db.ForeignKey('_users.id'))
affiliated_crew_id = db.Column(db.Integer, db.ForeignKey('crew.id'))
player_nick = db.Column(db.VARCHAR(50), unique=True)
player_highscore = db.Column(db.Integer)
player_badge = db.Column(db.VARCHAR(100))
player_rank = db.Column(db.Integer)
def __init__(self, player_unique_id, user_id, affiliated_crew_id
, player_nick):
self.player_unique_id = player_unique_id
self.user_id = user_id
self.affiliated_crew_id = affiliated_crew_id
self.player_nick = player_nick
self.player_highscore = 0
self.player_badge = None
self.player_rank = 0
I already have the proper columns in the SQL(as I written these from pre-made tables) it's all correct.
the part committing the sql is as follows:
player = Player(player_unique_id=00000, user_id=user_num, affiliated_crew_id=crew_id
, player_nick=nick)
db.session.add(player)
db.session.commit()
and it's returning this:
sqlalchemy.exc.InternalError: (pymysql.err.InternalError) (1054, "Unknown column '_users.id' in 'field list'") [SQL: 'INSERT INTO _players (player_unique_id, user_id, affiliated_crew_id, player_nick, player_highscore, player_badge, player_rank) VALUES (%(player_unique_id)s, _users.id, %(affiliated_crew_id)s, %(player_nick)s, %(player_highscore)s, %(player_badge)s, %(player_rank)s)'] [parameters: {'player_unique_id': 84658, 'affiliated_crew_id': '1', 'player_nick': 'player', 'player_highscore': 0, 'player_badge': None, 'player_rank': 0}]
what am I doing wrong here? searching didn't help so far...
I was using raw User class to get the User.id, which kept returning None.
since User was my flask-login's user class I had to bring my user id by using current_user.id.
so fixing my user_num init, which had user_num = User.id to user_num = current_user.id fixed everything...
thank you everyone who looked into the problem...
I need to allow a user block other users in my app. My problem occurs when I want to check if a user has been blocked by the current (logged-in user). How do I check if a particular user is in the blocked list of the current user? My models are below:
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
urid = Column(String(50), unique=True)
full_name = Column(String(100))
...
blockedlist = relationship('Blacklist', primaryjoin='Blacklist.user_id==User.id', back_populates='owner')
class Blacklist(db.Model):
__tablename__ = 'blacklist'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
blocked_id = Column(Integer, ForeignKey('users.id'))
date_blocked = Column(DateTime, default=func.now())
owner = relationship('User', primaryjoin='Blacklist.user_id==User.id', back_populates='blockedlist')
blocked = relationship('User', primaryjoin='Blacklist.blocked_id==User.id')
def __init__(self, user_id, blocked_id):
self.user_id = user_id
self.blocked_id = blocked_id
Basically, I want to check that a user's id is in the current user's list of blocked id
You can use .any on a relationship, like so:
alice = User(urid='alice', full_name='Alice')
bob = User(urid='bob', full_name='Bob')
session.add(Blacklist(owner=alice, blocked=bob))
session.commit()
bob_blocked_alice = (
session.query(User.blockedlist.any(blocked_id=alice.id))
.filter(User.id == bob.id)
.scalar()
)
print('Did Bob block Alice:', bob_blocked_alice)
alice_blocked_bob = (
session.query(User.blockedlist.any(blocked_id=bob.id))
.filter(User.id == alice.id)
.scalar()
)
print('Did Alice block Bob:', alice_blocked_bob)
As an aside, you can simplify your relationships using the foreign_keys parameter:
blockedlist = relationship('Blacklist', foreign_keys='Blacklist.user_id', back_populates='owner')
owner = relationship('User', foreign_keys=user_id, back_populates='blockedlist')
blocked = relationship('User', foreign_keys=blocked_id)
I've seen a few questions similar to this but none quite hit the nail on the head. Essentially I have three table models Center(), Business(), and CenterBusiness() in a Flask Application using SQLAlchemy. Currently I'm adding to said relationship in this manner:
biz = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
db.session.add(biz)
db.session.commit()
assoc = CenterBusiness(bizId=biz.id, cenId=session['center'])
db.session.add(assoc)
db.session.commit()
As you can see that's a bit ugly and I know there is a way to do it in one hit with the relationship as they are defined. I see on SQLAlchemy's docs they have a explanation of working with such a table but I can't seem to get it to work.
#Directly from SQLAlchemy Docs
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
assoc.business = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
Unfortunately, that doesn't seem to be doing the trick... Any help would be greatly appreciated and below I've posted the models involved.
class Center(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=False)
phone = db.Column(VARCHAR(10), nullable=False)
location = db.Column(VARCHAR(255), nullable=False)
businesses = db.relationship('CenterBusiness', lazy='dynamic')
employees = db.relationship('CenterEmployee', lazy='dynamic')
class Business(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=True)
typId = db.Column(TINYINT(2, unsigned=True),
db.ForeignKey('biz_type.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
nullable=False)
type = db.relationship('BizType', backref='businesses',
lazy='subquery')
name = db.Column(VARCHAR(255), nullable=False)
contact = db.Column(VARCHAR(255), nullable=False)
phone = db.Column(VARCHAR(10), nullable=False)
documents = db.relationship('Document', backref='business',
lazy='dynamic')
class CenterBusiness(db.Model):
cenId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('center.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
bizId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('business.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
info = db.relationship('Business', backref='centers',
lazy='joined')
archived = db.Column(TINYINT(1, unsigned=True), nullable=False,
server_default='0')
I was able to get this working, my problem lied in the following bit of code (error in bold):
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
**assoc.info** = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
As explained in my comment in the question:
Alright my issue was that I was not using the relationship key "info"
I have in my CenterBusiness model to define the appended association.
I was saying center.business thinking that the term business in that
case was arbitrary. However, I needed to actually reference that
relationship. As such, the appropriate key I had setup already in
CenterBusiness was info.
I will still accept any updates and/or better ways to handle this situation, though I think this is the best route at the time.
below example can help u
more details http://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)