[SOLVED] SqlAlchemy many to many relation with mm table

Issue

I am still a beginner in Python and I am stuck with the following relation.

Three tables:

  • tx_bdproductsdb_domain_model_product
  • sys_category
  • sys_category_record_mm

sys_category class looks like this:

class Category(Base):
    __tablename__ = "sys_category"

    uid = Column(
        Integer,
        ForeignKey("sys_category_record_mm.uid_local"),
        primary_key=True,
        autoincrement=True,
    )
    title = Column(String)
    products = relationship(
        "Product",
        uselist=False,
        secondary="sys_category_record_mm",
        back_populates="categories",
        foreign_keys=[uid],
    )

Products looks like this:

class Product(Base):
    __tablename__ = "tx_bdproductsdb_domain_model_product"

    uid = Column(
        Integer,
        ForeignKey(SysCategoryMMProduct.uid_foreign),
        primary_key=True,
        autoincrement=True,
    )
    
    category = Column(Integer)
    categories = relationship(
        Category,
        secondary=SysCategoryMMProduct,
        back_populates="products",
        foreign_keys=[uid],
    )

And here is the mm table class that should link the two.

class SysCategoryMMProduct(Base):
    __tablename__ = "sys_category_record_mm"

    uid_local = Column(Integer, ForeignKey(Category.uid), primary_key=True)
    uid_foreign = Column(
        Integer, ForeignKey("tx_bdproductsdb_domain_model_product.uid")
    )
    fieldname = Column(String)

I’m currently stuck, does anyone have any ideas? I get the following messages in the console:

sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Category.products - there are no foreign keys linking these tables via secondary table 'sys_category_record_mm'.  Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' and 'secondaryjoin' expressions.
[email protected]:/var/pythonWorks/crawler/develop/releases/current# python3 Scraper2.py 
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/sqlalchemy/orm/relationships.py", line 2739, in _determine_joins
    self.secondaryjoin = join_condition(
  File "<string>", line 2, in join_condition
  File "/usr/local/lib/python3.8/dist-packages/sqlalchemy/sql/selectable.py", line 1229, in _join_condition
    raise exc.NoForeignKeysError(
sqlalchemy.exc.NoForeignKeysError: Can't find any foreign key relationships between 'tx_bdproductsdb_domain_model_product' and 'sys_category_record_mm'.

sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Category.products - there are no foreign keys linking these tables via secondary table 'sys_category_record_mm'.  Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' and 'secondaryjoin' expressions.

Thank you 🙂

Solution

When using an association class you should reference the association directly. You need this instead of secondary because you have data associated with the link (ie. fieldname). I changed some of your naming schema to make it more clear.

There is a pretty good explanation of the association pattern in the sqlalchemy docs. There is a big red warning at the end of that section about mixing the use of the secondary and the Association pattern.

I use backref="related_categories" to automatically create the property related_categories on Product. This is a list of association objects, and not actual categories.

from sqlalchemy import (
    create_engine,
    Integer,
    String,
    ForeignKey,

)
from sqlalchemy.schema import (
    Column,
)
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy.orm import Session


Base = declarative_base()

# This connection string is made up
engine = create_engine(
    'postgresql+psycopg2://user:[email protected]/db',
    echo=False)


class Category(Base):
    __tablename__ = "categories"

    uid = Column(
        Integer,
        primary_key=True,
        autoincrement=True,
    )
    title = Column(String)


class Product(Base):
    __tablename__ = "products"

    uid = Column(
        Integer,
        primary_key=True,
        autoincrement=True,
    )

    title = Column(String)


class SysCategoryMMProduct(Base):
    __tablename__ = "categories_products"
    uid = Column(Integer, primary_key=True)
    category_uid = Column(Integer, ForeignKey("categories.uid"))
    product_uid = Column(Integer, ForeignKey("products.uid"))
    fieldname = Column(String)

    product = relationship(
        "Product",
        backref="related_categories",
    )

    category = relationship(
        "Category",
        backref="related_products",
    )


Base.metadata.create_all(engine)

with Session(engine) as session:
    category = Category(title="kitchen")
    session.add(category)
    product = Product(title="spoon")
    session.add(product)
    association = SysCategoryMMProduct(
        product=product,
        category=category,
        fieldname="Extra metadata")
    session.add(association)
    session.commit()

    category = session.query(Category).first()
    assert len(category.related_products) == 1
    assert category.related_products[0].product.related_categories[0].category == category

    q = session.query(Category).join(Category.related_products).join(SysCategoryMMProduct.product).filter(Product.title == "spoon")
    print (q)
    assert q.first() == category

The last query looks like:

SELECT categories.uid AS categories_uid, categories.title AS categories_title 
FROM categories JOIN categories_products ON categories.uid = categories_products.category_uid JOIN products ON products.uid = categories_products.product_uid 
WHERE products.title = 'spoon'

Answered By – Ian Wilson

Answer Checked By – Marie Seifert (BugsFixing Admin)

Leave a Reply

Your email address will not be published.