防止不相关的产品和变体组合的一种方法是创建一个从订单到产品的外键和一个
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']),
)
由于外键默认与simple匹配,因此组合外键到variation将允许添加variation id为空的行,但如果给定variation id,则组合必须引用现有行。此设置允许使用现有关系
按顺序生产
和
按顺序变化
对双方
Product
和
Variation
尽管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)'),
)
建立与
产品
在这个模型中有点复杂,需要
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
})
连接生成的可选择项映射回
产品
,但允许根据
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
只需注意,如果没有紧急加载,这将对数据库触发两个单独的select查询,以防出现变化顺序。