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

它可以很好地与关系/外键配合使用,但是主要的问题是:如果我要向订单中添加产品,我仍然可以添加其他产品的版本(使用Flask-Admin,或者只是flask shell)。

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

另一个解决方案:如何根据订单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'))

P.S. 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分别用于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
    })

联接产生的selectable映射回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']

左联接确保没有变化的产品也能正常工作:
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查询。

10-07 23:18