Sqlalchemy many to many mapping with extra fields - python

I created a many to many relationship with sqlalchemy like this:
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
Column('group', Integer)
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, collection_class=attribute_mapped_collection('group'), backref='books')
after that I created a test like following:
book = Book(title='first book',isbn='test')
book.subjects[0] = Subject(value='first subject')
book.subjects[1] = Subject(value='second subject')
session.add(book)
transaction.commit()
and it works fine. But what I really want is to store more than one subject with the same group value, so I tried the following test:
book = Book(title='first book',isbn='test')
book.subjects[0] = [Subject(value='first subject'),Subject(value='second subject')]
book.subjects[1] = [Subject(value='third subject'),Subject(value='forth subject')]
session.add(book)
transaction.commit()
but it does not work.
Can this be done using sqlalchemy?
Thanks in Advance

Razi
I think you are constructing wrong relation ship.
Your relation ship must be
book M2M subject
subject M2M group
So you have to create one more model for group and that must be assign as m2m in Subject
Your models will be like.
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
)
subject_group = Table('subject_groups', Base.metadata,
Column('group_id', Integer, ForeignKey('groups.id')),
Column('subject_id', Integer, ForeignKey('subjects.id')),
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
groups = relationship('Groups', secondary=subject_groups, backref='subjects')
class Groups(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, backref='books')
I also check the docs for attribute_mapped_collection. But each time I found that each key is associated with only one object not more then one. If you read anywhere then please provide the link so I can check that how it will be fit in your code.
I think this will be help you.

Related

Can't update SQLAlchemy association table extra columns

My models look like this:
class Company(DB_BASE):
__tablename__ = 'Company'
id = Column(Integer, primary_key=True, index=True)
...
products = relationship('Product', secondary=Company_Products, backref='Company')
class Product(DB_BASE):
__tablename__ = 'Product'
id = Column(Integer, primary_key=True, index=True)
...
companies = relationship('Company', secondary=Company_Products, backref='Product')
This is my association table
Company_Products = Table(
'Company_Products',
DB_BASE.metadata,
Column('id', Integer, primary_key=True),
Column('company_id', Integer, ForeignKey('Company.id')),
Column('product_id', Integer, ForeignKey('Product.id')),
Column('quantity', Integer, default=0),
Column('price_per_unit', Integer, default=0),
)
And this is how I'm querying the association table.
company_product = db.query(Company_Products).filter_by(product_id=id, company_id=user.company_id).first()
company_product.quantity = data.data['quantity']
company_product.price = data.data['price']
After creating the many-to-many relationship between a Company and a Product, I would like to modify the relationship extra data, quantity and price_per_unit in this instance. After querying the association object, modifying any attribute yields:
AttributeError: can't set attribute 'quantity'
Follow up on my question, the solution which ended up working for me is making a new model and using it to somewhat simulate an association table.
class Company_Products(DB_BASE):
__tablename__ = 'Company_Products'
id = Column(Integer, primary_key=True, index=True)
...
quantity = Column(String) # 1 - client, 2 - furnizor
price_per_unit = Column(String)
company_id = Column(Integer, ForeignKey('Company.id'))
company = relationship('Company', back_populates='products', lazy='select')
product_id = Column(Integer, ForeignKey('Product.id'))
product = relationship('Product', back_populates='companies', lazy='select')
This is definitely not the best solution, if I come up with something else or come across something which might work out, I will edit this.

How do you access items from an SQLAlchemy many-to-many relationship using Python?

My many-to-many model looks like this, with an association table:
class Puppy(Base):
__tablename__ = "puppy"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(80))
class Adopter(Base):
__tablename__ = "adopter"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String)
puppies = relationship('Puppy', secondary='puppy_adopters', backref="puppy")
puppy_adopters = Table('puppy_adopters', Base.metadata,
Column('puppy_id', Integer, ForeignKey('puppy.id')),
Column('adopter_id', Integer, ForeignKey('adopter.id')))
If I have created an Adopter named Bill, I can easily add and retrieve his puppies with Python:
bill.puppies.append(fido)
bill.puppies.append(rex)
for puppy in bill.puppies:
print puppy.name # Fido, Rex
The puppy_adopters association table is populated with Bill's id and Fido's id when I do this. But how do I see that Bill is related to Fido, using Python? I get a Python object for Fido using fido = session.query(Puppy).filter_by(name="Fido"), but there is no fido.adopters list that contains Bill. How can I see all the people that have adopted Fido?
I tried this line in the puppy table:
adopter_id = Column(Integer, ForeignKey('adopter.id'))
but it did not get populated when I added an adopter, and it couldn't hold multiple adopters anyway.
The SQLAlchemy docs provide what you need under their Many to Many section. What you are looking for is back_populates.
class Puppy(Base):
__tablename__ = "puppy"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String(80))
adopters = relationship('Adopter',
secondary="puppy_adopters",
back_populates="puppies")
class Adopter(Base):
__tablename__ = "adopter"
id = Column(Integer, primary_key=True, nullable=False)
name = Column(String)
puppies = relationship('Puppy',
secondary="puppy_adopters",
back_populates="adopters")
puppy_adopters = Table('puppy_adopters', Base.metadata,
Column('puppy_id', Integer, ForeignKey('puppy.id')),
Column('adopter_id', Integer, ForeignKey('adopter.id')))
Rex = Puppy(name='Rex')
Fido = Puppy(name='Fido')
Bob = Adopter(name='Bob')
Steve = Adopter(name='Steve')
Steve.puppies.append(Fido)
Bob.puppies.append(Fido)
Bob.puppies.append(Rex)
print [adopter.name for adopter in Fido.adopters] # ['Steve', 'Bob']
print [puppy.name for puppy in Bob.puppies] # ['Fido', 'Rex']

