Allowing orphans in SQLAlchemy relationships - python

In models defined using declarative, I have the following structure:
class ItemBin(Base, BaseModelMixin, PostCommitTriggersModelMixin):
__tablename__ = "item_bins"
id = Column(INTEGER(11), nullable=False, unique=True)
account_id = Column(INTEGER(11), primary_key=True, nullable=False, index=True)
sku = Column(String(200), primary_key=True, nullable=False, server_default=text("''"))
warehouse_id = Column(INTEGER(11), primary_key=True, nullable=False, server_default=text("'0'"))
location_id = Column(INTEGER(11), primary_key=True, nullable=False, index=True, server_default=text("'0'"))
bin = relationship("Bin", uselist=False, primaryjoin="foreign(ItemBin.location_id) == Bin.id")
class Bin(Base):
__tablename__ = "bins"
id = Column(Integer, nullable=False, unique=True, autoincrement=True)
account_id = Column(Integer, primary_key=True, nullable=False)
warehouse = Column(String(100), primary_key=True, nullable=False, server_default=text("''"), default="")
location = Column(String(100), primary_key=True, nullable=False, server_default=text("''"), default="")
The lack of FKs is by design.
Our code allows for orphan ItemBins to exist, they get picked up later on and a default parent is set on them, and the change gets logged. The problem is that when we tried to add a Bin.item_bins relationship like:
class Bin(...):
...
item_bins = relationship(ItemBin, primaryjoin="foreign(ItemBin.location_id) == Bin.id", lazy="dynamic")
Then when the code does item_bin.bin.delete() the system tries to set item_bin.location_id = NULL and that fails with "Dependency rule tried to blank-out primary key column item_bins.location_id..."
Is it possible to define a relationship and not have it update its children when the parent is deleted? We're making do without the relationship, but we're having to write repetitive queries to refer to the objects' children, instead of doing Bin.item_bins.any() and similar.

Related

Writing a composite Key in Flask SQL Alchemy [duplicate]

This question already has answers here:
sqlalchemy unique across multiple columns
(4 answers)
Closed 3 years ago.
I have my table like something below:
class Dummies(db.Model):
__tablename__ = 'dummies'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), index=True, nullable=True)
value = db.Column(db.String(100), index=True, nullable=True)
sum_id = db.Column(db.Integer, db.ForeignKey('pattern.id',
onupdate='CASCADE',
ondelete='CASCADE'),
index=True, nullable=False)
I am quite new to flask and trying to get around the syntax for this relationship, I want to represent. I want to use the name, value and sum_id to uniquely represent a row. Could someone help me how to write the relationship for this ?
If you want enforce the unique constraint on multiple columns you can explicitly set it like this:
__tablename__ = 'dummies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=True)
value = db.Column(db.String(100), nullable=True)
sum_id = db.Column(db.Integer, db.ForeignKey('pattern.id'), nullable=False)
UniqueConstraint('name', 'value', 'sum_id'
name='myMultipleColumnUniqueConstraint')
This way you can enforce multiple column unique constraint. Also just wanted point out that
onupdate => refers to a default value for the column.
There is no ondelete in sqlAlchemy as far as I know. but you can use:
cascade = "all, delete-orphan" ```
on Parent Table relationship to achieve the same effect as ondelete="cascade" as in django.
class ParentDummies(db.Model):
dummies= relationship("Dummy", cascade="all, delete-orphan")
Also, if you are trying make those columns as composite primary key, you can do so like this:
``` class Dummies(db.Model):
__tablename__ = 'dummies'
id = db.Column(db.Integer)
name = db.Column(db.String(100), nullable=True)
value = db.Column(db.String(100), nullable=True)
sum_id = db.Column(db.Integer, db.ForeignKey('pattern.id', nullable=False),
PrimaryKeyConstrain('name', 'value', 'sum_id', name="myCompositePk")
OR
class Dummies(db.Model):
__tablename__ = 'dummies'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=True, primary_key=True)
value = db.Column(db.String(100), nullable=True, primary_key=True)
sum_id = db.Column(db.Integer, db.ForeignKey('pattern.id'), nullable=False, primary_key=True)

How can I attach generic `Contact` to multiple different tables?

