ArtemBalandin81 / tech_accidents

Приложение для учета и фиксации простоев в бизнес-процессах УК ПИФ, АИФ, НПФ
MIT License
0 stars 0 forks source link

в модель простоев добавить поле image, document, priority #71

Closed ArtemBalandin81 closed 3 months ago

ArtemBalandin81 commented 6 months ago

Why?

Аналогично #57 сделать возможность прикрепления документов для модели простоев

How To Do?

  1. Модель файлов class FileAttached(Base) уже создана, в нее необходимо добавить связь с простоями suspensions: Mapped[list["Suspension"]] = relationship(secondary="suspensions_files", back_populates="files")
class FileAttached(Base):
    """Модель прикрепленных файлов: .png, .jpg., .pdf, .doc, etc"""

    __tablename__ = "files"

    name: Mapped[str] = mapped_column(String(256))
    file: Mapped[BLOB] = mapped_column(BLOB)
    tasks: Mapped[list["Task"]] = relationship(secondary="tasks_files", back_populates="files")
    suspensions: Mapped[list["Suspension"]] = relationship(secondary="suspensions_files", back_populates="files")

    def __repr__(self):
        return f"<FileAttached {self.name}>"
  1. Создать связанную таблицу файлы-простои:

    class SuspensionFiles(Base):
    """Модель отношений простои-прикрепленные файлы."""
    
    __tablename__ = "suspensions_files"
    
    id = None  # todo why is None?
    suspension_id: Mapped[int] = mapped_column(ForeignKey("suspensions.id"), primary_key=True)
    file_id: Mapped[int] = mapped_column(ForeignKey("files.id"), primary_key=True)
    
    def __repr__(self):
        return f"<Suspension {self.suspension_id} - Files {self.file_id}>"
  2. Внести изменения в модель простоев: files: Mapped[list["FileAttached"]] = relationship(secondary="suspensions_files", back_populates="suspensions")

  3. Подготовить автомиграцию: alembic revision --autogenerate -m "Add relashionship model SuspensionFiles"

  4. Правим автомиграцию, т.к. нельзя изменять тип данных datetime в 'created_at' и 'updated_at': - удаляем в функциях upgrade() и downgrade()

  5. применяем миграции: alembic upgrade head

  6. Алембик и sqllite совсем не дружат с изменением столбцов (типы, имена), поэтому простая команда alter не сработает:

    • Крайне аккуратно и по одной проводить миграции, связанные с изменением столбцов: Чтобы изменить столбцы в sqolite - создаем новый, копируем в него данные, удаляем старый, переименовываем - лучше это делать Running “Batch” Migrations for SQLite: https://alembic.sqlalchemy.org/en/latest/batch.html
ArtemBalandin81 commented 4 months ago

Пошаговая инструкция:

  1. Делаем резервную копию БД sqolite.db, и ранее успешных миграций (versions и pycache)

  2. alembic history

  3. alembic current

  4. alembic check

  5. alembic revision --autogenerate -m "Description migration" alembic revision -m 'rename username to new_username'

  6. Правка файла с миграциями вручную на предмет вычищения ошибок

  7. alembic upgrade head

  8. БД sqllite очень не любит изменения названия и типов данных столбцов!!!

  9. Чтобы изменить столбцы в sqolite - создаем новый, копируем в него данные, удаляем старый, переименовываем - лучше это делать Running “Batch” Migrations for SQLite and https://alembic.sqlalchemy.org/en/latest/batch.html

  10. Переименование:

with op.batch_alter_table("tasks") as batch_op:
    batch_op.alter_column('executor', new_column_name='executor_id')
  1. в sqolite миграции применяются построчно, так что даже миграции с ошибками приводят к изменениям в БД в той части, где они прошли.
  2. Чтобы очистить и вернуть статус до изменений - нужно скопировать прежние файлы с миграциями и вернуть pycache, а также заменить БД sqolite.db, т.к. она изменяются даже при неуспешных миграциях!!!
def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('suspensions_files',
    sa.Column('suspension_id', sa.Integer(), nullable=False),
    sa.Column('file_id', sa.Integer(), nullable=False),
    # sa.Column('created_at', sa.TIMESTAMP(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
    # sa.Column('updated_at', sa.TIMESTAMP(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
    sa.ForeignKeyConstraint(['file_id'], ['files.id'], ),
    sa.ForeignKeyConstraint(['suspension_id'], ['suspensions.id'], ),
    sa.PrimaryKeyConstraint('suspension_id', 'file_id')
    )

    with op.batch_alter_table("suspensions") as batch_op:
                # batch_op.add_column(
        #     sa.Column('suspension_start', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True)
        # )  # fill in the column with one data of datatime migration
        # batch_op.drop_column('datetime_start')
        # batch_op.add_column(
        #     sa.Column('suspension_finish', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True)
        # )
        # batch_op.drop_column('datetime_finish')
        batch_op.alter_column('datetime_start', new_column_name='suspension_start')  # somehow it seems to be working
        batch_op.alter_column('datetime_finish', new_column_name='suspension_finish')  # somehow it seems to be working

    with op.batch_alter_table("tasks") as batch_op:
        # batch_op.add_column(sa.Column('executor_id', sa.Integer(), nullable=False))
        # batch_op.drop_constraint(None, type_='foreignkey')
        # batch_op.create_foreign_key('tasks', 'user', ['executor_id'], ['id'])  # error: NOT NULL constraint failed:
        # batch_op.drop_column('executor')
        batch_op.alter_column('executor', new_column_name='executor_id')  # somehow but it seems to be working

def downgrade() -> None:
    with op.batch_alter_table("tasks") as batch_op:
        batch_op.alter_column('executor_id', new_column_name='executor')  # somehow but it seems to be working

    with op.batch_alter_table("suspensions") as batch_op:
        # batch_op.add_column(sa.Column('datetime_start', sa.Date(), nullable=True))  # fill in the column with one data
        # batch_op.drop_column('suspension_start')
        # batch_op.add_column(sa.Column('datetime_finish', sa.Date(), nullable=True))
        # batch_op.drop_column('suspension_finish')
        batch_op.alter_column('suspension_finish', new_column_name='datetime_finish')  # somehow it seems to be working
        batch_op.alter_column('suspension_start', new_column_name='datetime_start')  # somehow it seems to be working

    op.drop_table('suspensions_files')
ArtemBalandin81 commented 4 months ago

git commit -m 'feature/add_suspension_upload_manage_files_made_migrations_fix_models'

class SuspensionFiles(Base):
    """Модель отношений простои-прикрепленные файлы."""

    __tablename__ = "suspensions_files"

    id = None  # means identity through external keys not unique ids
    suspension_id: Mapped[int] = mapped_column(ForeignKey("suspensions.id"), primary_key=True)
    file_id: Mapped[int] = mapped_column(ForeignKey("files.id"), primary_key=True)

    def __repr__(self):
        return f"<Suspension {self.suspension_id} - Files {self.file_id}>"