Flask SQLAlchemy:如何添加依赖于另一个表的列?

时间:2018-06-28 19:07:32

标签: python flask sqlalchemy flask-sqlalchemy flask-admin

我的SQLAlchemy数据库中有三个表(使用Flask SQLAlchemy):产品,产品变型和订单。我想按顺序查看它包含的产品及其变体。

它可以很好地与关系/外键配合使用,但是主要的问题是:如果我要向订单中添加产品,我仍然可以添加其他产品的变体(使用Flask-Admin,或仅使用烧瓶壳)。

所以,主要问题::如何在表之间创建连接,因此只有在订单产品的变体中才可以添加变体?谢谢:)

另一种解决方案::如何在“订单”表中添加列,以便它根据变体ID从“产品”表中获取产品名称?我尝试使用column_propertyPost.query.get(variation_id),variation.parent_id,backhref variation.origin_product,但未成功:)

我的模特:

产品(例如Samsung 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')

产品差异(例如Samsung 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')

订购

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'))

PS product_id = db.Column(db.Integer, db.ForeignKey('variation.product_id'))在数据库中有效,我看到正确的产品ID。诸如Flask-Admin之类的外部工具仍将product_id列视为变体对象,因此没有用。需要一种方法来将产品对象连接到product_id。就像,连接产品ForeignKey,但基于variation_id

1 个答案:

答案 0 :(得分:1)

防止无关的产品和版本组合的一种方法是从订单到产品创建外键,从订单到变异创建overlapping composite foreign key。为了能够引用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']),
    )

由于外键默认为MATCH SIMPLE,因此组合式外键的变体将允许添加变体ID为NULL的行,但是如果给出了变体ID,则组合必须引用现有行。此设置允许将现有的 product_in_order variation_in_order 关系分别用于ProductVariation,而不是下面涉及的模型,尽管SQLAlchemy将(正确地警告以下事实:关系存在冲突,因为它们都设置了产品ID。创建订单时只需使用其中之一:

In [24]: o1 = Order(product_in_order=product)

In [25]: o2 = Order(variation_in_order=variation)

或遵循documentation about resolving the conflict。在此模型中,产品名称始终为

In [31]: o1.product_in_order.name

提供产品时,防止在订单中添加不相关的变化的另一种方法是在这种情况下完全避免添加变化,反之亦然:

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)'),
    )

在此模型中,建立与Product的关系要复杂一些,并且需要using a non primary mapper

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
    })

联接产生的可选内容被映射回Product,但也允许基于Variation.id进行选择:

Order.product = db.relationship(
    ProductVariation,
    primaryjoin=db.or_(Order.product_id == ProductVariation.c.id,
                       Order.variation_id == ProductVariation.c.variation_id))

通过这种方式,您可以使用{p> 1从Order实例访问产品名称

order.product.name

演示:

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']

左联接确保没有变化的产品也能正常工作:

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']

另一方面,您可以使用上述的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

请注意,如果不急于加载,则会在发生变更顺序时针对数据库触发2个单独的SELECT查询。