Отношение SQLAlchemy "один ко (некоторые из многих)" - PullRequest
0 голосов
/ 12 июля 2020

В настоящее время у меня есть модель 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"))

Я хочу иметь возможность добавлять больше типов измерений без необходимости каждый раз перестраивать структуру базы данных. Поэтому я подумал об использовании общей модели c 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, чтобы фильтровать по двум параметрам: идентификатору пользователя и типу измерения?

Или я могу как-то добиться этого с помощью прокси-сервера ? Или (правильно) используя аргумент primaryjoin из relationship?

1 Ответ

0 голосов
/ 12 июля 2020

Самый простой способ, который я нашел для достижения этого, - использовать динамическую c ленивую загрузку для отношения, как это также рекомендуется в этом ответе на аналогичный вопрос . Таким образом, возвращаемый тип - это уже не простой список, а объект 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))
...