SQLAlchemy一对多(多对多)关系

时间:2020-07-12 06:35:58

标签: python sqlalchemy flask-sqlalchemy model-associations

我目前有一个SQLAlchemy模型User,它具有两种与之相关的度量值:体重和身高。这些将作为外键添加。

class User(UserMixin, db.Model):
    __tablename__ = "user"
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(100))
    name = db.Column(db.String(1000))
    weight = db.relationship("Weight", cascade="all,delete")
    height = db.relationship("Height", cascade="all,delete")

class Weight(db.Model):
    __tablename__ = "weight"
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.DateTime)
    weight = db.Column(db.Float)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))

class Height(db.Model):
    __tablename__ = "height"
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.DateTime)
    height = db.Column(db.Float)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))

我希望能够添加更多类型的度量,而不必每次都重建数据库结构。因此,我考虑使用通用的Measurement模型,该模型的测量类型包含一列(不是Enum,因此我可以轻松添加新类型):

class User(UserMixin, db.Model):
    __tablename__ = "user"
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(100))
    name = db.Column(db.String(1000))
    measurements = db.relationship("Measurement", cascade="all,delete")

    @property
    def weight(self):
        pass    # <-- No idea what to do here

    @property
    def height(self):
        pass    # <-- No idea what to do here

class Measurement(db.Model):
    __tablename__ = "measurement"
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.DateTime)
    value = db.Column(db.Float)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
    measurement_type_id = db.Column(db.Integer, db.ForeignKey('measurement_type.id'))
    measurement_type = db.relationship("MeasurementType")

class MeasurementType(db.Model):
    __tablename__ = "measurement_type"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))
    unit = db.Column(db.String(100), nullable=True)

使用这种新模型,我如何只获得例如重量?使用当前模型,就这么简单,其中current_user是当前登录的用户:

current_user.weight

但是我想那一定是这样的:

current_user.measurements.filter(Measurement.measurement_type == "Weight")

(这不起作用,因为current_user.measurements返回一个列表。)

类似地,我该如何为测量添加新值?目前,我正在这样做:

current_user.weight.append(Weight(date=date, weight=weight))
db.session.commit()

我是否基本上需要复制relationship的低级实现才能通过用户ID和测量类型这两项进行过滤?

还是可以使用association proxy以某种方式实现这一目标?还是(正确)使用primaryjoin的{​​{1}}参数?

1 个答案:

答案 0 :(得分:0)

我发现实现此目标的最简单方法是在关系上使用动态延迟加载,正如在this answersimilar question上所建议的那样。这样,返回类型不再是简单的列表,而是一个SQL对象。这也使用简单的字符串作为度量类型,而不是查找表。

class User(UserMixin, db.Model):
    __tablename__ = "user"
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(100))
    name = db.Column(db.String(1000))
    measurements = db.relationship("Measurement", cascade="all,delete", lazy="dynamic")


class Measurement(db.Model):
    __tablename__ = "measurement"
    id = db.Column(db.Integer, primary_key=True)
    m_type = db.Column(db.String(20))
    date = db.Column(db.DateTime)
    value = db.Column(db.Float)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))

之后,您可以对测量使用常规的滤波方法:

list(current_user.measurements.filter_by(m_type="weight"))

添加新行也非常简单:

current_user.measurements.append(Measurement(m_type="weight", date=date, value=weight))