Отношения подкласса/наследования SQLAlchemy

class Geolocation(db.Model):
    __tablename__ = "geolocation"
    id = db.Column(db.Integer, primary_key=True)
    latitude = db.Column(db.Float)
    longitude = db.Column(db.Float)
    elevation = db.Column(db.Float)         # Meters
    # Relationships
    pin = db.relationship('Pin', uselist=False, backref="geolocation")

    def __init__(self, latitude, longitude, elevation):
        self.latitude = latitude
        self.longitude = longitude
        self.elevation = elevation

    def __repr__(self):
        return '<Geolocation %s, %s>' % (self.latitude, self.longitude)


class Pin(db.Model):
    __tablename__ = "pin"
    id = db.Column(db.Integer, primary_key=True)
    geolocation_id = db.Column(db.Integer, db.ForeignKey('geolocation.id'))  # True one to one relationship (Implicit child)

    def __init__(self, geolocation_id):
        self.geolocation_id = geolocation_id

    def __repr__(self):
        return '<Pin Object %s>' % id(self)      # Instance id merely useful to differentiate instances.


class User(Pin):
    #id = db.Column(db.Integer, primary_key=True)
    pin_id = db.Column(db.Integer, db.ForeignKey('pin.id'), primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)
    salt = db.Column(db.String(120), nullable=False)
    # Relationships
    #posts = db.relationship('Post', backref=db.backref('user'), lazy='dynamic')               #One User to many Postings.

    def __init__(self, username, password_hash, salt, geolocation_id):
        super(Pin, self).__init__(self, geolocation_id)
        self.username = username
        self.password_hash = password_hash
        self.salt = salt

    def __repr__(self):
        return '<User %r>' % self.username

Я не понимаю, как настроить идентификаторы и отношения с подклассами в SQLAlchemy (я использую Flask-SQLAlchemy). Мой общий план заключается в том, чтобы суперкласс Pin был высокоуровневым представлением всего, что имеет геолокацию (т. е. пользователя, места и т. д.).

Между булавкой и объектом геолокации существует отношение один к одному, поэтому геолокация не содержит, например, местоположение двух пользователей (или пользователя и места) одновременно. Теперь я хочу создать подкласс Pin для создания класса User. Объект пользователя должен иметь имя, пароль_хэш, соль, и я также хочу иметь возможность искать геолокацию пользователя через userObj.geolocation. Однако позже я хочу создать класс Place, который также является подклассом Pin, и я должен иметь возможность искать геолокацию Place через placeObj.geolocation . Учитывая объект геолокации, я должен иметь возможность использовать geolocationObj.pin для поиска пользователя/места/и т. д. которому соответствует объект геолокации. Вся причина, по которой я представил надкласс Pin, заключалась в том, чтобы гарантировать, что существует чистая связь один к одному между объектами Pin и Geolocation, а не связать Geolocation либо с пользователем, либо с человеком, что потребовало бы, чтобы таблица Geolocation имела user_id и place_id столбцы, один из которых всегда будет нулевым.

Я ожидал, что каждый пользователь будет автоматически иметь свойство .geolocation через родительский класс Pin, который ссылается на геолокацию, но похоже, что SQLAlchemy этого не делает. Как я могу заставить отношения подклассов работать для достижения моей цели иметь User и Place и, возможно, другие классы подкласса Pin, чтобы каждый из этих классов имел свойство геолокации через Pin и имел отношение один к одному между Pin и Geolocation?


person dgh    schedule 16.10.2012    source источник


Ответы (2)


Решение, которое я придумал. Это служит полным примером создания подклассов в SQLAlchemy в декларативном стиле и с использованием наследования соединения.

