From 4ce37eadf822ef3d368029230ee501dbc15bdfe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E6=98=95=E7=9D=BF?= <22371298@buaa.edu.cn> Date: Wed, 21 May 2025 23:11:28 +0800 Subject: [PATCH] =?UTF-8?q?[feat]:=20=E5=AE=9E=E7=8E=B0=E5=9B=9E=E6=94=B6?= =?UTF-8?q?=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...12\347\272\247\344\277\241\346\201\257.py" | 46 +++++++ ...36\346\224\266\347\253\231\350\241\250.py" | 40 ++++++ app/api/v1/endpoints/article.py | 30 ++++- app/api/v1/endpoints/note.py | 5 +- app/curd/article.py | 127 +++++++++++++++--- app/curd/note.py | 55 ++++++-- app/models/model.py | 20 ++- 7 files changed, 283 insertions(+), 40 deletions(-) create mode 100644 "alembic/versions/004c4aa2b3f3_\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250\345\242\236\345\212\240\344\270\212\347\272\247\344\277\241\346\201\257.py" create mode 100644 "alembic/versions/d6d6ae6d9680_\345\242\236\345\212\240\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250.py" diff --git "a/alembic/versions/004c4aa2b3f3_\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250\345\242\236\345\212\240\344\270\212\347\272\247\344\277\241\346\201\257.py" "b/alembic/versions/004c4aa2b3f3_\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250\345\242\236\345\212\240\344\270\212\347\272\247\344\277\241\346\201\257.py" new file mode 100644 index 0000000..d7742e9 --- /dev/null +++ "b/alembic/versions/004c4aa2b3f3_\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250\345\242\236\345\212\240\344\270\212\347\272\247\344\277\241\346\201\257.py" @@ -0,0 +1,46 @@ +"""个人回收站表增加上级信息 + +Revision ID: 004c4aa2b3f3 +Revises: d6d6ae6d9680 +Create Date: 2025-05-21 21:29:14.873544 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '004c4aa2b3f3' +down_revision: Union[str, None] = 'd6d6ae6d9680' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('articles_ibfk_1', 'articles', type_='foreignkey') + op.create_foreign_key(None, 'articles', 'folders', ['folder_id'], ['id'], ondelete='CASCADE') + op.drop_constraint('notes_ibfk_1', 'notes', type_='foreignkey') + op.create_foreign_key(None, 'notes', 'articles', ['article_id'], ['id'], ondelete='CASCADE') + op.add_column('self_recycle_bin', sa.Column('article_id', sa.Integer(), nullable=True)) + op.add_column('self_recycle_bin', sa.Column('folder_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'self_recycle_bin', 'folders', ['folder_id'], ['id'], ondelete='CASCADE') + op.create_foreign_key(None, 'self_recycle_bin', 'articles', ['article_id'], ['id'], ondelete='CASCADE') + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'self_recycle_bin', type_='foreignkey') + op.drop_constraint(None, 'self_recycle_bin', type_='foreignkey') + op.drop_column('self_recycle_bin', 'folder_id') + op.drop_column('self_recycle_bin', 'article_id') + op.drop_constraint(None, 'notes', type_='foreignkey') + op.create_foreign_key('notes_ibfk_1', 'notes', 'articles', ['article_id'], ['id']) + op.drop_constraint(None, 'articles', type_='foreignkey') + op.create_foreign_key('articles_ibfk_1', 'articles', 'folders', ['folder_id'], ['id']) + # ### end Alembic commands ### diff --git "a/alembic/versions/d6d6ae6d9680_\345\242\236\345\212\240\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250.py" "b/alembic/versions/d6d6ae6d9680_\345\242\236\345\212\240\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250.py" new file mode 100644 index 0000000..7043f11 --- /dev/null +++ "b/alembic/versions/d6d6ae6d9680_\345\242\236\345\212\240\344\270\252\344\272\272\345\233\236\346\224\266\347\253\231\350\241\250.py" @@ -0,0 +1,40 @@ +"""增加个人回收站表 + +Revision ID: d6d6ae6d9680 +Revises: 7af566a6091b +Create Date: 2025-05-14 11:25:12.719964 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'd6d6ae6d9680' +down_revision: Union[str, None] = '7af566a6091b' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('self_recycle_bin', + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('type', sa.Integer(), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.Text(), nullable=False), + sa.Column('create_time', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('type', 'id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('self_recycle_bin') + # ### end Alembic commands ### diff --git a/app/api/v1/endpoints/article.py b/app/api/v1/endpoints/article.py index a6e92a6..9159aba 100644 --- a/app/api/v1/endpoints/article.py +++ b/app/api/v1/endpoints/article.py @@ -11,7 +11,7 @@ from app.utils.get_db import get_db from app.utils.auth import get_current_user -from app.curd.article import crud_upload_to_self_folder, crud_get_self_folders, crud_get_articles_in_folder, crud_self_create_folder, crud_self_article_to_recycle_bin, crud_self_folder_to_recycle_bin, crud_read_article, crud_import_self_folder, crud_export_self_folder,crud_create_tag, crud_delete_tag, crud_get_article_tags, crud_all_tags_order, crud_change_folder_name, crud_change_article_name, crud_article_statistic, crud_self_tree, crud_self_article_statistic +from app.curd.article import crud_upload_to_self_folder, crud_get_self_folders, crud_get_articles_in_folder, crud_self_create_folder, crud_self_article_to_recycle_bin, crud_self_folder_to_recycle_bin, crud_read_article, crud_import_self_folder, crud_export_self_folder,crud_create_tag, crud_delete_tag, crud_get_article_tags, crud_all_tags_order, crud_change_folder_name, crud_change_article_name, crud_article_statistic, crud_self_tree, crud_self_article_statistic, crud_items_in_recycle_bin, crud_delete_forever, crud_recover from app.schemas.article import SelfCreateFolder router = APIRouter() @@ -72,13 +72,15 @@ async def self_create_folder(model: SelfCreateFolder, db: AsyncSession = Depends return {"msg": "User Folder Created Successfully", "folder_id": folder_id} @router.delete("/selfArticleToRecycleBin", response_model="dict") -async def self_article_to_recycle_bin(article_id: int = Query(...), db: AsyncSession = Depends(get_db)): - await crud_self_article_to_recycle_bin(article_id, db) +async def self_article_to_recycle_bin(article_id: int = Query(...), db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)): + user_id = user.get("id") + await crud_self_article_to_recycle_bin(article_id, user_id, db) return {"msg": "Article is moved to recycle bin"} @router.delete("/selfFolderToRecycleBin", response_model="dict") -async def self_folder_to_recycle_bin(folder_id: int = Query(...), db: AsyncSession = Depends(get_db)): - await crud_self_folder_to_recycle_bin(folder_id, db) +async def self_folder_to_recycle_bin(folder_id: int = Query(...), db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)): + user_id = user.get("id") + await crud_self_folder_to_recycle_bin(folder_id, user_id, db) return {"msg": "Folder is moved to recycle bin"} @router.post("/annotateSelfArticle", response_model="dict") @@ -201,4 +203,20 @@ async def self_tree(page_number: Optional[int] = Query(None, ge=1), page_size: O async def self_article_statistic(db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)): user_id = user.get("id") article_total_num, articles = await crud_self_article_statistic(user_id, db) - return {"article_total_num": article_total_num, "articles": articles} \ No newline at end of file + return {"article_total_num": article_total_num, "articles": articles} + +@router.get("/itemsInRecycleBin", response_model=dict) +async def items_in_recycle_bin(page_number: Optional[int] = Query(None, ge=1), page_size: Optional[int] = Query(None, ge=1), db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)): + user_id = user.get("id") + items = await crud_items_in_recycle_bin(user_id, page_number, page_size, db) + return {"items": items} + +@router.delete("/deleteForever", response_model=dict) +async def delete_forever(type: int = Query(...), id: int = Query(...), db: AsyncSession = Depends(get_db)): + await crud_delete_forever(type, id, db) + return {"msg": "Item and its child nodes deleted forever successfully"} + +@router.post("/recover", response_model=dict) +async def recover(type: int = Body(...), id: int = Body(...), db: AsyncSession = Depends(get_db)): + return_value = await crud_recover(type, id, db) + return return_value \ No newline at end of file diff --git a/app/api/v1/endpoints/note.py b/app/api/v1/endpoints/note.py index e48f218..56cb98c 100644 --- a/app/api/v1/endpoints/note.py +++ b/app/api/v1/endpoints/note.py @@ -14,8 +14,9 @@ async def create_note(note: NoteCreate, db: AsyncSession = Depends(get_db), curr return {"msg": "Note created successfully", "note_id": new_note.id} @router.delete("/{note_id}", response_model=dict) -async def delete_note(note_id: int, db: AsyncSession = Depends(get_db)): - note = await delete_note_in_db(note_id, db) +async def delete_note(note_id: int, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user)): + user_id = current_user["id"] + note = await delete_note_in_db(note_id, user_id, db) if not note: raise HTTPException(status_code=404, detail="Note not found") return {"msg": "Note deleted successfully"} diff --git a/app/curd/article.py b/app/curd/article.py index 283bd54..5656475 100644 --- a/app/curd/article.py +++ b/app/curd/article.py @@ -1,8 +1,8 @@ from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, delete +from sqlalchemy import select, delete, insert, desc from sqlalchemy import func, cast, Date from datetime import datetime, timedelta -from app.models.model import User, Group, Folder, Article, Note, Tag, user_group +from app.models.model import User, Group, Folder, Article, Note, Tag, user_group, self_recycle_bin async def crud_upload_to_self_folder(name: str, folder_id: int, db: AsyncSession): new_article = Article(name=name, folder_id=folder_id) @@ -46,25 +46,31 @@ async def crud_self_create_folder(name: str, user_id: int, db: AsyncSession): await db.refresh(new_folder) return new_folder.id -async def crud_self_article_to_recycle_bin(article_id: int, db: AsyncSession): - # 查询 article +async def crud_self_article_to_recycle_bin(article_id: int, user_id: int, db: AsyncSession): + # 维护 article 表 query = select(Article).where(Article.id == article_id) result = await db.execute(query) article = result.scalar_one_or_none() - - # 修改 visible 字段 article.visible = False + + # 维护 self_recycle_bin 表 + recycle = insert(self_recycle_bin).values(user_id=user_id, type=2, id=article_id, name=article.name, folder_id=article.folder_id) + await db.execute(recycle) + await db.commit() await db.refresh(article) -async def crud_self_folder_to_recycle_bin(folder_id: int, db: AsyncSession): - # 查询 folder +async def crud_self_folder_to_recycle_bin(folder_id: int, user_id: int, db: AsyncSession): + # 维护 folder 表 query = select(Folder).where(Folder.id == folder_id) result = await db.execute(query) folder = result.scalar_one_or_none() - - # 修改 visible 字段 folder.visible = False + + # 维护 self_recycle_bin 表 + recycle = insert(self_recycle_bin).values(user_id=user_id, type=1, id=folder_id, name=folder.name) + await db.execute(recycle) + await db.commit() await db.refresh(folder) @@ -166,11 +172,11 @@ async def crud_article_statistic(db: AsyncSession): tomorrow = datetime.now().date() + timedelta(days=1) seven_days_ago = datetime.now().date() - timedelta(days=6) - # 查询近7天内的笔记数目,按日期分组 + # 查询近7天内的文献数目,按日期分组 query = ( select( cast(Article.create_time, Date).label("date"), # 按日期分组 - func.count(Article.id).label("count") # 统计每日期的笔记数 + func.count(Article.id).label("count") # 统计每日期的文献数 ) .where( Article.create_time >= seven_days_ago, # 大于等于7天前的0点 @@ -243,11 +249,11 @@ async def crud_self_article_statistic(user_id: int, db: AsyncSession): tomorrow = datetime.now().date() + timedelta(days=1) seven_days_ago = datetime.now().date() - timedelta(days=6) - # 查询近7天内的笔记数目,按日期分组 + # 查询近7天内的文献数目,按日期分组 query = ( select( cast(Article.create_time, Date).label("date"), # 按日期分组 - func.count(Article.id).label("count") # 统计每日期的笔记数 + func.count(Article.id).label("count") # 统计每日期的文献数 ) .join(Folder, Article.folder_id == Folder.id) .where( @@ -273,4 +279,95 @@ async def crud_self_article_statistic(user_id: int, db: AsyncSession): if i == len(articles) or articles[i].get("date") != seven_days_ago + timedelta(days=i): articles.insert(i, {"date": seven_days_ago + timedelta(days=i), "count": 0}) - return article_total_num, articles \ No newline at end of file + return article_total_num, articles + +async def crud_items_in_recycle_bin(user_id: int, page_number: int, page_size: int, db: AsyncSession): + query = select( + self_recycle_bin.c.type, + self_recycle_bin.c.id, + self_recycle_bin.c.name, + self_recycle_bin.c.create_time + ).where(self_recycle_bin.c.user_id == user_id).order_by(desc(self_recycle_bin.c.create_time)) + + if page_number and page_size: + offset = (page_number - 1) * page_size + query = query.offset(offset).limit(page_size) + + result = await db.execute(query) + items = result.fetchall() + + return [{"type": item.type, "id": item.id, "name": item.name, "time": item.create_time.strftime("%Y-%m-%d %H:%M:%S")} for item in items] + +async def crud_delete_forever(type: int, id: int, db: AsyncSession): + query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id) + await db.execute(query) + if type == 1: + query = delete(Folder).where(Folder.id==id) + elif type == 2: + query = delete(Article).where(Article.id==id) + else: + query = delete(Note).where(Note.id==id) + await db.execute(query) + await db.commit() + +async def crud_recover(type: int, id: int, db: AsyncSession): + query = select(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id) + result = await db.execute(query) + item = result.first() + if type == 3: + # 检查上级文献存在性 + query = select(Article).where(Article.id == item.article_id) + result = await db.execute(query) + article = result.scalar_one_or_none() + article_name = article.name + article_visible = article.visible + # 检查上级文件夹存在性 + query = select(Folder).where(Folder.id == item.folder_id) + result = await db.execute(query) + folder = result.scalar_one_or_none() + folder_name = folder.name + folder_visible = folder.visible + # 若上级不存在,则给用户以提示信息,请用户先恢复相应的文件夹和文献 + if not article_visible or not folder_visible: + return {"info": "Note recovered failed, please check its upper-level node", "folder_name": folder_name, "article_name": article_name} + # 若上级存在,则正常恢复即可,在回收站表中删除该表项,并将Note表中visible改为True + query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id) + await db.execute(query) + query = select(Note).where(Note.id == id) + result = await db.execute(query) + note = result.scalar_one_or_none() + note.visible = True + await db.commit() + await db.refresh(note) + return {"info": "Note recovered successfully"} + if type == 2: + # 检查上级文件夹存在性 + query = select(Folder).where(Folder.id == item.folder_id) + result = await db.execute(query) + folder = result.scalar_one_or_none() + folder_name = folder.name + folder_visible = folder.visible + # 若上级不存在,则给用户以提示信息,请用户先恢复相应的文件夹 + if not folder_visible: + return {"info": "Article recovered failed, please check its upper-level node", "folder_name": folder_name} + # 若上级存在,则正常恢复即可,在回收站表中删除该表项,并将Article表中visible改为True + query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id) + await db.execute(query) + query = select(Article).where(Article.id == id) + result = await db.execute(query) + article = result.scalar_one_or_none() + article.visible = True + await db.commit() + await db.refresh(article) + return {"info": "Article recovered successfully"} + if type == 1: + # 正常恢复即可,在回收站表中删除该表项,并将Folder表中visible改为True + query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id) + await db.execute(query) + query = select(Folder).where(Folder.id == id) + result = await db.execute(query) + folder = result.scalar_one_or_none() + folder.visible = True + await db.commit() + await db.refresh(folder) + return {"info": "Folder recovered successfully"} \ No newline at end of file diff --git a/app/curd/note.py b/app/curd/note.py index 8b2ddf4..92f007b 100644 --- a/app/curd/note.py +++ b/app/curd/note.py @@ -1,8 +1,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select -from sqlalchemy import func, cast, Date +from sqlalchemy import func, cast, Date, insert from datetime import datetime, timedelta -from app.models.model import Note +from app.models.model import Note, self_recycle_bin, Article, Folder from app.schemas.note import NoteCreate, NoteUpdate, NoteFind, NoteResponse async def create_note_in_db(note: NoteCreate, db: AsyncSession, user_id: int): @@ -12,13 +12,20 @@ async def create_note_in_db(note: NoteCreate, db: AsyncSession, user_id: int): await db.refresh(new_note) return new_note -async def delete_note_in_db(note_id: int, db: AsyncSession): +async def delete_note_in_db(note_id: int, user_id: int, db: AsyncSession): stmt = select(Note).where(Note.id == note_id) result = await db.execute(stmt) note = result.scalar_one_or_none() if note: - note.visible = False # 将 visible 设置为 False,表示删除 - # await db.execute(note) + # 将 visible 设置为 False,表示删除 + note.visible = False + # 找 folder_id + stmt = select(Article).where(Article.id == note.article_id) + result = await db.execute(stmt) + article = result.scalar_one_or_none() + # 插入 self_recycle_bin 表 + recycle = insert(self_recycle_bin).values(user_id=user_id, type=3, id=note_id, name=note.title, article_id=note.article_id, folder_id=article.folder_id) + await db.execute(recycle) await db.commit() return note @@ -80,8 +87,8 @@ async def find_recent_notes_in_db(db: AsyncSession): 返回近7天内创建的笔记的数目和对应日期 """ # 获取当前日期和7天前的日期 - today = datetime.now().date() - seven_days_ago = today - timedelta(days=6) + tomorrow = datetime.now().date() + timedelta(days=1) + seven_days_ago = datetime.now().date() - timedelta(days=6) # 查询近7天内的笔记数目,按日期分组 stmt = ( @@ -91,7 +98,7 @@ async def find_recent_notes_in_db(db: AsyncSession): ) .where( Note.create_time >= seven_days_ago, # 筛选近7天的笔记 - Note.create_time <= today # 包括今天 + Note.create_time < tomorrow # 包括今天 ) .group_by(cast(Note.create_time, Date)) # 按日期分组 .order_by(cast(Note.create_time, Date)) # 按日期排序 @@ -104,6 +111,11 @@ async def find_recent_notes_in_db(db: AsyncSession): # 格式化结果为字典列表 recent_notes = [{"date": row.date, "count": row.count} for row in data] + # 若某日期没有记录,则为0 + for i in range(0, 7): + if i == len(recent_notes) or recent_notes[i].get("date") != seven_days_ago + timedelta(days=i): + recent_notes.insert(i, {"date": seven_days_ago + timedelta(days=i), "count": 0}) + return recent_notes async def find_self_recent_notes_in_db(db: AsyncSession, user_id: int): @@ -111,8 +123,8 @@ async def find_self_recent_notes_in_db(db: AsyncSession, user_id: int): 返回近7天内创建的笔记的数目和对应日期 """ # 获取当前日期和7天前的日期 - today = datetime.now().date() - seven_days_ago = today - timedelta(days=6) + tomorrow = datetime.now().date() + timedelta(days=1) + seven_days_ago = datetime.now().date() - timedelta(days=6) # 查询近7天内的笔记数目,按日期分组 stmt = ( @@ -120,10 +132,15 @@ async def find_self_recent_notes_in_db(db: AsyncSession, user_id: int): cast(Note.create_time, Date).label("date"), # 按日期分组 func.count(Note.id).label("count") # 统计每日期的笔记数 ) + .join(Article, Note.article_id == Article.id) + .join(Folder, Article.folder_id == Folder.id) .where( + Note.visible == True, + Article.visible == True, + Folder.visible == True, Note.create_time >= seven_days_ago, # 筛选近7天的笔记 - Note.create_time <= today, # 包括今天 - Note.creator_id == user_id # 筛选特定用户的笔记 + Note.create_time < tomorrow, # 包括今天 + Note.creator_id == user_id # 筛选特定用户的笔记 ) .group_by(cast(Note.create_time, Date)) # 按日期分组 .order_by(cast(Note.create_time, Date)) # 按日期排序 @@ -136,6 +153,11 @@ async def find_self_recent_notes_in_db(db: AsyncSession, user_id: int): # 格式化结果为字典列表 recent_notes = [{"date": row.date, "count": row.count} for row in data] + # 若某日期没有记录,则为0 + for i in range(0, 7): + if i == len(recent_notes) or recent_notes[i].get("date") != seven_days_ago + timedelta(days=i): + recent_notes.insert(i, {"date": seven_days_ago + timedelta(days=i), "count": 0}) + return recent_notes async def find_self_notes_count_in_db(db: AsyncSession, user_id: int): @@ -144,7 +166,14 @@ async def find_self_notes_count_in_db(db: AsyncSession, user_id: int): """ stmt = ( select(func.count(Note.id)) - .where(Note.creator_id == user_id) + .join(Article, Note.article_id == Article.id) + .join(Folder, Article.folder_id == Folder.id) + .where( + Note.creator_id == user_id, + Note.visible == True, + Article.visible == True, + Folder.visible == True + ) ) result = await db.execute(stmt) count = result.scalar_one_or_none() diff --git a/app/models/model.py b/app/models/model.py index daf82a2..8c89021 100644 --- a/app/models/model.py +++ b/app/models/model.py @@ -17,6 +17,18 @@ Column('group_id', Integer, ForeignKey('groups.id'), primary_key=True), ) +self_recycle_bin = Table( + 'self_recycle_bin', Base.metadata, + Column('user_id', Integer, ForeignKey('users.id')), + Column('type', Integer, primary_key=True), # 1: folder 2: article 3: note + Column('id', Integer, primary_key=True), + Column('name', Text, nullable=False), # 回收站显示 + Column('create_time', DateTime, default=func.now(), nullable=False), # 加入回收站的时间 + Column('article_id', Integer, ForeignKey('articles.id', ondelete="CASCADE")), + Column('folder_id', Integer, ForeignKey('folders.id', ondelete="CASCADE")) + # 最后两列为有上级时的上级节点信息,用于恢复时检查是否有上级节点在回收站中,和彻底删除时的级联删除 +) + class User(Base): __tablename__ = 'users' @@ -61,7 +73,7 @@ class Folder(Base): # 关系定义 user = relationship('User', back_populates='folders') group = relationship('Group', back_populates='folders') - articles = relationship('Article', back_populates='folder') + articles = relationship('Article', back_populates='folder', cascade="all, delete-orphan") __table_args__ = ( # 不能同时为空 @@ -74,14 +86,14 @@ class Article(Base): id = Column(Integer, primary_key=True, index=True, autoincrement=True) name = Column(Text, nullable=False) - folder_id = Column(Integer, ForeignKey('folders.id')) + folder_id = Column(Integer, ForeignKey('folders.id', ondelete="CASCADE")) create_time = Column(DateTime, default=func.now(), nullable=False) # 创建时间 update_time = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # 更新时间 visible = Column(Boolean, default=True, nullable=False) # 是否可见 False表示在回收站中 folder = relationship('Folder', back_populates='articles', lazy='selectin') - notes = relationship('Note', back_populates='article') + notes = relationship('Note', back_populates='article', cascade="all, delete-orphan") tags = relationship('Tag', back_populates='article') class Note(Base): @@ -90,7 +102,7 @@ class Note(Base): id = Column(Integer, primary_key=True, index=True, autoincrement=True) title = Column(String(100), nullable=False) content = Column(Text) # 将 content 字段类型改为 Text,以支持存储大量文本 - article_id = Column(Integer, ForeignKey('articles.id')) + article_id = Column(Integer, ForeignKey('articles.id', ondelete="CASCADE")) create_time = Column(DateTime, default=func.now(), nullable=False) # 创建时间 update_time = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # 更新时间 creator_id = Column(Integer, ForeignKey('users.id')) # 创建者ID