NoForeignKeysError on One to Many Relationship - SQLAlchemy - python

I'm writing the backend of a simple e-commerce site with Flask and Flask-SQLAlchemy but i'm having trouble with SQLAlchemy db creation.
The DB diagram is as follows
I wrote the models for my database but when i try to test them i'm getting this error message on the One to Many relationship between Parent and OrderItem:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Product.order_item - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
The models are the following:
class OrderItem(db.Model):
__tablename__ = 'OrderItem'
order_item_number = db.Column(db.Integer(), primary_key=True)
order_quantity = db.Column(db.Integer())
order_item_product_number = db.Column(db.Integer(), db.ForeignKey('Product.product_number'))
order_item_order_number = db.Column(db.Integer(), db.ForeignKey('Order.order_number'))
def __repr__(self):
return '<OrderItem {}{}>'.format(self.order_item_number, self.order_order_item)
class Product(db.Model):
_tablename__ = 'Product'
product_number = db.Column(db.Integer(), primary_key=True)
product_availability = db.Column(db.Boolean())
product_price = db.Column(db.Integer())
product_unit = db.Column(db.Integer())
product_discount = db.Column(db.Integer(), default=0)
order_item = db.relationship('OrderItem', backref='Product')
def __repr__(self):
return '<Product {}'.format(self.product_number)
Everything seems correct, i have the very same relationship pattern with Customer and Order but i don't get why i'm getting prompted with this error here.
I've already checked for any mistyping in attribute names but everything seems correct, from the table names to the attribute names.
Any help would be appreciated, thanks in advance.

Related

Simple Many-to-Many issue in Flask-Admin

