Many-to-many relationships from multiple models using flask-sqlalchemy - python

For the Flask project which I am doing, I need to implement a feature that would enable multiple-authors for an article. However, the requirement is that the authors would come from two different classes (models) — that is,
User
Committee
These two models will have to be related with the third table, which is,
Article
The models defined using flask-sqlalchemy as follows.
User model:
class User(db.model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.UnicodeText)
designation = db.Column(db.UnicodeText)
Committee model:
class Committee(db.model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.UnicodeText)
region = db.Column(db.UnicodeText)
Article model:
class Article(db.model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.UnicodeText)
body = db.Column(db.UnicodeText)
authors = db.relationship()
I know the approach to implement a many-to-many relationship involving either Article-User or Article-Committee pairs. However, I am trying to implement Article-(User, Committee) relationships. That is, one article will have one or more user and/or committee as the author(s), and one user or one committee will have one or more articles. Is this possible at all?

The many to many relationship link provided is a step in the right direction. I would build an "Associations" table that connects all three primary keys as foreign keys. This will allow multiple entries for all three models and create the many to many relationship in all directions.
Associations= db.Table('Associations',
db.Column('FKID_USER', db.Integer, db.ForeignKey('User.id'), primary_key=True),
db.Column('FKID_COMMITTEE', db.Integer, db.ForeignKey('Committee.id'), primary_key=True),
db.Column('FKID_ARTICLE', db.Integer, db.ForeignKey('Article.id'), primary_key=True)
)
Based on your description, you may end up with null a value in one column for an entry.

Related

What is missing in the TaggedBlogRecord model for inheritance in sqlalchemy for it to work?

I am trying modify the pyramid_blogr example. The only closest QA thread is this one and the link mentioned in that thread. I went through both of them. I am using concrete inheritance, by which I understand that separate tables will be created for each model. Still, when I query records from the second model, I get errors saying that the model do have columns like title or created in TaggedBlogRecord.
Errors
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: ====> entries.created <======
[SQL: SELECT entrieslanguage.id AS entrieslanguage_id
FROM entrieslanguage ORDER BY entries.created DESC
LIMIT ? OFFSET ?]
The sample code as follows
class BlogRecord(Base):
__tablename__ = 'entries'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255), unique=True, nullable=False)
body = Column(UnicodeText, default=u'')
created = Column(DateTime, default=datetime.datetime.utcnow)
edited = Column(DateTime, default=datetime.datetime.utcnow)
class TaggedBlogRecord(BlogRecord):
__tablename__ = 'taggedentries'
__mapper_args__ = {'concrete':True}
id = Column(Integer, primary_key=True)
tags = Column(Unicode(255))
Read https://docs.sqlalchemy.org/en/latest/orm/inheritance.html#concrete-table-inheritance more closely (emphasis added):
Two critical points should be noted:
We must define all columns explicitly on each subclass, even those of
the same name. A column such as Employee.name here is not copied out
to the tables mapped by Manager or Engineer for us.
while the Engineer
and Manager classes are mapped in an inheritance relationship with
Employee, they still do not include polymorphic loading. Meaning, if
we query for Employee objects, the manager and engineer tables are not
queried at all.

Delete all in a Many to Many secondary table association in sqlalchemy

