How can i do a foreign key refer to id to multiple tables?
class HistoryActivation(Base):
__tablename__ = 'active_control'
id = Column(Integer, primary_key=True)
id_entity = Column(Integer) #This one refer to any entity (id) that has a relation with this class.
date = Column(DATE)
user = Column(String(120))
active = Column(BOOLEAN)
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String(50))
history_activation = relationship("HistoryActivation",
primaryjoin="HistoryActivation.id_entidade == Person.id")
class Company(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True)
name = Column(String(50))
history_activation = relationship("HistoryActivation",
primaryjoin="HistoryActivation.id_entidade == Company.id")
class Whatever(Base): ...
So, id_entity refer to id to various tables, something like:
id_entity = Column(Integer, ForeignKey('Person.id or Company.id or Whatever.id').
Is possible to tell ForeignKey can refer more than one relation?
Related
I'm using SQLAlchemy with Postgres.
Code example from https://auth0.com/blog/sqlalchemy-orm-tutorial-for-python-developers/
I have 3 objects:
Actor
Stuntman
ContactDetails
In the original code example, the relationships are as follow:
Actor:ContactDetails = 1:0..*
Actor:Stuntman = 1:1
See Code:
class Actor(Base):
__tablename__ = 'actors'
id = Column(Integer, primary_key=True)
name = Column(String)
birthday = Column(Date)
class Stuntman(Base):
__tablename__ = 'stuntmen'
id = Column(Integer, primary_key=True)
name = Column(String)
active = Column(Boolean)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor", backref=backref("stuntman", uselist=False))
class ContactDetails(Base):
__tablename__ = 'contact_details'
id = Column(Integer, primary_key=True)
phone_number = Column(String)
address = Column(String)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor")
I'm struggling to extend this model to following relationships:
Actor:ContactDetails = 1:0..*
Actor:Stuntman = 1:1
Stuntman:ContactDetails = 1:0..*
Stuntmen work hard too and deserve ContactDetails. Who can help please?!
In my opinion best way to achieve what you need is using association table. This one should work:
actors_to_contact_details = Table('actors_to_contact_details', Base.metadata,
Column('actor_id', Integer, ForeignKey('actors.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'))
)
stuntmen_to_contact_details = Table('stuntmen_to_contact_details', Base.metadata,
Column('stuntman_id', Integer, ForeignKey('stuntmen.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'))
)
class Actor(Base):
__tablename__ = 'actors'
id = Column(Integer, primary_key=True)
name = Column(String)
birthday = Column(Date)
contact_details = relationship("ContactDetails", secondary=actors_to_contact_details)
class Stuntman(Base):
__tablename__ = 'stuntmen'
id = Column(Integer, primary_key=True)
name = Column(String)
active = Column(Boolean)
actor_id = Column(Integer, ForeignKey('actors.id'))
actor = relationship("Actor", backref=backref("stuntman", uselist=False))
contact_details = relationship("ContactDetails", backref="stuntman", secondary=stuntmen_to_contact_details)
class ContactDetails(Base):
__tablename__ = 'contact_details'
id = Column(Integer, primary_key=True)
phone_number = Column(String)
address = Column(String)
if you want, you can put unique=True in association table like this
actors_to_contact_details = Table('actors_to_contact_details', Base.metadata,
Column('actor_id', Integer, ForeignKey('actors.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'), unique=True)
)
stuntmen_to_contact_details = Table('stuntmen_to_contact_details', Base.metadata,
Column('stuntman_id', Integer, ForeignKey('stuntmen.id')),
Column('contact_detail_id', Integer, ForeignKey('contact_details.id'), unique=True)
)
Other option would be to put another nullable foreign key on Stuntman table, but I would go with solution presented above.
I would like to create an association between a Dataset object and all Category objects through the Annotation table.
A Dataset contains a collection of Annotations. Each Annotation has a single Category. I want Dataset.categories to contain the unique set of Categories made up of all the Categories of all the Annotations in that Dataset instance. I have tried doing this with a double association table (dataset_categories), but it is not working. What is the right way to do this? Here is my code so far:
Base = declarative_base()
dataset_categories = Table('dataset_categories', Base.metadata,
Column('dataset_id', Integer, ForeignKey('datasets.id')),
Column('annotation_id', Integer, ForeignKey('annotations.id')),
Column('category_id', Integer, ForeignKey('categories.id')))
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category", secondary=dataset_categories)
class Annotation(Base):
__tablename__ = 'annotations'
id = Column(Integer, primary_key=True)
category_id = Column(Integer, ForeignKey('categories.id'), nullable=False)
category = relationship("Category")
dataset_id = Column(Integer, ForeignKey('datasets.id'))
class Category(Base):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
dataset = relationship("Dataset", secondary=dataset_categories)
dataset_id = Column(Integer, ForeignKey('datasets.id'),
back_populates='categories')
Without the requirement that the association contain only the unique categories this would be as simple as using an association_proxy. One option is to define the collection class to use as set when defining the relationship:
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category", secondary="annotations", collection_class=set)
On the other hand the secondary table of a relationship does not have to be a base table, and so a simple select from annotations can be used:
class Dataset(Base):
__tablename__ = 'datasets'
id = Column(Integer, primary_key=True)
annotations = relationship("Annotation")
categories = relationship("Category",
secondary="""select([annotations.c.dataset_id,
annotations.c.category_id]).\\
distinct().\\
alias()""",
viewonly=True)
Using SQLalchemy and Flask-SQLAlchemy, I'm able to set up the following User class and sub classes Customer and Provider :
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=False)
user_type = Column(String(32), nullable=False)
__mapper_args__ = {
'polymorphic_on':'user_type',
'polymorphic_identity':'user_type'
}
class Customer(User):
__tablename__ = "customer"
id = Column(Integer, ForeignKey('user.id'), primary_key=True)
note_customer = Column(Integer)
__mapper_args__ = {
'polymorphic_identity':'customer'
}
class Provider(User):
__tablename__ = "provider"
id = Column(Integer, ForeignKey('user.id'), primary_key=True)
note_provider = Column(Integer)
__mapper_args__ = {
'polymorphic_identity':'provider'
}
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
number = Column(Integer)
street = Column(String(100))
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User)
Then I am able to create a new customer :
u = Customer(name= 'Mark', user_type='customer', note_customer = 15)
db.session.add(u)
db.session.commit()
But I would like to add an address value to my Customer Mark during his creation and I don't succeed in and can't figure out how doing it.
I guess I'm wrong with my relationship : for me it's a "one-to-one" type, but many solutions I tried give me errors telling that the address argument is not available for a Customer object type.
Thanks!
The problem is that you neither use a backref or explicitly create 2 back populating relationships. Because of this the User model class has no idea of the relationship and no Address instance can be passed to it or subclasses when constructing. The error you're receiving should be:
TypeError: 'address' is an invalid keyword argument for Customer
Using backref argument of relationship
from sqlalchemy.orm import backref
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
number = Column(Integer)
street = Column(String(100))
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User, backref=backref('address', uselist=False))
This creates a relationship attribute address on User model as if you'd added it manually:
address = relationship('Address', uselist=False, ...)
You need to use a backref() object in order to pass uselist=False since you want a one to one relationship.
Using explicit relationship attributes
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=False)
user_type = Column(String(32), nullable=False)
address = relationship('Address', uselist=False, back_populates='user')
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
number = Column(Integer)
street = Column(String(100))
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User, back_populates='address')
The back_populates parameters will establish event listeners that will mirror attribute operations between the classes.
With a proper relationship configuration in place creating a new Customer with an address can be done like this:
u = Customer(name='Mark', user_type='customer', note_customer=15,
address=Address(number=221, street='Baker Street'))
session.add(u)
session.commit()
or in separate steps:
u = Customer(name='Mark', user_type='customer', note_customer=15)
u.address = Address(number=221, street='Baker Street')
session.add(u)
session.commit()
The error is because your customer class isn't defined to have an address.
You could simply add another few lines to put Mark's address in, and tie the relationship to Mark.
mark_address = Address(number='123', street='Main St', user = u)
db.session.add(mark_address)
db.session.commit()
I would like to have a one to many relation between two ORM objects and extend this with a second relation that links to the same 'many' object while applying a constraint.
The following example may elaborate:
class Users(SQLABase):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
addresses = relationship('Addresses', backref='user')
class Addresses(SQLABase):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
historic = (String(1))
add1 = Column(String)
user = Column(Integer, ForeignKey('users.id'))
I would like an attribute 'Users.valid_addresses' that relates to the same 'addresses' table filtering where Addresses.historic == 'N' like the following query:
Session.Query(Addresses).filter_by(historic = 'N').all()
I'm looking for the "SQLAlchemy way".
Can I apply a condition to a relation?
Am I expected to iterate over the results of the current relation?
Should I create an additional 'valid_addresses' object based on an SQL
view of the addresses table applying the condition?
I have the feeling this has already been answered but I'm failing to phrase the question correctly.
This is covered in the SQLAlchemy docs under "Specifying Alternate Join Conditions".
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
boston_addresses = relationship("Address",
primaryjoin="and_(User.id==Address.user_id, "
"Address.city=='Boston')")
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'))
street = Column(String)
city = Column(String)
state = Column(String)
zip = Column(String)
I am using Python 2.7.2 and SQLAlchemy 0.9.3.
A Department has many groups. A Group has many members. A Member can only be in one group.
Currently to get a list of all the members of a Department. I first get all the groups and then get the members of those groups.
Is there a way to go directly from a department to members without going via groups table ?
class Department(Base):
__tablename__ = 'departments'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True)
location = Column(String(32), unique=True)
groups = relationship("Group", backref="department")
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String(64), unique=True)
department_id = Column(Integer, ForeignKey('departments.id'))
members = relationship("Member", backref="group")
class Member(Base):
__tablename__ = 'members'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
This kind of query requires a join statement:
q = session.query(Member).\
join(Member.group).\
join(Group.department).\
filter(Department.name=='depart_name')
result = q.all()
This will return a list of Member Objects where the member is part of the department depart_name