On 04/28/2017 09:49 PM, Alex Plugaru wrote:
Hello,

There are 3 tables: `*Account*`, `*Role*`, `*User*`. Both `*Role*` and `*User*` have a foreign key `*account_id*` that points to `*Account*`.

A user can have multiple roles, hence the `*roles_users*` table which acts as the secondary relation table between `*Role*` and `*User*`.

The `*Account*` table is a tenant table for our app, it is used to separate different customers.

Note that all tables have (besides `*Account*`) have composite primary keys with `*account_id*`. This is done for a few reasons, but let's say it's done to keep everything consistent.

Now if I have a simple secondary relationship (`*User.roles*` - the one that is commented out) all works as expected. Well kind of.. it throws a legitimate warning (though I believe it should be an error):


|
SAWarning:relationship 'User.roles'will copy column role.account_id to column roles_users.account_id,which conflicts withrelationship(s):'User.roles'(copies user.account_id to roles_users.account_id).Considerapplying viewonly=Trueto read-only relationships,orprovide a primaryjoin condition marking writable columns withthe foreign()annotation.
|

That's why I created the second relation `*User.roles*` - the one that is not commented out. Querying works as expected which has 2 conditions on join and everything. However I get this error when I try to save some roles on the user:


the relationship is set up backwards.

Given:

class A(Base):
   # ...

   b = relationship(B)

This is A->B, primary->secondary looks like:

A -> primaryjoin -> secondary -> secondaryjoin -> B

Also "foreign" isn't needed here since all the "foreign" is already set up on the tables. So correct set up is:

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey('account.id'), primary_key=True)
    name = Column(Text)

    roles = relationship(
        Role,
        secondary=roles_users,
        primaryjoin=and_(
            id == roles_users.c.user_id,
            account_id == roles_users.c.account_id),
        secondaryjoin=and_(
            Role.id == roles_users.c.role_id,
            Role.account_id == roles_users.c.account_id)
    )


The full example using SA 1.1.9:

|

fromsqlalchemy importcreate_engine,Column,Integer,Text,Table,ForeignKeyConstraint,ForeignKey,and_
fromsqlalchemy.ext.declarative importdeclarative_base
fromsqlalchemy.orm importforeign,relationship,Session


Base=declarative_base()




classAccount(Base):
     __tablename__ ='account'
     id =Column(Integer,primary_key=True)




roles_users =Table(
'roles_users',Base.metadata,
Column('account_id',Integer,primary_key=True),
Column('user_id',Integer,primary_key=True),
Column('role_id',Integer,primary_key=True),


ForeignKeyConstraint(['user_id','account_id'],['user.id','user.account_id']),
ForeignKeyConstraint(['role_id','account_id'],['role.id','role.account_id']),
)




classRole(Base):
     __tablename__ ='role'
     id =Column(Integer,primary_key=True)
     account_id =Column(Integer,ForeignKey('account.id'),primary_key=True)
     name =Column(Text)


def__str__(self):
return'<Role {} {}>'.format(self.id,self.name)




classUser(Base):
     __tablename__ ='user'
     id =Column(Integer,primary_key=True)
     account_id =Column(Integer,ForeignKey('account.id'),primary_key=True)
     name =Column(Text)


# This works as expected: It saves data in roles_users
# roles = relationship(Role, secondary=roles_users)


# This custom relationship - does not work
     roles =relationship(
Role,
         secondary=roles_users,
         primaryjoin=and_(foreign(Role.id)==roles_users.c.role_id,
Role.account_id ==roles_users.c.account_id),
         secondaryjoin=and_(foreign(id)==roles_users.c.user_id,
                            account_id ==roles_users.c.account_id))




engine =create_engine('sqlite:///')
engine.echo =True
Base.metadata.create_all(engine)
session =Session(engine)


# Create our account
a =Account()
session.add(a)
session.commit()


# Create 2 roles
u_role =Role()
u_role.id =1
u_role.account_id =a.id
u_role.name ='user'
session.add(u_role)


m_role =Role()
m_role.id =2
m_role.account_id =a.id
m_role.name ='member'
session.add(m_role)
session.commit()


# Create 1 user
u =User()
u.id =1
u.account_id =a.id
u.name ='user'


# This does not work
# u.roles = [u_role, m_role]
session.add(u)
session.commit()


# Works as expected
i =roles_users.insert()
i =i.values([
     dict(account_id=a.id,role_id=u_role.id,user_id=u.id),
     dict(account_id=a.id,role_id=m_role.id,user_id=u.id),
])
session.execute(i)


# re-fetch user from db
u =session.query(User).first()
forr inu.roles:
print(r)
|


FYI: I posted this on SO as well, but I haven't gotten a response there yet so trying here too: https://stackoverflow.com/questions/43690944/sqalchemy-custom-secondary-relation-with-composite-primary-keys
Hope it's ok.


Thank you for your help,
Alex.


--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected] <mailto:[email protected]>. To post to this group, send email to [email protected] <mailto:[email protected]>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to