SQLAlchemy双向多对多引发ArgumentError

问题描述 投票:0回答:1

我尝试通过第三个表“购物车”在两个表“用户”和“产品”之间建立多对多关系。由于购物车的每一行都有附加数据,我尝试使用关联对象模式,但显然有一些基本的东西我不理解。以下是模型声明:

class Cart(BaseModel):
    __tablename__ = 'cart'
    product_id: Mapped[int] = Column(Integer, ForeignKey('product.id'), primary_key=True)
    user_id: Mapped[int] = Column(Integer, ForeignKey('user.id'), primary_key=True)
    selling_price: Mapped[Decimal] = mapped_column(Numeric(precision=6, scale=2), nullable=True)
    quantity: Mapped[int]
    user: Mapped["User"] = relationship("Users", back_populates="product")
    product: Mapped["Product"] = relationship("Product", back_populates="user")


class User(BaseModel, UserMixin):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    firstname: Mapped[str] = mapped_column(String(20), unique=False, nullable=False)
    lastname: Mapped[str] = mapped_column(String(20), unique=False, nullable=False)
    email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False)
    password: Mapped[str] = mapped_column(String(60), nullable=False)
    products: Mapped[list['Product']] = relationship("Cart", back_populates='user')

    def add_to_cart(self, product_id: int):
        item_to_add = Cart.insert().values(product_id=product_id, user_id=self.id)
        self.session.execute(item_to_add)
        self.session.commit()
        flash('Your item has been added to your cart!', 'success')

    def __repr__(self):
        return f"User('{self.firstname}','{self.lastname}', '{self.email}','{self.id}')"


class Product(BaseModel):
    __tablename__ = "product"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(100), nullable=False)
    price: Mapped[Decimal] = mapped_column(Numeric(precision=6, scale=2), nullable=True)
    description: Mapped[str] = mapped_column(Text(), nullable=False)
    users: Mapped[list['User']] = relationship("Cart", back_populates='product')

    def __repr__(self):
        return f"Product('{self.name}', '{self.price}')"

在代码中的某一时刻,我执行此查询:

        q = select(User).where(User.email == form.email.data)
        user = conn.session.query(q).scalars().first()

因为代码嵌入到 Web 服务器中,所以进程不会终止,但我看到了这个回溯(routes.py 是其中包含 Web 视图功能的文件) - 为了阅读方便,我已经包装了最后一行:

Traceback (most recent call last):
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/flask/app.py", line 1478, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/flask/app.py", line 1458, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/flask/app.py", line 1455, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/flask/app.py", line 869, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/flask/app.py", line 867, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/flask/app.py", line 852, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/Users/sholden/Projects/Python/ecommerce/unwrap/routes.py", line 54, in login
    user = conn.session.query(q).scalars().first()
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2912, in query
    return self._query_cls(entities, self, **kwargs)
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/orm/query.py", line 276, in __init__
    self._set_entities(entities)
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/orm/query.py", line 288, in _set_entities
    self._raw_columns = [
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/orm/query.py", line 289, in <listcomp>
    coercions.expect(
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/sql/coercions.py", line 442, in expect
    return impl._implicit_coercions(
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/sql/coercions.py", line 506, in _implicit_coercions
    self._raise_for_expected(element, argname, resolved)
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/sql/coercions.py", line 1137, in _raise_for_expected
    return super()._raise_for_expected(
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/sql/coercions.py", line 710, in _raise_for_expected
    super()._raise_for_expected(
  File "/Users/sholden/.venvs/ecommerce-3.10/lib/python3.10/site-packages/sqlalchemy/sql/coercions.py", line 535, in _raise_for_expected
    raise exc.ArgumentError(msg, code=code) from err
  sqlalchemy.exc.ArgumentError: Column expression, FROM clause, or other columns
  clause element expected, got <sqlalchemy.sql.selectable.Select object at 0x104c04d90>.
  To create a FROM clause from a <class 'sqlalchemy.sql.selectable.Select'> object, use the
  .subquery() method. (Background on this error at https://sqlalche.me/e/20/89ve)

我已经尝试了

relationship
调用的各种参数组合,但是有关 SQLAlchemy 的文档和其他信息虽然数量庞大,但相互冲突并且通常没有帮助,到目前为止我还没有找到让查询工作的方法。

我错过了什么?

python sqlalchemy many-to-many relationship
1个回答
0
投票

我建议您查看多对多关联代理

我做了一些表面上的改变,并添加了一些断言,希望能展示你所追求的。他们使用关联对象和代理(如果您需要)

from decimal import Decimal
from sqlalchemy import ForeignKey, Integer, Numeric, String, Text, create_engine, select

from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, Session, relationship
from sqlalchemy.ext.associationproxy import association_proxy, AssociationProxy

class Base(DeclarativeBase):
    pass

class Cart(Base):
    __tablename__ = "cart"
    product_id: Mapped[int] = mapped_column(ForeignKey("product.id"), primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), primary_key=True)
    selling_price: Mapped[Decimal | None] = mapped_column(Numeric(precision=6, scale=2))
    quantity: Mapped[int]
    user: Mapped["User"] = relationship(back_populates="products")
    product: Mapped["Product"] = relationship(back_populates="users")

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    firstname: Mapped[str] = mapped_column(String(20))
    lastname: Mapped[str] = mapped_column(String(20))
    email: Mapped[str] = mapped_column(String(120), unique=True)
    password: Mapped[str] = mapped_column(String(60))
    products: Mapped[list["Cart"]] = relationship(back_populates="user")
    product_selling_prices_in_cart: AssociationProxy[list[Decimal]] = association_proxy("products", "selling_price")

class Product(Base):
    __tablename__ = "product"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(100))
    price: Mapped[Decimal | None] = mapped_column(Numeric(precision=6, scale=2))
    description: Mapped[str] = mapped_column(Text())
    users: Mapped[list["Cart"]] = relationship(back_populates="product")
    users_who_have_this_in_cart: AssociationProxy[list[int]] = association_proxy("users", "user_id")

engine = create_engine("sqlite:///test.sqlite")
Base.metadata.create_all(engine)

with Session(engine) as session, session.begin():
    # Insert some test data
    u1 = User(
        firstname="user", lastname="one", email="[email protected]", password="*"
    )
    u2 = User(
        firstname="user", lastname="two", email="[email protected]", password="*"
    )
    p1 = Product(name="product one", price=1.1, description="some product 1")
    p2 = Product(name="product two", price=2.2, description="some product 2")
    u1_p1_cart = Cart(quantity=1, selling_price=11.11, user=u1, product=p1)
    u1_p2_cart = Cart(quantity=1, selling_price=11.11, user=u1, product=p2)
    u2_p2_cart = Cart(quantity=2, selling_price=22.22, user=u2, product=p2)
    session.add_all((u1, u2, p1, p2, u1_p1_cart, u1_p2_cart, u2_p2_cart))

with Session(engine) as session:
    # check if the proxies work
    user_1 = session.execute(
        select(User).where(User.email == "[email protected]")
    ).scalar()
    assert user_1
    assert user_1.product_selling_prices_in_cart == [Decimal("11.11"), Decimal("11.11")]
    product_2 = session.execute(
        select(Product).where(Product.name == "product two")
    ).scalar()
    assert product_2
    assert product_2.users_who_have_this_in_cart == [1, 2]
© www.soinside.com 2019 - 2024. All rights reserved.