📂 file_own 项目全代码逐行注释

这是一个基于 Django 5.2 的文件存储系统。下面将从零开始,逐文件、逐行梳理每一处代码,帮助新手小白彻底理解整个项目的运作方式。

🐍 Python / Django 5.2 🗄️ MySQL 数据库 🎨 Bootstrap 5 前端 📦 文件上传 / 下载 / 回收站 👤 用户认证系统

🏗️ 项目架构总览

用户浏览器 ──请求──▶ Django (manage.py 启动服务)
                          │
                          ▼
                   ┌──────────────┐
                   │  urls.py     │  ← 路由分发:把 URL 分配到对应的视图函数
                   └──────┬───────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
        ┌──────────┐ ┌────────┐ ┌──────────┐
        │ views.py │ │forms.py│ │ models.py│
        │ (视图)   │ │ (表单) │ │ (数据库) │
        └────┬─────┘ └────────┘ └────┬─────┘
             │                       │
             ▼                       ▼
      ┌────────────┐          ┌───────────┐
      │ templates/ │          │  MySQL DB │
      │ (HTML模板) │          │  (数据库)  │
      └────────────┘          └───────────┘
             │
             ▼
      ┌────────────┐
      │  static/   │  ← CSS / JS / 图片等静态资源
      └────────────┘

📑 目录导航

01requirements.txt — 项目依赖清单 02manage.py — 项目启动入口 03file_own/settings.py — 全局配置 04file_own/urls.py — 主路由 05file_own/wsgi.py — WSGI部署 06file_own/asgi.py — ASGI部署 07File_app/apps.py — 应用配置 08File_app/models.py — 数据模型 09File_app/forms.py — 表单定义 10File_app/views.py — 视图函数 11File_app/urls.py — 应用路由 12File_app/admin.py — 后台管理 13migrations/0001 — 初始迁移 14migrations/0002 — 软删除迁移 15migrations/0003 — 字段修改迁移 16templates/base.html — 基础模板 17templates/home.html — 首页 18templates/upload.html — 上传页 19templates/type_list.html — 分类页 20templates/login.html — 登录页 21templates/register.html — 注册页 22templates/change_type.html — 修改类型 23templates/recycle_bin.html — 回收站 24static/css/style.css — 样式表 25static/js/script.js — 脚本
📋

requirements.txt

file_own/requirements.txt — 项目依赖清单

这个文件是干嘛的? 它就像一份"购物清单",列出了项目运行所需的所有 Python 第三方库。别人拿到你的项目后,只需运行 pip install -r requirements.txt,就能一次性安装所有依赖。
1Django>=5.2← 安装 Django 框架,版本至少 5.2。Django 是整个网站的后端骨架,帮你处理路由、数据库、模板等一切
2PyMySQL>=1.0.2← 安装 PyMySQL,它是 Python 连接 MySQL 数据库的驱动。没有它,Django 无法和 MySQL 通信
3django-crispy-forms>=2.0← 安装 crispy-forms,它能让 Django 自动生成的表单变得更好看,省去手写 HTML 表单样式的麻烦
4crispy-bootstrap5>=0.7← 安装 Bootstrap5 主题包,配合上一行的 crispy-forms 使用,让表单自动套用 Bootstrap5 的样式
🐍

manage.py

file_own/manage.py — 项目启动入口 / 管理工具

这个文件是干嘛的? 它是整个 Django 项目的"遥控器"。你在终端执行的 python manage.py runserver(启动服务器)、python manage.py migrate(同步数据库)等命令,都是通过它来调度的。一般不需要修改它。
1#!/usr/bin/env python← Shebang 行(Linux/Mac 专用)。告诉系统用哪个 Python 解释器来执行这个脚本,Windows 下会忽略这行
2"""Django's command-line utility for administrative tasks."""← 模块的文档字符串(docstring),简单描述这个文件的用途:Django 的命令行管理工具
3import os← 导入 Python 内置的 os 模块,它提供操作系统相关功能,比如读取环境变量(后面会用到)
4import sys← 导入 Python 内置的 sys 模块,它提供命令行参数(sys.argv)和 Python 解释器相关的信息
5import pymysql← 导入 PyMySQL 库。因为项目使用 MySQL 数据库,而 Django 默认只支持 PostgreSQL 等,需要 PyMySQL 来桥接
6pymysql.install_as_MySQLdb()← 关键操作!把 PyMySQL 伪装成 MySQLdb(MySQL 官方 Python 驱动),这样 Django 以为自己在用 MySQLdb,实际用的是 PyMySQL
7
8
9def main():← 定义主函数 main(),整个脚本的逻辑从这里开始执行
10 """Run administrative tasks."""← 函数的文档说明:运行管理任务
11 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'file_own.settings')← 设置环境变量:告诉 Django 去 file_own/settings.py 里读取配置。setdefault 的意思是"如果没设置过就设成这个值"
12 try:← 开始尝试(try-except 错误处理):接下来导入 Django,如果导入失败就给出友好提示
13 from django.core.management import execute_from_command_line← 从 Django 核心管理模块中导入命令行执行函数。这个函数能解析你在终端输入的命令(如 runserver、migrate 等)并执行
14 except ImportError as exc:← 如果导入失败(说明 Django 没安装),捕获这个 ImportError 异常
15 raise ImportError(← 重新抛出一个更友好的错误提示,告诉用户该怎么做
16 "Couldn't import Django. Are you sure it's installed and "← 错误信息:无法导入 Django
17 "available on your PYTHONPATH environment variable? Did you "← 提示用户检查 Python 路径
18 "forget to activate a virtual environment?"← 提示用户是否忘记激活虚拟环境
19 ) from exc← from exc 保留原始异常信息,方便调试
20 execute_from_command_line(sys.argv)← 核心!把命令行参数(如 ['manage.py', 'runserver'])传给 Django,让它执行对应的命令
21
22
23if __name__ == '__main__':← Python 经典写法:判断这个文件是否被直接运行(而不是被其他文件 import)。如果是直接运行,就执行下面的代码
24 main()← 调用上面定义的 main() 函数,启动 Django 管理命令
🐍

settings.py

file_own/file_own/settings.py — 项目全局配置中心

这个文件是干嘛的? 这是整个项目的"控制面板"。数据库连接、安装的 App、模板路径、静态文件、语言时区……几乎所有全局配置都在这里完成。修改这个文件就能改变项目的整体行为。
1"""← 三引号开始一段多行文档字符串
2Django settings for file_own project.← 说明这是 file_own 项目的设置文件
3-11...(省略文档说明)...← Django 自动生成的文档链接,提供参考地址
12"""← 文档字符串结束
13import os← 导入 os 模块,后面用于拼接文件路径(如静态文件目录)
14from pathlib import Path← 导入 pathlib 的 Path 类。Path 是现代 Python 处理路径的推荐方式,比字符串拼接更安全、更方便
15
16BASE_DIR = Path(__file__).resolve().parent.parent← 【重要】获取项目根目录的绝对路径。__file__ 是当前文件(settings.py)的路径,resolve() 解析为绝对路径,两次 .parent 就跳到了项目根目录
17
18# SECURITY WARNING: keep the secret key used in production secret!← 安全警告:上线时必须更换这个密钥!
19SECRET_KEY = 'django-insecure-5g1^(6f...'← Django 的加密密钥,用于 Session、CSRF 等安全机制的加密签名。开发时用默认值即可,上线必须换成随机字符串
20
21DEBUG = True← 开启调试模式。True 时出错会显示详细错误页面(方便开发);上线必须改为 False(否则泄露敏感信息)
22
23ALLOWED_HOSTS = ['*']← 允许访问的域名/IP 列表。'*' 表示允许所有(开发环境),上线应改为具体域名如 ['www.example.com']
24
25# Application definition← 下面开始定义"安装了哪些应用"
26INSTALLED_APPS = [← 【核心配置】注册所有已安装的 App。Django 启动时会加载列表中的每个 App
27 'django.contrib.admin',← Django 自带的后台管理系统,访问 /admin/ 就能管理数据库数据
28 'django.contrib.auth',← Django 自带的用户认证系统,提供登录、注册、权限管理等功能
29 'django.contrib.contenttypes',← 内容类型框架,让 Django 能追踪所有模型的元数据(一般不用管它)
30 'django.contrib.sessions',← Session 会话管理,用于在用户浏览器和服务端之间保持登录状态等
31 'django.contrib.messages',← 消息框架,用于在页面间传递一次性提示(如"上传成功!")
32 'django.contrib.staticfiles',← 静态文件管理,帮助收集和分发 CSS、JS、图片等静态资源
33 'File_app',← 【我们自己写的应用】文件管理 App,包含了文件上传、下载、删除等所有业务逻辑
34]← INSTALLED_APPS 列表结束
35CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"← 告诉 crispy-forms 允许使用 Bootstrap5 的模板包
36CRISPY_TEMPLATE_PACK = "bootstrap5"← 设置 crispy-forms 默认使用 Bootstrap5 样式渲染表单
37
38MIDDLEWARE = [← 【中间件列表】中间件就像"过滤器",每个请求进来和响应出去时都会经过这些中间件处理
39 'django.middleware.security.SecurityMiddleware',← 安全中间件:处理 HTTPS 重定向、安全头部等
40 'django.contrib.sessions.middleware.SessionMiddleware',← Session 中间件:为每个请求关联 Session(保持登录状态)
41 'django.middleware.common.CommonMiddleware',← 通用中间件:处理 URL 末尾斜杠等通用逻辑
42 'django.middleware.csrf.CsrfViewMiddleware',← CSRF 防护中间件:防止跨站请求伪造攻击(表单里必须加 {% csrf_token %})
43 'django.contrib.auth.middleware.AuthenticationMiddleware',← 认证中间件:把登录用户信息绑定到 request.user 上
44 'django.contrib.messages.middleware.MessageMiddleware',← 消息中间件:支持一次性消息提示功能
45 'django.middleware.clickjacking.XFrameOptionsMiddleware',← 点击劫持防护:防止网站被嵌入到恶意 iframe 中
46]← 中间件列表结束
47
48ROOT_URLCONF = 'file_own.urls'← 指定主路由文件的位置。所有 URL 请求首先到 file_own/urls.py 中匹配
49
50TEMPLATES = [← 模板引擎配置,告诉 Django 去哪里找 HTML 模板、用什么引擎渲染
51 {
52 'BACKEND': 'django.template.backends.django.DjangoTemplates',← 使用 Django 自带的模板引擎(还有 Jinja2 等可选)
53 'DIRS': [BASE_DIR / 'templates'],← 模板目录:告诉 Django 去项目根目录下的 templates/ 文件夹找模板文件
54 'APP_DIRS': True,← 允许 Django 自动去每个 App 内部的 templates/ 子目录找模板
55 'OPTIONS': {← 模板引擎的额外选项
56 'context_processors': [← 上下文处理器:在每个模板渲染前自动注入一些变量
57 'django.template.context_processors.debug',← 在模板中注入 DEBUG 变量(调试用)
58 'django.template.context_processors.request',← 在模板中注入 request 变量(可以在模板中访问请求信息)
59 'django.contrib.auth.context_processors.auth',← 在模板中注入 user 变量(可以在模板中判断用户是否登录)
60 'django.contrib.messages.context_processors.messages',← 在模板中注入 messages 变量(显示提示消息)
61 ],
62 },
63 },
64]← 模板配置结束
65
66WSGI_APPLICATION = 'file_own.wsgi.application'← 指定 WSGI 应用入口,部署到生产服务器(如 Nginx + Gunicorn)时会用到
67
68# Database← 下面配置数据库连接
69DATABASES = {← 【核心配置】数据库设置字典
70 'default': {← 'default' 是默认数据库的配置名,Django 支持多数据库
71 'ENGINE': 'django.db.backends.mysql',← 数据库引擎:使用 MySQL(通过前面安装的 PyMySQL 驱动)
72 'NAME': 'file_p',← 数据库名称:连接到 MySQL 中名为 file_p 的数据库
73 'USER': 'root',← 数据库用户名:使用 root 账户(生产环境应使用专用账户)
74 'PASSWORD': 'MySQL102346',← 数据库密码(生产环境应使用环境变量,不要硬编码)
75 'HOST': '127.0.0.1',← 数据库主机地址:127.0.0.1 表示本机(localhost)
76 'PORT': 3306,← 数据库端口号:3306 是 MySQL 的默认端口,必须是整数不能加引号
77 }
78}← 数据库配置结束
79
80AUTH_PASSWORD_VALIDATORS = [← 密码验证器列表:用户注册/改密码时,Django 会用这些规则检查密码强度
81 {'NAME': '...UserAttributeSimilarityValidator'},← 密码不能和用户名太相似(防止用用户名当密码)
82 {'NAME': '...MinimumLengthValidator'},← 密码最小长度验证(默认至少 8 位)
83 {'NAME': '...CommonPasswordValidator'},← 密码不能是常见密码(如 12345678、password)
84 {'NAME': '...NumericPasswordValidator'},← 密码不能全是数字(如 12345678)
85]
86
87LANGUAGE_CODE = 'zh-hans'← 网站语言设为简体中文(Django 内置的管理页面、错误提示都会变成中文)
88TIME_ZONE = 'Asia/Shanghai'← 时区设为上海(东八区),所有时间显示和存储都按这个时区
89USE_I18N = True← 开启国际化支持(Internationalization),允许翻译文本
90USE_TZ = True← 开启时区支持,数据库内部用 UTC 存储,显示时自动转换为上海时间
91
92STATIC_URL = 'static/'← 静态文件的 URL 前缀。访问 CSS/JS/图片时用 /static/css/style.css 这样的路径
93STATICFILES_DIRS = [← 额外的静态文件目录列表(开发时用)
94 BASE_DIR / "static"← 告诉 Django 去项目根目录下的 static/ 文件夹找静态文件
95]
96STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")← 收集静态文件的目标目录(运行 collectstatic 命令后,所有静态文件会复制到这里,给生产服务器用)
97
98MEDIA_URL = '/media/'← 用户上传文件的 URL 前缀。访问上传的文件时用 /media/2026/05/17/xxx.exe
99MEDIA_ROOT = BASE_DIR / 'media'← 用户上传文件的存储目录。文件实际保存在项目根目录下的 media/ 文件夹中
100
101DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'← 模型默认的自增主键类型。BigAutoField 支持更大的 ID 范围(防止数据量超大时 ID 不够用)
102
103LOGIN_REDIRECT_URL = '/'← 登录成功后跳转到哪里:'/' 表示跳转到首页
104LOGOUT_REDIRECT_URL = '/'← 退出登录后跳转到哪里:'/' 表示跳转到首页
105LOGIN_URL = 'login'← 未登录用户访问需要登录的页面时,自动跳转到 'login' 这个 URL
106
107DATA_UPLOAD_MAX_MEMORY_SIZE = 2147483648← 请求体最大内存限制:2GB(2147483648 字节 = 2 × 1024³),允许上传大文件
108FILE_UPLOAD_MAX_MEMORY_SIZE = 2147483648← 单个文件上传最大限制:2GB。小于此值的文件存在内存中,大于此值的文件会临时写入磁盘
🐍