I have following models and associations:
class CartProductsAssociation(db.Model):
__tablename__ = 'cart_products_association'
cart_id = db.Column(db.Integer, db.ForeignKey('carts.id',ondelete='CASCADE'),primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('products.id',ondelete='CASCADE'), primary_key=True)
quantity = db.Column(db.Integer)
product = db.relationship("Product", backref="cart_associations", cascade="all,delete",passive_deletes=True)
cart = db.relationship("Cart", backref="product_associations",cascade="all,delete",passive_deletes=True)
class Product(db.Model):
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
img_path = db.Column(db.String)
price = db.Column(db.Float, default=0.0)
product_categories = db.relationship(
"ProductCategory",
secondary=product_product_categories,
back_populates="products")
carts = db.relationship("Product", secondary="cart_products_association",passive_deletes=True,cascade="all,delete" )
class Cart(db.Model):
__tablename__ = 'carts'
id = db.Column(db.Integer, primary_key=True)
branch_id = db.Column(db.Integer, db.ForeignKey('branch.id'))
branch = db.relationship("Branch", back_populates="carts")
page_id = db.Column(db.Integer, db.ForeignKey('pages.id'))
page = db.relationship("Page", back_populates="carts")
shopper_id = db.Column(db.String, db.ForeignKey('shoppers.fb_user_id'))
shopper = db.relationship(
"Shopper",
back_populates="carts")
products = db.relationship("Product", secondary="cart_products_association")
cart_status = db.Column(db.Enum('user_unconfirmed','user_confirmed','client_unconfirmed','client_confirmed', name='cart_status'), default='user_unconfirmed')
When I am trying to delete a product I am getting following error:
AssertionError
AssertionError: Dependency rule tried to blank-out primary key column 'cart_products_association.cart_id' on instance '<CartProductsAssociation at 0x7f5fd41721d0>'
How can I solve it?
it solved the problem:
product = models.Product.query.get(product_id)
for ass in product.cart_associations:
db.session.delete(ass)
db.session.delete(product)
db.session.commit()
The error is caused by back references cart_associations and product_associations created by CartProductsAssociation. Since they don't have explicit cascades set, they have the default save-update, merge, and without delete the
default behavior is to instead de-associate ... by setting their foreign key reference to NULL.
Due to this when a Product is up for deletion SQLAlchemy will first fetch the related CartProductsAssociation objects and try to set the primary key to NULL.
It seems that originally there has been an attempt to use passive_deletes=True with ondelete='CASCADE', but the passive deletes have ended up on the wrong side of the relationship pair. This should produce a warning:
sqlalchemy/orm/relationships.py:1790: SAWarning: On CartProductsAssociation.product, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only.
If the relationships are configured as
class CartProductsAssociation(db.Model):
...
product = db.relationship(
"Product", backref=db.backref("cart_associations",
cascade="all",
passive_deletes=True))
cart = db.relationship(
"Cart", backref=db.backref("product_associations",
cascade="all",
passive_deletes=True))
instead, then when a Product instance that has not loaded its related CartProductsAssociation objects is deleted, SQLAlchemy will let the DB handle cascading. Note that the SQLAlchemy delete cascade is also necessary, or the error will come back if a Product instance that has loaded its related association objects is deleted. passive_deletes="all" can also be used, if there are some special triggers or such in place in the DB that must be allowed to fire.
When deleting a Product that has loaded both carts and cart_associations the situation is even more complicated, because both association object pattern and a many to many relationship are in use, and the 2 relationships do not coordinate changes together – see the warning in "Association Object". You might want to consider either making the other relationship viewonly, or use the association proxy extension across the association object relationship:
class Product:
...
carts = association_proxy(
'cart_associations', 'cart',
creator=lambda cart: CartProductsAssociation(cart=cart))
Finally, the delete cascade in Product.carts is a bit odd, though may be as designed, and will delete the related Cart objects along with the Product if they have been loaded, and additionally removes rows from the secondary table. On the other hand that relationship has passive deletes also, so the Cart objects are not deleted if not loaded when the Product is deleted, which would seem to conflict with the SQLAlchemy cascade.

Define Many to Many without a foreignKey in SQLAlchemy

I have existing data that I want to model. Its essentially:
class Investor(db.Model):
id = db.Column(id, primary_key=True)
investments = db.relationship('Investments', backref='investor')
class Round(db.Model):
id = db.Column('id', primary_key=True)
investments = db.Table(
'investments',
db.Column('investor_id', db.ForeignKey('investor.id')),
db.Column('round_id', db.ForeignKey('round.id')),
)
Now, every time I try execute this little model, I get the following error:
expression 'Investments' failed to locate a name
I understand, that investments, needs to be a class, but I've tried making a dummy class with db.model, and it hasn't really worked. In that version I get problems with asking for a primary join or a mapper. I'm quite confused, and a little guidance would help greatly.
If the many to many relationship is between Investor and Round, You can define the model as follows:
class Investor(db.Model):
id = db.Column(db.Integer, primary_key=True)
rounds = db.relationship('Round', secondary=investments, backref='investor')
class Round(db.Model):
id = db.Column(db.Integer, primary_key=True)
investments = db.Table(
'investments',
db.Column('investor_id', db.Integer, db.ForeignKey('investor.id')),
db.Column('round_id', db.Integer, db.ForeignKey('round.id')))

