From 1c4fb14e4f8fcb8f3c553d2b07ee5374623ec8ea Mon Sep 17 00:00:00 2001 From: default Date: Sun, 10 Nov 2024 09:42:57 +0000 Subject: [PATCH 1/6] soft-deletes --- .../versions/edcc1a448ffb_soft_deletes.py | 31 +++++++++++++++++++ rating_api/models/db.py | 9 +++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/edcc1a448ffb_soft_deletes.py diff --git a/migrations/versions/edcc1a448ffb_soft_deletes.py b/migrations/versions/edcc1a448ffb_soft_deletes.py new file mode 100644 index 0000000..04666f6 --- /dev/null +++ b/migrations/versions/edcc1a448ffb_soft_deletes.py @@ -0,0 +1,31 @@ +"""soft-deletes + +Revision ID: edcc1a448ffb +Revises: 7388a2c219d2 +Create Date: 2024-11-10 09:23:32.307828 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'edcc1a448ffb' +down_revision = '7388a2c219d2' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('comment', sa.Column('is_deleted', sa.Boolean(), nullable=False, server_default=sa.false())) + op.add_column('lecturer', sa.Column('is_deleted', sa.Boolean(), nullable=False, server_default=sa.false())) + op.add_column( + 'lecturer_user_comment', sa.Column('is_deleted', sa.Boolean(), nullable=False, server_default=sa.false()) + ) + + +def downgrade(): + op.drop_column('lecturer_user_comment', 'is_deleted') + op.drop_column('lecturer', 'is_deleted') + op.drop_column('comment', 'is_deleted') diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 747a983..3753c6e 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -34,6 +34,7 @@ class Lecturer(BaseDbModel): avatar_link: Mapped[str] = mapped_column(String, nullable=True) timetable_id: Mapped[int] = mapped_column(Integer, unique=True, nullable=False) comments: Mapped[list[Comment]] = relationship("Comment", back_populates="lecturer") + is_deleted: Mapped[bool] = False @hybrid_method def search_by_name(self, query: str) -> bool: @@ -65,8 +66,13 @@ class Comment(BaseDbModel): mark_freebie: Mapped[int] = mapped_column(Integer, nullable=False) mark_clarity: Mapped[int] = mapped_column(Integer, nullable=False) lecturer_id: Mapped[int] = mapped_column(Integer, ForeignKey("lecturer.id")) - lecturer: Mapped[Lecturer] = relationship("Lecturer", back_populates="comments") + lecturer: Mapped[Lecturer] = relationship( + "Lecturer", + back_populates="comments", + primaryjoin="and_(Comment.lecturer_id == Lecturer.id, not_(Lecturer.is_deleted))", + ) review_status: Mapped[ReviewStatus] = mapped_column(DbEnum(ReviewStatus, native_enum=False), nullable=False) + is_deleted: Mapped[bool] = False class LecturerUserComment(BaseDbModel): @@ -75,3 +81,4 @@ class LecturerUserComment(BaseDbModel): lecturer_id: Mapped[int] = mapped_column(Integer, ForeignKey("lecturer.id")) create_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, nullable=False) update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, nullable=False) + is_deleted: Mapped[bool] = False From 9845e353c53cc6fd02faeaabcfa0f8baa877f834 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 10 Nov 2024 09:57:49 +0000 Subject: [PATCH 2/6] relationship fix --- rating_api/models/db.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 3753c6e..e35ae5b 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -7,7 +7,7 @@ from sqlalchemy import UUID, DateTime from sqlalchemy import Enum as DbEnum -from sqlalchemy import ForeignKey, Integer, String, and_, func, or_, true +from sqlalchemy import ForeignKey, Integer, String, Boolean, and_, func, or_, true from sqlalchemy.ext.hybrid import hybrid_method from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -33,8 +33,8 @@ class Lecturer(BaseDbModel): middle_name: Mapped[str] = mapped_column(String, nullable=False) avatar_link: Mapped[str] = mapped_column(String, nullable=True) timetable_id: Mapped[int] = mapped_column(Integer, unique=True, nullable=False) - comments: Mapped[list[Comment]] = relationship("Comment", back_populates="lecturer") - is_deleted: Mapped[bool] = False + comments: Mapped[list[Comment]] = relationship("Comment", back_populates="lecturer", cascade="all, delete-orphan") + is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) @hybrid_method def search_by_name(self, query: str) -> bool: @@ -72,7 +72,7 @@ class Comment(BaseDbModel): primaryjoin="and_(Comment.lecturer_id == Lecturer.id, not_(Lecturer.is_deleted))", ) review_status: Mapped[ReviewStatus] = mapped_column(DbEnum(ReviewStatus, native_enum=False), nullable=False) - is_deleted: Mapped[bool] = False + is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) class LecturerUserComment(BaseDbModel): @@ -81,4 +81,4 @@ class LecturerUserComment(BaseDbModel): lecturer_id: Mapped[int] = mapped_column(Integer, ForeignKey("lecturer.id")) create_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, nullable=False) update_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow, nullable=False) - is_deleted: Mapped[bool] = False + is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) From c46f8f0ff5db4b9d1281d17dbbd2e9c6d7464ecb Mon Sep 17 00:00:00 2001 From: default Date: Sun, 10 Nov 2024 10:45:56 +0000 Subject: [PATCH 3/6] tests fix --- tests/test_routes/test_comment.py | 3 ++- tests/test_routes/test_lecturer.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index e933026..80b130b 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -97,7 +97,6 @@ def test_get_comment(client, dbsession): assert response.status_code == status.HTTP_404_NOT_FOUND comment = Comment.query(session=dbsession).filter(Comment.uuid == response_comment.json()["uuid"]).one_or_none() assert comment is not None - dbsession.delete(comment) dbsession.delete(lecturer) dbsession.commit() @@ -122,6 +121,8 @@ def test_delete_comment(client, dbsession): dbsession.commit() response = client.delete(f'{url}/{comment.uuid}') assert response.status_code == status.HTTP_200_OK + response = client.get(f'{url}/{comment.uuid}') + assert response.status_code == status.HTTP_404_NOT_FOUND random_uuid = uuid.uuid4() response = client.delete(f'{url}/{random_uuid}') assert response.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index e98858a..9eb894e 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -200,3 +200,6 @@ def test_delete_lecturer(client, dbsession): assert response.status_code == status.HTTP_200_OK response = client.delete(f"{url}/{lecturer.id}") assert response.status_code == status.HTTP_404_NOT_FOUND + lecturer.is_deleted = True + dbsession.delete(lecturer) + dbsession.commit() From d11447cfe7f00e0e8bc10e85858d213cb194484f Mon Sep 17 00:00:00 2001 From: default Date: Sun, 10 Nov 2024 10:47:23 +0000 Subject: [PATCH 4/6] linting --- rating_api/models/db.py | 4 ++-- rating_api/schemas/models.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index e35ae5b..3d262bf 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -5,9 +5,9 @@ import uuid from enum import Enum -from sqlalchemy import UUID, DateTime +from sqlalchemy import UUID, Boolean, DateTime from sqlalchemy import Enum as DbEnum -from sqlalchemy import ForeignKey, Integer, String, Boolean, and_, func, or_, true +from sqlalchemy import ForeignKey, Integer, String, and_, func, or_, true from sqlalchemy.ext.hybrid import hybrid_method from sqlalchemy.orm import Mapped, mapped_column, relationship diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index 3a6f1b0..de510a8 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -20,7 +20,6 @@ class CommentGet(Base): lecturer_id: int - class CommentPost(Base): subject: str text: str From 957d038b5c8492c8101b66ca74237092e9f0441b Mon Sep 17 00:00:00 2001 From: default Date: Sun, 10 Nov 2024 10:49:45 +0000 Subject: [PATCH 5/6] lint --- migrations/versions/edcc1a448ffb_soft_deletes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/versions/edcc1a448ffb_soft_deletes.py b/migrations/versions/edcc1a448ffb_soft_deletes.py index 04666f6..627d5bd 100644 --- a/migrations/versions/edcc1a448ffb_soft_deletes.py +++ b/migrations/versions/edcc1a448ffb_soft_deletes.py @@ -6,8 +6,8 @@ """ -from alembic import op import sqlalchemy as sa +from alembic import op # revision identifiers, used by Alembic. From dd2ed4f8f6df10117fdb6d12bc036fabd3a3c137 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 10 Nov 2024 12:14:43 +0000 Subject: [PATCH 6/6] remove cascade --- rating_api/models/db.py | 2 +- tests/test_routes/test_comment.py | 7 +++++-- tests/test_routes/test_lecturer.py | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rating_api/models/db.py b/rating_api/models/db.py index 3d262bf..a25a820 100644 --- a/rating_api/models/db.py +++ b/rating_api/models/db.py @@ -33,7 +33,7 @@ class Lecturer(BaseDbModel): middle_name: Mapped[str] = mapped_column(String, nullable=False) avatar_link: Mapped[str] = mapped_column(String, nullable=True) timetable_id: Mapped[int] = mapped_column(Integer, unique=True, nullable=False) - comments: Mapped[list[Comment]] = relationship("Comment", back_populates="lecturer", cascade="all, delete-orphan") + comments: Mapped[list[Comment]] = relationship("Comment", back_populates="lecturer") is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) @hybrid_method diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index 80b130b..d31c912 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -97,6 +97,7 @@ def test_get_comment(client, dbsession): assert response.status_code == status.HTTP_404_NOT_FOUND comment = Comment.query(session=dbsession).filter(Comment.uuid == response_comment.json()["uuid"]).one_or_none() assert comment is not None + dbsession.delete(comment) dbsession.delete(lecturer) dbsession.commit() @@ -126,7 +127,9 @@ def test_delete_comment(client, dbsession): random_uuid = uuid.uuid4() response = client.delete(f'{url}/{random_uuid}') assert response.status_code == status.HTTP_404_NOT_FOUND - comment = Comment.query(session=dbsession).filter(Comment.uuid == comment.uuid).one_or_none() - assert comment is None + comment1 = Comment.query(session=dbsession).filter(Comment.uuid == comment.uuid).one_or_none() + assert comment1 is None + comment.is_deleted = True + dbsession.delete(comment) dbsession.delete(lecturer) dbsession.commit() diff --git a/tests/test_routes/test_lecturer.py b/tests/test_routes/test_lecturer.py index 9eb894e..39027d0 100644 --- a/tests/test_routes/test_lecturer.py +++ b/tests/test_routes/test_lecturer.py @@ -201,5 +201,7 @@ def test_delete_lecturer(client, dbsession): response = client.delete(f"{url}/{lecturer.id}") assert response.status_code == status.HTTP_404_NOT_FOUND lecturer.is_deleted = True + comment.is_deleted = True + dbsession.delete(comment) dbsession.delete(lecturer) dbsession.commit()