file_own/urls.py

file_own/file_own/urls.py — 项目主路由(URL 总调度中心)

这个文件是干嘛的? 它就像一个"电话总机",当用户访问某个 URL 时,这个文件决定把请求转给谁处理。它把所有 URL 规则集中管理,是整个网站的"目录"。
1from django.contrib import admin← 导入 Django 的后台管理模块,用于注册 /admin/ 路由
2from django.urls import path, include← 导入 path(定义 URL 路径)和 include(引入其他路由文件)
3from django.conf.urls.static import static← 导入 static 辅助函数,用于在开发模式下提供静态文件和媒体文件的访问
4from django.conf import settings← 导入项目配置(settings.py 中的所有设置),后面用来判断是否为 DEBUG 模式
5
6urlpatterns = [← 【核心】URL 路由列表。Django 从上到下依次匹配,匹配到第一个就停止
7 path('admin/', admin.site.urls),← 访问 /admin/ 开头的所有 URL → 交给 Django 后台管理系统处理(自动提供增删改查界面)
8 path('', include('File_app.urls')),← 访问根路径 '' 开头的 URL → 交给 File_app/urls.py 处理。空字符串表示"所有请求都先交给它",相当于默认应用
9 path('accounts/', include('django.contrib.auth.urls'))← 访问 /accounts/ 开头的 URL → 交给 Django 内置认证系统处理(自动提供 /accounts/login/、/accounts/logout/ 等路由)
10]← 路由列表结束
11
12if settings.DEBUG:← 只在调试模式(DEBUG=True)下执行下面的代码,生产环境不会走这里
13 urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)← 开发模式下,让 /media/xxx 这样的 URL 能直接访问用户上传的文件(生产环境应由 Nginx 处理)
14 urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)← 开发模式下,让 /static/xxx 这样的 URL 能直接访问静态文件(CSS/JS/图片)
🐍

wsgi.py

file_own/file_own/wsgi.py — WSGI 部署接口

这个文件是干嘛的? WSGI(Web Server Gateway Interface)是 Python Web 应用和 Web 服务器之间的标准接口。当你用 Gunicorn、uWSGI 等生产服务器部署时,它们就是通过这个文件找到你的 Django 应用的。开发时不需要关心它。
1import os← 导入 os 模块,用于设置环境变量
2from django.core.wsgi import get_wsgi_application← 导入 Django 的 WSGI 应用工厂函数
3os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'file_own.settings')← 告诉 Django 配置文件在哪里(和 manage.py 中一样的操作)
4application = get_wsgi_application()← 创建 WSGI 应用实例。生产服务器(如 Gunicorn)会调用这个 application 对象来处理 HTTP 请求
🐍

asgi.py

file_own/file_own/asgi.py — ASGI 异步部署接口

这个文件是干嘛的? ASGI(Asynchronous Server Gateway Interface)是 WSGI 的升级版,支持异步(WebSocket 等)。如果你的项目需要实时通信功能(如聊天室),就需要用它。当前项目暂时不需要,但 Django 默认生成了它。
1import os← 导入 os 模块
2from django.core.asgi import get_asgi_application← 导入 Django 的 ASGI 应用工厂函数(和 WSGI 类似,但支持异步)
3os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'file_own.settings')← 设置配置文件路径
4application = get_asgi_application()← 创建 ASGI 应用实例,供异步服务器(如 Daphne、Uvicorn)使用
🐍

File_app/apps.py

file_own/File_app/apps.py — 应用配置信息

这个文件是干嘛的? 每个 Django App 都有一个 apps.py,它告诉 Django 这个 App 叫什么名字、用什么默认主键类型等元信息。一般自动生成,很少手动修改。
1from django.apps import AppConfig← 导入 Django 的应用配置基类 AppConfig,所有 App 的配置类都要继承它
2
3class FileAppConfig(AppConfig):← 定义 File_app 的配置类,继承自 AppConfig
4 default_auto_field = 'django.db.models.BigAutoField'← 该应用下所有模型默认使用的自增主键类型(BigAutoField = 64位整数,范围超大)
5 name = 'File_app'← 应用的名称,必须和文件夹名一致。Django 通过这个名字来识别和加载应用
🐍

File_app/models.py

file_own/File_app/models.py — 数据模型(数据库表结构定义)

这个文件是干嘛的? 这是整个项目最核心的文件之一!它定义了数据库里的"表长什么样"。每个 class 对应一张数据表,每个字段对应一列。Django 会根据这些定义自动创建数据库表(通过 migrate 命令)。
1from django.db import models← 【必须】导入 Django 的 models 模块。所有数据库模型(数据表)都必须继承 models.Model 基类
2import os← 导入操作系统接口模块,后面用于处理文件路径(如获取文件扩展名 os.path.splitext)
3import uuid← 导入 UUID 模块。UUID 能生成全球唯一的 ID(如 a1b2c3d4-e5f6-...),用作文件记录的主键,避免 ID 冲突和被猜测
4from django.contrib.auth.models import User← 导入 Django 内置的 User 模型。用于关联"哪个用户上传了这个文件"(外键关系)
5from django.utils import timezone← 导入 Django 的时区工具,用于获取当前时间(带时区信息的 now())
6
7
8def generate_upload_path(instance, filename):← 定义一个函数,用于生成上传文件的存储路径。Django 的 FileField 会调用它来决定文件存到哪
9 """生成上传路径"""← 函数的文档说明
10 ext = filename.split('.')[-1]← 从原始文件名中提取扩展名。比如 "报告.pdf" 按 '.' 分割后取最后一个元素 → "pdf"
11 filename = f"{uuid.uuid4().hex}.{ext}"← 用 UUID 生成全新的文件名(如 a1b2c3d4e5f6.pdf),防止文件名冲突和中文路径问题
12 return os.path.join(← 拼接最终路径,返回类似 "2026/05/25/a1b2c3d4.pdf" 的相对路径
13 timezone.now().strftime('%Y/%m/%d'),← 按当前日期生成目录,如 "2026/05/25"。这样文件按日期分文件夹存放,方便管理
14 filename← UUID 文件名
15 )← 最终路径示例:media/2026/05/25/a1b2c3d4e5f6.pdf
16
17
18class File(models.Model):← 【核心】定义 File 模型类 = 数据库中的 file_file 表。继承 models.Model 后,Django 会自动帮你建表
18class File(models.Model):← 【核心】定义 File 模型类 = 数据库中的 file_file 表。继承 models.Model 后,Django 会自动帮你建表
19 # 文件映射类型← 下面定义一个"元组列表",用来限定文件的分类。每个元组 (值, 显示名) 对应下拉选项的一项
20 FILE_TYPES = (← 【常量】FILE_TYPES 是一个元组,包含所有支持的文件类型。在表单下拉框和管理后台中都会用到
21 ('image', '图片'),← 数据库中存 'image',页面上显示"图片"
22 ('document', '文档'),← 文档类型:Word、PDF、PPT 等
23 ('audio', '音频'),← 音频类型:MP3、WAV 等
24 ('video', '视频'),← 视频类型:MP4、AVI 等
25 ('application', '应用'),← 应用程序类型:EXE、APK 等安装包
26 ('text', '文本'),← 纯文本/代码文件:.py、.js、.html 等
27 ('other', '其他')← 兜底类型:不属于以上分类的都归为"其他"
28 )← FILE_TYPES 元组结束
29
30 # 支持的文件拓展名← 下面是一个字典,键=文件类型,值=该类型允许的文件扩展名列表
31 SUPPORTED_EXTENSIONS = {← 【常量】用于上传时校验文件扩展名是否合法,相当于"白名单"
32 'image': ['.jpg', '.jpeg', '.png', ...],← 图片类允许的扩展名列表
33 'document': ['.doc', '.docx', '.pdf', ...],← 文档类允许的扩展名列表
34 'audio': ['.mp3', '.wav', '.flac', ...],← 音频类允许的扩展名列表
35 'video': ['.mp4', '.avi', '.mkv', ...],← 视频类允许的扩展名列表
36 'application': ['.apk', '.exe', ...],← 应用程序类允许的扩展名列表
37 'text': ['.txt', '.py', '.js', ...],← 文本/代码类允许的扩展名列表
38 'other': [...] # 兜底← 其他类型包含所有已知扩展名作为兜底匹配
39 }← SUPPORTED_EXTENSIONS 字典结束
40
41 # ===== 数据库字段定义 =====← 下面每一行对应数据库表中的一列(一个字段)
42 id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)← 【主键】用 UUID 做主键而非自增 ID。好处:ID 不可预测(安全)、全球唯一(不会冲突)。editable=False 表示后台不可编辑
43 name = models.CharField(max_length=255, verbose_name='文件名')← 文件名字段,最大 255 个字符。verbose_name 是后台管理/表单中显示的名称
44 file = models.FileField(upload_to=generate_upload_path, verbose_name='文件')← 文件字段,存储实际的文件。upload_to 调用前面的函数来决定文件存放路径(如 media/2026/05/25/xxx.pdf)
45 file_size = models.PositiveIntegerField(verbose_name='文件大小 (字节)')← 文件大小字段,正整数类型。存储文件的字节数,后面有方法将其格式化为 KB/MB
46 file_type = models.CharField(max_length=20, choices=FILE_TYPES, verbose_name='文件类型')← 文件类型字段,choices=FILE_TYPES 意味着只能从前面定义的 7 种类型中选一个
47 uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name='上传时间')← 上传时间字段。auto_now_add=True 表示创建记录时自动填入当前时间,之后不可更改
48 uploaded_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='上传者')← 【外键】关联到 User 表,表示"谁上传的"。CASCADE 表示如果用户被删除,该用户上传的所有文件记录也一起删除
49 description = models.TextField(blank=True, verbose_name='描述')← 文件描述字段,TextField 支持大段文本。blank=True 表示表单中可以为空不填
50 download_count = models.PositiveIntegerField(default=0, verbose_name='下载次数')← 下载次数计数器,默认为 0。每次有人下载文件时 +1
51 is_deleted = models.BooleanField(default=False, verbose_name='是否已删除')← 软删除标记。True 表示文件已被"删除"(移到回收站),但数据库记录还在
52 deleted_at = models.DateTimeField(null=True, blank=True, verbose_name='删除时间')← 删除时间字段。null=True 表示数据库中可以为空(未删除时就没有删除时间)
53
54 class Meta:← 【元数据类】Meta 是 Django 模型的特殊内部类,用来配置模型的额外信息(如排序、显示名等),不会生成数据库字段
55 verbose_name = '文件'← 单数形式显示名,在 Admin 后台侧边栏显示
56 verbose_name_plural = '文件'← 复数形式显示名。Django 默认会加 's',中文不需要加
57 ordering = ['-uploaded_at']← 默认排序:按上传时间倒序(最新的排最前面)。'-' 前缀表示降序
58
59 def __str__(self):← 定义模型的字符串表示。当 print(file) 或在 Admin 中显示时,会调用这个方法返回文件名
60 return self.name← 返回文件名作为字符串表示
61
62 @property← @property 装饰器:让方法可以像属性一样调用(file.extension 而不是 file.extension())
63 def extension(self):← 获取文件扩展名的属性方法
64 return os.path.splitext(self.name)[1].lower()← splitext 将文件名按 '.' 分成 (名字, 扩展名),取 [1] 即扩展名,lower() 转小写
65
66 @property← 又一个属性装饰器
67 def formatted_size(self):← 将文件大小(字节)转换为人类可读的格式(如 "2.35 MB")
68 size = self.file_size← 获取文件大小的原始字节数
69 for unit in ['B', 'KB', 'MB', 'GB', 'TB']:← 依次尝试 B → KB → MB → GB → TB
70 if size < 1024.0:← 如果当前值小于 1024,就用当前单位
71 return f"{size:.2f} {unit}"← 返回格式化字符串,如 "2.35 MB"(保留 2 位小数)
72 size /= 1024.0← 除以 1024,升到下一个单位
73 return f"{size:.2f} TB"← 兜底:如果文件超过 1024 TB(不太可能),就显示 TB
74
75 @property← 属性装饰器
76 def icon_class(self):← 根据文件类型返回对应的 Bootstrap Icons 图标 CSS 类名
77 type_icons = {← 字典映射:文件类型 → Bootstrap Icons 类名
78 'image': 'bi bi-image',← 图片类型 → 图片图标
79 'document': 'bi bi-file-earmark-text',← 文档类型 → 文档图标
80 'audio': 'bi bi-music-note-beamed',← 音频类型 → 音乐图标
81 'video': 'bi bi-film',← 视频类型 → 胶卷图标
82 'application': 'bi bi-app',← 应用类型 → 应用图标
83 'text': 'bi bi-file-earmark-code',← 文本类型 → 代码文件图标
84 'other': 'bi bi-file-earmark'← 其他类型 → 通用文件图标
85 }← 字典结束
86 return type_icons.get(self.file_type, 'bi bi-file-earmark')← 用 get 方法安全获取图标类名,找不到类型时返回默认的通用文件图标
87
88 @classmethod← @classmethod 装饰器:让方法属于类而非实例。调用方式:File.get_file_type('test.jpg')
89 def get_file_type(cls, filename):← 根据文件名自动判断文件类型。上传时如果没有手动选择类型,就调用这个方法
90 ext = os.path.splitext(filename)[1].lower()← 提取文件扩展名并转小写
91 for file_type, extensions in cls.SUPPORTED_EXTENSIONS.items():← 遍历每种文件类型及其允许的扩展名列表
92 if ext in extensions:← 如果扩展名匹配到了某个类型
93 return file_type← 返回该类型名(如 'image'、'document')
94-100 # (备用 if/elif 硬编码匹配逻辑)← 如果上面的字典匹配失败(理论上不会),用硬编码的 if/elif 再匹配一遍作为兜底
101 else: return 'other'← 实在无法识别就归为 'other'(其他)
102
103
104class DeletedFile(models.Model):← 【第二个模型】DeletedFile 表 = 回收站。当用户"删除"文件时,不是真删,而是在这张表里创建一条记录
105 """已删除文件记录(回收站)"""← 文档字符串:说明这是回收站模型
106 file_name = models.CharField(max_length=255, verbose_name='文件名')← 被删除的文件名(复制过来的副本信息)
107 original_path = models.CharField(max_length=500, verbose_name='原文件路径')← 文件的物理存储路径(如 media/2026/05/25/xxx.pdf),恢复时需要用它找到文件
108 file_size = models.PositiveIntegerField(verbose_name='文件大小 (字节)')← 文件大小
109 file_type = models.CharField(max_length=20, verbose_name='文件类型')← 文件类型(如 image、document)
110 description = models.TextField(blank=True, verbose_name='描述')← 文件描述(从原文件记录复制过来的)
111 deleted_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='删除者')← 外键关联用户表,记录"是谁删除的"。用于限制用户只能恢复/删除自己的回收站文件
112 deleted_at = models.DateTimeField(auto_now_add=True, verbose_name='删除时间')← 删除时间,记录自动填入
113
114 class Meta:← 元数据配置
115 verbose_name = '回收站'← 后台显示名
116 verbose_name_plural = '回收站'← 后台复数显示名
117 ordering = ['-deleted_at']← 按删除时间倒序排列(最近删除的排最前面)
118
119 def __str__(self):← 字符串表示
120 return self.file_name← 返回被删除的文件名
🐍

File_app/forms.py

file_own/File_app/forms.py — 表单定义(用户输入的界面控件)

这个文件是干嘛的? 表单(Form)是用户和数据库之间的"桥梁"。它定义了用户在网页上看到的输入框、下拉框、文件选择器等控件,并负责校验用户提交的数据是否合法。
1from django import forms← 导入 Django 的表单模块,用于创建 HTML 表单
2from .models import File← 从当前目录的 models.py 中导入 File 模型
3from django.contrib.auth.models import User← 导入 Django 自带的用户模型,用于注册表单
4
5
6class FileUploadForm(forms.ModelForm):← 【文件上传表单】继承 ModelForm,自动根据 File 模型生成表单字段,省去手写字段的麻烦
7 file_type = forms.ChoiceField(← 额外添加一个"文件类型"下拉框字段(模型中虽然有这个字段,但我们想自定义选项)
8 choices=[('', '自动识别')] + list(File.FILE_TYPES),← 下拉选项:第一项是"自动识别"(空值),后面拼接模型中定义的 7 种文件类型
9 required=False,← 非必填,用户可以不选(默认"自动识别")
10 label='文件类型',← 表单中显示的标签文字
11 widget=forms.Select(attrs={'class': 'form-select'})← 使用 <select> 下拉框控件,添加 Bootstrap 的 form-select 样式类
12 )← file_type 字段定义结束
13
14 class Meta:← 内部配置类,告诉 Django 这个表单关联哪个模型、显示哪些字段
15 model = File← 关联 File 模型
16 fields = ['file', 'description']← 表单只显示这两个字段(file 文件选择框 + description 描述文本框)
17 widgets = {← 自定义字段的 HTML 控件样式
18 'file': forms.FileInput(attrs={← file 字段用文件上传控件(<input type="file">)
19 'class': 'form-control',← 添加 Bootstrap 的 form-control 样式
20 'accept': '.jpg,.png,.pdf,.exe,...'← 设置 accept 属性,文件选择框默认只显示这些格式(用户仍可切换为"所有文件")
21 }),← file 字段控件配置结束
22 'description': forms.Textarea(attrs={← description 字段用多行文本域(<textarea>)
23 'class': 'form-control',← Bootstrap 样式
24 'rows': 3,← 文本框高度为 3 行
25 'placeholder': '添加文件描述.........'← 灰色占位提示文字
26 })← description 字段控件配置结束
27 }← widgets 字典结束
28
29 def clean_file(self):← 【钩子函数】Django 表单验证的"钩子",命名规则:clean_ + 字段名。验证 file 字段时自动调用
30 file = self.cleaned_data.get('file')← 从已清理的数据中获取用户上传的文件对象
31 if file:← 检查文件是否存在(防止空数据报错)
32 if file.size > 2048 * 1024 * 1024:← 检查文件大小:2048×1024×1024 = 2GB。超过就报错
33 raise forms.ValidationError('文件大小不能超过2GB')← 抛出验证错误,错误信息会自动显示在表单上方
34 ext = file.name.split('.')[-1]← 提取文件后缀名(如 image.jpg → jpg)
35 allowed_extensions = set()← 创建空集合,用于存放所有允许的扩展名
36 for extensions in File.SUPPORTED_EXTENSIONS.values():← 遍历模型中定义的所有类型的扩展名列表
37 allowed_extensions.update(extensions)← 将每种类型的扩展名列表合并到集合中
38 if f".{ext}" not in allowed_extensions:← 检查文件后缀是否在白名单中
39 raise forms.ValidationError(f"不支持的文件类型:.{ext}")← 不在白名单就报错
40 return file← 【重要】验证通过后必须返回 file 对象,否则数据丢失无法保存
41
42
43class UserRegistrationForm(forms.ModelForm):← 【用户注册表单】继承 ModelForm,基于 Django 内置的 User 模型
44 """用户注册表单"""← 文档说明
45 password = forms.CharField(widget=forms.PasswordInput, label='密码')← 密码字段,使用 PasswordInput 控件(输入时显示圆点而非明文)
46 password_confirm = forms.CharField(widget=forms.PasswordInput, label='确认密码')← 确认密码字段,用于让用户再输入一次密码,防止输错
47
48 class Meta:← 表单元数据配置
49 model = User← 关联 Django 内置的 User 模型
50 fields = ['username', 'email', 'password', 'password_confirm']← 表单包含 4 个字段:用户名、邮箱、密码、确认密码
51 widgets = { ... }← 为每个字段设置 Bootstrap 样式和 placeholder 提示文字(结构与上传表单类似,省略)
52
53 def clean(self):← 【全局验证钩子】clean() 会在所有字段验证完后调用,用于跨字段校验
54 cleaned_data = super().clean()← 先调用父类的 clean() 获取已清理的数据
55 password = cleaned_data.get('password')← 获取密码
56 password_confirm = cleaned_data.get('password_confirm')← 获取确认密码
57 if password and password_confirm and password != password_confirm:← 如果两次密码不一致
58 raise forms.ValidationError('两次输入的密码不一致')← 抛出错误提示
59 return cleaned_data← 返回清理后的数据
60
61 def save(self, commit=True):← 重写 save 方法:因为密码不能直接存储明文,需要调用 set_password() 加密
62 user = super().save(commit=False)← 先创建 User 对象但不保存到数据库(commit=False)
63 user.set_password(self.cleaned_data['password'])← 【关键】用 set_password 对密码进行哈希加密后再存储(绝不能存明文密码!)
64 if commit: user.save()← 如果 commit=True,才真正写入数据库
65 return user← 返回创建好的用户对象
🐍

File_app/views.py

file_own/File_app/views.py — 视图函数(业务逻辑的核心)

这个文件是干嘛的? 视图(View)是 Django MVC 架构中的"C(控制器)"。每个视图函数接收一个 HTTP 请求,处理业务逻辑(查数据库、校验权限等),然后返回一个 HTML 页面或 JSON 数据。这是整个项目代码量最大、最核心的文件!
1from django.shortcuts import render, redirect, get_object_or_404← render:渲染模板返回 HTML;redirect:重定向到其他 URL;get_object_or_404:查数据库,找不到就返回 404
2from django.http import HttpResponse, Http404, FileResponse, JsonResponse← Http404:抛出 404 错误;FileResponse:返回文件下载流;JsonResponse:返回 JSON 数据(AJAX 用)
3from django.contrib import messages← 消息框架:在页面间传递一次性提示(如"上传成功!")
4from django.contrib.auth.decorators import login_required← 登录验证装饰器:加在视图函数上面,未登录用户访问时自动跳转到登录页
5from django.core.paginator import Paginator← 分页器:将大量数据分成多页显示
6from django.db.models import Q← Q 对象:用于构建复杂的数据库查询条件(如 OR 查询)
7from django.utils.encoding import escape_uri_path← 对文件名进行 URL 编码,防止中文文件名在下载时乱码
8from django.contrib.auth import login← login 函数:在注册完成后自动登录用户
9from .models import File, DeletedFile← 导入我们定义的两个模型
10from django.utils import timezone← 时区工具,用于获取当前时间(软删除时记录删除时间)
11from .forms import FileUploadForm, UserRegistrationForm← 导入前面定义的两个表单类
12import os← 导入 os 模块,用于文件操作(检查文件是否存在、删除文件等)
13
14
15def home(request):← 【主页视图】显示所有文件列表,支持搜索、按类型过滤、分页
16 files = File.objects.filter(is_deleted=False).order_by('-uploaded_at')← 查询所有未被删除的文件,按上传时间倒序排列
17 search_query = request.GET.get('search', '')← 获取搜索关键词(GET 参数 ?search=xxx),没有则返回空字符串
18 if search_query:← 如果有搜索内容
19 files = files.filter(← 在已有查询结果上进一步过滤
20 Q(name__icontains=search_query) |← Q 对象:文件名包含关键词(icontains = 不区分大小写的包含)
21 Q(description__icontains=search_query) |← 或者描述包含关键词(| 表示 OR 关系)
22 Q(file_type__icontains=search_query)← 或者文件类型包含关键词
23 )← 三个条件满足任一即可(OR 查询)
24 file_type = request.GET.get('type', '')← 获取类型过滤参数(GET 参数 ?type=image)
25 if file_type:← 如果选择了特定类型
26 files = files.filter(file_type=file_type)← 只保留该类型的文件
27 paginator = Paginator(files, 12)← 创建分页器:每页显示 12 条记录
28 page_number = request.GET.get('page')← 获取当前页码(GET 参数 ?page=2)
29 page_obj = paginator.get_page(page_number)← 获取当前页的数据对象(包含数据、页码信息等)
30 context = { 'page_obj': page_obj, ... }← 构造传递给模板的上下文字典
31 return render(request, 'home.html', context)← 用 home.html 模板渲染,返回完整的 HTML 页面
32
33@login_required← 【装饰器】要求用户必须登录,否则跳转到登录页
34def upload_file(request):← 【上传文件视图】处理文件上传的 GET(显示表单)和 POST(提交文件)请求
35 if request.method == 'POST':← 如果是 POST 请求(用户提交了文件)
36 form = FileUploadForm(request.POST, request.FILES)← 用提交的数据填充表单(POST 数据 + FILES 上传文件)
37 if form.is_valid():← 验证表单数据是否合法(文件大小、扩展名等)
38 file_instance = form.save(commit=False)← 创建 File 对象但暂不保存到数据库(还要补充额外字段)
39 file_instance.uploaded_by = request.user← 设置上传者 = 当前登录用户
40 file_instance.name = request.FILES['file'].name← 设置文件名 = 上传文件的原始文件名
41 file_instance.file_size = request.FILES['file'].size← 设置文件大小 = 上传文件的字节数
42 selected_type = form.cleaned_data.get('file_type')← 获取用户选择的文件类型(可能为空 = "自动识别")
43 if selected_type:← 如果用户手动选了类型
44 file_instance.file_type = selected_type← 使用用户选择的类型
45 else:← 否则(选了"自动识别")
46 file_instance.file_type = File.get_file_type(file_instance.name)← 调用模型中的方法自动判断类型
47 file_instance.save()← 现在所有字段都填好了,保存到数据库
48 messages.success(request, '文件上传成功!')← 添加成功提示消息(下次页面加载时显示)
49 return redirect('home')← 重定向到首页
50 else:← 如果是 GET 请求(首次打开上传页面)
51 form = FileUploadForm()← 创建一个空表单
52 return render(request, 'upload.html', {'form': form})← 渲染上传页面模板
53
54def download_file(request, file_id):← 【下载文件视图】根据 file_id 查找文件并以附件形式返回给浏览器
55 file_instance = get_object_or_404(File, id=file_id)← 根据 UUID 查找文件记录,找不到则返回 404
56 file_instance.download_count += 1← 下载计数 +1
57 file_instance.save(update_fields=['download_count'])← 只更新 download_count 字段(优化:不更新其他字段)
58 if not os.path.exists(file_instance.file.path):← 检查物理文件是否还在磁盘上
59 raise Http404("文件不存在")← 文件不在就抛 404
60 response = FileResponse(← 创建文件响应:以二进制流的形式返回文件内容
61 open(file_instance.file.path, 'rb'),← 以二进制只读模式打开文件
62 content_type='application/octet-stream'← 设置 Content-Type 为二进制流,触发浏览器下载而不是在线预览
63 )← FileResponse 结束
64 response['Content-Disposition'] = f'attachment; filename="{escape_uri_path(file_instance.name)}"'← 设置下载时的文件名(escape_uri_path 防止中文乱码)
65 response['Content-Length'] = file_instance.file_size← 设置文件大小,浏览器能显示下载进度
66 return response← 返回文件流
67
68def file_detail(request, file_id):← 【文件详情视图】(当前模板 detail.html 未创建,预留功能)
69 file_instance = get_object_or_404(File, id=file_id)← 根据 ID 获取文件
70 return render(request, 'detail.html', {'file': file_instance})← 渲染详情页模板
71
72def file_list_by_type(request, file_type):← 【按类型筛选视图】根据 URL 中传入的 file_type 参数,只显示该类型的文件列表
73 """按类型列出文件"""← 函数的文档说明
74 files = File.objects.filter(file_type=file_type).order_by('-uploaded_at')← 查询指定类型的所有文件,按上传时间倒序排列(最新的在前)
75
76 paginator = Paginator(files, 12)← 创建分页器,每页显示 12 条记录
77 page_number = request.GET.get('page')← 获取 URL 中的页码参数(如 ?page=2)
78 page_obj = paginator.get_page(page_number)← 获取当前页的数据(包含文件列表、页码、是否有上/下一页等信息)
79
80 context = {← 构造传给模板的上下文数据
81 'page_obj': page_obj,← 当前页的数据对象
82 'file_type': file_type,← 当前的文件类型标识(如 'image')
83 'file_type_display': dict(File.FILE_TYPES).get(file_type, '未知类型'),← 把 'image' 转成 '图片' 这样的中文显示名(用 dict 将元组转为字典查找)
84 }
85 return render(request, 'type_list.html', context)← 用 type_list.html 模板渲染分类文件列表页面
86
87def register(request):← 【用户注册视图】处理用户注册的 GET(显示注册表单)和 POST(提交注册信息)请求
88 """用户注册视图"""← 函数的文档说明
89 if request.method == 'POST':← 如果是 POST 请求(用户提交了注册表单)
90 form = UserRegistrationForm(request.POST)← 用提交的数据填充注册表单
91 if form.is_valid():← 验证表单数据(用户名是否合法、两次密码是否一致等)
92 user = form.save()← 保存用户到数据库(form.save 内部会调用 set_password 加密密码)
93 login(request, user)← 注册成功后自动登录该用户(不用跳转到登录页再输一遍密码)
94 messages.success(request, '注册成功!')← 显示"注册成功"的提示消息
95 return redirect('home')← 重定向到首页
96 else:← 如果是 GET 请求(首次打开注册页面)
97 form = UserRegistrationForm()← 创建一个空的注册表单
98
99 return render(request, 'registration/register.html', {'form': form})← 渲染注册页面模板
100
101@login_required← 必须登录才能访问修改类型页面
102def change_file_type(request, file_id):← 【修改文件类型视图】允许文件上传者更改文件的分类
103 """修改文件类型"""
104 file_instance = get_object_or_404(File, id=file_id)← 根据 UUID 查找文件记录
105
106 # 检查是否为文件上传者← 权限检查:只有上传者本人才能修改文件类型
107 if file_instance.uploaded_by != request.user:← 如果当前登录用户 ≠ 文件上传者
108 messages.error(request, '您只能修改自己上传的文件!')← 显示错误提示
109 return redirect('home')← 直接跳转回首页,拒绝操作
110
111 if request.method == 'POST':← 如果是 POST 请求(用户提交了新类型)
112 new_type = request.POST.get('file_type')← 从表单获取用户选择的新类型
113 if new_type in dict(File.FILE_TYPES):← 验证新类型是否合法(必须在预定义的 7 种类型中)
114 file_instance.file_type = new_type← 更新文件类型
115 file_instance.save()← 保存到数据库
116 messages.success(request, '文件类型已更新!')← 显示成功提示
117 else:← 如果类型不合法
118 messages.error(request, '无效的文件类型!')← 显示错误提示
119 return redirect('home')← 不管成功失败都返回首页
120
121 return render(request, 'file_app/change_type.html', {← GET 请求时渲染修改类型页面
122 'file': file_instance,← 传递当前文件信息(显示文件名、当前类型)
123 'file_types': File.FILE_TYPES← 传递所有可选的文件类型列表
124 })
125
126@login_required← 必须登录才能删除文件
127def delete_file(request, file_id):← 【删除文件视图】软删除:不真正删除文件,而是标记 is_deleted=True 并移入回收站
128 """删除文件(软删除,移入回收站)"""
129 file_instance = get_object_or_404(File, id=file_id)← 查找要删除的文件
130
131 if file_instance.uploaded_by != request.user:← 权限检查:只能删除自己上传的文件
132 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← 判断是否为 AJAX 请求(前端 JS 发来的异步请求)
133 return JsonResponse({'success': False, 'message': '权限不足'})← AJAX 请求返回 JSON 格式的错误信息
134 messages.error(request, '您只能删除自己上传的文件!')← 普通请求用 messages 显示错误
135 return redirect('home')← 返回首页
136
137 if request.method == 'POST':← 只接受 POST 请求(防止误触 GET 链接导致删除)
138 # 创建回收站记录← 在 DeletedFile 表中创建一条记录,保存被删文件的信息
139 DeletedFile.objects.create(← 创建回收站记录
140 file_name=file_instance.name,← 记录文件名
141 original_path=file_instance.file.path,← 记录文件的物理路径(恢复时需要)
142 file_size=file_instance.file_size,← 记录文件大小
143 file_type=file_instance.file_type,← 记录文件类型
144 description=file_instance.description,← 记录文件描述
145 deleted_by=request.user← 记录是谁删除的
146 )
147
148 file_instance.is_deleted = True← 将文件标记为"已删除"(软删除核心逻辑)
149 file_instance.deleted_at = timezone.now()← 记录删除时间
150 file_instance.save()← 保存修改
151
152 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← 如果是 AJAX 请求(前端 JS 无刷新删除)
153 return JsonResponse({'success': True})← 返回 JSON 成功响应,前端 JS 据此移除卡片
154
155 messages.success(request, '文件已移至回收站!')← 普通请求显示成功消息
156
157 return redirect('home')← 返回首页
158
159@login_required← 必须登录才能恢复文件
160def restore_file(request, deleted_id):← 【恢复文件视图】从回收站中恢复被软删除的文件
161 """恢复回收站中的文件"""
162 deleted_file = get_object_or_404(DeletedFile, id=deleted_id)← 根据 deleted_id 查找回收站记录
163
164 if deleted_file.deleted_by != request.user:← 权限检查:只能恢复自己删除的文件
165 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← AJAX 请求返回 JSON 错误
166 return JsonResponse({'success': False, 'message': '权限不足'})
167 messages.error(request, '您只能恢复自己删除的文件!')← 普通请求显示错误消息
168 return redirect('recycle_bin')← 返回回收站页面
169
170 if request.method == 'POST':← 只接受 POST 请求
171 if os.path.exists(deleted_file.original_path):← 先检查磁盘上的物理文件是否还在
172 try:← 尝试恢复文件
173 file_instance = File.objects.get(name=deleted_file.file_name)← 在 File 表中找到对应的文件记录(虽然被软删但记录还在)
174 file_instance.is_deleted = False← 取消删除标记
175 file_instance.deleted_at = None← 清除删除时间
176 file_instance.save()← 保存修改 → 文件恢复成功
177 deleted_file.delete()← 从回收站表中删除该条记录
178
179 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← AJAX 返回成功
180 return JsonResponse({'success': True})
181 messages.success(request, '文件已恢复!')← 普通请求显示成功消息
182 except File.DoesNotExist:← 如果 File 表中的原始记录已不存在
183 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← AJAX 返回错误
184 return JsonResponse({'success': False, 'message': '原文件记录不存在'})
185 messages.error(request, '原文件记录不存在!')← 普通请求显示错误
186 else:← 如果物理文件已不存在(磁盘上被删了)
187 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← AJAX 返回错误
188 return JsonResponse({'success': False, 'message': '物理文件已丢失'})
189 messages.error(request, '物理文件已被删除,无法恢复!')← 普通请求显示错误
190
191 return redirect('recycle_bin')← 返回回收站页面
192
193@login_required← 必须登录才能彻底删除
194def permanent_delete(request, deleted_id):← 【彻底删除视图】从回收站中永久删除文件(物理文件 + 数据库记录全部删除)
195 """彻底删除文件(从回收站永久删除)"""
196 deleted_file = get_object_or_404(DeletedFile, id=deleted_id)← 查找回收站记录
197
198 if deleted_file.deleted_by != request.user:← 权限检查
199 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← AJAX 错误响应
200 return JsonResponse({'success': False, 'message': '权限不足'})
201 messages.error(request, '您只能删除自己的回收站文件!')
202 return redirect('recycle_bin')
203
204 if request.method == 'POST':← 只接受 POST
205 if os.path.exists(deleted_file.original_path):← 检查磁盘上文件是否还在
206 os.remove(deleted_file.original_path)← 【危险操作】删除磁盘上的物理文件(不可恢复!)
207
208 deleted_file.delete()← 删除回收站中的记录
209
210 try:← 尝试删除 File 表中对应的记录
211 file_instance = File.objects.get(name=deleted_file.file_name, is_deleted=True)← 找到已标记删除的原始记录
212 file_instance.delete()← 从数据库中彻底删除该记录
213 except File.DoesNotExist:← 如果记录已经不存在,跳过
214 pass← 什么都不做
215
216 if request.headers.get('x-requested-with') == 'XMLHttpRequest':← AJAX 成功响应
217 return JsonResponse({'success': True})
218 messages.success(request, '文件已彻底删除!')← 普通请求显示成功
219
220 return redirect('recycle_bin')← 返回回收站
221
222@login_required← 必须登录才能查看回收站
223def recycle_bin(request):← 【回收站视图】显示当前用户已删除的文件列表
224 """回收站视图"""
225 deleted_files = DeletedFile.objects.filter(deleted_by=request.user)← 只查询当前用户删除的文件(每人只看自己的回收站)
226
227 paginator = Paginator(deleted_files, 12)← 分页,每页 12 条
228 page_number = request.GET.get('page')← 获取页码
229 page_obj = paginator.get_page(page_number)← 获取当前页数据
230
231 return render(request, 'file_app/recycle_bin.html', {'page_obj': page_obj})← 渲染回收站模板
🐍

File_app/urls.py

file_own/File_app/urls.py — 应用路由(File_app 的 URL 分发器)

这个文件是干嘛的? 它是 File_app 应用内部的"电话分机表"。主路由(file_own/urls.py)把请求转到这里,这里再根据具体的 URL 路径分配到对应的视图函数。每个 Django App 通常都有自己的 urls.py。
1from django.urls import path← 导入 path 函数,用于定义 URL 路由规则
2from . import views← 从当前目录(File_app)导入 views.py 中的所有视图函数
3
4urlpatterns = [← 【路由列表】Django 按顺序从上到下匹配 URL,匹配到第一个就停止
5 path('', views.home, name='home'),← 访问根路径 / → 调用 home 视图(首页文件列表)。name='home' 可在模板中用 {% url 'home' %} 反向解析
6 path('upload/', views.upload_file, name='upload_file'),← 访问 /upload/ → 调用上传文件视图
7 path('download/<uuid:file_id>/', views.download_file, name='download_file'),← 访问 /download/xxx-uuid/ → 下载文件。<uuid:file_id> 是路径参数,Django 自动校验 UUID 格式并传给视图函数
8 path('file/<uuid:file_id>/', views.file_detail, name='file_detail'),← 访问 /file/xxx-uuid/ → 查看文件详情
9 path('type/<str:file_type>/', views.file_list_by_type, name='file_list_by_type'),← 访问 /type/image/ → 按类型筛选文件。<str:file_type> 是字符串参数
10 path('register/', views.register, name='register'),← 访问 /register/ → 用户注册
11 path('file/<uuid:file_id>/change_type/', views.change_file_type, name='change_file_type'),← 访问 /file/xxx/change_type/ → 修改文件类型
12 path('file/<uuid:file_id>/delete/', views.delete_file, name='delete_file'),← 访问 /file/xxx/delete/ → 软删除文件(移入回收站)
13 path('recycle-bin/', views.recycle_bin, name='recycle_bin'),← 访问 /recycle-bin/ → 查看回收站
14 path('recycle-bin/<int:deleted_id>/restore/', views.restore_file, name='restore_file'),← 访问 /recycle-bin/123/restore/ → 恢复文件。<int:deleted_id> 是整数参数
15 path('recycle-bin/<int:deleted_id>/permanent-delete/', views.permanent_delete, name='permanent_delete')← 访问 /recycle-bin/123/permanent-delete/ → 彻底删除文件
16]← 路由列表结束
🐍

File_app/admin.py

file_own/File_app/admin.py — Django 后台管理配置

这个文件是干嘛的? Django 自带一个强大的后台管理界面(访问 /admin/),这个文件就是告诉 Django"如何在后台管理 File 模型"。配置好后,管理员可以在后台直接查看、搜索、编辑文件记录,无需编写额外的页面。
1from django.contrib import admin← 导入 Django 的后台管理模块
2from .models import File← 导入我们要管理的 File 模型
3
4@admin.register(File)← 【装饰器】将 File 模型注册到后台管理。等价于 admin.site.register(File, FileAdmin)
5class FileAdmin(admin.ModelAdmin):← 自定义 File 模型在后台的行为和显示方式
6 list_display = ['name', 'file_type', 'formatted_size', 'uploaded_by', 'uploaded_at', 'download_count']← 后台列表页显示哪些列(文件名、类型、大小、上传者、时间、下载次数)
7 list_filter = ['file_type', 'uploaded_at', 'uploaded_by']← 后台右侧的过滤器:可按类型、时间、上传者筛选
8 search_fields = ['name', 'description']← 后台顶部的搜索框:可按文件名和描述搜索
9 readonly_fields = ['id', 'file_size', 'download_count', 'uploaded_at']← 只读字段:这些字段在编辑页面中显示但不能修改(如 UUID、文件大小等自动生成的值)
10 date_hierarchy = 'uploaded_at'← 在列表页顶部添加日期导航条(可按年/月/日快速筛选)
11
12 fieldsets = (← 将编辑页面的字段分组显示,让后台更整洁
13 ('基本信息', {← 第一组:基本信息
14 'fields': ('id', 'name', 'file', 'description', 'file_type')← 这组包含的字段
15 }),
16 ('统计信息', {← 第二组:统计信息
17 'fields': ('file_size', 'download_count', 'uploaded_at', 'uploaded_by'),← 这组包含的字段
18 'classes': ('collapse',)← 默认折叠这组字段(点击展开),因为统计信息不常修改
19 }),
20 )
21
22 def formatted_size(self, obj):← 自定义列:调用模型的 formatted_size 属性显示人类可读的文件大小(如 "2.5 MB")
23 return obj.formatted_size← 返回 @property 计算好的格式化大小
24
25 formatted_size.short_description = '文件大小'← 设置该列在后台的表头名称
🐍

migrations/0001_initial.py

file_own/File_app/migrations/0001_initial.py — 初始数据库迁移

这个文件是干嘛的? 这是 Django 自动生成的迁移文件,相当于"数据库建表脚本"。当你执行 python manage.py migrate 时,Django 就会根据这个文件在数据库中创建对应的表。永远不要手动修改迁移文件!
1# Generated by Django 5.2.x← 注释:由 Django 自动生成
2from django.conf import settings← 导入项目配置(需要知道 User 模型的位置)
3from django.db import migrations, models← 导入迁移工具和模型字段类型
4import django.db.models.deletion← 导入外键的删除行为模块(CASCADE 等)
5import uuid← 导入 uuid 模块(因为 File 模型用 UUID 做主键)
6
7class Migration(migrations.Migration):← 迁移类,继承自 migrations.Migration
8 initial = True← 标记为初始迁移(第一次建表)
9 dependencies = [← 依赖关系:这个迁移必须在哪些迁移之后执行
10 migrations.swappable_dependency(settings.AUTH_USER_MODEL),← 依赖 User 模型的迁移(因为 File 表有外键指向 User 表,必须先建 User 表)
11 ]
12 operations = [← 操作列表:要执行的数据库操作
13 migrations.CreateModel(← 创建一张新表
14 name='File',← 表名(对应 File 模型)
15 fields=[← 字段列表(对应模型中的每个字段)
16 ('id', models.UUIDField(...)),← UUID 主键字段
17 ('name', models.CharField(max_length=255)),← 文件名字段
18 ('file', models.FileField(...)),← 文件路径字段
19 # ... 其余字段省略 ...← 每个模型字段都会在这里对应一条
20 ],
21 ),
22 ]← 操作列表结束
🐍

migrations/0002_*.py

file_own/File_app/migrations/0002_*.py — 添加软删除字段迁移

这个文件是干嘛的? 当你在模型中新增了 is_deleteddeleted_at 字段后,运行 makemigrations 就会生成这个迁移文件。它告诉数据库:"请在已有的 File 表上新增这两列"。
1class Migration(migrations.Migration):← 第二次迁移
2 dependencies = [← 依赖上一次迁移(0001_initial)
3 ('File_app', '0001_initial'),← 必须在 0001 之后执行
4 ]
5 operations = [← 操作列表
6 migrations.AddField(model_name='file', name='is_deleted', ...),← 给 File 表添加 is_deleted 列(布尔型,默认 False)
7 migrations.AddField(model_name='file', name='deleted_at', ...),← 给 File 表添加 deleted_at 列(日期时间型,允许为空)
8 migrations.AlterModelOptions(name='file', ...),← 修改模型的 Meta 选项(如排序方式)
9 ]
🐍

migrations/0003_*.py

file_own/File_app/migrations/0003_*.py — 字段修改迁移

这个文件是干嘛的? 当修改了已有字段的属性(如修改 verbose_name、修改 max_length 等),Django 会生成这个迁移文件来同步数据库的变更。
1class Migration(migrations.Migration):← 第三次迁移
2 dependencies = [
3 ('File_app', '0002_alter_file_...'),← 依赖第二次迁移
4 ]
5 operations = [← 操作列表
6 migrations.AlterField(model_name='file', name='file_size', ...),← 修改 file_size 字段的属性
7 migrations.AlterModelOptions(...),← 修改模型的 Meta 选项
8 ]
🎨

templates/base.html

file_own/templates/base.html — 基础模板(所有页面的"母版")

这个文件是干嘛的? 这是整个网站的"骨架模板"。所有其他页面都通过 {% extends 'base.html' %} 继承它,这样就无需在每个页面重复写导航栏、页脚、CSS/JS 引用。Django 的模板继承机制让代码复用变得非常简单。
1{% load static %}← 加载 Django 的静态文件标签库。加载后才能使用 {% static '...' %} 来引用 CSS/JS/图片
2<!DOCTYPE html>← 声明文档类型为 HTML5
3<html lang="zh-CN">← HTML 根标签,lang="zh-CN" 告诉浏览器这是简体中文页面
4<head>← 头部区域:包含元信息、CSS 引用、页面标题等(不会显示在页面上)
5 <meta charset="UTF-8">← 字符编码设为 UTF-8,支持中文等多语言字符
6 <meta name="viewport" ...>← 视口设置:让页面在手机/平板上也能正确显示(响应式设计的基础)
7 <title>文件存储系统</title>← 浏览器标签页上显示的默认标题
8 <!-- Bootstrap 5 CSS -->← 引入 Bootstrap 5 的样式表(CDN 方式),提供现成的按钮、卡片、导航栏等组件样式
9 <link rel="stylesheet" href="https://cdn...bootstrap.min.css">← Bootstrap 5 CSS 文件链接
10 <!-- Bootstrap Icons -->← 引入 Bootstrap 图标库,提供 1000+ 免费图标(如 bi-upload、bi-download 等)
11 <link rel="stylesheet" href="https://cdn...bootstrap-icons.css">← Bootstrap Icons CSS 文件链接
12 <link rel="stylesheet" href="{% static 'css/style.css' %}">← 引入项目自定义的 CSS 文件。{% static %} 标签会自动解析正确的 URL 路径
13 <link rel="shortcut icon" href="{% static 'images/ico.png' %}">← 设置浏览器标签页的小图标(favicon)
14 <title>{% block title %}文件存储系统{% endblock %}</title>← 【模板块】子页面可以用 {% block title %}新标题{% endblock %} 覆盖默认标题
15</head>← 头部区域结束
16<body>← 页面主体开始(所有可见内容都在这里)
17 <nav class="navbar navbar-expand-lg navbar-dark bg-primary">← 【导航栏】Bootstrap 的响应式导航栏组件。navbar-dark=浅色文字,bg-primary=蓝色背景
18 <div class="container">← Bootstrap 容器:居中且限宽,让内容不会撑满整个屏幕
19 <a class="navbar-brand" href="{% url 'home' %}">← 导航栏的品牌/Logo 链接,点击回到首页。{% url 'home' %} 自动解析为 '/'
20 <i class="bi bi-cloud-arrow-up"></i>文件存储系统← 图标 + 文字(Bootstrap Icons 的云上传图标)
21 </a>
22 <button class="navbar-toggler" ...>← 手机端的"汉堡菜单"按钮(小屏幕下折叠导航栏)
23 <div class="collapse navbar-collapse">← 可折叠的导航内容区域
24 <ul class="navbar-nav me-auto">← 左侧导航链接列表
25 <li><a href="{% url 'home' %}">首页</a></li>← "首页"链接
26 <li><a href="{% url 'upload_file' %}">上传文件</a></li>← "上传文件"链接
27 {% if user.is_authenticated %}← 【条件判断】如果用户已登录,才显示"回收站"链接
28 <li><a href="{% url 'recycle_bin' %}">回收站</a></li>← "回收站"链接(仅登录可见)
29 {% endif %}← 条件判断结束
30 </ul>← 左侧导航结束
31 <ul class="navbar-nav">← 右侧导航链接(用户信息区域)
32 {% if user.is_authenticated %}← 如果用户已登录
33 <span>欢迎,{{ user.username }}</span>← 显示"欢迎,用户名"。{{ user.username }} 是 Django 模板变量语法
34 <form action="{% url 'logout' %}" method="post">← 退出登录表单(用 POST 防止 CSRF 攻击)
35 {% csrf_token %}← CSRF 令牌:Django 的安全机制,防止跨站请求伪造
36 <button type="submit">退出</button>← 退出按钮
37 </form>
38 {% else %}← 如果用户未登录
39 <a href="{% url 'login' %}">登录</a>← 显示"登录"链接
40 <a href="{% url 'register' %}">注册</a>← 显示"注册"链接
41 {% endif %}← 条件判断结束
42 </ul>← 右侧导航结束
43 </div>
44 </div>
45 </nav>← 导航栏结束
46
47 <main class="container mt-4">← 主内容区域。container=居中限宽,mt-4=上边距 4 级
48 {% if messages %}← 如果有消息(如"上传成功"、"删除失败"等提示)
49 {% for message in messages %}← 遍历所有消息
50 <div class="alert alert-{{ message.tags }}">← 消息提示框。message.tags 会是 success/error/warning 等,对应不同颜色
51 {{ message }}← 显示消息内容
52 <button class="btn-close"></button>← 关闭按钮(Bootstrap 自带关闭动画)
53 </div>
54 {% endfor %}← 循环结束
55 {% endif %}← 条件结束
56
57 {% block content %}← 【核心】内容块占位。子页面的内容会填充到这里(如首页的文件列表、上传页的表单等)
58 {% endblock %}← 内容块结束
59 </main>← 主内容区域结束
60
61 <footer class="bg-light text-center">← 页脚区域
62 <p>&copy;2026 文件储存系统</p>← 版权信息
63 </footer>← 页脚结束
64
65 <form id="csrf-form" style="display:none;">← 隐藏的 CSRF 表单:供 AJAX 请求获取 CSRF Token 使用
66 {% csrf_token %}← 生成一个隐藏的 CSRF input 标签
67 </form>
68
69 <script src="https://cdn...bootstrap.bundle.min.js"></script>← 引入 Bootstrap JS(包含下拉菜单、模态框等交互功能)
70 <script src="{% static 'js/script.js' %}"></script>← 引入项目自定义的 JavaScript 文件
71 {% block extra_js %}{% endblock %}← 【额外 JS 块】子页面可以在这里添加自己独有的 JavaScript 代码
72</body>← 页面主体结束
73</html>← HTML 文档结束
🎨

templates/home.html

file_own/templates/home.html — 首页模板(文件列表 + 搜索 + 分页)