I'm adding Flask-Admin to an existing Flask app (using Python 3, and MySQL with SQLAlchemy), and I simply cannot figure out how to get a many-to-many relationship to render correctly. I've read a number of questions about this here, and it looks like I am following the right practices.
I have a Quotation table, a Subject table, and a QuotationSubject table, which has to be an actual class rather than an association table, but I don't care about the extra columns in the association table for this purpose; they're things like last_modified that I don't need to display or edit. The relationships seem to work in the rest of the application.
Trimming out the fields and definitions that don't matter here, I have:
class Quotation(db.Model):
__tablename__ = 'quotation'
id = db.Column(db.Integer, primary_key=True)
word = db.Column(db.String(50))
description = db.Column(db.Text)
created = db.Column(db.TIMESTAMP, default=db.func.now())
last_modified = db.Column(db.DateTime, server_default=db.func.now())
subject = db.relationship("QuotationSubject", back_populates="quotation")
def __str__(self):
return self.word
class Subject(db.Model):
__tablename__ = 'subject'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
created = db.Column(db.TIMESTAMP, default=db.func.now())
last_modified = db.Column(db.DateTime, server_default=db.func.now())
quotation = db.relationship("QuotationSubject", back_populates="subject")
def __str__(self):
return self.name
class QuotationSubject(db.Model):
__tablename__ = 'quotation_subject'
id = db.Column(db.Integer, primary_key=True)
quotation_id = db.Column(db.Integer, db.ForeignKey('quotation.id'), default=0, nullable=False)
subject_id = db.Column(db.Integer, db.ForeignKey('subject.id'), default=0, nullable=False)
created = db.Column(db.TIMESTAMP, default=db.func.now())
last_modified = db.Column(db.DateTime, server_default=db.func.now())
quotation = db.relationship('Quotation', back_populates='subject', lazy='joined')
subject = db.relationship('Subject', back_populates='quotation', lazy='joined')
In my admin.py, I simply have:
class QuotationModelView(ModelView):
column_searchable_list = ['word', 'description']
form_excluded_columns = ['created', 'last_modified']
column_list = ('word', 'subject')
admin.add_view(QuotationModelView(Quotation, db.session))
And that's it.
In my list view, instead of seeing subject values, I get the associated entry in the QuotationSubject table, e.g.
test <QuotationSubject 1>, <QuotationSubject 17>, <QuotationSubject 18>
book <QuotationSubject 2>
Similarly, in my create view, instead of getting a list of a dozen or so subjects, I get an enormous list of everything from the QuotationSubject table.
I've looked at some of the inline_models stuff, suggested by some posts here, which also hasn't worked, but in any case there are other posts (e.g. Flask-Admin view with many to many relationship model) which suggest that what I'm doing should work. I'd be grateful if someone could point out what I'm doing wrong.
First of all, I fear there's something missing from your question because I don't see the Citation class defined. But that doesn't seem to be the problem.
The most classic example of many-to-many relationships in Flask is roles to users. Here is what a working role to user M2M relationship can look like:
class RolesUsers(Base):
__tablename__ = 'roles_users'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.id'))
class Role(RoleMixin, Base):
__tablename__ = 'role'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
def __repr__(self):
return self.name
class User(UserMixin, Base):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), index=True, unique=True)
roles = db.relationship('Role', secondary='roles_users',
backref=db.backref('users', lazy='dynamic'))
And in Flask-Admin:
from user.models import User, Role
admin.add_view(PVCUserView(User, db.session))
admin.add_view(PVCModelView(Role, db.session))
Note that the relationship is only declared once, with a backref so it's two-way. It looks like you're using back_populates for this, which I believe is equivalent.
For the case you're describing, it looks like your code declares relationships directly to the M2M table. Is this really what you want? You say that you don't need access to the extra columns in the QuotationSubject table for Flask-Admin. But do you need them elsewhere? It seems very odd to me to have a call to quotation or subject actually return an instance of QuotationSubject. I believe this is why Flask-Admin is listing all the QuotationSubject rows in the create view.
So my recommendation would be to try setting your relationships to point directly to the target model class while putting the M2M table as the secondary.
If you want to access the association model in other places (and if it really can't be an Association Proxy for some reason) then create a second relationship in each model class which explicitly points to it. You will then likely need to exclude that relationship in Flask-Admin using form_excluded_columns and column_exclude_list.

SQLAlchemy many-to-many with single link for multiple tables

I'm writing a SQLAlchemy based ORM for a database where multiple pairs of tables are connected by a single link table. This link table has four columns: from_table_name, from_table_key, to_table_key, to_table_key.
I want to set up a many to many relationship between two tables in the database. Normally I would do something like:
class Table1(Base):
__tablename__ = 'table_1'
id = Column(Integer, primary_key=True)
# some other columns here
data_from_table_2 = relationship('Table2', secondary='link')
class Table2(Base):
__tablename__ = 'table_2'
id = Column(Integer, primary_key=True)
# some other columns here
data_from_table_1 = relationship('Table1', secondary='link')
class Link(Base):
__tablename__ = 'link'
table_1_id = Column(Integer, ForeignKey('table_1.id'), primary_key=True)
table_2_id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
However, this doesn't work in the given case because I need to specify not just the key in my link table, but also the name of the table being related.
For inspiration, I've been looking through the SQLAlchemy docs on relationships here but haven't found anything useful. Does anyone know how to set up a many to many relationship between the two tables above?
As pointed out in the comments to this question, the presented database schema is a confusing design. However, the premise of the question indicates that the schema was created externally and it is up to the developer to come up with an ORM.
The requested relationship can bee implemented manually via class properties and custom queries. For example:
class Table1(Base):
__tablename__ = 'table_1'
id = Column(Integer, primary_key=True)
#property
def data_from_table_2(self):
with Session() as session:
return session.query(Reference).filter(
Link.table_1_id == self.id and
Link.table_1_name == self.__tablename__
).all()
Note that depending on your use case, you may want to use the session attached to the database object
#property
def data_from_table_2(self):
session = object_session(self)
return session.query(Reference).filter(
Link.table_1_id == self.id and
Link.table_1_name == self.__tablename__
).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.

sqlalchemy foreign key relationship attributes

I have a User table and a Friend table. The Friend table holds two foreign keys both to my User table as well as a status field. I am trying to be able to call attributes from my User table on a Friend object. For example, I would love to be able to do something like, friend.name, or friend.email.
class User(Base):
""" Holds user info """
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(25), unique=True)
email = Column(String(50), unique=True)
password = Column(String(25))
admin = Column(Boolean)
# relationships
friends = relationship('Friend', backref='Friend.friend_id',primaryjoin='User.id==Friend.user_id', lazy='dynamic')
class Friend(Base):
__tablename__ = 'friend'
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
friend_id = Column(Integer, ForeignKey(User.id), primary_key=True)
request_status = Column(Boolean)
When I get friend objects all I have is the 2 user_ids and i want to display all properties of each user so I can use that information in forms, etc. I am new to sqlalchemy - still trying to learn more advanced features. This is just a snippet from a larger Flask project and this feature is going to be for friend requests, etc. I've tried to look up association objects, etc, but I am having a hard with it.
Any help would be greatly appreciated.
First, if you're using flask-sqlalchemy, why are you using directly sqlalchemy instead of the Flask's db.Model?
I strongly reccomend to use flask-sqlalchemy extension since it leverages the sessions and some other neat things.
Creating a proxy convenience object is straightforward. Just add the relationship with it in the Friend class.
class Friend(Base):
__tablename__ = 'friend'
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
friend_id = Column(Integer, ForeignKey(User.id), primary_key=True)
request_status = Column(Boolean)
user = relationship('User', foreign_keys='Friend.user_id')
friend = relationship('User', foreign_keys='Friend.friend_id')
SQLAlchemy will take care of the rest and you can access the user object simply by:
name = friend.user.name
If you plan to use the user object every time you use the friend object specify lazy='joined' in the relationship. This way it loads both object in a single query.

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