SQLAlchemy декларативное само-соединение многих ко многим через объект Association - PullRequest
10 голосов
/ 05 сентября 2011

У меня есть таблица Users и таблица Friends, которая отображает пользователей на других пользователей, поскольку у каждого пользователя может быть много друзей. Это отношение явно симметрично: если пользователь A является другом пользователя B, то пользователь B также является другом пользователя A, я сохраняю это отношение только один раз. В таблице Friends есть дополнительные поля, помимо двух идентификаторов пользователя, поэтому я должен использовать объект ассоциации.

Я пытаюсь определить это отношение в декларативном стиле в классе Users (который расширяет декларативную базу), но я не могу понять, как это сделать. Я хочу иметь доступ ко всем друзьям данного пользователя через собственность друзей, так сказать friends = bob.friends.

Какой лучший подход к этой проблеме? Я попытался опубликовать здесь множество различных настроек, но ни одна из них не работала по разным причинам.

РЕДАКТИРОВАТЬ: моя последняя попытка выглядит следующим образом:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)

    # Relationships
    friends1 = relationship('Friends', primaryjoin=lambda: id==Friends.friend1ID)
    friends2 = relationship('Friends', primaryjoin=lambda: id==Friends.friend2ID)


class Friends(Base):
    __tablename__ = 'friends'
    id = Column(Integer, primary_key=True)
    friend1ID = Column(Integer, ForeignKey('users.id') )
    friend2ID = Column(Integer, ForeignKey('users.id') )
    status = Column(Integer)

    # Relationships
    vriend1 = relationship('Student', primaryjoin=student2ID==Student.id)
    vriend2 = relationship('Student', primaryjoin=student1ID==Student.id)

Это, однако, приводит к следующей ошибке:

InvalidRequestError: Table 'users' is already defined for this MetaData instance.  Specify 'extend_existing=True' to redefine options and columns on an existing Table object.

Я должен признать, что на данный момент я полностью сбит с толку из-за многих неудачных попыток и мог совершить более одной глупой ошибки в приведенном выше.

Ответы [ 3 ]

20 голосов
/ 06 сентября 2011

Это конкретное исключение вызвано описанием таблицы более одного раза, либо путем многократного определения отображения класса (скажем, в интерактивном интерпретаторе, либо в функции, которую можно вызывать более одного раза), либо путем смешивания декларативного класса стилейотображения с отражением таблицы.В первом случае исключите повторный вызов;запустите новый интерпретатор, если вы делаете это в интерактивном режиме, или исключите дополнительные вызовы функций (возможно, это будет полезно для объекта-одиночки / borg).

В последнем случае просто выполните то, что говорит исключение, добавьте __table_args__ = {'extend_existing': True} как дополнительная переменная класса в ваших определениях класса.Делайте это только в том случае, если вы действительно уверены, что таблица описана правильно дважды, как в случае с отражением таблицы.

1 голос
/ 25 февраля 2016

У меня была эта ошибка при использовании Flask-SQLAlchemy, но другие решения не работали.

Ошибка произошла только на нашем производственном сервере, хотя все работало нормально на моем компьютере и на тестовом сервере.

У меня был класс 'Model', от которого унаследованы все мои другие классы базы данных:

class Model(db.Model):

    id = db.Column(db.Integer, primary_key=True)

По какой-то причине ORM дал классам, которые унаследовали от этого класса, одну и ту же таблицу имя как этот класс.То есть для каждого класса, который он пытался создать для него таблицу, называемую таблицей «модель».

Решением было явное именование дочерних таблиц с помощью переменной класса tablename :

class Client(Model):

    __tablename__ = "client"

    email = db.Column(db.String)
    name = db.Column(db.String)
    address = db.Column(db.String)
    postcode = db.Column(db.String)
0 голосов
/ 06 сентября 2011

Как уже упоминалось в комментарии, я предпочитаю расширенную модель, в которой Friendship - это отдельная сущность, а связи между друзьями - это отдельные сущности. Таким образом, можно хранить как симметричные, так и асимметричные свойства (например, что один человек думает о другом). Таким образом, модель ниже должна показать вам, что я имею в виду:

...
class User(Base):
    __tablename__ =  "user"

    id = Column(Integer, primary_key=True)
    name = Column(String(255), nullable=False)

    # relationships
    friends = relationship('UserFriend', backref='user',
            # ensure that deletes are propagated
            cascade='save-update, merge, delete',
    )

class Friendship(Base):
    __tablename__ =  "friendship"

    id = Column(Integer, primary_key=True)
    # additional info symmetrical (common for both sides)
    status = Column(String(255), nullable=False)
    # @note: also could store a link to a Friend who requested a friendship

    # relationships
    parties = relationship('UserFriend', 
            back_populates='friendship',
            # ensure that deletes are propagated both ways
            cascade='save-update, merge, delete',
        )

class UserFriend(Base):
    __tablename__ =  "user_friend"

    id = Column(Integer, primary_key=True)
    friendship_id = Column(Integer, ForeignKey(Friendship.id), nullable=False)
    user_id = Column(Integer, ForeignKey(User.id), nullable=False)
    # additional info assymmetrical (different for each side)
    comment = Column(String(255), nullable=False)
    # @note: one could also add 1-N relationship where one user might store
    # many different notes and comments for another user (a friend)
    # ...

    # relationships
    friendship = relationship(Friendship,
            back_populates='parties',
            # ensure that deletes are propagated both ways
            cascade='save-update, merge, delete',
        )

    @property
    def other_party(self):
        return (self.friendship.parties[0] 
                if self.friendship.parties[0] != self else
                self.friendship.parties[1]
                )

    def add_friend(self, other_user, status, comment1, comment2):
        add_friendship(status, self, comment1, other_user, comment2)

# helper method to add a friendship
def add_friendship(status, usr1, comment1, usr2, comment2):
    """ Adds new link to a session.  """
    pl = Friendship(status=status)
    pl.parties.append(UserFriend(user=usr1, comment=comment1))
    pl.parties.append(UserFriend(user=usr2, comment=comment2))
    return pl

Таким образом, добавив дружба довольно легко.
Так же обновляет любые его атрибуты. Вы можете создать больше вспомогательных методов, таких как add_friend.
При указанной выше конфигурации cascade удаление a User or Friendship or UserFriend обеспечит удаление обеих сторон.
Выбор всех друзей так прост, как вы хотите: print user.friends

Реальная проблема с этим решением состоит в том, чтобы гарантировать, что есть точно 2 UserFriend ссылки для каждого Friendship. Опять же, при манипулировании объектами из кода это не должно быть проблемой, но база данных потенциально может быть несовместимой, если кто-то импортирует / манипулирует некоторыми данными непосредственно на стороне SQL.

...