class Geolocation(Base):
    __tablename__ = "geolocation"
    id = Column(Integer, primary_key=True)
    latitude = Column(Float)
    longitude = Column(Float)
    elevation = Column(Float)         # Meters
    # Relationships
    person = relationship('Pin', uselist=False, backref="geolocation")

    def __init__(self, latitude, longitude, elevation):
        self.latitude = latitude
        self.longitude = longitude
        self.elevation = elevation

    def __repr__(self):
        return '<Geolocation %s, %s>' % (self.latitude, self.longitude)


class Pin(Base):
    __tablename__ = 'pin'
    id = Column(Integer, primary_key=True)
    geolocation_id = Column(Integer, ForeignKey('geolocation.id'), unique=True, nullable=False)  # True one to one relationship (Implicit child)
    type = Column('type', String(50))              # discriminator
    __mapper_args__ = {'polymorphic_on': type}

    def __init__(self, geolocation_id):
        self.geolocation_id = geolocation_id


class User(Pin):
    __tablename__ = 'user'
    id = Column(Integer, ForeignKey('pin.id'), primary_key=True)
    __mapper_args__ = {'polymorphic_identity': 'user',
                       'inherit_condition': (id == Pin.id)}
    user_id = Column(Integer, autoincrement=True, primary_key=True, unique=True)
    username = Column(String(80), unique=True)
    password_hash = Column(String(120))
    salt = Column(String(120))
    posts = relationship('Posting', primaryjoin="(User.user_id==Posting.user_id)", backref=backref('user'), lazy='dynamic')   #One User to many Postings.

    def __init__(self, username, password_hash, salt, geo_id):
        super(User, self).__init__(geo_id)
        self.username = username
        self.password_hash = password_hash
        self.salt = salt

    def __repr__(self):
        return '<User %s>' % (self.username)


class Posting(Pin):
    __tablename__ = 'posting'
    id = Column(Integer, ForeignKey('pin.id'), primary_key=True)
    __mapper_args__ = {'polymorphic_identity': 'posting',
                        'inherit_condition': (id == Pin.id)}
    posting_id = Column(Integer, autoincrement=True, primary_key=True, unique=True)
    creation_time = Column(DateTime)
    expiration_time = Column(DateTime)
    user_id = Column(Integer, ForeignKey('user.user_id'))              # One User to many Postings

    def __init__(self, creation_time, expiration_time, user_id, geo_id):
        super(Posting, self).__init__(geo_id)
        # For now, require creation time to be passed in. May make this default to current time.
        self.creation_time = creation_time
        self.expiration_time = expiration_time
        self.user_id = user_id

    def __repr__(self):
        #TODO come up with a better representation
        return '<Post %s>' % (self.creation_time)
person dgh    schedule 17.10.2012
comment
Только с точки зрения моделирования данных, я не думаю, что primary_key=True имеет смысл для Posting.id, если не может быть только одна публикация на пин. - person Bklyn; 05.09.2018
comment
Я получал эту ошибку с моей собственной схемой: NoForeignKeysError: Не удалось определить условие соединения между родительскими и дочерними таблицами в отношении Child.objects — нет внешних ключей, связывающих эти таблицы. Убедитесь, что ссылающиеся столбцы связаны с ForeignKey или ForeignKeyConstraint, или укажите выражение «primaryjoin». Решение состояло в том, чтобы явно указать первичное соединение, как показано здесь. Спасибо @DGH! - person Nick K9; 20.04.2019

Вот документация по сопоставлению иерархий наследования и по делает это декларативно в SQLAlchemy.

Я полагаю, вам понадобится вариант наследования объединенных таблиц, означающий, что каждый класс в вашей цепочке родительских классов имеет свою собственную таблицу с уникальными для нее столбцами. По сути, вам нужно добавить столбец дискриминатора в таблицу pin, чтобы обозначить тип подкласса для каждого контакта, и некоторые свойства двойного подчеркивания для ваших классов, чтобы описать конфигурацию наследования для SQLAlchemy.

person Simon    schedule 16.10.2012
comment
Вот почему ответы только по ссылкам - мусор - person Wayne Werner; 31.08.2016