Skip to content

Instantly share code, notes, and snippets.

@mazz
Created June 7, 2017 20:33
Show Gist options
  • Save mazz/7d63e521316859f4ae852e5cea5d84eb to your computer and use it in GitHub Desktop.
Save mazz/7d63e521316859f4ae852e5cea5d84eb to your computer and use it in GitHub Desktop.
Migration fails when changing to single table inheritance
class MediaBase(Base):
#: The table in the database
__tablename__ = "mediabase"
#: Database primary key for the row (running counter)
id = Column(Integer, autoincrement=True, primary_key=True)
# table inheritance
media_type = Column(String(32), nullable=False)
#: Publicly exposed non-guessable
uuid = Column(UUID(as_uuid=True), default=uuid4)
localizedname = Column(Unicode(128), default=None)
#: url
url = Column(Unicode(384), default=None)
# full iso language-locale identifier i.e. zh-Hans-US
language_id = Column(String(16), default=None)
# name of the person presenting the material
presenter_name = Column(Unicode(64), default=None)
source_material = Column(Unicode(128), default=None)
# table inheritance
__mapper_args__ = {'polymorphic_on': media_type}
def __repr__(self):
"""Shell and debugger presentation."""
return '{} ({}) {} <{}>'.format(self.localizedname, self.language_id, str(self.uuid), self.url)
def __str__(self):
"""Python default and admin UI string presentation."""
return '{} ({}) presenter: {} source: {} <{}>'.format(self.localizedname, self.language_id, self.presenter_name, self.source_material, self.url)
class MediaChapter(MediaBase):
#: The table in the database
__tablename__ = "mediachapter"
__mapper_args__ = {'polymorphic_identity': 'chapter'}
id = Column(Integer, ForeignKey('mediabase.id'), primary_key=True)
#: Which chapter this media is part of
chapter_id = Column(Integer, ForeignKey('chapter.id'))
chapter = relationship("Chapter", back_populates="mediachapter")
@mazz
Copy link
Author

mazz commented Jun 7, 2017


"""MediaBase-MediaChapter subclassing

Revision ID: a00980918d75
Revises: e74ba4203098
Create Date: 2017-06-07 16:10:29.807437

"""

# revision identifiers, used by Alembic.
revision = 'a00980918d75'
down_revision = 'e74ba4203098'
branch_labels = None
depends_on = None

import datetime
import websauna.system.model.columns
from sqlalchemy.types import Text  # Needed from proper creation of JSON fields as Alembic inserts astext_type=Text() row

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('mediabase',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('media_type', sa.String(length=32), nullable=False),
    sa.Column('uuid', postgresql.UUID(as_uuid=True), nullable=True),
    sa.Column('localizedname', sa.Unicode(length=128), nullable=True),
    sa.Column('url', sa.Unicode(length=384), nullable=True),
    sa.Column('language_id', sa.String(length=16), nullable=True),
    sa.Column('presenter_name', sa.Unicode(length=64), nullable=True),
    sa.Column('source_material', sa.Unicode(length=128), nullable=True),
    sa.PrimaryKeyConstraint('id', name=op.f('pk_mediabase'))
    )
    op.alter_column('group', 'created_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.alter_column('group', 'updated_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.create_foreign_key(op.f('fk_mediachapter_id_mediabase'), 'mediachapter', 'mediabase', ['id'], ['id'])
    op.drop_column('mediachapter', 'source_material')
    op.drop_column('mediachapter', 'presenter_name')
    op.drop_column('mediachapter', 'localizedname')
    op.drop_column('mediachapter', 'url')
    op.drop_column('mediachapter', 'uuid')
    op.drop_column('mediachapter', 'language_id')
    op.alter_column('user_activation', 'created_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.alter_column('user_activation', 'expires_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=False)
    op.alter_column('user_activation', 'updated_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.alter_column('users', 'activated_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.alter_column('users', 'created_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.alter_column('users', 'last_auth_sensitive_operation_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.alter_column('users', 'last_login_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    op.alter_column('users', 'updated_at',
               existing_type=postgresql.TIMESTAMP(timezone=True),
               type_=websauna.system.model.columns.UTCDateTime(),
               existing_nullable=True)
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.alter_column('users', 'updated_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.alter_column('users', 'last_login_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.alter_column('users', 'last_auth_sensitive_operation_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.alter_column('users', 'created_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.alter_column('users', 'activated_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.alter_column('user_activation', 'updated_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.alter_column('user_activation', 'expires_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=False)
    op.alter_column('user_activation', 'created_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.add_column('mediachapter', sa.Column('language_id', sa.VARCHAR(length=16), autoincrement=False, nullable=True))
    op.add_column('mediachapter', sa.Column('uuid', postgresql.UUID(), autoincrement=False, nullable=True))
    op.add_column('mediachapter', sa.Column('url', sa.VARCHAR(length=384), autoincrement=False, nullable=True))
    op.add_column('mediachapter', sa.Column('localizedname', sa.VARCHAR(length=128), autoincrement=False, nullable=True))
    op.add_column('mediachapter', sa.Column('presenter_name', sa.VARCHAR(length=64), autoincrement=False, nullable=True))
    op.add_column('mediachapter', sa.Column('source_material', sa.VARCHAR(length=128), autoincrement=False, nullable=True))
    op.drop_constraint(op.f('fk_mediachapter_id_mediabase'), 'mediachapter', type_='foreignkey')
    op.alter_column('group', 'updated_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.alter_column('group', 'created_at',
               existing_type=websauna.system.model.columns.UTCDateTime(),
               type_=postgresql.TIMESTAMP(timezone=True),
               existing_nullable=True)
    op.drop_table('mediabase')
    # ### end Alembic commands ###

@mazz
Copy link
Author

mazz commented Jun 7, 2017

class Chapter(Base):

    #: The table in the database
    __tablename__ = "chapter"

    #: Database primary key for the row (running counter)
    id = Column(Integer, autoincrement=True, primary_key=True)
    absolute_id = Column(Integer)

    #: Publicly exposed non-guessable id
    uuid = Column(UUID(as_uuid=True), default=uuid4)
    basename = Column(Unicode(64), default=None)

    book_id = Column(Integer, ForeignKey('book.id'))
    book = relationship("Book", back_populates="chapters")

    #: Relationship mapping between mediachapter and chapter.
    #: Each mediachapter can have only one chapter.
    #: Deleting chapter deletes its mediachapter.
    mediachapter = relationship("MediaChapter",
                           back_populates="chapter",
                           lazy="dynamic",
                           cascade="all, delete-orphan",
                           single_parent=True)

    def __repr__(self):
        """Shell and debugger presentation."""
        return "{} {}".format(self.book.basename, self.id)

    def __str__(self):
        """Python default and admin UI string presentation."""
        if hasattr(self.book, 'basename'):
            return "{} {}".format(self.book.basename, self.absolute_id)
        else:
            return "{} {}".format('(not set)', self.absolute_id)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment