85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
from datetime import datetime
|
|
from app.extensions import db
|
|
|
|
|
|
class Folder(db.Model):
|
|
__tablename__ = 'folders'
|
|
__table_args__ = (
|
|
# 同一用户同一父目录下名称唯一
|
|
# 注意:MySQL 对 NULL 不强制唯一,根目录重名由路由层补充校验
|
|
db.UniqueConstraint('user_id', 'parent_id', 'name',
|
|
name='uq_folder_user_parent_name'),
|
|
)
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
user_id = db.Column(db.Integer,
|
|
db.ForeignKey('users.id', ondelete='CASCADE'),
|
|
nullable=False, index=True)
|
|
name = db.Column(db.String(128), nullable=False)
|
|
parent_id = db.Column(db.Integer,
|
|
db.ForeignKey('folders.id', ondelete='CASCADE'),
|
|
nullable=True, index=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow,
|
|
onupdate=datetime.utcnow, nullable=False)
|
|
|
|
# 自引用关系:父删 → 子自动删
|
|
children = db.relationship(
|
|
'Folder',
|
|
backref=db.backref('parent', remote_side=[id]),
|
|
lazy='select',
|
|
cascade='all, delete-orphan'
|
|
)
|
|
# 文件夹内的资源
|
|
resources = db.relationship(
|
|
'Resource',
|
|
backref='folder',
|
|
lazy='dynamic',
|
|
foreign_keys='Resource.folder_id'
|
|
)
|
|
|
|
# ── 业务方法 ─────────────────────────────────────────────────
|
|
|
|
def is_ancestor_of(self, target_id: int) -> bool:
|
|
"""检测 self 是否是 target_id 的祖先(防止移动到自身子树产生循环引用)"""
|
|
visited = set()
|
|
current = Folder.query.get(target_id)
|
|
while current is not None:
|
|
if current.id == self.id:
|
|
return True
|
|
if current.id in visited:
|
|
break
|
|
visited.add(current.id)
|
|
current = current.parent
|
|
return False
|
|
|
|
def get_breadcrumb(self) -> list:
|
|
"""从根到当前节点的路径列表(用于面包屑导航)"""
|
|
path, node = [], self
|
|
while node is not None:
|
|
path.append(node)
|
|
node = node.parent
|
|
path.reverse()
|
|
return path
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'parent_id': self.parent_id,
|
|
'resource_count': self.resources.count(),
|
|
'children': [c.to_dict() for c in sorted(self.children, key=lambda x: x.name)]
|
|
}
|
|
|
|
@staticmethod
|
|
def get_user_tree(user_id: int) -> list:
|
|
"""返回某用户所有根文件夹的完整树(递归序列化)"""
|
|
roots = (Folder.query
|
|
.filter_by(user_id=user_id, parent_id=None)
|
|
.order_by(Folder.name)
|
|
.all())
|
|
return [f.to_dict() for f in roots]
|
|
|
|
def __repr__(self):
|
|
return f'<Folder {self.id} {self.name!r}>'
|