sqlalchemy полиморфное отношение многих ко многим - PullRequest
1 голос
/ 04 февраля 2010

Я пытаюсь разрешить пользователям «избирать» различные элементы в моем веб-приложении. Так, например, пользователь может добавить комментарий в избранное и новость. Затем я хочу запросить все элементы, которые выбрал пользователь, и полиморфно загрузить связанные объекты (будь то новость, комментарий и т. Д.), Чтобы отобразить список объектов для пользователя. Я знаю, как создать полиморфные отношения один-к-одному, однако я не смог понять многих-многих.

Заранее спасибо

EDIT

В моих полиморфных отношениях один к одному у меня есть отношение один-много между пользователями и действиями пользователя и полиморфные отношения один-один между действиями пользователя и объектом, над которым было выполнено действие. Так что в этом случае моя таблица действий пользователя выглядит так:

class UserAction:  
  pass

user_actions = Table('user_action', metadata,  
    Column('id', Integer, autoincrement=True, primary_key=True),
    Column('module', String(30)),
    Column('created', DateTime, default=datetime.now),  
    Column('user_id', Integer, ForeignKey('user.id'))  
)

таблица новостей (один из объектов, к которым можно получить доступ через действие пользователя):

class News:  
  pass

news = Table('news', metadata,  
    Column('id', Integer, autoincrement=True, primary_key=True),  
    Column('title', String(30), nullable=False),  
    Column('action_id', Integer, ForeignKey('user_action.id'))  
)

И картографы:

mapper(UserAction, user_actions, polymorphic_on=user_actions.c.module, polymorphic_identity='user_action')  
mapper(News, news, inherits=UserAction, polymorphic_identity='news')

Как видите, существует четкая взаимосвязь между объектом новостей и связанной с ним записью user_action. Это связано с тем, что внешний ключ user_action находится в таблице новостей. Если бы я хотел создать полиморфное отношение «многие ко многим», в котором многие пользователи могли бы отдавать предпочтение множеству различных типов объектов, как бы я поступил так? Большое спасибо.

1 Ответ

2 голосов
/ 09 февраля 2010

Ваш пример определяет отношение один-ко-многим между UserAction и News. Мне это кажется беспорядком, поскольку я не вижу причин, по которым News наследуется от UserAction. Чтобы разрешить нескольким пользовательским действиям ссылаться на одну новость, вы должны определить промежуточную таблицу с двумя внешними ключами: один ссылается на UserAction, а другой на News. Я вижу два разумных способа сделать его полиморфным:

  1. Используйте отдельную промежуточную таблицу для каждого предпочтительного класса модели и определите различные отношения в каждом UserAction подклассах.
  2. Определите отдельный внешний ключ для каждой предпочитаемой модели в промежуточной таблице и сопоставьте его с иерархией классов с наследованием одной таблицы (что-то вроде UserActionItem, UserActionNewsItem и т. Д.).

Но учтите, что все вышеперечисленное предназначено для связи UserAction и некоторых моделей входа с отношением «многие ко многим». В то время как поиск пользователей по записям кажется мне более похожим на связь User с моделями записей.

Обновление : Ниже приведен рабочий пример. Единственная проблема, которую я вижу с этим, состоит в том, что это позволяет дублировать.

from sqlalchemy import *
from sqlalchemy.orm import mapper, relation, sessionmaker
from sqlalchemy.ext.associationproxy import association_proxy

metadata = MetaData()

users = Table(
    'users', metadata,
    Column('id', Integer, nullable=False, primary_key=True),
)

news = Table(
    'news', metadata,
    Column('id', Integer, nullable=False, primary_key=True),
)

comments = Table(
    'comments', metadata,
    Column('id', Integer, nullable=False, primary_key=True),
)

favitems = Table(
    'favitems', metadata,
    Column('id', Integer, nullable=False, primary_key=True),
    Column('user_id', Integer, ForeignKey(users.c.id), nullable=False),
    Column('item_type', Integer, nullable=False),
    Column('news_id', Integer, ForeignKey(news.c.id)),
    Column('comment_id', Integer, ForeignKey(comments.c.id)),
)

class News(object): pass

class Comment(object): pass

class FavItem(object):
    TYPE_NEWS = 1
    TYPE_COMMENT = 2
    def __new__(cls, item=None):
        if isinstance(item, News):
            cls = FavNews
        elif isinstance(item, Comment):
            cls = FavComment
        return object.__new__(cls)

class FavNews(FavItem):
    def __init__(self, item):
        self.item_type = self.TYPE_NEWS
        self.item = item

class FavComment(FavItem):
    def __init__(self, item):
        self.item_type = self.TYPE_COMMENT
        self.item = item

class User(object):
    favorites = association_proxy('_favitems', 'item', creator=FavItem)

mapper(News, news)

mapper(Comment, comments)

mapper(FavItem, favitems,
       polymorphic_on=favitems.c.item_type)

mapper(FavNews, favitems,
       inherits=FavItem,
       polymorphic_identity=FavItem.TYPE_NEWS,
       properties={
            'item': relation(News),
       })

mapper(FavComment, favitems,
       inherits=FavItem,
       polymorphic_identity=FavItem.TYPE_COMMENT,
       properties={
            'item': relation(Comment),
       })

mapper(User, users,
       properties={
            '_favitems': relation(FavItem),
       })

engine = create_engine('sqlite://')
metadata.create_all(engine)
session = sessionmaker(engine)()

user = User()
news1 = News()
news2 = News()
comment1 = Comment()
comment2 = Comment()
user.favorites = [news1, news2, comment1, comment2]
session.add(user)
session.commit()
user_id = user.id

session.expunge_all()
user = session.query(User).get(user_id)
print user.favorites
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...