Don't understand error message on SQLalchemy [duplicate]

This question already has an answer here:
The foreign key associated with column 'x.y' could not ... generate a foreign key to target column 'None'
(1 answer)
Closed 4 years ago.
I've got three tables: users, funds, and fund types. Each fund has a fund type, each user has a list of funds, and each user can also have a list of fund types that they have created.
Schema:
class Fund(Base):
__tablename__ = 'fds_funds'
fds_fund_id = Column(Integer, primary_key=True)
fds_name = Column(String(128))
fds_symbol = Column(String(5))
fds_fdt_fund_type_id = Column(Integer, ForeignKey('fdt_fund_type_id'))
fund_type = relationship('FundType', backref=backref('fds_funds', uselist=False))
class FundType(Base):
__tablename__ = 'fdt_fund_types'
fdt_fund_type_id = Column(Integer, primary_key=True)
fdt_type_name = Column(String(128))
fdt_usr_user_id = Column(Integer, ForeignKey('usr_user_id'), nullable=True)
user_funds = Table('usf_user_funds', Base.metadata,
Column('usf_usr_user_id', Integer, ForeignKey('usr_users.usr_user_id')),
Column('usf_fds_fund_id', Integer, ForeignKey('fds_funds.fds_fund_id'))
)
class User(Base):
"""
Application's user model.
"""
__tablename__ = 'usr_users'
usr_user_id = Column(Integer, primary_key=True)
usr_email = Column(Unicode(50))
_usr_password = Column('password', Unicode(64))
fund_types = relationship('FundType', foreign_keys='FundType.fdt_usr_user_id')
funds = relationship('Fund', secondary=user_funds)
I'm following the documentation here, and it appears to very clearly say that the first argument to the foreign key designation should be the column name, not the table name, but I'm getting this error when I run the initialize_DB script:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'fdt_fund_types.fdt_usr_user_id' could not find table
'usr_user_id' with which to generate a foreign key to target column
'None'
Have I misread the documentation?
I'm not really sure what fixed it, but I did some rearranging and messing with the references. For posterity, this works:
user_funds = Table('usf_user_funds', Base.metadata,
Column('usf_usr_user_id', Integer, ForeignKey('usr_users.usr_user_id')),
Column('usf_fds_fund_id', Integer, ForeignKey('fds_funds.fds_fund_id'))
)
class User(Base):
"""
Application's user model.
"""
__tablename__ = 'usr_users'
usr_user_id = Column(Integer, primary_key=True)
usr_email = Column(Unicode(50))
_usr_password = Column('password', Unicode(64))
fund_types = relationship('FundType', foreign_keys='FundType.fdt_usr_user_id')
funds = relationship('Fund', secondary=user_funds)
class FundType(Base):
__tablename__ = 'fdt_fund_types'
fdt_fund_type_id = Column(Integer, primary_key=True)
fdt_type_name = Column(String(128))
fdt_usr_user_id = Column(Integer, ForeignKey('usr_users.usr_user_id'), nullable=True)
class Fund(Base):
__tablename__ = 'fds_funds'
fds_fund_id = Column(Integer, primary_key=True)
fds_name = Column(String(128))
fds_symbol = Column(String(5))
fds_fdt_fund_type_id = Column(Integer, ForeignKey('fdt_fund_types.fdt_fund_type_id'))
fund_type = relationship('FundType', backref=backref('funds', uselist=False))

