我尝试通过第三个表“购物车”在两个表“用户”和“产品”之间建立多对多关系。由于购物车的每一行都有附加数据,我尝试使用关联对象模式,但显然有一些基本的东西我不理解。以下是模型声明:
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 的文档和其他信息虽然数量庞大,但相互冲突并且通常没有帮助,到目前为止我还没有找到让查询工作的方法。
我错过了什么?
我做了一些表面上的改变,并添加了一些断言,希望能展示你所追求的。他们使用关联对象和代理(如果您需要)
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]