☰
基于Django的读书节宣传系统:毕业设计实战与避坑指南
2026/10/7 3:01:25 网站建设 项目流程

1. 项目概述与核心需求拆解

1.1 这个毕业设计到底在做什么

说到计算机专业的毕业设计,每年都有一大批同学在“选题—拖延—赶工—答辩”的循环里挣扎。而我又一次接到的这个“江城读书节宣传系统的设计与实现”,乍看只是常见的CRUD管理系统,但真上手做下来,才发现它把Web开发里的很多经典知识点都串起来了,非常适合作为毕业设计。简单说,这个系统要解决的问题很明确:读书节这类文化活动需要对外展示活动信息、推荐书目、发布动态,还要能管理报名用户和活动数据,光靠公众号推文和Excel表格根本撑不住。

如果你正在选毕设题目,又在纠结是选“图书管理系统”“校园活动报名系统”还是“宣传网站”,那我建议你把目光放在这个“读书节宣传系统”上。它比单纯的图书管理多了活动运营属性,比纯展示型官网多了后台管理和用户交互,难度适中,演示效果好,答辩时也容易讲出东西。

1.2 适合谁参考以及系统能带来什么价值

这套系统适合三类人来参考:第一类是计算机专业大四学生,需要完成毕业设计并顺利通过答辩;第二类是想转行做后台开发的人,想通过一个完整的项目把Django的知识串起来;第三类是想给社团、文化机构做活动网站的非技术人员,至少能通过这篇文了解一套活动宣传系统的构成和成本。

技术栈方面,核心是Python的Django框架。为什么选它?后文我会详细展开,但一句话概括就是:Django自带Admin后台、ORM数据库操作、用户认证体系,意味着你能在两周内做出一个带管理端和用户端的完整系统,这在毕设周期里太重要了。你别指望像大厂一样用Spring Cloud微服务去拆一个读书节宣传系统——那就是拿大炮打蚊子,自己累死不说,答辩老师还会追问你“为什么这么复杂”。

2. 功能模块设计与技术选型思路

2.1 模块划分:从用户和运营两个视角倒推

在动手写代码之前,我习惯先画功能脑图。这个系统站在普通访客的角度,需要看到读书节的活动介绍、日程安排、书单推荐、往届精彩瞬间,还要能在线报名参加活动、收藏感兴趣的书目。站在后台管理员的角度,需要管理轮播图、发布活动公告、审核报名、维护书单数据、查看报名统计。

所以我把系统拆成几个核心模块:

模块面向对象核心功能
活动宣传模块游客展示活动首页、活动详情、日程表
图书推荐模块游客/注册用户书单列表、图书详情、收藏功能
在线报名模块注册用户活动报名、报名状态查询、取消报名
后台管理模块管理员内容发布、报名审核、数据统计
用户系统全部注册、登录、个人信息维护

这个拆分方式的好处是:每个模块都可以单独讲解,答辩时老师问“你的系统有哪些功能”,你能清楚说出五个模块,并且每个模块都能演示,而不是笼统地扯“系统管理”。

2.2 为什么选Django而不是Java、PHP或小程序

题目里提到了django、java、PHP、小程序这些热词,我自己也带过不少做毕设的同学,这里说点掏心窝的话:选技术栈不是选最火的,而是选最适合你当前目标和时间预算的。

如果你选Java,意味着你要面对Spring Boot + MyBatis Plus + Maven依赖管理这一整套生态,光环境配置和依赖冲突就能耗掉一周;选PHP的话,原生语法写起来快,但前后端分离、接口规范这些现代开发方式,你在毕业设计里反而不容易体现“设计感”;小程序则是另一种思路——微信小程序做前端,后端还是要有人做,你还得解决审核、真机调试、域名备案这些额外事务。

而Django有这些让毕设党的日子好过很多的特质:

  • 自带Admin后台。模型建好后,Django自动生成管理界面,你不用额外写后台页面的代码。对于那些“只要增删改查”的管理操作,等于白送。
  • ORM写起来像写Python。查询图书、筛选热门书目这类操作,不用拼接SQL,代码可读性高,答辩时说“数据层完全面向对象”显得很专业。
  • 用户认证模块完整。注册、登录、密码加密、会话管理,一套现成的。你在Java里要搬Spring Security,配置写半天,在Django里几行代码就接上了。
  • 模板语法简单。我后面会展示,一个循环渲染活动列表,三行模板代码搞定,前端基础一般的同学也能快速出页面。

