SQLAlchemy Полиморфные отношения с конкретным наследованием - PullRequest
5 голосов
/ 10 февраля 2012

Я использую конкретную таблицу наследования с SQLAlchemy.В классе модели с декларативным стилем я успешно настроил его.

Мой код выглядит так:

class Entry(AbstractConcreteBase, db.Model):
    """Base Class of Entry."""

    id = db.Column(db.Integer, primary_key=True, nullable=False)
    created = db.Column(db.DateTime, nullable=False)
    post_id = declared_attr(lambda c: db.Column(db.ForeignKey("post.id")))
    post = declared_attr(lambda c: db.relationship("Post", lazy="joined"))

    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    @declared_attr
    def __mapper_args__(cls):
        # configurate subclasses about concrete table inheritance
        return {'polymorphic_identity': cls.__name__,
                'concrete': True} if cls.__name__ != "Entry" else {}

class TextEntry(Entry):
    """Text and Article Entry."""

    text = db.deferred(db.Column(db.Text, nullable=False))

class PhotoEntry(Entry):
    """Photo Entry."""

    path = db.deferred(db.Column(db.String(256), nullable=False))

Он отлично работает при тестировании в оболочке:

>>> from models.entry import Entry
>>>
>>> Entry.query.all()
[<PhotoEntry 'Title' created by tonyseek>,
 <PhotoEntry 'TITLE 2' created by tonyseek>,
 <PhotoEntry 'Title 3' created by tonyseek>,
 <PhotoEntry 'Title 4' created by tonyseek>,
 <TextEntry 'Title' created by tonyseek>]

Тогда у меня возникают проблемы при установлении отношений в других моделях.Каждая запись имеет внешний ключ post_id для присоединения к модели Post, но я не смог определить обратную ссылку в Post.Это не может работать:

class Post(db.Model):
    """An Post."""

    id = db.Column(db.Integer, primary_key=True, nullable=False)
    description = db.Column(db.Unicode(140), nullable=False)
    entries = db.relationship(Entry, lazy="dynamic")

Возникло исключение и сказано:

InvalidRequestError: Не удалось инициализировать один или несколько сопоставителей - не удается продолжить инициализацию других сопоставителей,Первоначальное исключение: класс models.entry.Entry не сопоставлен.

Очевидно, Entry является абстрактным классом, который не может быть сопоставлен с реально существующей таблицей.У документа на официальном сайте есть пример, но его базовый класс не является абстрактным.Теперь, как мне установить полиморфные отношения с абстрактной моделью?

1 Ответ

7 голосов
/ 11 февраля 2012

Я нашел причину проблемы и ее решение.

Согласно документу официального сайта sqlalchemy, абстрактный класс может быть отображенным классом, поскольку функция polymorphic_union может создавать виртуальную таблицу.

Я использую модель склонительного стиля, а не строю маппер вручную, поэтому виртуальная таблица pjoin не должна создаваться вручную. Базовый класс AbstractConcreteBase имеет метод __delcare_last__, который создаст pjoin с функцией polymorphic_union, но он будет вызываться при запуске события after_configured.

Связь с Entry в Post будет создана после создания класса Post, в это время событие after_configured не было инициировано, поэтому функция __delcare_last__ не создала виртуальную таблицу pjoin и сопоставил его с Entry. Таким образом, исключение «Класс models.entry.Entry не сопоставлен». будет поднят.

Теперь я произвожу рефакторинг модели Post, пусть она создаст связь с Entry в функции __delcare_last__, тогда это будет успех из-за инициированного события и сопоставленного Entry.

Мой новый реализованный класс выглядит так:

class Post(db.Model):
    """An Post."""

    id = db.Column(db.Integer, primary_key=True, nullable=False)
    description = db.Column(db.Unicode(140), nullable=False)

    @classmethod
    def __declare_last__(cls):
        cls.entries = db.relationship(Entry, viewonly=True)

    def attach_entries(self, entries):
        """Attach Entries To This Post.

        Example:
            >>> post = Post("An Interesting News", "Wow !!!")
            >>> text_entry = TextEntry(*t_args)
            >>> photo_entry = PhotoEntry(*p_args)
            >>> post.attach_entries([text_entry, photo_entry])
            >>> len(post.entries)
            2
            >>> db.session.commit()
            >>>
        """
        for entry in entries:
            self.entries.append(entry)
            entry.post = self
            db.session.add(entry)
...