I have two relationships to the same table. When I add an element to one relationship, this does not reflect to the other relationship until I submit the session. Is there a way to force "update" the relationships?
Concrete example:
class Event(ManagerBase):
"""Defines an event."""
__tablename__ = 'eventing_events'
id = Column(Integer, primary_key=True)
device_id = Column(Integer, ForeignKey(EventingDevice.id), nullable=False)
device = relation(EventingDevice)
type_id = Column(Integer, ForeignKey(EventType.id), nullable=False)
type = relation(EventType)
datetime = Column(DateTime, nullable=False)
summary = Column(String(500))
fields = relation("EventFieldValue",
viewonly=True,
collection_class=attribute_mapped_collection("field.name"))
class EventFieldValue(ManagerBase):
"""The value of a single field of an event."""
__tablename__ = 'eventing_event_field_values'
event_id = Column(Integer, ForeignKey(Event.id), primary_key=True)
event = relation(Event, backref=backref("field_values",
collection_class=attribute_mapped_collection("field")))
field_id = Column(Integer, ForeignKey(Field.id), primary_key=True)
field = relation(Field)
value = Column(Text)
I have two realations from Event to EventFieldValue: fields and field_values (via backref of event). When I add a EventFieldValue to event.field_values, it does not reflect in event.fields until I commit the session.
Because you have two relations, sqlalchemy have to make requests for each one, and doesn't share their cache in the session.
You should take a look at Association proxies, that seems to be exactly what you need. They allow you to define only one relation and to put proxies on the top of them to access stuff in the relation more easily.
Flushing the session should solve this problem. It updates your session with all the new state but doesn't do a commit. You can also look into Refresh/Expire which will reload your objects.
Related
I'm making a very simple warehouse management system, and I'd like for users to be able to create templates for items. The template will show up on a list, and then can individually be used to create instances of an item that will also gain a quantity and warehouse attribute.
The goal is, if one of the item templates gets modified to specify a different size or price, the size or price attributes of the actual item instance gets changed as well.
Here is my code in case that helps you visualize what I'm trying to do. I'm not sure if this is possible or if there is a different solution I should consider. It's my first time working with Flask SQLAlchemy.
class ItemTemplate(db.model):
"""This template will simply store the information related to an item type.
Individual items that will be associated with the warehouse they're stored in
will inherit this information from the item templates."""
_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15), unique=True, nullable=False)
price = db.column(db.Float, nullable=False)
cost = db.column(db.Float, nullable=False)
size = db.column(db.Integer, nullable=False)
lowThreshold = db.column(db.Integer, nullable=False)
# Actual items
class Item(db.model):
"""This template will be used to represent the actual items that are associated with a warehouse."""
_id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Integer, primary_key=True)
"""Here I want the Item attributes to be able to just point to attributes from the ItemTemplate class.
ItemTemplate(name='tape') <--- will be a template with the information for tape.
Item(name='tape') <--- will be an actually instance of tape that should inherit all the attributes from the tape template.
I want these attributes to be like pointers so that if the tape template has its name changed, for instance, to
'scotch tape', all the Item instances that point to the tape template will have their names changed."""
# Warehouse
class Warehouse(db.model):
_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15), unique=True, nullable=False)
capacity = db.Column(db.column(db.Integer, nullable=False))
items = db.relationship("Item", backref="warehouse", lazy=True)```
As I understand, you just declare a One-Many relationship between ItemTemplate and Item, that one template will be used for many items.
Define Model
Just try to declare their relationship like this
class ItemTemplate(db.model):
_id = db.Column(db.Integer, primary_key=True)
... # Other attribute
instances = db.relationship('item', backref='item_template', lazy=True)
class Item(db.model):
_id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Integer, primary_key=True)
item_template_id = db.Column(db.Integer, db.ForeignKey('item_template._id'), nullable=False)
Docs for more information about relationship:
https://flask-sqlalchemy.palletsprojects.com/en/2.x/models/#one-to-many-relationships
Query
Next time querying, just join two tables and you can have your ItemTemplate.name
items_qr = db.session.query(Item, ItemTemplate.name).join(ItemTemplate)
for item, item_name in items_qr:
print(item.id, item_name)
SQLAlchemy Doc for query.join(): https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.join
Some relative SO questions may help
flask Sqlalchemy One to Many getting parent attributes
One-to-many Flask | SQLAlchemy
Do you set your foreign keys as nullable=false if always expect a foreign key on that column in the database?
I'm using sqlalchemy and have set my models with required foreign keys. This sometimes causes me to run session.commit() more often, since I need the parent model to have an id and be fully created in order to build a child object in the ORM. What is considered best practice? My models are below:
class Location(Base):
__tablename__ = 'locations'
id = Column(Integer, primary_key=True)
city = Column(String(50), nullable=False, unique=True)
hotels = relationship('Hotel', back_populates='location')
class Hotel(Base):
__tablename__ = 'hotels'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False, unique=True)
phone_number = Column(String(20))
parking_fee = Column(String(10))
location_id = Column(Integer, ForeignKey('locations.id'), nullable=False)
location = relationship('Location', back_populates='hotels')
You don't need to do session.commit() to get an ID; session.flush() will do.
Even better, you don't need to get an ID at all if you set the relationship because SQLalchemy will figure out the order to do the INSERTs in. You can simply do:
loc = Location(city="NYC", hotels=[Hotel(name="Hilton")])
session.add(loc)
session.commit()
and it will work fine.
I would suggest that you'd better not set nullable=False. Make foreign key nullable is very reasonable in many situations. In your scenario, for example, if I want to insert a hotel whose location is currently underdetermined, you can not accomplish this with the foreign key not null. So the best practice when using foreign key is to set it nullable.
See necessary nullable foreign key Any example of a necessary nullable foreign key?
I have this code, thats been mostly taken from the sqlalchemy site
class Order(Base):
__tablename__ = 'order'
id = Column(Integer, Sequence('tri_id_seq'), primary_key=True)
text = Column(String(1024), nullable=False)
items = relationship("Item", cascade="save-update, delete-orphan, merge, delete", backref="parent")
class Item(Base):
__tablename__ = 'item'
id = Column(Integer, Sequence('tri_id_seq'), primary_key=True)
text = Column(String(1024), nullable=False)
parent_id = Column(Integer, ForeignKey('order.id'))
I want deletes to Order to cascade down and delete its items as well. In code:
# test insert/delete - save data to mssql server
i1 = Item(text="item one")
i2 = Item(text="item two")
o = Order(text="one", items=[i1, i2])
session.add(o)
session.commit()
session.delete(o) # delete it
# tests to make sure items/order gone ....
session.commit()
This works ok.
BUT if I try and delete an Order in MS SQL management studio. i.e.
DELETE FROM [dbo].[order] WHERE id = 1
I get the error
"the DELETE statement conflicted with the REFERENCE constraint FK__item__parent_id_1D00044F" The conflict error blah blah....
I guess theres something missing on the relationship definitions but I can't see it.
Any help/thoughts?
ta.
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete='CASCADE'), nullable=False)
user = relationship("User", backref=backref('comments', cascade="all,delete", order_by=id))
This kind of setup works for me, my User class doesn't have any special fields except for primary key.
So basically, this works as intended, when I delete the user - his comments are gone as well.
It doesn't have to be deletion through SQLAlchemy either, this code creates an appropriate table structure, that even if you delete user manually (sql query) - cascade deletion will still work.
Am trying to setup a postgresql table that has two foreign keys that point to the same primary key in another table.
When I run the script I get the error
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Company.stakeholder - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
That is the exact error in the SQLAlchemy Documentation yet when I replicate what they have offered as a solution the error doesn't go away. What could I be doing wrong?
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='company_id')
stakeholder = relationship("Company", foreign_keys='stakeholder_id')
I have seen similar questions here but some of the answers recommend one uses a primaryjoin yet in the documentation it states that you don't need the primaryjoin in this situation.
Tried removing quotes from the foreign_keys and making them a list. From official documentation on Relationship Configuration: Handling Multiple Join Paths
Changed in version 0.8: relationship() can resolve ambiguity between
foreign key targets on the basis of the foreign_keys argument alone;
the primaryjoin argument is no longer needed in this situation.
Self-contained code below works with sqlalchemy>=0.9:
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine(u'sqlite:///:memory:', echo=True)
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys=[company_id])
stakeholder = relationship("Company", foreign_keys=[stakeholder_id])
Base.metadata.create_all(engine)
# simple query test
q1 = session.query(Company).all()
q2 = session.query(Stakeholder).all()
The latest documentation:
http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#handling-multiple-join-paths
The form of foreign_keys= in the documentation produces a NameError, not sure how it is expected to work when the class hasn't been created yet. With some hacking I was able to succeed with this:
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='Stakeholder.company_id')
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder = relationship("Company",
foreign_keys='Stakeholder.stakeholder_id')
In other words:
… foreign_keys='CurrentClass.thing_id')
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.