I'm trying to create a set of tables which can all have a Contactassigned to them.
class Contact(Base):
__tablename__ = "contact"
id = Column(Integer, primary_key=True)
name = Column(String, index=True, nullable=False, doc="Name of the contact.")
phone = Column(String, index=True, doc="Phone number of the contact.")
Contacts can be linked to from various other tables, and one record can have more than one contact in different fields.
class BusinessGroup(Base):
__tablename__ = "business_group"
id = Column(Integer, primary_key=True)
name = Column(String, index=True, nullable=False, doc="Name of the group.")
main_contact = Column(Integer, ForeignKey("contact.id"), doc="Main contact details for the group.")
second_contact = Column(Integer, ForeignKey("contact.id"), doc="Second contact details for the group.")
class Vendor(Base):
__tablename__ = "vendor"
id = Column(Integer, primary_key=True)
name = Column(String, index=True, nullable=False, doc="Name of the vendor.")
contact = Column(Integer, ForeignKey("contact.id"), doc="Main contact details for the vendor.")
This setup seems to work, but in flask-admin, no contact fields show up when creating a new item for either BusinessGroup or Vendor.
How can I make this design work? Or should I be modelling this kind of relationship in a different way entirely?
I ended up subclassing the Contact table so that I could have:
class MainContact(Contact):
__tablename__ = "main_contact"
id = Column(Integer, ForeignKey("contact.id"), primary_key=True)
business_group = relationship("BusinessGroup", back_populates="main_contact")
business_group_id = Column(Integer, ForeignKey("business_group.id"), nullable=False)
...
class SecondContact(Contact):
__tablename__ = "second_contact"
id = Column(Integer, ForeignKey("contact.id"), primary_key=True)
business_group = relationship("BusinessGroup", back_populates="second_contact")
business_group_id = Column(Integer, ForeignKey("business_group.id"), nullable=False)
...
class BusinessGroup(Base):
__tablename__ = "business_group"
id = Column(Integer, primary_key=True)
name = Column(String, index=True, nullable=False, doc="Name of the group.")
main_contact = relationship(
"MainContact", back_populates="business_group", uselist=False,
doc="Main contact details for the business_group."
)
second_contact = relationship(
"SecondContact", back_populates="business_group", uselist=False,
doc="Second contact details for the business_group."
)
As well as requiring subclassing so that we can have two different contacts referred to from the same model, the other important part was adding the foreign key relationships, so that the contacts show up in the Flask Admin panel.

SqlAlchemy Error on Creating multiple foreign key to one table

I am new using sqlAlchemy and having problem creating new tables, specially when it comes around 2 foreign keys pointing to 1 table:
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want = db.relationship('Currency', backref='want', lazy='dynamic')
have = db.relationship('Currency', backref='have', lazy='dynamic')
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
have_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
The error I am getting is:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|CurrencyPairs|currency_pairs'. Original exception was: Could not determine join condition b
etween parent/child tables on relationship CurrencyPairs.want - 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 refe
rence to the parent table
I have try different things but I get same result.
What am I doing wrong?
Thanks In advance.
I know this is an old question, but I had the same problem. I hope to help others with the answer.
This issue is addressed in the sqlalchemy documentation.
https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want_currency = relationship("Currency", foreign_keys='[Currency.want_id]', back_populates="want_currency_pairs")
have_currency = relationship("Currency", foreign_keys='[Currency.have_id]', back_populates="have_currency_pairs")
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.want_id]", back_populates="want_currency")
have_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.have_id]", back_populates="have_currency")
The way you wrote the code, sqlalchemy can't really understand which relationship to choose, because you have 2 of the same relationship. So you have to describe to sqlalchemy that there are 2 relationships to the same table.

How to inserting data into interrelated relationship tables using SQLAlchemy (not flask_sqlalchemy or flask-merge)?