How to create a field with a list of foreign keys in SQLAlchemy?

I am trying to store a list of models within the field of another model. Here is a trivial example below, where I have an existing model, Actor, and I want to create a new model, Movie, with the field Movie.list_of_actors:
import uuid
from sqlalchemy import Boolean, Column, Integer, String, DateTime
from sqlalchemy.schema import ForeignKey
rom sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Actor(Base):
__tablename__ = 'actors'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
nickname = Column(String)
academy_awards = Column(Integer)
# This is my new model:
class Movie(Base):
__tablename__ = 'movies'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
# How do I make this a list of foreign keys???
list_of_actors = Column(UUID(as_uuid=True), ForeignKey('actors.id'))
I understand that this can be done with a many-to-many relationship, but is there a more simple solution? Note that I don't need to look up which Movie's an Actor is in - I just want to create a new Movie model and access the list of my Actor's. And ideally, I would prefer not to add any new fields to my Actor model.
I've gone through the tutorials using the relationships API, which outlines the various one-to-many/many-to-many combinations using back_propagates and backref here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html But I can't seem to implement my list of foreign keys without creating a full-blown many-to-many implementation.
But if a many-to-many implementation is the only way to proceed, is there a way to implement it without having to create an "association table"? The "association table" is described here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many ? Either way, an example would be very helpful!
Also, if it matters, I am using Postgres 9.5. I see from this post there might be support for arrays in Postgres, so any thoughts on that could be helpful.
Update
It looks like the only reasonable approach here is to create an association table, as shown in the selected answer below. I tried using ARRAY from SQLAlchemy's Postgres Dialect but it doesn't seem to support Foreign Keys. In my example above, I used the following column:
list_of_actors = Column('actors', postgresql.ARRAY(ForeignKey('actors.id')))
but it gives me an error. It seems like support for Postgres ARRAY with Foreign Keys is in progress, but still isn't quite there. Here is the most up to date source of information that I found: http://blog.2ndquadrant.com/postgresql-9-3-development-array-element-foreign-keys/
If you want many actors to be associated to a movie, and many movies be associated to an actor, you want a many-to-many. This means you need an association table. Otherwise, you could chuck away normalisation and use a NoSQL database.
An association table solution might resemble:
class Actor(Base):
__tablename__ = 'actors'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
nickname = Column(String)
academy_awards = Column(Integer)
class Movie(Base):
__tablename__ = 'movies'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
actors = relationship('ActorMovie', uselist=True, backref='movies')
class ActorMovie(Base):
__tablename__ = 'actor_movies'
actor_id = Column(UUID(as_uuid=True), ForeignKey('actors.id'))
movie_id = Column(UUID(as_uuid=True), ForeignKey('movies.id'))
If you don't want ActorMovie to be an object inheriting from Base, you could use sqlachlemy.schema.Table.

How to declared one-to-many if there are 2 fields for a same foreign key

I'm new to python(sqlalchemy), and I'm learning to build web site with pylons and sqlalchemy.
I have a problem when I declare the relationship between models. I've tried it several hours, but failed. But I think it should be a basic question.
I have two classes: User and Article, user can create articles, and modified the other people's article(like wiki).
So a user has created-articles and edited-articles.
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True)
title = ...
user_id = Column(Integer, ForeignKey('users.id'))
editor_id = Column(Integer, ForeignKey('users.id'))
# relations
user = relationship('User', backref='articles') # -> has error
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(20))
def __init__(self):
pass
But there is an error displayed:
InvalidRequestError: One or more mappers failed to compile. Exception was probably suppressed within a hasattr() call. Message was: Could not determine join condition between parent/child tables on relationship Article.user. Specify a 'primaryjoin' expression. If this is a many-to-many relationship, 'secondaryjoin' is needed as well.
I tried to add primaryjoin to the line('has error'), but don't know what it should be. I tried some codes, but none works.
Thank you in advance!
Ah, thats obvious one.
Article class has two references to User, user_id and editor_id, so SQLA does not know which one of them to use for your relation. Just use explicit primaryjoin:
user = relation('User', backref='articles', primaryjoin="Article.user_id==User.id")

Categories