"""guides plugin baseline: guides, guiderevisions, guideimages. A new plugin, so this builds its tables rather than stamping an anchor over core-owned ones (contrast the cutover plugins, whose 0001 is a no-op). Idempotent: each table is created only if it is not already there, so a database where an earlier run half-succeeded can be upgraded again without hand-editing alembic_version_guides. """ from alembic import op import sqlalchemy as sa from sqlalchemy import inspect revision = 'guides0001baseline' down_revision = None branch_labels = None depends_on = None def _tables(): return set(inspect(op.get_bind()).get_table_names()) def upgrade(): existing = _tables() if 'guides' not in existing: op.create_table( 'guides', sa.Column('guideid', sa.Integer(), nullable=False), sa.Column('title', sa.String(length=300), nullable=False), # 191 because this is UNIQUE-indexed and MySQL caps an index key at # 767 bytes; utf8mb4 is 4 bytes per character. sa.Column('slug', sa.String(length=191), nullable=False), sa.Column('summary', sa.String(length=1000)), sa.Column('tags', sa.String(length=500)), sa.Column('status', sa.String(length=20), nullable=False, server_default='draft'), sa.Column('blocksjson', sa.dialects.mysql.MEDIUMTEXT() if op.get_bind().dialect.name == 'mysql' else sa.Text(), nullable=False), sa.Column('publishedrevisionid', sa.Integer()), sa.Column('createdby', sa.Integer()), sa.Column('updatedby', sa.Integer()), sa.Column('publishedat', sa.DateTime()), # BaseModel contributes these to every model in the app; a plugin # migration has to spell them out or the table is missing columns # the ORM selects on every query. sa.Column('createddate', sa.DateTime(), nullable=False), sa.Column('modifieddate', sa.DateTime(), nullable=False), sa.Column('isactive', sa.Boolean(), nullable=False), sa.Column('revision', sa.Integer(), nullable=False, server_default='1'), sa.PrimaryKeyConstraint('guideid'), sa.UniqueConstraint('slug', name='uq_guides_slug'), ) op.create_index('idx_guides_status', 'guides', ['status']) if 'guiderevisions' not in existing: op.create_table( 'guiderevisions', sa.Column('guiderevisionid', sa.Integer(), nullable=False), sa.Column('guideid', sa.Integer(), nullable=False), sa.Column('revision', sa.Integer(), nullable=False), sa.Column('title', sa.String(length=300), nullable=False), sa.Column('summary', sa.String(length=1000)), sa.Column('tags', sa.String(length=500)), sa.Column('blocksjson', sa.dialects.mysql.MEDIUMTEXT() if op.get_bind().dialect.name == 'mysql' else sa.Text(), nullable=False), sa.Column('contenthash', sa.String(length=64), nullable=False), sa.Column('note', sa.String(length=500)), sa.Column('createdby', sa.Integer()), # BaseModel contributes these to every model in the app; a plugin # migration has to spell them out or the table is missing columns # the ORM selects on every query. sa.Column('createddate', sa.DateTime(), nullable=False), sa.Column('modifieddate', sa.DateTime(), nullable=False), sa.Column('isactive', sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint('guiderevisionid'), sa.ForeignKeyConstraint(['guideid'], ['guides.guideid'], ondelete='CASCADE'), ) op.create_index('idx_guiderevisions_guide', 'guiderevisions', ['guideid']) op.create_index('idx_guiderevisions_hash', 'guiderevisions', ['contenthash']) if 'guideimages' not in existing: op.create_table( 'guideimages', sa.Column('guideimageid', sa.Integer(), nullable=False), sa.Column('guideid', sa.Integer()), sa.Column('filename', sa.String(length=255)), sa.Column('mimetype', sa.String(length=100), nullable=False), sa.Column('bytesize', sa.Integer(), nullable=False), sa.Column('data', sa.dialects.mysql.MEDIUMBLOB() if op.get_bind().dialect.name == 'mysql' else sa.LargeBinary(), nullable=False), sa.Column('uploadedby', sa.Integer()), # BaseModel contributes these to every model in the app; a plugin # migration has to spell them out or the table is missing columns # the ORM selects on every query. sa.Column('createddate', sa.DateTime(), nullable=False), sa.Column('modifieddate', sa.DateTime(), nullable=False), sa.Column('isactive', sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint('guideimageid'), sa.ForeignKeyConstraint(['guideid'], ['guides.guideid'], ondelete='CASCADE'), ) op.create_index('idx_guideimages_guide', 'guideimages', ['guideid']) def downgrade(): for table in ('guideimages', 'guiderevisions', 'guides'): op.drop_table(table)