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

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.

Related

Allowing orphans in SQLAlchemy relationships

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.

sqlalchemy, how to Base.metadata.drop_all with cascade?

I'm building setup and teardown for my pytest tests. I cannot drop tables because there are numerous dependiences between tables. Still, I need to drop all tables after each test.
#pytest.fixture()
def test_db():
model.Base.metadata.create_all(bind=test_engine)
db = TestSessionLocal()
yield db
model.Base.metadata.drop_all(bind=test_engine)
it throws me psycopg2.errors.DependentObjectsStillExist
I have also included cascades in my model but it does not work.
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
name = Column(String)
surname = Column(String)
position = Column(String)
skills = relationship("UserSkills", cascade="all, delete", passive_deletes=True)
manager = Column(Integer, ForeignKey("manager.id", ondelete='CASCADE'))
account_type = Column(Enum(AccountType), default=AccountType.USER)
created = Column(DateTime, default=datetime.datetime.utcnow)
How can I add cascade option to model.Base.metadata.drop_all(bind=test_engine) ?

sqlalchemy query(join) to retrieve sum of votes for a post

I have 3 models
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True, nullable=False)
content = Column(String, nullable=False)
published = Column(Boolean)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User")
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String)
password = Column(String)
class Vote(Base):
__tablename__ = "votes"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
post_id = Column(Integer, ForeignKey("posts.id"), primary_key=True)
I would like to generate a query that retrieves one or all posts and automatically sum up all of the votes for said post. I know how to do this in sql but not sure how to accomplish this using SQLAlchemy.
The sql query would look like this:
SELECT posts.*, count(votes.post_id) as votes from posts left join votes on (posts.id = votes.post_id) where posts.id = 1 group by posts.id;
Also is there a way to make it so i don't have to create a custom query and instead have the post model automatically populate the sum of all votes with a reference or something like that?

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()

Categories