sor*_*tas 6 python sqlalchemy flask flask-sqlalchemy flask-admin
我的 SQLAlchemy 数据库中有三个表(使用 Flask SQLAlchemy):产品、产品变体和订单。我想在订单中查看包含哪些产品及其变体。
它适用于关系/外键,但主要问题是:如果我将产品添加到订单中,我仍然可以添加其他产品的变体(使用 Flask-Admin,或仅使用 Flask shell)。
所以,主要问题是:如何在表之间创建连接,以便只有当它们是订单产品的变体时才能添加变体?谢谢 :)
另一个解决方案:如何将列添加到 Orders 表,以便根据变体 ID 从 Product 表中获取产品名称?我尝试使用column_property, Post.query.get(variation_id), variation.parent_id, backhrefvariation.origin_product但没有任何成功:)
我的模型:
产品(如三星 Galaxy 7)
class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120), index=True)
    brand = db.Column(db.String(120))
    variations = db.relationship('Variation', backref='origin_product', lazy='dynamic')
    orders = db.relationship('Order', backref='product_in_order', lazy='dynamic')
Run Code Online (Sandbox Code Playgroud)
产品变化(如三星 Galaxy 7 Blue 32GB)
class Variation(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120), index=True)
    price = db.Column(db.Integer)
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    orders = db.relationship('Order', backref='variation_in_order', lazy='dynamic')
Run Code Online (Sandbox Code Playgroud)
命令
class Order(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    variation_id = db.Column(db.Integer, db.ForeignKey('variation.id'))
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
Run Code Online (Sandbox Code Playgroud)
PS product_id = db.Column(db.Integer, db.ForeignKey('variation.product_id'))在 db 中工作,我看到正确的产品 ID。仍然像 Flask-Admin 这样的外部工具将product_id列视为变体对象,所以没有用。需要一种方法,将产品对象连接到product_id. 像,连接产品ForeignKey,但基于variation_id.
防止不相关的产品和变体组合的一种方法是创建从订单到产品的外键以及从订单到变体的重叠复合外键。为了能够引用variation.id, variation.product_id产品 id 的组合,也应该成为主键的一部分,并且 id 必须显式地给出自动递增行为:
class Variation(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'),
                           primary_key=True)
class Order(db.Model):
    product_id = db.Column(db.Integer, nullable=False)
    variation_id = db.Column(db.Integer)
    __table_args__ = (
        db.ForeignKeyConstraint([product_id], ['product.id']),
        db.ForeignKeyConstraint([product_id, variation_id],
                                ['variation.product_id', 'variation.id']),
    )
Run Code Online (Sandbox Code Playgroud)
由于外键默认为 MATCH SIMPLE,因此变体的复合外键将允许添加变体 id 为 NULL 的行,但如果给出了变体 id,则该组合必须引用现有行。此设置允许分别使用现有的关系product_in_order和variation_in_order来Product代替Variation下面更复杂的模型,尽管SQLAlchemy会(正确地)警告关系存在冲突的事实,因为它们都设置了产品id。创建订单时只需使用其中之一:
In [24]: o1 = Order(product_in_order=product)
In [25]: o2 = Order(variation_in_order=variation)
Run Code Online (Sandbox Code Playgroud)
或按照有关解决冲突的文档进行操作。在此模型中,产品名称始终可用
In [31]: o1.product_in_order.name
Run Code Online (Sandbox Code Playgroud)
防止在提供产品时向订单添加不相关变体的另一种选择是完全防止在这种情况下添加变体,反之亦然:
class Order(db.Model):
    ...
    variation_id = db.Column(db.Integer, db.ForeignKey('variation.id'))
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    __table_args__ = (
        # Require either a variation or a product
        db.CheckConstraint(
            '(variation_id IS NOT NULL AND product_id IS NULL) OR '
            '(variation_id IS NULL AND product_id IS NOT NULL)'),
    )
Run Code Online (Sandbox Code Playgroud)
在此模型中构建关系Product有点复杂,并且需要使用非主映射器:
product_variation = db.outerjoin(
    Product, db.select([Variation.id,
                        Variation.product_id]).alias('variation'))
ProductVariation = db.mapper(
    Product, product_variation, non_primary=True,
    properties={
        'id': [product_variation.c.product_id,
               product_variation.c.variation_product_id],
        'variation_id': product_variation.c.variation_id
    })
Run Code Online (Sandbox Code Playgroud)
连接产生的可选择项被映射回,但也Product允许基于以下条件进行选择:Variation.id
Order.product = db.relationship(
    ProductVariation,
    primaryjoin=db.or_(Order.product_id == ProductVariation.c.id,
                       Order.variation_id == ProductVariation.c.variation_id))
Run Code Online (Sandbox Code Playgroud)
这样您就可以从Order实例访问产品名称
order.product.name
Run Code Online (Sandbox Code Playgroud)
演示:
In [2]: p1 = Product(name='Product 1')
In [3]: v11 = Variation(product=p1)
In [4]: v12 = Variation(product=p1)
In [5]: p2 = Product(name='Product 2')
In [6]: v21 = Variation(product=p2)
In [9]: session.add_all([p1, p2])
In [10]: session.add_all([v11, v12, v21])
In [11]: session.commit()
In [12]: o1 = Order(product_id=p1.id)
In [13]: o2 = Order(variation_id=v12.id)
In [14]: o3 = Order(variation_id=v11.id)
In [15]: o4 = Order(product_id=p2.id)
In [16]: o5 = Order(variation_id=v21.id)
In [17]: session.add_all([o1, o2, o3, o4, o5])
In [18]: session.commit()
In [25]: [o.product.name for o in session.query(Order).all()]
Out[25]: ['Product 1', 'Product 1', 'Product 1', 'Product 2', 'Product 2']
Run Code Online (Sandbox Code Playgroud)
LEFT JOIN 确保没有变化的产品也能正常工作:
In [26]: p3 = Product(name='Product 3')
In [27]: session.add(p3)
In [28]: session.commit()
In [29]: session.add(Order(product_id=p3.id))
In [30]: session.commit()
In [31]: [o.product.name for o in session.query(Order).all()]
Out[31]: ['Product 1', 'Product 1', 'Product 1', 'Product 2', 'Product 2', 'Product 3']
Run Code Online (Sandbox Code Playgroud)
另一方面,您可以使用CheckConstraint所描述的和普通的结构,而不是这种相当复杂的结构property:
class Order(db.Model):
    ...
    @property
    def product(self):
        if self.product_in_order:
            return self.product_in_order
        else:
            return self.variation_in_order.origin_product
Run Code Online (Sandbox Code Playgroud)
请注意,如果不急切加载,这将在出现变化顺序时对数据库触发 2 个单独的 SELECT 查询。
|   归档时间:  |  
           
  |  
        
|   查看次数:  |  
           3357 次  |  
        
|   最近记录:  |