2.3 数据库怎么做表设计

这一部分我让你直接照着敲都没问题。数据库我用的是MySQL,因为毕设答辩时老师大概率会问一句“你用的什么数据库,为什么不用SQLite?”——你说MySQL是生产环境常见的选择,显得你有考虑过部署场景,这就够了。

核心表设计思路如下:

活动表(Activity):存读书节各类活动的标题、简介、开始时间、结束时间、地点、封面图、最大报名人数。

图书表(Book):存推荐书目的书名、作者、出版社、ISBN、简介、封面、所属分类。

用户表(User):使用Django内置的auth.User扩展,增加手机号、学号/工号、头像等字段。

报名表(SignUp):关联用户ID和活动ID,记录报名时间、状态(待审核/已通过/已取消)。

收藏表(Favorite):关联用户ID和图书ID,记录收藏时间。

这里要特别注意一个细节点:报名表为什么要单独建?因为一个用户可能报名多个活动,一个活动也有多个用户报名,这是典型的多对多关系,但报名状态是“按次记录”的,所以不能简单用ManyToManyField,而是建一张中间表手动管理。这个细节拿去答辩讲,比背概念强一百倍。

3. 核心功能实现与关键代码解析

3.1 项目创建与基础配置实操

老规矩,先创建虚拟环境,这一步能避免你后面把依赖装得乱七八糟,而且写进毕设文档里,是“规范开发”的加分项。直接在终端里依次执行:

mkvirtualenv reading_festival pip install django==4.2.5 mysqlclient pillow django-admin startproject festival_project cd festival_project python manage.py startapp festival

这里装pillow是因为要处理图片上传,不装的话,管理员在后台传活动封面,立刻给你报错。装mysqlclient是因为Python连MySQL需要这个驱动。

然后打开settings.py,把festival应用注册进去,顺便配置数据库连接。Django默认连的是SQLite,你要切换成MySQL,就在DATABASES字段里这样改:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'reading_festival', 'USER': 'root', 'PASSWORD': '你自己设的密码', 'HOST': '127.0.0.1', 'PORT': '3306', } }

同时在文件里补上静态资源和媒体文件的配置,以后上传的封面图会保存在项目的media目录下:

import os MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

3.2 模型编写:把表结构转成代码

现在开始建模型。这一步是核心中的核心,模型写得好,后台管理和前端展示都顺畅。打开festival/models.py,按下面写:

from django.db import models from django.contrib.auth.models import User class Activity(models.Model): STATUS_CHOICES = ( ('draft', '未开始'), ('open', '报名中'), ('closed', '已截止'), ('done', '已结束'), ) title = models.CharField('活动名称', max_length=100) description = models.TextField('活动简介') start_time = models.DateTimeField('开始时间') end_time = models.DateTimeField('结束时间') location = models.CharField('活动地点', max_length=200) cover = models.ImageField('封面图', upload_to='activity_covers/', blank=True) max_people = models.IntegerField('名额上限', default=100) status = models.CharField('状态', max_length=10, choices=STATUS_CHOICES, default='draft') created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title class Book(models.Model): title = models.CharField('书名', max_length=200) author = models.CharField('作者', max_length=100) publisher = models.CharField('出版社', max_length=100) isbn = models.CharField('ISBN', max_length=20) summary = models.TextField('内容简介') cover = models.ImageField('封面图', upload_to='book_covers/', blank=True) category = models.CharField('分类', max_length=50, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title class SignUp(models.Model): STATUS_CHOICES = ( ('pending', '待审核'), ('approved', '已通过'), ('cancelled', '已取消'), ) user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='报名用户') activity = models.ForeignKey(Activity, on_delete=models.CASCADE, verbose_name='报名活动') status = models.CharField('状态', max_length=10, choices=STATUS_CHOICES, default='pending') sign_time = models.DateTimeField('报名时间', auto_now_add=True) class Meta: unique_together = ('user', 'activity') verbose_name = '活动报名' verbose_name_plural = '活动报名' def __str__(self): return f"{self.user.username} - {self.activity.title}" class Favorite(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户') book = models.ForeignKey(Book, on_delete=models.CASCADE, verbose_name='图书') created_at = models.DateTimeField('收藏时间', auto_now_add=True) class Meta: unique_together = ('user', 'book') verbose_name = '图书收藏' verbose_name_plural = '图书收藏' def __str__(self): return f"{self.user.username} - {self.book.title}"

写完后执行两条命令同步数据库:

python manage.py makemigrations python manage.py migrate

3.3 视图与路由:把每个页面串起来

模型建好只是地基,接下来要看Django怎么把数据交给页面。先写视图,核心是活动列表页。在festival/views.py里:

from django.shortcuts import render, get_object_or_404 from .models import Activity, Book def activity_list(request): activities = Activity.objects.filter(status__in=['open', 'draft']).order_by('start_time') return render(request, 'festival/activity_list.html', {'activities': activities}) def activity_detail(request, pk): activity = get_object_or_404(Activity, pk=pk) signed_count = activity.signup_set.count() ctx = { 'activity': activity, 'signed_count': signed_count, } return render(request, 'festival/activity_detail.html', ctx) def book_list(request): books = Book.objects.all().order_by('-created_at') return render(request, 'festival/book_list.html', {'books': books})

然后配置路由。在festival/urls.py里写:

from django.urls import path from . import views urlpatterns = [ path('activities/', views.activity_list, name='activity_list'), path('activities/<int:pk>/', views.activity_detail, name='activity_detail'), path('books/', views.book_list, name='book_list'), ]

再去主项目的urls.py注册一下:

from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('', include('festival.urls')), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

3.4 页面模板:三行模板语法渲染一书架书

Django的模板系统是它最亲民的地方,你不用懂JS框架也能做动态页面。我用图书列表页举例,在templates/festival/book_list.html里:

{% extends 'base.html' %} {% block content %} <div class="container"> <h2 class="section-title">推荐书单</h2> <div class="book-grid"> {% for book in books %} <div class="book-card"> <img src="{{ book.cover.url }}" alt="{{ book.title }}"> <h3>{{ book.title }}</h3> <p>{{ book.author }} · {{ book.publisher }}</p> <p class="book-summary">{{ book.summary|truncatechars:60 }}</p> <a href="{% url 'book_detail' book.pk %}" class="btn">查看详情</a> </div> {% empty %} <p>暂无推荐书目,敬请期待。</p> {% endfor %} </div> </div> {% endblock %}

{% for %}循环、{{ book.title }}变量插值、{% empty %}空列表提示、truncatechars过滤器,这几样搞明白,Django模板你已经会用八成。{{ book.cover.url }}这句会返回图片的实际访问URL,前提是MEDIA配置正确。

4. 报名流程设计:用户注册、登录与状态管理

4.1 用Django内置认证搞定用户体系

用户系统我直接用的Django内置auth应用,因为自己写密码哈希和会话管理纯属给自己挖坑。你要做的核心工作只有两件:一是实现注册、登录、登出的视图函数,二是在报名时判断用户是否已登录。

注册视图我给了参考:

from django.shortcuts import render, redirect from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login def register(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect('activity_list') else: form = UserCreationForm() return render(request, 'registration/register.html', {'form': form})

这里要强调一个毕设答辩必问的点:UserCreationForm默认不带邮箱、手机号这些字段,所以你如果想让用户填手机号,需要自己写一个继承表单类。展示代码如下:

from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User class RegisterForm(UserCreationForm): email = forms.EmailField(required=True) mobile = forms.CharField(max_length=11, required=True) class Meta: model = User fields = ('username', 'email', 'mobile')

4.2 报名功能的完整逻辑

真正的核心是报名逻辑,不能只做一个“点了按钮就插入一条记录”的假功能。你必须考虑三个问题:这个活动是否还在报名期、用户是否已经报过名、名额是否已满。

参考实现如下:

from django.shortcuts import get_object_or_404, redirect from django.contrib import messages from django.contrib.auth.decorators import login_required from django.utils import timezone from .models import Activity, SignUp @login_required def activity_signup(request, pk): activity = get_object_or_404(Activity, pk=pk) # 判断活动是否在报名期 if activity.status != 'open' or activity.end_time < timezone.now(): messages.error(request, '该活动不在报名期内') return redirect('activity_detail', pk=activity.pk) # 判断用户是否重复报名 if SignUp.objects.filter(user=request.user, activity=activity).exists(): messages.warning(request, '您已经报过名了') return redirect('activity_detail', pk=activity.pk) # 判断名额是否已满 if activity.signup_set.filter(status='approved').count() >= activity.max_people: messages.error(request, '活动名额已满') return redirect('activity_detail', pk=activity.pk) signup = SignUp.objects.create( user=request.user, activity=activity, status='pending' ) messages.success(request, '报名成功,请等待管理员审核') return redirect('activity_detail', pk=activity.pk)

这里有个我没明说但你该注意到的小细节:报名记录建的时候状态是pending,不是直接approved。为什么?因为“管理员审核”这个动作一旦参与进来,整个系统就从“学生项目”变成了“有业务流程的系统”,答辩时高下立判。你可以在后台Admin里审核通过,也可以在用户视图里开发“我的报名-列表-状态”页面。反正逻辑上,你留了审核这个口子,就不会被问住。

5. 后台管理配置:少写一百行代码的秘诀

5.1 注册Admin模型,后台直接管理数据

Django的Admin是我决定薅的最大羊毛。在festival/admin.py里注册模型后,所有数据都能在后台管理界面操作。代码少到令人发指:

from django.contrib import admin from .models import Activity, Book, SignUp, Favorite @admin.register(Activity) class ActivityAdmin(admin.ModelAdmin): list_display = ('title', 'start_time', 'end_time', 'location', 'status') list_filter = ('status', 'start_time') search_fields = ('title', 'location') date_hierarchy = 'start_time' @admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'publisher', 'category') search_fields = ('title', 'author') list_filter = ('category',) @admin.register(SignUp) class SignUpAdmin(admin.ModelAdmin): list_display = ('user', 'activity', 'status', 'sign_time') list_filter = ('status', 'activity') search_fields = ('user__username', 'activity__title')

search_fields里写user__username,意思是跨表查询用户表的username字段。这个下划线双写是Django ORM的规则,后台搜索框立马就能按用户名搜报名记录了,非常方便。这也算是我实操里用的最频繁的一个技巧。

5.2 用list_filter和date_hierarchy让筛选一目了然

很多人刚接触Admin,只看得到默认表格,不知道右边的筛选器可以自己配。加了list_filter = ('status', 'start_time')之后,后台就会自动出现“按状态筛选”的下拉栏。加了date_hierarchy = 'start_time'之后,顶部还会多一个按日期钻取的年/月/日导航条。

这两个配置能让你的毕业设计演示时显得很“会”:老师问“你怎么管理活动状态”,你直接打开Admin页面,点一下筛选,当场给他看效果。零成本装杯,划算。

6. 数据展示增强:让首页真正有“宣传感”

6.1 首页聚合多个模块的数据

如果你一上来就甩给用户一个平平无奇的列表页,那是浪费了“宣传系统”这个定位。宣传系统的首页应该像一张活动海报,把最能吸引人的内容聚合在一起:头图轮播放最热活动,中间是精选书单,下方是近期日程表。

实现方法是写一个home视图,把多个查询结果打包返回:

from django.shortcuts import render from .models import Activity, Book def home(request): latest_activities = Activity.objects.filter(status__in=['open', 'draft']).order_by('start_time')[:3] featured_books = Book.objects.order_by('-isbn')[:4] upcoming_activities = Activity.objects.filter(start_time__gte=timezone.now()).order_by('start_time')[:5] ctx = { 'latest_activities': latest_activities, 'featured_books': featured_books, 'upcoming_activities': upcoming_activities, } return render(request, 'festival/home.html', ctx)

然后在home.html模板里,用几个for循环把数据渲染成三个板块。这样页面信息量立刻丰富起来,老师看到首页时会觉得“这小子做了不少工作”,其实每个板块都是同一套模板语法,工作量完全可控。

6.2 给页面加点动态效果:轮播图与懒加载

纯后台渲染只能出静态页面,你再想让界面好看一点,不需要上React,用原生JS或一点jQuery就够了。我推荐做一个自动轮播的首页Banner:后台在模型里加一个is_banner布尔字段,标记哪些活动放轮播,然后在模板里配合CSS动画实现渐变切换。

另外,图书列表页图片多,一页下来加载压力不小。用浏览器原生的loading="lazy"属性就能解决:

<img src="{{ book.cover.url }}" alt="{{ book.title }}" loading="lazy">

这个属性一行代码实现图片懒加载,不用引任何库。

7. 常见问题排查与避坑指南

7.1 学习路线避坑指南

题目的相关热词里全是坑啊。这里提两个最常见的:

  • django执行查询-删除对象:新手常写Book.objects.get(pk=1).delete(),然后忘了赋值或没有try-except,结果对象不存在时就抛DoesNotExist异常,导致整个页面崩掉。正确做法是先判断存在:
book = Book.objects.filter(pk=1).first() if book: book.delete()
  • django创建app:创建完app忘记在INSTALLED_APPS注册,结果一运行就报No module named 'festival'。注意注册的是配置类名festival.apps.FestivalConfig或直接用festival即可。

7.2 图片上传失败的连环坑

图片报错算是我被问得最多的问题,刚写完模型一传图就报错,特别容易劝退新人。先看你的控制台,报错分两种:

一是没装pillow,会直接提示ModuleNotFoundError: No module named 'PIL'。这个好办,pip install pillow解决。

二是admin里看得到图片,但前端页面图片裂了。这是MEDIA配置没生效。检查你有没有在根urls.py里加static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),同时确认图片真的上传到了media目录下面。

