代码之家  ›  专栏  ›  技术社区  ›  sortas

flask sqlalchemy:如何添加依赖于另一个表的列?

  •  4
  • sortas  · 技术社区  · 6 年前

    我的sqlalchemy数据库中有三个表(使用flask sqlalchemy):产品、产品变化和订单。我想看一下订单,它包括哪些产品及其变化。

    它可以很好地处理关系/外键,但主要问题是:如果我将产品添加到订单中,我仍然可以添加其他产品的变体(使用flask admin,或仅使用flask shell)。

    所以,主要问题是: 如何创建表之间的连接,以便只有在变量是订单产品的变量时才能添加变量?谢谢:)

    另一个解决方案: 如何将列添加到ORDERS表中,以便根据变量ID从PRODUCT表中获取产品名称?我试着用 column_property 我是说, Post.query.get(variation_id )我是说, variation.parent_id ,后退 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')) 在数据库中工作,我看到正确的产品ID。仍然是外部工具,如Flask Admin,请参阅 product_id 列作为变量对象,因此没有用处。需要一种方法,将产品对象连接到 产品ID 是的。比如,连接产品 ForeignKey ,但基于 variation_id 是的。

    1 回复  |  直到 6 年前
        1
  •  1
  •   Ilja Everilä    6 年前

    防止不相关的产品和变体组合的一种方法是创建一个从订单到产品的外键和一个 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查询,以防出现变化顺序。