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

我的SQLAlchemy数据库中有三个表(使用Flask SQLAlchemy):产品,产品变体和订单.我想在订单中看到它包含哪种产品及其变体.

它适用于关系/外键,但主要问题是:如果我按订单添加产品,我仍然可以添加其他产品的变体(使用Flask-Admin,或只是烧瓶外壳).

那么,主要问题是:如何在表之间建立连接,因此只有在它们是订单产品的变体时才能添加变体?谢谢 :)

另一个解决方案:如何将列添加到Orders表中,以便根据变体ID从Product表中获取产品名称?我尝试使用column_property,Post.query.get(variation_id),variation.parent_id,backhref variation.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')

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

附: product_id = db.Column(db.Integer,db.ForeignKey(‘variation.product_id’))在db中工作,我看到正确的产品ID.仍然像Flask-Admin这样的外部工具将product_id列视为变体对象,因此没有用处.需要一种方法,将产品对象连接到product_id.比如,连接产品ForeignKey,但是基于variation_id.

最佳答案 防止不相关产品和变体组合的一种方法是从订单到产品创建外键,从订单到变更创建
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而不是下面涉及的更多模型,尽管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))

这样,您就可以使用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']

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

另一方面,您可以使用所描述的CheckConstraint和普通属性来代替这种相当复杂的构造:

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查询.

点赞