7.3 报名功能开发中容易踩的坑

我先把自己踩过的记住:报名表里用了unique_together = ('user', 'activity')做唯一约束,这本身没毛病,但如果你在判断“是不是重复报名”时先查一次库,在创建时又没加get_or_create或异常处理,并发情况下可能重复写入报错。毕设里并发量小,一般不会有问题,但你在文档里提一嘴“这里用唯一约束保证数据一致性”,比不讲原因强很多。

还有一个坑是on_delete=models.CASCADE。我的建议是把报名记录关联到用户时用CASCADE没问题,但你把图书推荐表和用户收藏表关联时,删除图书会把所有用户的收藏记录也删了,这在真实业务里比较危险。毕设答辩时你可以主动说一句“真实生产环境我会把用户产生的数据用PROTECT保护起来”,这句话会让人觉得你考虑到了数据安全,能加分。

7.4 让查询变快的两个小技巧

如果你的图书表里存了上千条数据,列表页开始变卡,可以试试两个优化法:

一是给经常查询的字段加索引:

title = models.CharField('书名', max_length=200, db_index=True) isbn = models.CharField('ISBN', max_length=20, db_index=True)

二是使用select_related或prefetch_related解决N+1查询问题。比如首页展示活动列表时,每个活动都要查一次关联的报名人数,普通写法会产生N+1条SQL。用annotate一次查出:

from django.db.models import Count Activity.objects.annotate(signup_count=Count('signup'))

这样一条SQL就搞定所有统计。答辩的时候能说出这个词,老师就知道你真的懂性能优化。

8. 个人经验与扩展方向

8.1 从毕设到项目的思考

做完这套系统,有两点我自己体会最深。第一点,毕设不是越复杂越好,而是越“完整”越好。一个从前端页面、后台CRUD、用户交互到数据库设计、部署上线,每个环节都能说清楚的项目,比一个听起来是分布式高并发的“纸老虎”拿分高得多。第二点,带着运营视角做技术,页面和功能才能立住。你如果只是机械地增删改查,做完之后你可能连自己系统有哪些页面都讲不清;但如果先想“用户来了怎么看活动、怎么报名、管理员怎么处理”,系统的脉络就会自然浮现。

8.2 后续还能怎么扩展

如果你时间充裕,想在本项目基础上做增量,我有三个建议方向:

一是在现有系统上加一个数据可视化统计页面,展示每个活动的报名人数趋势、图书分类占比,用ECharts实现。视觉冲击力强,工作量可控,答辩时讲“用图表驱动运营决策”立刻拔高格局。

二是把前端升级成小程序版本。我见过很多同学这样组合毕业设计:Django做后端API,小程序做前端展示端,一套代码管两个端。当然前提是你有时间学小程序基础,否则风险不小。

三是引入Redis做活动热门度排行,首页显示“最热活动Top5”。这个方向能展示你对缓存的理解,但需要额外搭Redis环境,对于毕设来说性价比略低,看你自己取舍。

最后说一句真心话:拿到项目之后,先梳理需求,再搭环境,再写代码,每一个流程都要留下截图和记录。这些截图后期放进展会设计文档里,排版漂亮,答辩时老师问“你的开发过程是怎样的”,你直接把文档翻给他看,那种从容,比你临时编故事要强得多了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询