I am new to SQLAlchemy, I am trying to build a practice project using SQLAlchemy. I have created the database containing tables with the following relationship. Now my questions are :
How to INSERT data into the tables as they are interdependent?
Does this form a loop in database design?
Is the looping database design, a bad practice? How to resolve this if its a bad practice?
Department.manager_ssn ==> Employee.SSN
and
Employee.department_id ==> Department.deptid
database relationship diagram
and following is the current version of code creating this exact database.
# Department table
class Departments(Base):
__tablename__ = "Departments"
# Attricutes
Dname= Column(String(15), nullable=False)
Dnumber= Column(Integer, primary_key=True)
Mgr_SSN= Column(Integer, ForeignKey('Employees.ssn'), nullable=False)
employees = relationship("Employees")
# Employee table
class Employees(Base):
__tablename__ = "Employees"
# Attributes
Fname = Column(String(30), nullable=False)
Minit = Column(String(15), nullable=False)
Lname = Column(String(15), nullable=False)
SSN = Column(Integer, primary_key=True)
Bdate = Column(Date, nullable=False)
Address = Column(String(15), nullable=False)
Sex = Column(String(1), default='F', nullable=False)
Salary = Column(Integer, nullable=False)
Dno = Column(Integer, ForeignKey('Departments.Dnumber'), nullable=False)
departments = relationship("Departments")
Please provide the solution in SQLAlchemy only and not in flask-sqlalchemy or flask-migrate and I am using Python 3.6.
You can avoid such circular reference design altogether by
Declaring the foreign key constraint on just one side of the relationship
Use a boolean flag to denote if the employee is a manager
class Department(Base):
__tablename__ = 'departments'
department_id = Column(Integer, primary_key=True)
employees = relationship('Employee', lazy='dynamic', back_populates='department')
class Employee(Base):
__tablename__ = 'employees'
employee_id = Column(Integer, primary_key=True)
is_manager = Column(Boolean, nullable=False, default=False)
department_id = Column(Integer, ForeignKey('departments.department_id'), nullable=False)
department = relationship('Department', back_populates='employees')
You can find the manager of the department using
department = session.query(Department).get(..)
department.employees.filter(Employee.is_manager == True).one_or_none()

sqlalchemy schema: there is no unique constraint matching given keys for referenced table

I'm trying to create 2 tables using SQLAlchemy models that are identical in structure but distinct in purpose, the Whitelist and Blacklist which also reference a Magazine (not shown). They both fkey to an instance of a Campaign (say a political campaign) which in turn has an fkey to a Politician model (also not shown).
I'm picturing the Whitelist/Blacklist to be many:1 to the Campaign since there could be multiple white/blacklists for, but when running the migration I get sqlalchemy.exc.ProgrammingError: (ProgrammingError) there is no unique constraint matching given keys for referenced table "campaigns". There also needs to be a M:1 from Campaign to Politician.
Can someone please explain why this is causing a unique constraint error since the whitelist and blacklist are on separate tables? Also, how would I be able to make this relational schema work?
class Campaign(Base):
__tablename__ = "campaigns"
id = Column(Integer, primary_key=True, nullable=False)
politician_id = Column(Integer, ForeignKey('politician.id'), nullable=False)
description = Column(Text, nullable=True)
class Whitelist(Base):
__tablename__ = "whitelist"
id = Column(Integer, primary_key=True, nullable=False)
campaign_id = Column(Integer, ForeignKey('campaign.id'), nullable=False)
magazine_id = Column(Integer, ForeignKey('magazine.id'), nullable=False)
class Blacklist(Base):
__tablename__ = "blacklist"
id = Column(Integer, primary_key=True, nullable=False)
campaign_id = Column(Integer, ForeignKey('campaign.id'), nullable=False)
magazine_id = Column(Integer, ForeignKey('magazine.id'), nullable=False)
It would seem that the relationship is indeed illegal because of the M:1 from the Whitelist/Blacklist to Campaign, while Campaign itself is a M:1 to Politician. I instead scrapped that approach and the models now look like:
class Campaign(Base):
__tablename__ = "campaigns"
id = Column(Integer, primary_key=True, nullable=False)
politician_id = Column(Integer, ForeignKey('politician.id'), nullable=False)
description = Column(Text, nullable=True)
class Listing(Base):
__tablename__ = "listings"
id = Column(Integer, primary_key=True, nullable=False)
campaign_id = Column(Integer, ForeignKey('campaign.id'), nullable=False)
magazine_id = Column(Integer, ForeignKey('magazine.id'), nullable=False)
listing_type = Column(String, nullable=False)
The original approach was intended to fit a little nicer into Flask-Admin but this way is more performant since it will reduce the additional query and join necessary in the previous incarnation. For those that may be thinking of this type of approach to use with Flask-Admin, you could use the inline_model() to make the UI a little cleaner and easier to manage.

Categories