SQLAlchemy Relationships between 3 tables - python

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

Related

How to delete mapped children from associations table in sqlalchemy using relationships

documentations says:
Mapping a one-to-many or many-to-many relationship results in a collection of values accessible through an attribute on the parent instance. By default, this collection is a list
So I have user with roles and I can access it using user.roles and user.roles.append() method works fine, but I can't remove all mapped entries.
I tried:
user.roles = []
also I tried clear() method and remove() in loop but I always get
sqlalchemy.orm.exc.FlushError: Can't flush None value found in collection Users.roles
this is example of my tables relationships
class Users(sa.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
login = Column(String(50), unique=False)
roles = relationship('Roles', secondary='user_roles_map',
cascade='all, delete')
class Roles(sa.Model):
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(40), unique=True)
class UserRolesMap(sa.Model):
__tablename__ = 'user_roles_map'
id_seq = Sequence(__tablename__ + "_id_seq")
id = Column(Integer(), id_seq, server_default=id_seq.next_value(),
unique=True, nullable=False)
user_id = Column(
Integer, ForeignKey('users.id'),
primary_key=True)
role_id = Column(
Integer, ForeignKey('roles.id'),
primary_key=True)
is it possible to remove all entries by accessing the roles attribute from the parent entry?
call flush() was required after user.roles = []

How to validate table field with information from different table using sql.alchemy.orm.validates?

Imagine I have 2 tables Person and Group. There is a relationship between these 2 tables. A Person has a nickname and that nickname needs to be unique in the Group that Person is linked to. How would I write validate_nickname for the Person table?
I wanted to collect every nickname of Person belonging to the Group that the Person being added belongs to and verify if the nickname being added already exists. I'm just not sure how to query every Person as I don't have access to any SQLAlchemy.Session inside validate_nickname.
Small toy example:
from sqlalchemy.orm import validates, relationship
class Group(Base):
__tablename__ = 'group'
id = Column(Integer, primary_key=True)
name = Column(String)
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
name = Column(String)
nickname = Column(String)
group = relationship("Group", uselist=False)
#validates('nickname')
def validate_nickname(self, key, nickname):
# collect nickname from every Person with group == group of person being added
# check if nickname of person being added does not exist in list generated above
# raise an exception in case the nickname already exists
return address
Thanks to #IljaEverilä for the answer. I added a group_id foreign key to Person (which I forgot to add in the example) and added a UniqueConstraint with group_id and name to __table_args__.
from sqlalchemy.orm import validates, relationship
from sqlalchemy.schema import UniqueConstraint
class Group(Base):
__tablename__ = 'group'
id = Column(Integer, primary_key=True)
name = Column(String)
class Person(Base):
__tablename__ = 'person'
__table_args__ = (
UniqueConstraint('group_id', 'nickname', name='_group_id_nickname_uc'),
)
id = Column(Integer, primary_key=True)
name = Column(String)
nickname = Column(String)
group_id = Column(Integer, ForeignKey('group.id'))
group = relationship("Group")

SQLAlchemy, Foreign Key Refering for various tables

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?

SQLAlchemy - Array of multiple types without inheritance

I have an existing set of sqlalchemy models like so
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String)
first_name = Column(String)
last_name = Column(String)
email = Column(String)
user_group = Table('user_group', Base.metadata,
Column('user_id', Integer, ForeignKey('users.id')),
Column('group_id', Integer, ForeignKey('groups.id'))
)
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String)
members = relationship("User", secondary=user_group)
and I would like to create a third model which can have a 1 to many relationship with either a User or a Group type. So I want to end up with something that can be used like this
class Contact(Base):
__tablename__ = 'contacts'
id = Column(Integer, primary_key=True)
type_id = Column(Integer) # the id of the group or user row which it refers to.
type = Column(String) # 'group' or 'user'
class Thing(Base):
__tablename__ = 'things'
relationship('Contact') # Array of Contacts
where
thing = Thing()
thing.contacts[0].type # returns 'group' or 'user'
thing.contacts[0].contact # returns the actual User or Group object
I know this can be done with inheritance but is there any alternative? I'd rather not have to subclass my basic User and Group models.
Thanks in advance!
Read Generic Associations section of the documentation.
Your description matches the case of the Generic ForeignKey

SQLAlchemy Conditional Relationships

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)

Categories