ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django和MySQL的旅游攻略论坛系统开发实战

基于Django和MySQL的旅游攻略论坛系统开发实战 简介这是一套基于Python Django框架开发的旅游攻略论坛交流系统毕业设计源码案例面向计算机相关专业学生及Django全栈学习者。系统完整覆盖前台与后台前台支持游客浏览、用户注册登录登录后可发布包含文字和图片的旅游攻略也可在论坛的综合交流、旅游心得、杂谈等板块发帖互动能够关注感兴趣的用户并在个人主页管理关注内容、自己的帖子和评论支持编辑资料、上传头像以及通过邮箱或手机验证码绑定账号后台则提供用户、攻略、帖子等全部信息的增删改查与集中管理。技术栈为PyCharm Django3.0 Python3.7 MySQL5.6代码分层清晰前端基于Bootstrap等组件构建适合理解Django MTV开发模式。资源包共341个文件以Python后端源码、JavaScript交互逻辑、CSS样式与HTML页面模板为主导辅以JPG/PNG图片素材、SQL数据库脚本和字体资源压缩包大小约12.84MB目录结构清晰可直接导入PyCharm运行。目前已有220人学习浏览适合作为毕业设计或课程设计参考也可当作Django全栈开发实战练习配套SQL脚本可快速初始化数据库前后端交互完整便于二次功能拓展与主题移植。1. 一套旅游攻略论坛帮你把毕业设计从选题拖到答辩答辩现场最怕的不是功能少而是被评委一句“你这个论坛和别人有什么区别”问住。这个基于 Django 的旅游攻略论坛交流系统把用户注册登录、攻略发布、评论交流、收藏、目的地分类检索串成一条完整链路数据落在 MySQL 里开发调试全程在 PyCharm 中进行属于毕业设计里最稳的一类选题不追新框架不堆微服务把 PyCharm Django MySQL 这条主流路线吃透就能讲清楚每个环节的来龙去脉。适合做 Web 方向毕设的在校生也适合想快速搭一个内容型社区原型的后端入行者。你要做的不是照抄代码而是把这条链路里“为什么这样设计”想明白答辩时才有话可说。2. 环境与项目骨架PyCharm、Django、MySQL 怎么搭才不折腾2.1 版本搭配Python 3.10 Django 4.2 LTS MySQL 8.0 的理由做这个系统环境选型比写代码更影响心态。我常用的组合是 Python 3.10 Django 4.2 LTS MySQL 8.0。Django 的 LTS 版本维护周期长网上的教程、毕设论文参考案例基本都是围绕 LTS 版本写的遇到报错搜解决方案的成功率明显更高。MySQL 8.0 是当前新机器默认安装的版本自带 Workbench建库、看表、跑 SQL 都方便。PyCharm 直接用 Community 社区版就够它免费开放支持 Python 调试、Django 模板和数据库面板毕业设计这个规模完全覆盖得住没必要在这个点上额外花钱。版本搭配的隐藏教训是别装太新的 Django 主版本。每年都有同学从官网 pip 下来最新版结果教材里的写法在新版里已经调整路由声明、settings 配置、admin 样式全都不一样最后改代码的时间比写代码还长。锁定 LTS 版本等于把不确定性降到最低。MySQL 驱动这块也别急着装 mysqlclient后面第 5 章会说到它在 Windows 上编译容易翻车我一般直接用 PyMySQL省事。2.2 创建项目和 appvenv、django-admin、startapp 的顺序虚拟环境必须建这是第一个要养成的习惯。同一个电脑上可能同时有多个 Django 项目依赖版本互相干扰是常事venv 把每个项目的依赖隔离到独立目录里删了重来也不影响其他项目。# 创建虚拟环境。Windows 下激活命令是 venv\Scripts\activate python -m venv venv source venv/bin/activate # macOS / Linux 写法 # 安装依赖Django 锁定 LTS 版本PyMySQL 做数据库驱动Pillow 给图片字段用 pip install django4.2.* pymysql Pillow # 创建项目 travel_forum再创建一个名为 posts 的应用 django-admin startproject travel_forum cd travel_forum python manage.py startapp postsdjango-admin startproject 生成的是项目级配置目录startapp 生成的是业务应用目录。论坛的攻略、评论、分类这些功能都放在 posts 这个 app 里项目级只做全局配置和路由分发。PyMySQL 在这里替代了 mysqlclient 的角色它是纯 Python 写的 MySQL 驱动pip 安装不会碰 C 编译器这也是我推荐它的核心原因。Pillow 是 Django ImageField 的依赖后面模型里要传封面图提前装好省得再回头补。2.3 settings.py 三处必改INSTALLED_APPS、DATABASES、语言时区建好项目后第一个要打开的文件就是 settings.py。新手最容易忽略的是把新创建的 app 加进 INSTALLED_APPS不加的话后面 makemigrations 会提示 No changes detected白折腾半天。# travel_forum/settings.py 中需要改动的位置 # 1. 把 posts 加进应用列表 INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, posts, # 新加的 app ] # 2. 数据库切到 MySQL import pymysql pymysql.install_as_MySQLdb() # 让 Django 通过 PyMySQL 访问 MySQL DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: travel_forum, # 数据库名需要提前建好 USER: root, PASSWORD: 这里填你的MySQL密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } } # 3. 语言与时区。单机毕设直接关掉 UTC 更省心 LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ FalseENGINE 固定写成 django.db.backends.mysqlDjango 靠这个字符串决定用哪个数据库方言生成 SQL。NAME 是数据库名不是表名USER 和 PASSWORD 对应 MySQL 的登录账号。OPTIONS 里 charset 必须写 utf8mb4mysql 默认的 utf8 在 MySQL 8.0 里是个别名实际存不了 emoji 和生僻字攻略正文里出现表情符号时用 utf8mb4 才不会报数据截断错误。USE_TZ False 这句在很多教材里没讲透。它的作用是让 Django 不再把所有时间先转成 UTC 再存进 MySQL而是直接把本地时间写入数据库。单机、单时区的毕设系统根本用不到 UTC 转换机制关掉之后时间字段的取值和展示完全一致省掉后面一整套时区换算的麻烦。2.4 建库、迁移、启动从 0 到看到 Django 火箭页配置完数据库先在 MySQL 里把库建出来这一步不进 Django直接在 MySQL 命令行或 Workbench 里执行。注意字符集要跟 settings 里的 charset 保持一致。# 在 MySQL 中先建库字符集务必指定 utf8mb4 mysql -u root -p CREATE DATABASE travel_forum DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;回到项目目录后先跑 migrate 生成 Django 内置的表。注意 migrate 和 makemigrations 的区别makemigrations 是根据 models.py 生成迁移脚本文件migrate 才是把迁移脚本真正应用进数据库。第一次跑 migrate 时Django 自带的 auth、admin、session 这些模块的表会一并建好。python manage.py migrate python manage.py runserver浏览器打开 http://127.0.0.1:8000看到 Django 的火箭欢迎页说明项目骨架已经通了。但要确认 MySQL 链路真的通建议再执行一条语句python manage.py shell -c from django.db import connection cursor connection.cursor() cursor.execute(SELECT VERSION()) print(cursor.fetchone()) 能打印出类似 (8.0.36,) 的 MySQL 版本号说明 Django 和 MySQL 之间的连接、驱动、账号权限全部正常。这一步是很多人的盲区——runserver 能起来不代表数据库通因为 Django 默认会在第一次真正执行 SQL 时才去连 MySQL而不是启动时就连。3. 数据模型设计用户、攻略、评论、收藏怎样用外键串起来3.1 先画实体关系四张核心表是论坛的骨架论坛系统的核心是内容。围绕内容拆表最稳的切法是四张业务表加一张用户表。用户直接复用 Django 自带的 auth.User它已经带了用户名、密码哈希、邮箱、权限组省掉自己写注册登录的核心逻辑。攻略表 Post 是主表负责标题、正文、目的地分类、作者、发布时间、浏览量评论表 Comment 挂在 Post 下面一条攻略可以有多条评论收藏表 Favorite 是用户和攻略之间的多对多关系但不要图省事用 Django 的 ManyToManyField 直接挂在 User 上——单独建表能记录“谁在什么时候收藏了什么”后面做“我的收藏”页面、判断当前用户是否已收藏都方便得多。外键关系上要注意 on_delete 的选型。Post.author 指向 User 用 CASCADE用户注销时他的攻略和评论连带清掉符合常规理解Post.category 指向 Category 用 SET_NULL分类被删时帖子保留但 category 字段置空避免误删内容。Comment.post 用 CASCADE帖子没了评论自然没有存在的意义。Favorite 表用 UniqueConstraint 约束 user 和 post 的组合唯一确保同一个人不能重复收藏同一条攻略。3.2 models.py 关键代码与字段参数拆解把下面这份代码放进 posts/models.py基本覆盖了一个旅游攻略论坛的核心数据模型from django.db import models from django.contrib.auth.models import User from django.utils import timezone class Category(models.Model): name models.CharField(分类名, max_length50, uniqueTrue) slug models.SlugField(URL标识, max_length60, uniqueTrue) desc models.TextField(描述, blankTrue) class Meta: verbose_name 目的地分类 verbose_name_plural verbose_name ordering [name] def __str__(self): return self.name class Post(models.Model): author models.ForeignKey(User, on_deletemodels.CASCADE, related_nameposts, verbose_name作者) title models.CharField(标题, max_length120) content models.TextField(正文) category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue, blankTrue, related_nameposts, verbose_name目的地) city models.CharField(城市, max_length50, blankTrue) cover models.ImageField(封面图, upload_tocovers/%Y/%m/, blankTrue, nullTrue) views models.PositiveIntegerField(浏览量, default0) created models.DateTimeField(发布时间, defaulttimezone.now) updated models.DateTimeField(更新时间, auto_nowTrue) class Meta: ordering [-created] indexes [ models.Index(fields[category, created], nameidx_category_created), ] def __str__(self): return self.title class Comment(models.Model): post models.ForeignKey(Post, on_deletemodels.CASCADE, related_namecomments, verbose_name攻略) author models.ForeignKey(User, on_deletemodels.CASCADE, related_namecomments, verbose_name评论者) content models.TextField(评论内容) created models.DateTimeField(评论时间, defaulttimezone.now) class Meta: ordering [created] def __str__(self): return f{self.author} 评论 {self.post} class Favorite(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namefavorites, verbose_name用户) post models.ForeignKey(Post, on_deletemodels.CASCADE, related_namefavorited_by, verbose_name攻略) created models.DateTimeField(收藏时间, defaulttimezone.now) class Meta: constraints [ models.UniqueConstraint(fields[user, post], nameunique_user_post_favorite), ]字段参数里最值得讲的是 related_name。它决定了反向查询的名字没写 related_name 时想拿“某条攻略的所有评论”得写 post.comment_set.all()名字丑且容易忘指定 related_namecomments 后直接 post.comments.all() 就行。同理user.posts.all() 拿某人发布的所有攻略user.favorites.all() 拿某人收藏的所有攻略。这个命名在第 4 章写视图时能省很多事。on_delete 参数是答辩时的高频问题五种取值里毕设场景主要用前两种on_delete 取值行为适用场景CASCADE删除主表记录时连带删除引用它的记录用户注销清空其攻略和评论SET_NULL主表删除后外键字段置为 NULL分类删除后保留帖子内容PROTECT存在引用时禁止删除主表记录有核心价值的分类不允许删SET_DEFAULT主表删除后外键恢复默认值极少用到需要字段有默认值DO_NOTHING数据库层不做任何操作可能导致孤儿数据不建议Meta 里的 ordering 对应 MySQL 的 ORDER BY。Post 的 ordering [-created] 表示默认按发布时间倒序最新攻略排最前Comment 的 ordering [created] 则让评论按时间正序排列符合论坛楼层逻辑。Indexes 里我加了一个 category 和 created 的复合索引答辩时可以说“这个索引支撑了按分类筛选并按时间排序的分页查询”数据量上万后这个索引的收益才明显但说出来是加分项。3.3 迁移、后台注册与测试数据让页面先有内容可看模型写完之后执行迁移命令把表建进 MySQL。顺序不要颠倒先 makemigrations 生成迁移文件再 migrate 应用python manage.py makemigrations posts python manage.py migrate接着在 posts/admin.py 里注册模型后台管理页面才能操作这些表。travel_forum 这类系统通常需要管理员审核内容admin 是保留功能from django.contrib import admin from .models import Category, Post, Comment, Favorite admin.register(Category) class CategoryAdmin(admin.ModelAdmin): prepopulated_fields {slug: (name,)} admin.register(Post) class PostAdmin(admin.ModelAdmin): list_display (title, author, category, created) list_filter (category, created) search_fields (title, content) admin.site.register(Comment) admin.site.register(Favorite)prepopulated_fields 会在后台录入分类时自动根据 name 生成 slug这是 Django admin 提供的自动 URL 标识生成能力。list_display 决定后台列表显示哪些列list_filter 在右侧生成筛选器search_fields 给列表页加搜索框。最后创建管理员账号并顺手造一条测试数据后面调页面时不用对着空列表发呆python manage.py createsuperuser # 通过 shell 创建一条测试攻略 python manage.py shell -c from django.contrib.auth.models import User from posts.models import Post u User.objects.create_user(demo, passworddemo123456) Post.objects.create(authoru, title杭州两日游攻略, content西湖-灵隐-九溪路线如下...) print(测试数据已创建) 这条命令同时展示了 Django 执行查询-删除对象的反向链如果执行 u.delete()由于 Post.author 用了 CASCADE这个用户的全部攻略会被连带删除。理解这一点答辩时被问到“删除用户会不会留下孤儿数据”就能直接答上来。4. 核心功能实现从列表页到发布评论的完整请求链路4.1 路由设计/post/、/post/new/、/comment/ 的 URL 规划Django 的路由配置主线是浏览器请求 URLURLconf 匹配到视图函数视图操作模型最后渲染模板。每个 app 的路由建议写在 app 内部的 urls.py 里再用 include 挂到项目主路由避免所有路径堆在一个文件里越来越乱。# posts/urls.py from django.urls import path from . import views app_name posts urlpatterns [ path(, views.post_list, namelist), path(post/int:pk/, views.post_detail, namedetail), path(post/new/, views.post_create, namecreate), path(post/int:pk/comment/, views.post_comment, namecomment), path(post/int:pk/favorite/, views.toggle_favorite, namefavorite), path(post/int:pk/delete/, views.post_delete, namedelete), path(category/slug:slug/, views.category_posts, namecategory), ]path 里 int:pk 是路径转换器int 限定这个位置只能匹配数字Django 会自动把它转成 int 类型传给视图函数slug 匹配字母、数字、连字符和下划线适合放分类标识。name 参数给路由起别名模板里通过 {% url posts:detail post.id %} 反向解析 URL这样即使以后改了 URL 结构模板代码不用动。app_name posts 是命名空间项目里如果还有 users、orders 等其他 app路由重名时靠它区分。主路由 travel_forum/urls.py 里只需一行 includefrom django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(posts.urls)), ]4.2 视图层列表分页、详情计数、评论与收藏的写法视图是整套系统的业务核心。先把列表、详情、发布这三个最基础的视图写出来from django.shortcuts import render, get_object_or_404, redirect from django.core.paginator import Paginator from django.contrib.auth.decorators import login_required from django.contrib import messages from .models import Post, Comment, Category, Favorite def post_list(request): q request.GET.get(q, ).strip() posts Post.objects.select_related(author, category).all() if q: posts posts.filter(title__icontainsq) paginator Paginator(posts, 10) page paginator.get_page(request.GET.get(page)) return render(request, posts/list.html, {posts: page, q: q}) def post_detail(request, pk): post get_object_or_404( Post.objects.select_related(author, category), pkpk) post.views 1 post.save(update_fields[views]) comments post.comments.select_related(author).all() return render(request, posts/detail.html, {post: post, comments: comments}) login_required def post_create(request): if request.method POST: title request.POST.get(title, ).strip() content request.POST.get(content, ).strip() category_id request.POST.get(category) or None if title and content: Post.objects.create(authorrequest.user, titletitle, contentcontent, category_idcategory_id) messages.success(request, 攻略已发布) return redirect(posts:list) categories Category.objects.all() return render(request, posts/create.html, {categories: categories})select_related 是这里最关键的性能写法。post_list 里如果不加它模板每显示一条攻略的作者和分类Django 就会额外执行一次查询——10 条攻略就是 1 10 10 次查询这就是经典的 N1 查询问题数据量上来之后页面会肉眼可见地慢。select_related 通过 SQL 的 JOIN 一次性把 author 和 category 查出来整个列表页只需要 1 次查询。update_fields[views] 是 detail 视图里的细节。浏览量自增时只需要更新 views 这一个字段不写 update_fields 的话Django 会把整行所有字段都写一遍。get_object_or_404 的语义是找到了就返回对象找不到直接抛 404比手动 try/except 干净。再补上评论提交、收藏切换和删除三个操作型视图login_required def post_comment(request, pk): post get_object_or_404(Post, pkpk) if request.method POST: content request.POST.get(content, ).strip() if content: Comment.objects.create(postpost, authorrequest.user, contentcontent) return redirect(posts:detail, pkpk) login_required def toggle_favorite(request, pk): post get_object_or_404(Post, pkpk) fav, created Favorite.objects.get_or_create(userrequest.user, postpost) if not created: fav.delete() return redirect(posts:detail, pkpk) login_required def post_delete(request, pk): post get_object_or_404(Post, pkpk) if post.author ! request.user: messages.error(request, 只能删除自己发布的攻略) return redirect(posts:detail, pkpk) post.delete() return redirect(posts:list)get_or_create 返回两个值对象本身和一个布尔值 createdTrue 表示新建False 表示已存在。收藏按钮第一次点击是收藏第二次点击是取消收藏逻辑就这样两行实现。post_delete 里先校验 post.author 和 request.user 是否一致这是内容型论坛最基本的权限判断——不校验的话任何登录用户都能删别人的帖子这是答辩时一定会被追问的安全漏洞。4.3 模板要点CSRF、表单回显和分页控件模板层不需要写得多华丽但三个细节必须处理到位表单要带 CSRF 令牌、列表页翻页要带上搜索参数、评论表单提交后要回到原帖。!-- list.html 里的分页片段 -- {% if posts.has_previous %} a href?page{{ posts.previous_page_number }}q{{ q|urlencode }}上一页/a {% endif %} span第 {{ posts.number }} / {{ posts.paginator.num_pages }} 页/span {% if posts.has_next %} a href?page{{ posts.next_page_number }}q{{ q|urlencode }}下一页/a {% endif %}这里最容易忽略的是把 q 参数拼进翻页链接。搜索“杭州”后翻到第二页如果不带 q第二页就会回到全量列表用户以为搜索结果丢了。urlencode 过滤器保证搜索词里含特殊字符时 URL 仍然合法。详情页的评论表单长这样form methodpost action{% url posts:comment post.id %} {% csrf_token %} textarea namecontent required placeholder写下你的评论/textarea button typesubmit发布评论/button /form{% csrf_token %} 是 Django 的表单令牌Django 默认开启了 CSRF 中间件所有 POST 请求不带这个令牌会直接返回 403。这个机制在答辩时被问“你怎么防跨站请求伪造”时直接回答“Django 的 CSRF 中间件 模板中的 csrf_token 令牌”就行。4.4 权限与安全login_required 和 CSRF 不能省上面的操作型视图都加了 login_required 装饰器它的作用是未登录用户访问这些 URL 时自动跳转到登录页。跳转目标需要在 settings.py 里指定# settings.py LOGIN_URL /accounts/login/登录页本身可以直接用 Django auth 模块内置的 LoginView不需要自己写认证逻辑# 项目主路由 urls.py from django.contrib.auth import views as auth_views urlpatterns [ path(accounts/login/, auth_views.LoginView.as_view( template_nameposts/login.html), namelogin), ]LoginView 自带表单校验、CSRF 防护、登录成功后跳转 next 参数的功能比自己手写 session 操作安全得多。整个系统的安全底线就三条所有写操作必须登录、所有 POST 表单必须带 CSRF 令牌、删除和修改操作必须校验对象归属。这三条做到已经能挡住绝大多数毕设答辩会提到的攻击场景。5. Django MySQL 避坑清单五个高发连接问题的排查顺序5.1 mysqlclient 在 Windows 上编译失败现象pip install mysqlclient 直接报错日志里出现 Microsoft Visual C 14.0 is required或者编译到一半报一堆红字找不到 mysql.h 头文件。原因mysqlclient 是 C 扩展安装时需要本机的 MySQL C 客户端库和 Visual C 编译环境。Windows 上默认没有这套环境即使装了也不一定对得上 MySQL 版本这是 Windows 上跑 Django MySQL 最常见的开局翻车点。解决别跟编译环境较劲两步切换到 PyMySQL。先卸载已经失败的 mysqlclient 尝试再把下面两行加到 settings.py 顶部# settings.py 第一行 import pymysql pymysql.install_as_MySQLdb()install_as_MySQLdb() 会把 PyMySQL 注册成 Django 认识的 MySQLdb 模块之后 ENGINE 还是写 django.db.backends.mysql代码不用再改。这套做法在 Django 4.2 和 PyMySQL 1.x 组合下很稳跑 migrate 和 runserver 都不会有感知差异。5.2 MySQL 8.0 认证插件导致连接报错现象migrate 时报错 Authentication plugin caching_sha2_password cannot be loaded或者 MySQL 客户端能登录但 Django 连不上报 2059 错误。原因MySQL 8.0 默认的账号认证插件改成了 caching_sha2_password而旧版本的 PyMySQL 或 mysqlclient 只认识老的 mysql_native_password两者对不上。解决优先升级 PyMySQL 到 1.0 以上新版驱动已经支持 caching_sha2_password。如果升级了还报错检查 MySQL 版本和现有账号的认证插件-- 查看当前 root 账号使用的认证插件 SELECT user, host, plugin FROM mysql.user WHERE user root; -- 需要临时改回老插件时执行 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;要注意MySQL 8.4 及以上版本已经开始默认禁用 mysql_native_password这条路会越走越窄所以新装环境优先升级驱动不要一上来就改认证插件。5.3 时间显示差 8 小时或更多现象admin 后台看到的发布时间比实际时间少一截或者页面显示的时间和数据库里存的不一致。原因USE_TZ True 时Django 在数据库里统一存 UTC 时间渲染页面时才转换成本地时间。如果代码里混用了 datetime.now() 和 timezone.now()进入数据库的就是混合时区的时间显示自然乱。这是把“Django 时区机制”和“本地时间直觉”混在一起导致的典型问题。解决单机毕设系统直接在 settings.py 里关掉 UTC 转换# settings.py TIME_ZONE Asia/Shanghai USE_TZ False设置后 Django 会把 TIME_ZONE 指定的本地时间直接写入 MySQL不再做 UTC 转换。如果以后项目要部署到海外服务器、面向多时区用户再考虑改回 USE_TZ True 并统一用 timezone.now() 生成时间但毕设场景完全不需要承担这套复杂度。5.4 改了模型却提示 No changes detected现象改了 models.py 加了字段或新表执行 makemigrations posts 时却提示 No changes detectedmigrate 后数据库也没变化。原因最常见是 app 没有注册进 INSTALLED_APPS。Django 的 makemigrations 只扫描已注册的 appsettings.py 里 INSTALLED_APPS 没有 postsmodels.py 写得再完整它也看不到。另一种情况是多人开发或反复改模型后产生了迁移文件冲突。解决先检查 INSTALLED_APPS 里有没有 posts然后确认是否真的没有待生成的迁移# 检查 app 是否被 Django 正确加载 python manage.py shell -c import posts; print(posts.__file__) # 查看待生成的迁移 python manage.py makemigrations --check --dry-run--check 让 Django 只检查有没有待生成的迁移而不实际生成文件--dry-run 配合打印出将要执行的内容。如果确实检测不到用 python manage.py makemigrations --merge 合并冲突的迁移文件最脏的办法是删掉该 app 的 migrations 目录下除init.py 之外的文件再重新生成这是后悔药但只建议在本地开发且数据库里没有重要数据时用线上环境这样搞会丢数据。5.5 图片上传后页面 404现象封面图或头像上传成功文件也确实保存到了项目目录但页面上 img 标签显示裂图直接访问图片 URL 返回 404。原因Django 开发服务器默认只处理静态文件 CSS/JS不处理用户在运行时上传的 MEDIA 文件。settings.py 里缺 MEDIA_URL 和 MEDIA_ROOTURLconf 里也没有把 /media/ 交给开发服务器。解决settings.py 加两行# settings.py MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media然后修改主路由 urls.py只在 DEBUG 模式下挂载开发环境的 media 服务# travel_forum/urls.py from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)这里要分清 STATIC 和 MEDIASTATIC 是项目自带的 CSS、JS、图片MEDIA 是用户通过 ImageField 上传的文件。它们由两个独立的配置控制很多人只配了 STATIC_URLMEDIA 自然 404。生产环境部署时这行 static 不能直接用需要交给 Nginx 或对象存储处理但本地开发这是最快解法。6. 上线前自检用一段冒烟测试验证完整链路答辩不求人功能写完别急着截几张图就去答辩先把核心链路跑成自动化测试。Django 自带的 TestCase 不需要额外装库基于 Python 标准库 unittest运行一条命令就能把注册、发帖、评论、收藏这整条链路验证一遍。我习惯在 posts/tests.py 里维护这样一个冒烟测试from django.test import TestCase from django.contrib.auth.models import User from .models import Post, Comment, Favorite class ForumSmokeTest(TestCase): def setUp(self): self.user User.objects.create_user(test, passwordtest123456) def test_create_post_and_comment(self): post Post.objects.create( authorself.user, title周末杭州两日游, content西湖-灵隐-九溪路线如下... ) comment Comment.objects.create( postpost, authorself.user, content收藏了 ) Favorite.objects.create(userself.user, postpost) self.assertEqual(post.comments.count(), 1) self.assertEqual(post.favorited_by.count(), 1) def test_delete_user_cascades_posts(self): post Post.objects.create( authorself.user, title临时攻略, content将被连带删除 ) self.user.delete() self.assertFalse(Post.objects.filter(pkpost.pk).exists())运行方式就一条命令python manage.py test posts。Test 里默认使用一个独立的测试数据库不会污染开发数据跑完自动销毁。第一个用例验证“发帖 → 评论 → 收藏”的完整链路第二个用例验证 CASCADE 级联删除行为这两条正好对应答辩时评委最爱问的“数据一致性”问题。答辩时如果被问“你的系统能承受多大访问量”不用慌把第 4 章的 select_related、分页参数和第 3 章的复合索引讲清楚已经能自圆其说。进阶方向里热门攻略缓存和写操作事务是两条最容易被认可的路缓存用 django.core.cache 包一个 get_or_set 缓存首页热门榜写操作可以用 transaction.atomic 把“生成评论 更新帖子评论数”包在一起保证任何一步失败都不会留下半截数据。这两个改动都不大但能体现你对“访问压力”和“数据一致性”有概念。交付前最后一条建议把那几个边界情况——搜索词为空、评论内容为空、重复收藏、删除别人的帖子——全部用 test client 跑一遍。我习惯把它们都写进 tests.py答辩时打开绿色对勾比打开 demo 页面更有说服力。这个习惯帮我避开过不止一次翻车数据模型设计阶段多想清楚 related_name后面能少改三小时的模板。希望帮到你。本文还有配套的精品资源点击获取
返回列表