这个文件是干嘛的? 这是网站的首页,也是最重要的页面。它展示了所有已上传的文件卡片,支持搜索、按类型过滤、分页浏览,以及 AJAX 无刷新删除功能。
1{% extends 'base.html' %}← 继承 base.html 基础模板。本页面只需填充 content 块,导航栏/页脚等由 base 提供
2
3{% block title %}文件列表 - 文件存储系统{% endblock %}← 覆盖 base.html 中的 title 块,设置本页标题
4
5{% block content %}← 开始填充内容块(以下所有内容都会出现在 base.html 的 {% block content %} 位置)
6<div class="row mb-4">← Bootstrap 栅格系统:row 是一行,mb-4=下边距
7 <div class="col-md-8">← 左侧 8/12 宽度区域:显示标题和描述
8 <h1><i class="bi bi-files"></i> 文件存储系统</h1>← 页面大标题 + 文件图标
9 <p>安全,高效的文件存储和分享平台</p>← 副标题描述文字
10 </div>
11 <div class="col-md-4">← 右侧 4/12 宽度区域:上传按钮
12 <a href="{% url 'upload_file' %}" class="btn btn-primary btn-lg">← 上传按钮,点击跳转到上传页面
13 上传文件
14 </a>
15 </div>
16</div>
17
18{# 搜索和过滤 #}← Django 模板注释:搜索和过滤区域
19<div class="card mb-4">← Bootstrap 卡片组件,包裹搜索框
20 <form method="GET" class="row g-3">← 搜索表单,用 GET 方法(参数会显示在 URL 中,方便分享和书签)
21 <input type="text" name="search" value="{{ search_query }}">← 搜索输入框。value="{{ search_query }}" 保持搜索词不丢失
22 <select name="type">← 类型筛选下拉框
23 <option value="">所有类型</option>← 默认选项:不过滤类型
24 {% for key, display in file_types %}← 遍历所有文件类型(7 种)
25 <option value="{{ key }}">{{ display }}</option>← 每个类型生成一个 option(如 value="image" 显示"图片")
26 {% endfor %}← 循环结束
27 </select>
28 <button type="submit">搜索</button>← 搜索按钮
29 </form>
30</div>
31
32{# 文件列表 #}← 文件卡片网格区域
33<div class="row">← 一行文件卡片
34 {% for file in page_obj %}← 遍历当前页的文件列表(page_obj 是分页后的数据)
35 <div class="col-xl-3 col-lg-4 col-md-6 mb-4" id="file-card-{{ file.id }}">← 响应式列:大屏4列/中屏3列/小屏2列。id 用于 AJAX 删除时定位卡片
36 <div class="card h-100 shadow-sm">← 文件卡片。h-100=等高,shadow-sm=轻微阴影
37 <i class="{{ file.icon_class }}"></i>← 文件图标。icon_class 是模型的 @property,根据类型返回不同图标类名
38 <h6>{{ file.name|truncatechars:20 }}</h6>← 文件名。truncatechars:20 是模板过滤器,超过 20 字符截断显示"..."
39 <small>{{ file.formatted_size }}</small>← 文件大小(如 "2.5 MB"),调用模型的 @property
40 <p>{{ file.description|default:"无描述" }}</p>← 文件描述。default 过滤器:如果描述为空,显示"无描述"
41 <small>{{ file.uploaded_at|date:'m-d H:i' }}</small>← 上传时间,date 过滤器格式化为"月-日 时:分"
42 <a href="{% url 'download_file' file.id %}">下载</a>← 下载按钮。file.id 是 UUID,自动传给 URL 路由
43 {% if user == file.uploaded_by %}← 只有上传者本人才能看到修改/删除按钮
44 <a href="{% url 'change_file_type' file.id %}">修改类型</a>← 修改类型按钮
45 <button onclick="confirmDelete('{{ file.id }}', '{{ file.name }}')">删除</button>← 删除按钮:点击调用 JS 的 confirmDelete 函数,弹出确认框
46 {% endif %}← 权限判断结束
47 </div>
48 </div>
49 {% empty %}← 【空状态】如果文件列表为空(没有任何文件或搜索无结果),显示以下内容
50 <h4>暂无文件</h4>← 空状态提示
51 {% endfor %}← for 循环结束
52</div>
53
54{# 分页 #}← 分页导航区域
55{% if page_obj.has_other_pages %}← 如果总页数 > 1 页才显示分页
56<nav>← HTML5 导航语义标签
57 <ul class="pagination">← Bootstrap 分页组件
58 {% if page_obj.has_previous %}← 如果有上一页
59 <a href="?page={{ page_obj.previous_page_number }}...">上一页</a>← 上一页链接(URL 中保留搜索和类型参数)
60 {% endif %}
61 <span>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页</span>← 显示"第 X / Y 页"
62 {% if page_obj.has_next %}← 如果有下一页
63 <a href="?page={{ page_obj.next_page_number }}...">下一页</a>← 下一页链接
64 {% endif %}
65 </ul>
66</nav>
67{% endif %}
68
69{# 删除确认模态框 #}← Bootstrap 模态框弹窗(确认删除用)
70<div class="modal fade" id="deleteModal">← 模态框容器。fade=淡入动画,id 用于 JS 控制显示/隐藏
71 <h5>确认删除</h5>← 弹窗标题
72 <p>确定要将 "<strong id="modal-file-name"></strong>" 移至回收站吗?</p>← 弹窗内容。JS 会动态填入文件名
73 <button id="btn-confirm-delete">移至回收站</button>← 确认删除按钮(JS 绑定 AJAX 删除逻辑)
74</div>
75{% endblock %}← content 块结束
76
77{% block extra_js %}← 开始填充额外 JS 块(首页独有的 JavaScript 代码)
78<script>
79 let currentFileId = null;← 全局变量:存储当前要删除的文件 ID
80 let deleteModal = null;← 全局变量:存储 Bootstrap 模态框实例
81
82 function confirmDelete(fileId, fileName) {← 点击删除按钮时调用:打开确认弹窗
83 currentFileId = fileId;← 记住要删除的文件 ID
84 document.getElementById('modal-file-name').textContent = fileName;← 在弹窗中显示文件名
85 deleteModal.show();← 显示模态框
86 }
87
88 function executeDelete() {← 点击"确认删除"按钮时执行 AJAX 删除
89 const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;← 从隐藏表单中获取 CSRF Token
90 fetch(`/file/${currentFileId}/delete/`, {← 发送 AJAX POST 请求到删除接口
91 method: 'POST',← 请求方法为 POST
92 headers: {← 请求头
93 'X-CSRFToken': csrfToken,← 携带 CSRF Token(安全验证)
94 'X-Requested-With': 'XMLHttpRequest'← 告诉后端这是 AJAX 请求
95 }
96 })
97 .then(data => {← 请求成功后
98 const card = document.getElementById(`file-card-${currentFileId}`);← 找到对应的文件卡片 DOM 元素
99 card.style.opacity = '0';← 淡出动画:透明度变为 0
100 setTimeout(() => card.remove(), 400);← 400ms 后从 DOM 中移除卡片(无刷新删除效果)
101 });
102 }
103</script>
104{% endblock %}← extra_js 块结束
🎨

templates/upload.html

file_own/templates/upload.html — 文件上传页面

这个文件是干嘛的? 文件上传页面的模板。用户在这里选择文件、填写描述、选择文件类型,然后点击上传。表单使用了 Django 的 ModelForm 自动生成控件。
1{% extends 'base.html' %}← 继承基础模板
2
3{% block title %}上传文件 - 文件存储系统{% endblock %}← 页面标题
4
5{% block content %}← 开始内容块
6<div class="row justify-content-center">← 居中布局
7 <div class="col-md-8">← 内容宽度 8/12
8 <div class="card shadow">← 卡片容器
9 <div class="card-header bg-primary text-white">← 卡片头部:蓝色背景白色文字
10 <h4>上传文件</h4>← 标题
11 </div>
12 <div class="card-body">← 卡片内容区域
13 <form method="post" enctype="multipart/form-data">← 【关键】enctype="multipart/form-data" 是文件上传表单必须的属性,否则文件无法提交
14 {% csrf_token %}← CSRF 安全令牌
15 <label>选择文件</label>← 文件选择标签
16 {{ form.file }}← Django 自动渲染的文件上传控件(<input type="file">)
17 <div class="form-text">← 帮助文字
18 支持的文件格式:图片、文档、音频、视频...最大 2GB← 告诉用户支持哪些格式和大小限制
19 </div>
20 {{ form.file_type }}← 文件类型选择下拉框(含"自动识别"选项)
21 {{ form.description }}← 文件描述多行文本框
22 <a href="{% url 'home' %}">返回</a>← 返回按钮
23 <button type="submit">上传文件</button>← 提交按钮
24 </form>
25 </div>
26 </div>
27 </div>
28</div>
29{% endblock %}← 内容块结束
🎨

templates/type_list.html

file_own/templates/type_list.html — 按类型分类的文件列表页

这个文件是干嘛的? 当用户点击某个文件类型(如"图片")时,跳转到此页面,显示该类型下的所有文件。页面布局和首页类似,但只显示单一类型的文件。
1{% extends 'base.html' %}← 继承基础模板
2{% load static %}← 加载静态文件标签
3
4{% block content %}← 内容块开始
5<h2>{{ category_name }}</h2>← 页面标题:显示分类名称(如"图片")
6<p>共有 {{ files|length }} 个文件</p>← 显示文件总数。|length 是模板过滤器,返回列表长度
7<a href="{% url 'home' %}">返回总览</a>← 返回首页链接
8
9{% if files %}← 如果有文件
10 {% for file in files %}← 遍历文件列表
11 <div class="card">← 每个文件一个卡片
12 {% if file.file_type == 'Image' %}← 如果是图片类型
13 <img src="{{ file.file.url }}">← 直接显示图片缩略图
14 {% else %}← 其他类型
15 <i class="bi bi-file-earmark"></i>← 显示通用文件图标
16 {% endif %}
17 <h5>{{ file.original_name }}</h5>← 显示文件名
18 <a href="{% url 'download_file' file.id %}">下载文件</a>← 下载按钮
19 </div>
20 {% endfor %}← 文件列表循环结束
21{% else %}← 【空状态】如果该分类下没有文件
22 <p>该分类下暂无文件</p>← 空状态提示文字
23 <a href="{% url 'upload_file' %}">去上传</a>← 引导用户去上传页面
24{% endif %}← 条件判断结束
25
26<style>← 页面内嵌样式(仅在此页面生效)
27 .hover-shadow:hover {← 鼠标悬停时的阴影效果
28 transform: translateY(-5px);← 向上移动 5px(卡片"浮起"效果)
29 transition: all 0.3s ease;← 过渡动画 0.3 秒(平滑过渡)
30 }
31</style>
32{% endblock %}← content 块结束
🎨

templates/registration/login.html

file_own/templates/registration/login.html — 用户登录页面

这个文件是干嘛的? 用户登录页面。放在 registration/ 目录下是 Django 的惯例——Django 内置的认证系统(auth)会自动查找这个路径下的 login.html。用户输入用户名和密码后提交表单,Django 会自动验证并登录。
1{% extends 'base.html' %}← 继承基础模板,复用导航栏和页脚
2
3{% block content %}← 开始内容块
4<div class="row justify-content-center">← Bootstrap 栅格:居中显示登录卡片
5 <div class="col-md-6">← 占屏幕宽度的一半(6/12)
6 <div class="card">← Bootstrap 卡片组件
7 <div class="card-header">← 卡片头部
8 <h3>用户登录</h3>← 页面标题
9 </div>
10 <div class="card-body">← 卡片内容区域
11 {% if form.errors %}← 如果表单有错误(用户名或密码不对)
12 <div class="alert alert-danger">← 红色警告提示框
13 用户名或密码错误,请重试。← 错误提示文字
14 </div>
15 {% endif %}← 条件结束
16 <form method="post" action="{% url 'login' %}">← 表单提交到 Django 内置的 login 视图(POST 方式)
17 {% csrf_token %}← CSRF 安全令牌(防止跨站请求伪造)
18 <input type="hidden" name="next" value="{{ next }}">← 隐藏字段:登录后自动跳转回之前访问的页面(Django 自动传入)
19 <label>用户名</label>← 用户名标签
20 <input type="text" name="username" required>← 用户名输入框。name="username" 是 Django auth 要求的字段名
21 <label>密码</label>← 密码标签
22 <input type="password" name="password" required>← 密码输入框。type="password" 让输入内容显示为圆点
23 <button type="submit">登录</button>← 登录按钮
24 </form>
25 </div>
26 <div class="card-footer text-center">← 卡片底部(居中对齐)
27 <p>还没有账号?<a href="{% url 'register' %}">注册</a></p>← 引导未注册用户去注册页面
28 </div>
29 </div>
30 </div>
31</div>
32{% endblock %}← content 块结束
🎨

templates/registration/register.html

file_own/templates/registration/register.html — 用户注册页面

这个文件是干嘛的? 用户注册页面。新用户在这里填写用户名、邮箱、密码(含确认密码),提交后系统会创建新账号。表单由 views.py 中的 UserRegistrationForm 提供支持密码验证等功能。
1{% extends 'base.html' %}← 继承基础模板
2
3{% block content %}← 内容块开始
4<div class="row justify-content-center">← 居中布局
5 <div class="col-md-6">← 半宽卡片
6 <div class="card">← Bootstrap 卡片
7 <div class="card-header">
8 <h3>用户注册</h3>← 页面标题
9 </div>
10 <div class="card-body">
11 {% if form.errors %}← 如果表单验证有错误
12 <div class="alert alert-danger">← 红色错误提示框
13 <ul>← 错误列表(可能有多条错误,如"密码不一致"、"用户名已存在"等)
14 {% for field in form %}← 遍历表单的每个字段
15 {% for error in field.errors %}← 遍历当前字段的所有错误
16 <li>{{ error }}</li>← 显示每条错误信息
17 {% endfor %}
18 {% endfor %}
19 {% for error in form.non_field_errors %}← 非字段级别的错误(如表单整体的验证错误)
20 <li>{{ error }}</li>← 显示非字段错误
21 {% endfor %}
22 </ul>
23 </div>
24 {% endif %}← 错误判断结束
25 <form method="post">← 注册表单(POST 提交到 register 视图)
26 {% csrf_token %}← CSRF 令牌
27 <label>用户名</label>← 用户名标签
28 {{ form.username }}← Django 自动渲染用户名输入框(UserRegistrationForm 中定义的字段)
29 <label>邮箱</label>← 邮箱标签
30 {{ form.email }}← 邮箱输入框
31 <label>密码</label>← 密码标签
32 {{ form.password }}← 密码输入框
33 <label>确认密码</label>← 确认密码标签
34 {{ form.password_confirm }}← 确认密码输入框(后端会校验两次密码是否一致)
35 <button type="submit">注册</button>← 注册按钮
36 </form>
37 </div>
38 <div class="card-footer text-center">← 卡片底部
39 <p>已有账号?<a href="{% url 'login' %}">登录</a></p>← 引导已有用户去登录页面
40 </div>
41 </div>
42 </div>
43</div>
44{% endblock %}← content 块结束
🎨

templates/file_app/change_type.html

file_own/templates/file_app/change_type.html — 修改文件类型页面

这个文件是干嘛的? 允许文件的上传者修改文件的分类类型。比如上传时系统自动识别为"文档",但用户想改为"代码"类型,就可以在这里手动修改。
1{% extends 'base.html' %}← 继承基础模板
2
3{% block content %}← 内容块开始
4<div class="row justify-content-center">← 居中布局
5 <div class="col-md-6">← 半宽卡片
6 <div class="card">← Bootstrap 卡片
7 <div class="card-header">
8 <h4>修改文件类型</h4>← 页面标题
9 </div>
10 <div class="card-body">
11 <p><strong>文件名:</strong>{{ file.name }}</p>← 显示当前文件名
12 <p><strong>当前类型:</strong>{{ file.get_file_type_display }}</p>← 显示当前类型的中文名称(调用 Django 的 get_FOO_display 方法)
13 <form method="post">← 表单提交(POST 方式)
14 {% csrf_token %}← CSRF 安全令牌
15 <label>选择新类型</label>← 下拉框标签
16 <select name="file_type">← 文件类型下拉选择框
17 {% for key, display in file_types %}← 遍历所有可选的文件类型(7 种)
18 <option value="{{ key }}"← 每个 option 的 value 是类型代码
19 {% if file.file_type == key %}selected{% endif %}>← 如果当前类型和这个 option 匹配,加上 selected(默认选中)
20 {{ display }}← 显示中文类型名(如"图片"、"视频"等)
21 </option>
22 {% endfor %}← 循环结束
23 </select>
24 <a href="{% url 'home' %}">取消</a>← 取消按钮:回到首页
25 <button type="submit">保存</button>← 保存按钮:提交修改
26 </form>
27 </div>
28 </div>
29 </div>
30</div>
31{% endblock %}← content 块结束
🎨

templates/file_app/recycle_bin.html

file_own/templates/file_app/recycle_bin.html — 回收站页面(恢复 + 彻底删除)

这个文件是干嘛的? 回收站页面,展示所有已被"软删除"的文件。用户可以:① 恢复文件(移回正常列表)② 彻底删除(永久移除,不可恢复)。两个操作都通过 AJAX 实现无刷新交互。这是项目中最复杂的模板之一,包含大量 JavaScript 交互逻辑。
1{% extends 'base.html' %}← 继承基础模板
2
3{% block content %}← 内容块开始
4<div class="row mb-4">← 页面头部行
5 <div class="col-md-8">← 左侧:标题和描述
6 <h1><i class="bi bi-trash"></i> 回收站</h1>← 回收站标题 + 垃圾桶图标
7 <p>已删除的文件会保留在这里,您可以恢复或彻底删除</p>← 说明文字
8 </div>
9 <div class="col-md-4 text-end">← 右侧:返回按钮(右对齐)
10 <a href="{% url 'home' %}">返回主页</a>← 返回首页链接
11 </div>
12</div>
13
14<div class="row" id="recycle-list">← 回收站文件列表容器。id 用于 JS 定位
15 {% for deleted_file in page_obj %}← 遍历当前页的已删除文件(分页数据)
16 <div class="col-xl-3 col-lg-4 col-md-6 mb-4" id="file-card-{{ deleted_file.id }}">← 响应式卡片列。id 用于 AJAX 操作后定位并移除卡片
17 <div class="card h-100 shadow-sm border-danger">← 文件卡片。border-danger=红色边框(表示已删除)
18 <div class="card-body text-center">← 卡片内容(居中排版)
19 <i class="bi bi-file-earmark-x"></i>← "已删除文件"图标(带 X 的文件图标)
20 <h6>{{ deleted_file.file_name|truncatechars:20 }}</h6>← 文件名(最多 20 字符)
21 <small>{{ deleted_file.file_size|filesizeformat }}</small>← 文件大小。filesizeformat 过滤器自动转为 KB/MB/GB
22 <p>{{ deleted_file.description|truncatechars:50|default:"无描述" }}</p>← 文件描述(最多 50 字符,空则显示"无描述")
23 <small>{{ deleted_file.deleted_at|date:'Y-m-d H:i' }}</small>← 删除时间
24 </div>
25 <div class="card-footer bg-white">← 卡片底部:操作按钮区域
26 <form class="restore-form" data-id="{{ deleted_file.id }}">← 恢复表单。data-id 存储文件 ID(JS 通过 dataset.id 获取)
27 {% csrf_token %}← CSRF 令牌
28 <button type="submit" class="btn btn-success">恢复</button>← 绿色恢复按钮(JS 拦截表单提交,改用 AJAX)
29 </form>
30 <button onclick="showPermanentDeleteModal('{{ deleted_file.id }}', '{{ deleted_file.file_name }}')">← 红色彻底删除按钮(弹出确认框)
31 </div>
32 </div>
33 </div>
34 {% empty %}← 【空状态】回收站为空时显示
35 <h4>回收站为空</h4>← 空状态提示
36 <p>没有已删除的文件</p>
37 {% endfor %}← 循环结束
38</div>
39
40{% if page_obj.has_other_pages %}← 分页导航(和首页类似)
41<nav>
42 <ul class="pagination justify-content-center">← 居中的分页组件
43 {% if page_obj.has_previous %}
44 <a href="?page={{ page_obj.previous_page_number }}">上一页</a>← 上一页
45 {% endif %}
46 <span>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页</span>← 页码信息
47 {% if page_obj.has_next %}
48 <a href="?page={{ page_obj.next_page_number }}">下一页</a>← 下一页
49 {% endif %}
50 </ul>
51</nav>
52{% endif %}
53
54{# 彻底删除确认模态框 #}← Bootstrap 模态框:确认彻底删除的弹窗
55<div class="modal fade" id="permanentDeleteModal">← 模态框容器
56 <h5>彻底删除</h5>← 弹窗标题
57 <button class="btn-close" data-bs-dismiss="modal"></button>← 关闭按钮(Bootstrap 内置属性,点击关闭弹窗)
58 <p>确定要永久删除 "<strong id="permanent-file-name"></strong>" 吗?</p>← 弹窗内容(JS 动态填入文件名)
59 <p class="text-danger fw-bold">此操作不可恢复!</p>← 红色警告:提醒用户不可恢复
60 <button data-bs-dismiss="modal">取消</button>← 取消按钮
61 <button id="confirm-permanent-delete-btn">彻底删除</button>← 确认彻底删除按钮(JS 绑定 AJAX 请求)
62</div>
63{% endblock %}← content 块结束
64
65{% block extra_js %}← 额外 JS 块开始(回收站独有的 JavaScript 代码)
66<script>
67 function getCookie(name) {← 辅助函数:从浏览器 Cookie 中获取指定值
68 let cookieValue = null;← 初始值为 null
69 if (document.cookie && document.cookie !== '') {← 如果浏览器有 Cookie
70 const cookies = document.cookie.split(';');← 将所有 Cookie 按分号拆分为数组
71 for (let i = 0; i < cookies.length; i++) {← 遍历每个 Cookie
72 const cookie = cookies[i].trim();← 去除前后空格
73 if (cookie.substring(0, name.length + 1) === (name + '=')) {← 如果 Cookie 名匹配
74 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));← 解码并获取 Cookie 值
75 break;← 找到后跳出循环
76 }
77 }
78 }
79 return cookieValue;← 返回 Cookie 值
80 }
81 const csrftoken = getCookie('csrftoken');← 获取 Django 设置的 CSRF Cookie(AJAX 请求需要携带)
82
83 // --- 恢复功能 AJAX ---← 恢复文件的 AJAX 逻辑
84 document.querySelectorAll('.restore-form').forEach(form => {← 找到所有恢复表单并逐个绑定事件
85 form.addEventListener('submit', function(e) {← 监听表单提交事件
86 e.preventDefault();← 阻止默认的表单提交行为(改用 AJAX)
87 const fileId = this.dataset.id;← 从 data-id 属性获取文件 ID
88 const btn = this.querySelector('.restore-btn');← 获取按钮元素
89 const originalText = btn.innerHTML;← 保存按钮原始文字(用于恢复)
90 btn.disabled = true;← 禁用按钮(防止重复点击)
91 btn.innerHTML = '加载动画...';← 显示加载动画(spinner-border 旋转圈)
92 fetch(`/recycle-bin/${fileId}/restore/`, {← 发送 AJAX POST 请求到恢复接口
93 method: 'POST',← POST 方法
94 headers: {
95 'X-CSRFToken': csrftoken,← CSRF Token
96 'X-Requested-With': 'XMLHttpRequest'← 标记为 AJAX 请求
97 }
98 })
99 .then(data => {← 请求成功后
100 if (data.success) {← 如果后端返回 success=true
101 const card = document.getElementById(`file-card-${fileId}`);← 找到对应的卡片
102 card.style.transition = 'all 0.5s ease';← 设置过渡动画
103 card.style.opacity = '0';← 淡出效果
104 setTimeout(() => card.remove(), 500);← 500ms 后从 DOM 移除(无刷新消失)
105 } else {← 如果恢复失败
106 alert('恢复失败');← 弹出错误提示
107 btn.innerHTML = originalText;← 恢复按钮文字
108 btn.disabled = false;← 重新启用按钮
109 }
110 });
111 });
112 });
113
114 // --- 彻底删除功能 AJAX ---← 彻底删除的 AJAX 逻辑
115 let currentPermanentDeleteId = null;← 存储当前要彻底删除的文件 ID
116 const permDeleteModal = new bootstrap.Modal(...);← 创建 Bootstrap 模态框实例
117
118 function showPermanentDeleteModal(fileId, fileName) {← 点击删除按钮时调用
119 currentPermanentDeleteId = fileId;← 记住要删除的文件 ID
120 document.getElementById('permanent-file-name').textContent = fileName;← 在弹窗中显示文件名
121 permDeleteModal.show();← 打开弹窗
122 }
123
124 document.getElementById('confirm-permanent-delete-btn')← 获取"彻底删除"确认按钮
125 .addEventListener('click', function() {← 点击时执行彻底删除
126 const btn = this;← 按钮引用
127 btn.disabled = true;← 禁用按钮
128 btn.innerHTML = '删除中...';← 显示加载状态
129 fetch(`/recycle-bin/${currentPermanentDeleteId}/permanent-delete/`, {← 发送 AJAX POST 请求到彻底删除接口
130 method: 'POST',
131 headers: { 'X-CSRFToken': csrftoken }← 携带 CSRF Token
132 })
133 .then(data => {
134 if (data.success) {← 删除成功
135 const card = document.getElementById(`file-card-${currentPermanentDeleteId}`);← 找到卡片
136 card.style.opacity = '0';← 淡出
137 card.style.transform = 'scale(0.9)';← 缩小效果(增强视觉反馈)
138 setTimeout(() => card.remove(), 500);← 500ms 后移除
139 permDeleteModal.hide();← 关闭弹窗
140 } else {← 删除失败
141 alert('删除失败');← 弹出错误提示
142 }
143 })
144 .finally(() => {← 无论成功或失败都执行
145 btn.disabled = false;← 恢复按钮可用
146 btn.innerHTML = originalText;← 恢复按钮文字
147 });
148 });
149</script>
150{% endblock %}← extra_js 块结束
🎨

static/css/style.css

file_own/static/css/style.css — 项目自定义样式表

这个文件是干嘛的? 项目的自定义 CSS 样式文件。在 Bootstrap 的基础上添加额外的样式定制,如文件图标大小、卡片悬停动画、按钮圆角、加载动画、进度条样式等。这些样式让网站更有个性,不只是 Bootstrap 的默认外观。
1/* 自定义样式 */← CSS 注释:标记以下为自定义样式
2body {← body 标签的样式(应用到整个页面)
3 background-color: #f8f9fa;← 页面背景色:浅灰色(比纯白更柔和)
4}
5
6.file-icon {← 文件图标的容器样式
7 width: 80px;← 宽度 80px
8 height: 80px;← 高度 80px
9 margin: 0 auto;← 水平居中(margin: 0 auto 是经典的居中技巧)
10 display: flex;← Flexbox 弹性布局
11 align-items: center;← 垂直居中
12 justify-content: center;← 水平居中(Flex 方式)
13 border-radius: 10px;← 圆角 10px
14 background-color: rgba(13, 110, 253, 0.1);← 浅蓝色半透明背景(Bootstrap 主色的 10% 透明度)
15}
16
17.card {← 所有卡片的样式
18 transition: transform 0.2s, box-shadow 0.2s;← 过渡动画:变形和阴影变化各 0.2 秒(让动画平滑)
19}
20
21.card:hover {← 鼠标悬停在卡片上时的样式(伪类选择器)
22 transform: translateY(-5px);← 向上移动 5px(卡片"浮起"效果)
23 box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important;← 更深的阴影(增加立体感)。!important 覆盖 Bootstrap 默认值
24}
25
26.navbar-brand {← 导航栏品牌文字样式
27 font-weight: bold;← 加粗
28}
29
30.btn {← 所有按钮的样式
31 border-radius: 8px;← 圆角 8px(比 Bootstrap 默认的 4px 更圆润)
32}
33
34.form-control:focus,← 输入框获得焦点时的样式(用户点击输入框时)
35.form-select:focus {← 下拉框获得焦点时的样式
36 border-color: #86b7fe;← 边框变为浅蓝色
37 box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);← 蓝色发光效果(提示用户当前正在编辑哪个输入框)
38}
39
40/* 响应式设计 */← 媒体查询:针对小屏幕设备的样式调整
41@media (max-width: 768px) {← 当屏幕宽度 ≤ 768px(手机/小平板)时应用以下样式
42 .file-icon {← 小屏幕上缩小文件图标
43 width: 60px; height: 60px;← 缩小到 60px
44 }
45 .card-body {← 小屏幕上缩小卡片内边距
46 padding: 1rem;← 内边距缩小为 1rem
47 }
48}
49
50/* 加载动画 */← 按钮加载状态的旋转动画
51.loading {← 加载指示器样式
52 display: inline-block;← 行内块元素(可以和文字同行)
53 width: 20px; height: 20px;← 大小 20px
54 border: 3px solid rgba(255,255,255,.3);← 半透明白色边框(圆环底色)
55 border-radius: 50%;← 50% 圆角 → 变成圆形
56 border-top-color: #fff;← 顶部边框为纯白色(旋转时产生"缺口"效果)
57 animation: spin 1s ease-in-out infinite;← 应用 spin 动画:1 秒一圈,无限循环
58}
59
60@keyframes spin {← 定义旋转动画
61 to { transform: rotate(360deg); }← 从 0° 旋转到 360°(一圈)
62}
63
64/* 进度条样式 */← 上传进度条的样式
65.progress {← 进度条外框
66 height: 10px;← 高度 10px
67 border-radius: 5px;← 圆角
68}
69.progress-bar {← 进度条内部填充
70 border-radius: 5px;← 同样的圆角(保持外观一致)
71}

static/js/script.js

file_own/static/js/script.js — 项目公共 JavaScript 脚本

这个

static/js/script.js

file_own/static/js/script.js — 项目公共 JavaScript 脚本

这个文件是干嘛的? 项目的公共 JavaScript 文件,被 base.html 引入到所有页面中。主要包含两个功能:① 文件上传时自动禁用按钮并显示"上传中"加载动画,防止重复提交;② 选择文件后在控制台输出文件信息(预览/调试用)。
1// 文件上传进度处理← 注释:说明本段代码的功能
2document.addEventListener('DOMContentLoaded', function() {← 【核心事件】当 HTML 文档完全加载完毕后执行回调函数(类似 jQuery 的 $(document).ready())
3 // 文件上传表单处理← 注释:处理上传表单
4 const uploadForm = document.querySelector('form[method="post"]');← 查找页面中 method="post" 的表单(即上传表单)。querySelector 返回第一个匹配的元素
5 if(uploadForm) {← 如果找到了上传表单(只有在上传页才会存在)
6 uploadForm.addEventListener('submit', function(e) {← 监听表单的 submit(提交)事件
7 const submitBtn = this.querySelector('button[type="submit"]');← 找到表单中的提交按钮
8 if(submitBtn) {← 如果按钮存在
9 submitBtn.innerHTML = '<span class="loading"></span> 上传中...';← 替换按钮内容为加载动画 + "上传中..."文字(利用 style.css 中定义的 .loading 旋转动画)
10 submitBtn.disabled = true;← 禁用按钮(防止用户重复点击导致多次提交)
11 }
12 });
13 }
14
15 // 文件预览← 注释:文件选择后的预览/调试功能
16 const fileInput = document.querySelector('input[type="file"]');← 查找文件上传控件(<input type="file">)
17 if(fileInput) {← 如果找到了文件输入框
18 fileInput.addEventListener('change', function(e) {← 监听 change 事件(用户选择文件后触发)
19 const file = e.target.files[0];← 获取用户选择的第一个文件(File 对象,包含 name、size 等属性)
20 if(file) {← 如果确实选择了文件
21 console.log(`选择了文件: ${file.name}, 大小: ${file.size} bytes`);← 在浏览器控制台输出文件信息(调试用)。模板字符串用反引号和 ${} 插入变量
22 }
23 });
24 }
25});← DOMContentLoaded 事件回调结束
26
27// 确认下载← 注释:下载确认功能
28function confirmDownload(url, fileName) {← 全局函数:下载前弹出确认框。可在 HTML 的 onclick 中调用
29 if(confirm(`确定要下载文件 "${fileName}" 吗?`)) {← confirm() 弹出浏览器原生确认对话框,返回 true/false
30 window.location.href = url;← 用户确认后,跳转到下载 URL(触发文件下载)
31 }
32}← 函数结束

📋 Django file_own 项目总结

🐍 Django 5.2 🗄️ MySQL + PyMySQL 🎨 Bootstrap 5 ⚡ AJAX 无刷新交互 🔐 用户认证系统 📁 文件上传/下载/回收站
核心模块 models.py → views.py → urls.py → templates/
数据流向 用户请求 → URL 路由 → 视图函数 → 模型操作数据库 → 模板渲染响应
安全机制 CSRF Token + 登录验证 @login_required + 软删除模式
前端交互 Bootstrap 5 组件 + fetch API (AJAX) + CSS 动画
文件总数 25 个核心文件(已全部注释完毕 ✅)

— 文档完毕 · 共 25 个文件 · 逐行中文注释 —