While writing new test I found quite curious case:
models.ArticleTag(tag=tag, article=article)
Triggers INSERT statement on database even though there is no Session.add after.
However if I change code to look like this:
models.ArticleTag(tag.id=tag.id, article=article)
No INSERT will ever happen.
Article in this case is fresh object that is not in db yet and tag is something that we have already in db.
My models look like this:
class Tag(object):
__tablename__ = 'tag'
title = sqlalchemy.Column(sqlalchemy.Unicode(255), nullable=False)
type = sqlalchemy.Column(sqlalchemy.Unicode(255))
uid = sqlalchemy.Column(sqlalchemy.Unicode(32), primary_key=True, nullable=False)
__table_args__ = (
sqlalchemy.UniqueConstraint('type', 'title', name='title_type_unique'),
)
class ArticleTag(object):
"""Represent m2m table between Tags and Article.
association obj for mapping m2m articles<->tags
"""
__tablename__ = 'article_tag'
article_uid = sqlalchemy.Column(sqlalchemy.Unicode(32), sqlalchemy.ForeignKey('article.uid'), primary_key=True)
tag_uid = sqlalchemy.Column(sqlalchemy.Unicode(32), sqlalchemy.ForeignKey('tag.uid'), primary_key=True)
priority = sqlalchemy.Column(sqlalchemy.Integer, default=0)
tag = sqlalchemy.orm.relationship('Tag', backref='articles')
class Article(object):
__tablename__ = 'article'
uid = sqlalchemy.Column(sqlalchemy.Unicode(32), primary_key=True, nullable=False)
title = sqlalchemy.Column(sqlalchemy.Unicode(255), nullable=False)
meta_description = sqlalchemy.Column(sqlalchemy.Unicode(255))
meta_title = sqlalchemy.Column(sqlalchemy.Unicode(255))
body = sqlalchemy.Column(sqlalchemy.Unicode)
is_active = sqlalchemy.Column(sqlalchemy.Boolean, default=True)
# any tags we have
article_tags = sqlalchemy.orm.relationship('ArticleTag', backref='article', cascade='all, delete-orphan', order_by=lambda: sqlalchemy.desc(ArticleTag.priority))
#property
def tags(self):
"""Helper method that will return tags sorted by priority.
"""
return [art_tag.tag for art_tag in self.article_tags]
My guess is that I do not know something quite important about sqlalchemy.
Anyone can point me a direction?
Related
Context: I'm making an auctioning website for which I am using Flask-SQLAlchemy. My tables will need to have a many-to-many relationship (as one artpiece can have many user bids and a user can bid on many artpieces)
My question is: it is possible to add another column to my joining table to contain the id of the user bidding, the id of artpiece that they are bidding on and also how much they bid? Also if yes, how would I include this bid in the table when I add a record to said table?
bid_table = db.Table("bid_table",
db.Column("user_id", db.Integer, db.ForeignKey("user.user_id")),
db.Column("item_id", db.Integer, db.ForeignKey("artpiece.item_id"))
)
class User(db.Model):
user_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
username = db.Column(db.Integer, unique=True, nullable=False)
email = db.Column(db.String(50), unique =True, nullable=False)
password = db.Column(db.String(60), nullable=False)
creation_date = db.Column(db.DateTime, default=str(datetime.datetime.now()))
bids = db.relationship("Artpiece", secondary=bid_table, backref=db.backref("bids", lazy="dynamic"))
class Artpiece(db.Model):
item_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
artist = db.Column(db.String(40), nullable=False)
buyer = db.Column(db.String(40), nullable=False)
end_date = db.Column(db.String(40))
highest_bid = db.Column(db.String(40))
It is possible to do this with SQL Alchemy, but it's very cumbersome in my opinion.
SQLAlchemy uses a concept called an Association Proxy to turn a normal table into an association table. This table can have whatever data fields you want on it, but you have to manually tell SQLAlchemy which columns are foreign keys to the other two tables in question.
This is a good example from the documentation.
In your case, the UserKeyword table is the association proxy table that you want to build for your user/bid scenario.
The special_key column is the arbitrary data you would store like the bid amount.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import backref, declarative_base, relationship
Base = declarative_base()
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)
Check out the full documentation for instructions on how to access and create this kind of model.
Having used this in a real project, it's not particularly fun and if you can avoid it, I would recommend it.
https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
'''sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class Comment->comment'. Original exception was: relationship 'post' expects a class or a mapper argument (received: )
i want to relationship between them but it shows me this error i am beginner in sqlalchemy so please solve this.
'''
class Posts(db.Model):
Sno = db.Column(db.Integer,primary_key=True)
title = db.Column(db.String(80),unique=False,nullable=False)
slug = db.Column(db.String(21),nullable=False)
content= db.Column(db.String(120),nullable=False)
date = db.Column(db.String(12), unique=True)
img_file = db.Column(db.String(12), unique=False, nullable=True)
class Comment(db.Model):
__tablename__ = 'comment'
Sno = db.Column(db.Integer, primary_key=True)
Name = db.Column(db.String(50),unique=False,nullable=False)
Email = db.Column(db.String(120),nullable=False)
Message = db.Column(db.String(120), nullable=False)
post_sno = db.Column(db.Integer, db.ForeignKey('post.Sno'), unique=False, nullable=False)
post = db.relationship('posts', backref=db.backref('posts', lazy=True))
date = db.Column(db.String(12), unique=True,default=datetime.utcnow)
status = db.Column(db.Boolean,default=False)
def __repr__(self):
return '<Comment %r>' %self.Name
Try:
class Posts(db.Model):
columns = your_columns(xyz, abc)
comment_id = db.Column(
db.Integer,
db.ForeignKey('comment.id'),
nullable=False
)
class Comment(db.Model):
columns = your_columns(xyz, abc)
posts = db.relationship('Posts', backref='comment', lazy=True)
The relationship should point to the class which the orm is trying to make a list from. So, the orm is trying to make a list of Posts for each comment. The backref allows you to reference the other side of the relationship using a .object notation. So, if you build a Posts and you want to get its Comment that looks like:
posts = Posts.get_or_404(1)
comment = posts.comment # this is what your "backref" refers to
As an aside, I think you probably have your relationships mixed up. I imagine that comments are a many-to-one relationship to posts, not the other way around.
If that's the case, it should be something like:
class Post(db.Model):
columns = your_columns(xyz, abc)
comments = db.relationship('Comment', backref='post', lazy=True)
class Comment(db.Model):
columns = your_columns(xyz, abc)
post_id = db.Column(
db.Integer,
db.ForeignKey('post.id'),
nullable=False
)
Then you can get all comments for posts like this:
post = Post.get_or_404(1)
comments = post.comments
And you can get the post for each comment like this:
comment = Comment.get_or_404(1)
post = comment.post
You can read more on the Flask SQLAlchemy doc page.
In the Flask sqlalchemy documentation an example of using a simple many to many relationship is given:
tags = db.Table('tags',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
)
class Page(db.Model):
id = db.Column(db.Integer, primary_key=True)
tags = db.relationship('Tag', secondary=tags,
backref=db.backref('pages', lazy='dynamic'))
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
Where one can use the following syntax to reach out to the related objects:
Page.tags
What I am trying to accomplish is basically to add the relationship below to the one above:
tag_children = db.Table('tag_children,',
db.Column('parent_id', db.Integer, db.ForeignKey('tags.tag_id')),
db.Column('child_id', db.Integer, db.ForeignKey('tags.tag_id'))
)
So that each page has tags attached to it, but each tag can have multiple children in the scope of that page. I've made a show case for a Page called cars below where I have it's tags and their respective children tags.
(Page) Cars:
Mercedes
A-series
B-series
C-series
Tesla
Tesla Roadster
Model X
Model S
Model 3
All the list items above are tag objects
I want to be able to use the following syntax bellow to get the related objects:
Page.tag.children
For instance (obvously dummy code below, but I want to be clear about what the intended purpose of the relationship is):
Cars.tesla.children
I think, you don't need another table for tag_children. Try to use SQLAlchemy Adjacency Lists:
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('tag.id'))
children = relationship("Tag",
backref=backref('parent', remote_side=[id])
)
With this schema you may use syntax like:
for tag in page.tags: # where page is a Page instance received from db
print tag.children
It is a common syntax for working with SQLAlchemy models. Try to use it instead of the proposed Cars.tesla.children.
Something like Cars['tesla'].children may be implemented via getitem method, but i think, it's very unclear way.
Full code snippet:
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, primary_key=True)
name = Column(String(256))
tags = relationship('Tag', secondary='tags',
backref=backref('pages', lazy='dynamic'))
def __str__(self):
return self.name
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(256))
parent_id = Column(Integer, ForeignKey('tag.id'))
children = relationship(
"Tag",
backref=backref('parent', remote_side=[id])
)
def __str__(self):
return self.name
class Tags(Base):
__tablename__ = 'tags'
tag_id = Column(Integer, ForeignKey('tag.id'), primary_key=True)
page_id = Column(Integer, ForeignKey('page.id'), primary_key=True)
And test case:
# Create page and tags
session.add(Page(id=1, name="cars"))
session.add(Tag(id=1, name="Mercedes"))
session.add(Tag(id=2, name="A-series", parent_id=1))
session.add(Tag(id=3, name="B-series", parent_id=1))
session.add(Tag(id=4, name="C-series", parent_id=1))
session.add(Tag(id=5, name="Tesla"))
session.add(Tag(id=6, name="Tesla Roadster", parent_id=5))
session.add(Tag(id=7, name="Model X", parent_id=5))
session.add(Tag(id=8, name="Model S", parent_id=5))
session.add(Tag(id=9, name="Model 3", parent_id=5))
# Fill relation
session.add(Tags(tag_id=1, page_id=1))
session.add(Tags(tag_id=5, page_id=1))
session.commit()
print session.query(Page).get(1) # >>> cars
print session.query(Page).get(1).tags # >>> Mercedes, Tesla
print session.query(Page).get(1).tags[1].children # >>> Tesla models
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)
I have rather simple models like these:
TableA2TableB = Table('TableA2TableB', Base.metadata,
Column('tablea_id', BigInteger, ForeignKey('TableA.id')),
Column('tableb_id', Integer, ForeignKey('TableB.id')))
class TableA(Base):
__tablename__ = 'TableA'
id = Column(BigInteger, primary_key=True)
infohash = Column(String, unique=True)
url = Column(String)
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas')
class TableB(Base):
__tablename__ = 'TableB'
id = Column(Integer, primary_key=True)
url = Column(String, unique=True)
However, sqla generates queries like
SELECT "TableB".id, "TableB".url AS "TableB_url" FROM "TableB", "TableA2TableB"
WHERE "TableA2TableB".tableb_id = "TableB".id AND "TableA2TableB".tablea_id = 408997;
But why is there a cartesian product in the query when the attributes selected are those in TableB? TableA2TableB shouldn't be needed.
Thanks
As it is right now, there is a backref relationship in TableB (tableas) and it's loaded because the default loading mode is set to select.
You may want to change the TableA.tablebs to
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas', lazy="dynamic")