"""Record who handed a drive back, not just when. usbcheckouts held the checkout identity (sso, checkoutname) and, for the return, only checkintime. Returning a drive for a colleague is a supported kiosk flow - usb_kiosk_allow_return_for_others gates it and the kiosk asks for confirmation - but the row it wrote attributed the check-in to the HOLDER. The person who actually walked the drive back was recorded nowhere, so the custody history and the audit report both named somebody who was not there. Two columns rather than one: the badge is what the scanner emits and what joins to a directory, and the name is what a report has to print when the directory no longer resolves that badge. Nullable, and not backfilled. A row closed before this column existed is one where the returner was not recorded; copying the holder's badge into it would manufacture evidence that the holder returned it, which is the exact claim this migration exists to stop making. Idempotent, so a re-run on an already-migrated database is a no-op. Revision ID: usb0004checkinidentity Revises: usb0003scanfields """ from alembic import op import sqlalchemy as sa revision = 'usb0004checkinidentity' down_revision = 'usb0003scanfields' branch_labels = None depends_on = None NEW_COLUMNS = ( ('checkinsso', sa.String(length=20)), ('checkinname', sa.String(length=100)), ) def upgrade(): bind = op.get_bind() insp = sa.inspect(bind) if 'usbcheckouts' not in insp.get_table_names(): return cols = {c['name'] for c in insp.get_columns('usbcheckouts')} for name, coltype in NEW_COLUMNS: if name not in cols: op.add_column('usbcheckouts', sa.Column(name, coltype, nullable=True)) def downgrade(): bind = op.get_bind() insp = sa.inspect(bind) if 'usbcheckouts' not in insp.get_table_names(): return cols = {c['name'] for c in insp.get_columns('usbcheckouts')} for name, _ in NEW_COLUMNS: if name in cols: op.drop_column('usbcheckouts', name)