简介:这是一套面向Python初学者与毕业设计学生的Django实战项目资源,完整实现了一个功能完备的图书管理系统,覆盖MVT架构开发全流程。资源包含2613个文件,以1634个JavaScript脚本(支撑前台交互与后台EasyUI组件)、271个HTML页面(前后台视图结构)、240个CSS样式文件(含Bootstrap与EasyUI主题)及37个核心Python源码文件为主,辅以MySQL数据库文件、图片资源与配置文档,整体压缩包仅5.95MB,轻量易部署。已有661人学习下载,适合作为课程设计、毕设选题或Django进阶训练素材。读者可直接运行并深入理解图书类型、图书实体等模型设计,掌握字符串、浮点数、日期、富文本、图片上传、外键关联等全类型字段实践,同时获得前后端分离式UI方案——前台基于Bootstrap响应式布局,后台采用EasyUI定制化管理界面,规避Django默认admin的简陋样式,具备真实商业系统开发参考价值。
1. 这不是又一个“增删改查”Demo:Django图书管理系统如何真正跑通MVT闭环并支撑毕业设计答辩
很多同学拿到“基于Django的图书管理系统”课题时,第一反应是抄个CRUD页面、连上MySQL、改两行模板就交差——结果在答辩现场被问到“为什么用EasyUI不用Admin?Bootstrap前台怎么和后台数据流对接?MVT里Model层字段约束和View层权限校验怎么协同?”当场卡壳。这项目真正的价值不在“能查书”,而在用Django原生MVT架构把业务逻辑、数据验证、前后端分离、权限控制四个维度拧成一股绳。它适合需要展示完整Web开发能力的本科毕设,也适合作为从Flask转向Django的企业级项目练手。核心难点从来不是写代码,而是理解Django如何用models.py定义领域实体、用views.py承载业务规则、用templates/实现表现层解耦——三者通过URL路由精确绑定,而非靠JS拼接或硬编码跳转。下文不讲“怎么安装Django”,只聚焦如何让这个系统在本地真实运行、可调试、可扩展、可答辩。
2. 搭建可验证的Django+MySQL环境:避开pip源、字符集、时区三大暗坑
2.1 创建隔离Python环境并安装指定版本Django
毕业设计必须明确Django版本,避免因新旧API差异导致源码无法运行。当前主流教学环境采用Django 4.2.x(LTS长期支持版),兼容Python 3.8–3.11。不要直接pip install django——国内镜像源常缓存旧包,且未指定版本易引发依赖冲突。
# 创建虚拟环境(推荐venv,无需额外安装) python -m venv book_env source book_env/bin/activate # Linux/macOS # book_env\Scripts\activate.bat # Windows # 使用清华源安装Django 4.2.13(2024年稳定版) pip install --upgrade pip pip install django==4.2.13 -i https://pypi.tuna.tsinghua.edu.cn/simple/提示:
django==4.2.13必须带双等号,否则pip可能升级到5.x(已移除django.contrib.auth.views.LoginView.get_context_data等毕设常用方法)。若提示ModuleNotFoundError: No module named 'django',检查是否激活了虚拟环境(命令行前缀应显示(book_env))。
2.2 配置MySQL服务并创建专用数据库
图书管理系统对MySQL要求不高,但字符集和时区配置错误会导致中文乱码、时间字段错位。不要用phpMyAdmin默认创建的数据库——它常设为latin1,而Django默认期望utf8mb4。
-- 登录MySQL后执行(root用户) CREATE DATABASE book_system CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; CREATE USER 'book_user'@'localhost' IDENTIFIED BY 'BookPass123!'; GRANT ALL PRIVILEGES ON book_system.* TO 'book_user'@'localhost'; FLUSH PRIVILEGES;注意:
utf8mb4是MySQL对UTF-8的完整实现,支持emoji和生僻汉字;utf8只是其子集(实际为utf8mb3),Django 4.2+已弃用。若执行报错Unknown collation: 'utf8mb4_unicode_ci',说明MySQL版本低于5.5.3,请先升级MySQL或改用utf8_general_ci(不推荐)。
2.3 修改Django settings.py连接MySQL
在settings.py中替换默认SQLite配置。关键参数必须显式声明,不能依赖Django默认值:
# settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 必须指定mysql引擎 'NAME': 'book_system', 'USER': 'book_user', 'PASSWORD': 'BookPass123!', 'HOST': '127.0.0.1', # 不要用localhost(触发socket连接,易超时) 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', # 强制客户端字符集 'init_command': "SET time_zone = '+00:00';", # 避免Django时区转换错误 }, 'TEST': { 'CHARSET': 'utf8mb4', 'COLLATION': 'utf8mb4_unicode_ci', } } }逻辑说明:
HOST设为127.0.0.1而非localhost,强制走TCP/IP协议,绕过MySQL Unix socket权限问题;init_command将数据库时区设为UTC,与DjangoTIME_ZONE = 'UTC'保持一致,避免DateTimeField存取偏差。若启动时报django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module,需安装mysqlclient:pip install mysqlclient(Windows需先装Visual Studio Build Tools)。
3. 实现MVT三层解耦:从Model字段设计到View权限控制的完整链路
3.1 Model层:用ForeignKey和choices构建图书领域模型
图书管理系统不是简单Book(title, author),必须体现借阅业务逻辑。以下模型结构覆盖90%毕设需求,且严格遵循Django ORM最佳实践:
# models.py from django.db import models from django.contrib.auth.models import User class Category(models.Model): name = models.CharField(max_length=50, unique=True, verbose_name="分类名称") description = models.TextField(blank=True, verbose_name="分类描述") class Meta: verbose_name = "图书分类" verbose_name_plural = "图书分类" class Book(models.Model): STATUS_CHOICES = [ ('available', '可借阅'), ('borrowed', '已借出'), ('lost', '遗失'), ('damaged', '损坏'), ] isbn = models.CharField(max_length=13, unique=True, verbose_name="ISBN") title = models.CharField(max_length=200, verbose_name="书名") author = models.CharField(max_length=100, verbose_name="作者") publisher = models.CharField(max_length=100, blank=True, verbose_name="出版社") publish_date = models.DateField(null=True, blank=True, verbose_name="出版日期") category = models.ForeignKey(Category, on_delete=models.PROTECT, verbose_name="所属分类") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='available', verbose_name="状态") stock = models.PositiveIntegerField(default=1, verbose_name="库存数量") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = "图书" verbose_name_plural = "图书" ordering = ['-created_at'] def __str__(self): return f"{self.title} ({self.isbn})"参数说明:
on_delete=models.PROTECT防止误删分类导致图书数据丢失;choices提供状态枚举,前端可直接用{{ book.get_status_display }}渲染中文;ordering定义默认排序,避免ListView无序显示。不要用CASCADE删除分类——毕设答辩时会被质疑数据完整性。
3.2 View层:基于Class-Based View实现借阅流程控制
借书操作需校验库存、更新状态、记录日志。用UpdateView比函数视图更安全,且天然支持get_object()权限检查:
# views.py from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import UpdateView, ListView, DetailView from django.urls import reverse_lazy from django.contrib import messages from django.db import transaction from .models import Book, BorrowRecord from .forms import BorrowForm class BookListView(ListView): model = Book template_name = 'books/book_list.html' context_object_name = 'books' paginate_by = 10 class BookDetailView(DetailView): model = Book template_name = 'books/book_detail.html' class BorrowBookView(LoginRequiredMixin, UpdateView): model = Book form_class = BorrowForm template_name = 'books/borrow_confirm.html' success_url = reverse_lazy('book_list') def get_queryset(self): # 仅允许借阅状态为available的图书 return Book.objects.filter(status='available') def form_valid(self, form): with transaction.atomic(): book = self.get_object() if book.stock < 1: messages.error(self.request, "该图书库存不足,无法借阅") return self.form_invalid(form) # 创建借阅记录 BorrowRecord.objects.create( user=self.request.user, book=book, borrow_date=timezone.now().date() ) # 更新图书状态和库存 book.status = 'borrowed' book.stock -= 1 book.save() messages.success(self.request, f"成功借阅《{book.title}》") return super().form_valid(form)逻辑说明:
LoginRequiredMixin强制登录,get_queryset()过滤可借图书,transaction.atomic()确保借阅记录和库存更新原子性。若学生未登录点击借阅,自动跳转至/login/——此路径需在settings.py中配置LOGIN_URL = '/login/'。
3.3 Template层:Bootstrap前台与EasyUI后台的模板继承策略
项目标题明确要求“前台Bootstrap+后台EasyUI”,意味着需物理分离模板目录,避免CSS冲突:
# 项目结构 myproject/ ├── templates/ │ ├── base_front.html # Bootstrap基础模板(前台) │ ├── books/ │ │ ├── book_list.html # 前台图书列表(继承base_front) │ ├── admin/ │ ├── base_back.html # EasyUI基础模板(后台) │ └── books/ │ └── book_manage.html # 后台管理页(继承base_back)base_front.html示例(精简版):
<!-- templates/base_front.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{% block title %}图书管理系统{% endblock %}</title> <!-- Bootstrap 5.3 CDN --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container"> <a class="navbar-brand" href="{% url 'book_list' %}">📚 图书管理系统</a> </div> </nav> <div class="container mt-4"> {% block content %}{% endblock %} </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </body> </html>关键点:EasyUI模板需单独引入
jquery.easyui.min.js和easyui.css,且不能与Bootstrap的jQuery共存——因此后台URL必须独立(如/admin/books/),前台URL走/books/,通过Django URL分发隔离。
4. 部署与调试:用runserver暴露端口、用shell验证数据流、用log定位EasyUI请求失败
4.1 启动开发服务器并验证MySQL连接
python manage.py runserver 0.0.0.0:8000不是万能的——它默认只监听127.0.0.1,外部设备无法访问。毕设演示需局域网共享:
# 启动并允许外网访问(仅限内网演示) python manage.py runserver 0.0.0.0:8000 # 查看本机IP(Linux/macOS) ip addr show | grep "inet " | grep -v "127.0.0.1" # Windows查看IP ipconfig | findstr "IPv4"验证步骤:浏览器访问
http://<本机IP>:8000/admin/,输入superuser账号。若出现Django Admin登录页,说明MySQL连接成功;若报OperationalError: (1045, "Access denied for user..."),检查settings.py中数据库密码是否与MySQL创建用户时一致。
4.2 用Django shell调试Model关系与QuerySet
避免在视图中写错ORM查询。用shell实时验证:
# python manage.py shell >>> from books.models import Book, Category >>> # 创建测试分类 >>> cat = Category.objects.create(name="计算机科学", description="编程与算法") >>> # 创建测试图书(关联分类) >>> book = Book.objects.create( ... isbn="9787302530125", ... title="Python Web开发实战", ... author="董伟明", ... publisher="电子工业出版社", ... category=cat, ... stock=5 ... ) >>> # 查询所有计算机类图书 >>> Book.objects.filter(category__name="计算机科学").count() 1 >>> # 检查反向关系(Category下的图书数) >>> cat.book_set.count() 1参数说明:
category__name是Django ORM的双下划线语法,表示跨表查询;book_set是ForeignKey自动生成的反向管理器名(若Book.category未设related_name,则默认为模型名小写_set)。
4.3 解析EasyUI请求失败的典型日志
EasyUI常因CSRF或JSON格式问题返回403/400。在settings.py中开启详细日志:
# settings.py LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.security.csrf': { 'handlers': ['console'], 'level': 'DEBUG', }, 'django.db.backends': { 'handlers': ['console'], 'level': 'DEBUG', }, }, }当EasyUI提交表单失败时,终端会输出:
[django.security.csrf] WARNING: Forbidden (CSRF token missing or incorrect): /admin/books/save/解决方案:在EasyUI表单中添加CSRF token隐藏字段:
<!-- templates/admin/books/book_manage.html --> <form id="bookForm" method="post"> {% csrf_token %} <!-- 必加! --> <input type="text" name="title" /> <input type="submit" value="保存" /> </form>注意:EasyUI的
$.post()请求若未携带X-CSRFToken头,需手动设置:
// EasyUI JS中 $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } } });5. 毕设答辩必备技巧:用manage.py导出初始数据、用inspectdb逆向生成Model、用custom command批量导入图书
5.1 用dumpdata生成可复现的初始数据集
答辩时需快速还原演示环境。将测试图书、分类导出为JSON:
# 导出Category和Book模型数据(不含auth等系统表) python manage.py dumpdata books.Category books.Book --indent 2 > fixtures/initial_data.json # 加载数据(清空表后重新导入) python manage.py flush --no-input python manage.py loaddata fixtures/initial_data.json参数说明:
--indent 2生成可读JSON;fixtures/目录需手动创建;loaddata会自动按外键依赖顺序导入,无需担心Category未导入就加载Book。
5.2 用inspectdb从现有MySQL表生成Django Model
若已有MySQL图书表(如学校教务系统导出),用inspectdb省去手写Model:
# 假设MySQL中已有book表 python manage.py inspectdb book > books/models_auto.py # 手动编辑models_auto.py:修正字段类型(如varchar→CharField)、添加verbose_name、设置Meta关键修正项:
id = models.AutoField(primary_key=True)→ 改为models.BigAutoField(Django 3.2+默认);datetime字段需改为models.DateTimeField;外键字段名末尾加_id需删除(如category_id→category)。
5.3 编写Custom Command批量导入CSV图书数据
避免手动添加上百本图书。创建management/commands/import_books.py:
# books/management/commands/import_books.py import csv from django.core.management.base import BaseCommand from books.models import Book, Category class Command(BaseCommand): help = '从CSV文件批量导入图书' def add_arguments(self, parser): parser.add_argument('csv_file', type=str, help='CSV文件路径') def handle(self, *args, **options): with open(options['csv_file'], 'r', encoding='utf-8-sig') as f: reader = csv.DictReader(f) for row in reader: try: category, _ = Category.objects.get_or_create( name=row['category'] ) Book.objects.create( isbn=row['isbn'], title=row['title'], author=row['author'], publisher=row['publisher'], category=category, stock=int(row['stock']) ) self.stdout.write(f"导入成功: {row['title']}") except Exception as e: self.stdout.write(self.style.ERROR(f"导入失败 {row['title']}: {e}"))使用方式:
python manage.py import_books data/books.csvCSV格式要求:首行必须为
isbn,title,author,publisher,category,stock;encoding='utf-8-sig'解决Windows记事本BOM头问题;get_or_create避免重复创建分类。此命令可直接放入答辩PPT演示环节,体现工程化能力。
本文还有配套的精品资源,点击获取