Trouble mapping multiple foreign keys to parent table

I have two tables, users and contacts. I query the contacts table and get a list of a user's contacts. I would then like to be able to write Contact.first_name (where first_name is a row from the users table) and print out that contact's first name.
Currently, my Contact object does not recognize any attributes of the user table.
Here is some code:
class User(Base):
""" Basic User definition """
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
contacts = relationship('Contact', backref='users')
class Contact(Base):
__tablename__ = 'contacts'
id = Column(Integer, primary_key=True)
user_id = Column(Integer)
contact_id = Column(Integer)
__table_args__ = (ForeignKeyConstraint([id], [User.id]), {})
Here is my query:
Contact.query.filter(Contact.user_id == self.user_id).filter(Contact.state == True).all()
To be honest, I'm unsure of how to properly map my two foreign keys Contact.user_id and Contact.contact_id to the User.id row. Maybe this is the source of my problem?
I'm very new to using SQLAlchemy, so this is a learning experience here. Thanks for your help.
What you have here is class User which essentially refers to itself. In other words, it's a self-referential many-to-many relationship. Your model definitions should look like this:
# This is so called association table, which links two tables in many-to-many
# relationship. In this case it links same table's ('users') different rows.
user_contacts = Table(
'user_contacts', Base.metadata,
Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
Column('contact_id', Integer, ForeignKey('users.id'), primary_key=True),
)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
contacts = relationship(
'User',
secondary=user_contacts,
primaryjoin=id==user_contacts.c.user_id,
secondaryjoin=id==user_contacts.c.contact_id
)
Then you can do things like the following:
u1 = User(first_name='Foo', last_name='Foo')
u2 = User(first_name='Bar', last_name='Bar')
u3 = User(first_name='Baz', last_name='Baz')
u1.contacts = [u2, u3]
session.add(u1)
session.commit()
# ... and in some other place in your code ...
u = User.query.get(1)
print u.contacts[0].first_name

How to build many-to-many relations using SQLAlchemy: a good example

I have read the SQLAlchemy documentation and tutorial about building many-to-many relation but I could not figure out how to do it properly when the association table contains more than the 2 foreign keys.
I have a table of items and every item has many details. Details can be the same on many items, so there is a many-to-many relation between items and details
I have the following:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
My association table is (It's defined before the other 2 in the code):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
In the documentation, it's said that I need to use the "association object". I could not figure out how to use it properly, since it's mixed declarative with mapper forms and the examples seem not to be complete. I added the line:
details = relation(ItemDetail)
as a member of Item class and the line:
itemDetail = relation('Detail')
as a member of the association table, as described in the documentation.
when I do item = session.query(Item).first(), the item.details is not a list of Detail objects, but a list of ItemDetail objects.
How can I get details properly in Item objects, i.e., item.details should be a list of Detail objects?
From the comments I see you've found the answer. But the SQLAlchemy documentation is quite overwhelming for a 'new user' and I was struggling with the same question. So for future reference:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
Like Miguel, I'm also using a Declarative approach for my junction table. However, I kept running into errors like
sqlalchemy.exc.ArgumentError: secondary argument <class 'main.ProjectUser'> passed to to relationship() User.projects must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
With some fiddling, I was able to come up with the following. (Note my classes are different than OP's but the concept is the same.)
Example
Here's a full working example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
Notes
reference the table name in the secondary argument like secondary='project_users' as opposed to secondary=ProjectUser
use back_populates instead of backref
I made a detailed writeup about this here.
Previous Answer worked for me, but I used a Class base approach for the table ItemDetail. This is the Sample code:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')

Categories