ARTICLE DETAIL

资讯详情

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

Django毕业设计:二手交易网站最小可行系统

Django毕业设计:二手交易网站最小可行系统 简介这是一套基于Python与Django框架开发的二手商品交易网站完整源码面向高校计算机专业学生及Web开发初学者适用于毕业设计、课程设计与小型项目实践解决从零构建用户注册登录、商品发布管理、搜索浏览到交易记录查询等核心电商功能的需求。资源包共98个文件含30个Python业务逻辑文件如views.py、models.py、controller.py、9个HTML模板页含goods_list.html、user_issue.html等、6个JS/CSS前端资源及10张商品示例图片结构清晰体现Django典型MVT分层设计压缩包仅673KB轻量易部署。已有228人学习下载可直接运行调试代码已通过基础功能测试附带README.md说明与完整目录模块login、trade、goodsIssue等便于理解用户认证、商品流转与订单管理三大业务闭环是掌握Django实战开发的优质入门级参考项目。1. 为什么用 Django 做二手商品交易网站比 Flask 或纯 Python Web 框架更省毕业设计时间你正在赶计算机专业本科毕业设计选题卡在“做点实际的、能跑起来、有后台管理、能交源码、答辩不被问倒”这五条硬杠上。这时候翻 GitHub 搜“二手交易”一堆 VueNode.js 的项目——但你只会 Python老师要求用 Django再搜“Django 二手”要么是半成品 demo没登录、没图片上传、没订单状态流转要么是商业项目代码加密、文档缺失、部署脚本藏在付费包里。结果就是你花三天搭好基础路由第四天发现商品详情页无法关联用户收藏第五天卡在 Django Admin 里怎么让“待审核商品”只对管理员可见、普通用户只能看到“已上架”状态……最后通宵改模板答辩时被问“为什么不用信号量处理库存扣减”当场哑火。这个标题里的“基于 Python Django 开发的二手商品交易网站”不是泛泛而谈的 Web 项目而是面向毕业设计场景的最小可行闭环系统它必须包含用户注册/登录带邮箱验证、商品发布含多图上传与分类标签、搜索与筛选按价格区间、城市、发布时间、在线沟通雏形站内消息通知、订单生成与状态机待付款→已发货→已完成→已评价、以及一个可直接进后台批量审核/导出数据的 Django Admin 界面。所有功能模块都用 Django 原生能力实现——不用额外学 Redis 缓存策略不强求 WebSocket 实时聊天不硬套 Celery 异步任务。源码不是玩具但也不追求高并发压测指标它经得起答辩老师点开 admin 后台删一条测试商品、再刷新前端看是否同步消失也禁得住你把settings.py里DEBUGFalse改完后在本地用python manage.py runserver --noreload跑通静态文件路径。适合两类人一是大四学生需要一份结构清晰、注释完整、数据库 ER 图可直接截图进论文、部署步骤写进附录就能过审的参考实现二是刚转行的初级后端想通过一个真实业务场景非博客/投票/TODO理解 Django ORM 如何建模一对多/多对多关系、如何用ModelForm替代手写表单验证、如何用login_required和UserPassesTestMixin控制访问权限边界。它不教你怎么写单元测试覆盖率到 90%但会告诉你python manage.py test跑哪几个用例能覆盖核心路径。2. 用 Django 4.2 Python 3.11 搭建二手交易网站的最小依赖与目录结构2.1 为什么选 Django 4.2 而不是最新 5.x 或老版本 3.2Django 4.2 是 LTS长期支持版本官方维护至 2026 年 4 月毕业设计提交后若需补材料或二次答辩仍有安全补丁可更新。相比 Django 5.x2023 年底发布LTS 尚未确认4.2 的第三方库兼容性更稳——比如django-crispy-forms用 2.0 版本即可完美适配而 5.x 需要升级到 2.2django-filter在 4.2 下默认支持SearchFilter与NumberFilter组合无需额外写FilterSet类就能实现“按价格区间关键词”联合筛选这对二手商品搜索足够用。更重要的是Django 4.2 的runserver默认启用--reload-typestat在 Windows 上热重载稳定避免你改完views.py却要手动 CtrlC 再启动的烦躁。提示不要用 Python 3.12——虽然 Django 4.2 官方支持但pillow处理商品图片缩略图必需在 3.12 下编译失败率高Python 3.11 是当前最平衡的选择pip install django4.2.11可直接安装无编译报错。2.2 初始化项目与核心 app 划分逻辑执行以下命令创建项目骨架注意路径命名规范避免空格和中文# 创建虚拟环境并激活 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装 Django 4.2.11 pip install django4.2.11 pillow django-filter # 创建项目项目名用英文小写如 secondhand django-admin startproject secondhand . # 创建核心 app按业务域拆分非技术层 python manage.py startapp users python manage.py startapp products python manage.py startapp orders python manage.py startapp messages此时目录结构应为secondhand/ ├── manage.py ├── secondhand/ # 项目配置包 │ ├── __init__.py │ ├── settings.py # 关键DATABASES、INSTALLED_APPS、STATIC_ROOT 必须设对 │ ├── urls.py # 主路由聚合各 app 的 urls.py │ └── wsgi.py ├── users/ # 用户认证与资料 ├── products/ # 商品发布、分类、搜索 ├── orders/ # 订单状态流转、支付模拟 ├── messages/ # 站内信轻量级不接 WebSocket └── templates/ # 公共模板基类 base.htmlINSTALLED_APPS中必须加入INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 第三方 django_filters, # 商品筛选必备 crispy_forms, # 表单渲染美化毕业设计界面整洁度加分项 # 自定义 app users, products, orders, messages, ]settings.py中关键配置项复制即用参数说明见下表配置项值说明DEBUGTrue开发期 /False答辩前关系到错误页面是否暴露代码路径答辩前务必关掉ALLOWED_HOSTS[localhost, 127.0.0.1, [::1]]开发[your-domain.com]部署DEBUGFalse时必须显式声明否则 500 错误STATIC_URL/static/前端引用 CSS/JS 的路径前缀STATIC_ROOTBASE_DIR / staticfilespython manage.py collectstatic输出目录宝塔部署时指向此路径MEDIA_URL/media/用户上传图片的 URL 前缀MEDIA_ROOTBASE_DIR / media上传文件物理存储路径需在urls.py中显式添加static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)2.3 数据库模型设计紧扣二手交易核心实体关系二手交易的本质是“人-物-钱-信”四元组。products/models.py中定义商品主模型from django.db import models from django.contrib.auth.models import User from django.core.validators import MinValueValidator, MaxValueValidator class Category(models.Model): name models.CharField(max_length50, uniqueTrue) slug models.SlugField(max_length50, uniqueTrue) # 用于 URL如 /category/shouji/ def __str__(self): return self.name class Product(models.Model): STATUS_CHOICES [ (draft, 草稿), (pending, 待审核), (published, 已上架), (sold, 已售出), (closed, 已下架), ] title models.CharField(max_length200) description models.TextField() price models.DecimalField(max_digits10, decimal_places2, validators[MinValueValidator(0.01)]) category models.ForeignKey(Category, on_deletemodels.PROTECT, related_nameproducts) owner models.ForeignKey(User, on_deletemodels.CASCADE, related_nameproducts) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultdraft) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) def __str__(self): return f{self.title} ({self.get_status_display()})关键点说明on_deletemodels.PROTECT用于Category防止删除分类时误删所有商品答辩时老师问“如果删了手机分类商品去哪了”你能答“Django 会抛 IntegrityError强制开发者先迁移商品”related_nameproducts让user.products.all()可直接查该用户所有商品比user.product_set.all()更语义化status字段用choices而非整数枚举Admin 界面自动渲染下拉菜单且get_status_display()返回中文如已上架前端模板中{% if product.status published %}可直接用。orders/models.py定义订单状态机简化版满足毕业设计要求class Order(models.Model): STATUS_CHOICES [ (pending, 待付款), (paid, 已付款), (shipped, 已发货), (completed, 已完成), (cancelled, 已取消), ] buyer models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders_as_buyer) seller models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders_as_seller) product models.ForeignKey(Product, on_deletemodels.CASCADE, related_nameorders) amount models.DecimalField(max_digits10, decimal_places2) status models.CharField(max_length10, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) class Meta: ordering [-created_at]注意这里没引入OrderItem订单明细表因为二手商品单笔订单只含一个商品加明细表反而增加答辩解释成本。若老师问“以后支持购物车怎么办”可答“当前架构预留扩展只需新增OrderItem模型并关联Orderamount字段移至明细表”。3. 实现商品发布、搜索与权限控制的三步落地法3.1 用 ModelForm 快速构建商品发布表单绕过手写 HTML 验证products/forms.pyfrom django import forms from .models import Product, Category class ProductForm(forms.ModelForm): # 覆盖默认字段添加图片多文件上传支持 images forms.ImageField( widgetforms.ClearableFileInput(attrs{multiple: True}), requiredFalse ) class Meta: model Product fields [title, description, price, category, status] widgets { description: forms.Textarea(attrs{rows: 4}), price: forms.NumberInput(attrs{step: 0.01}), } def clean_price(self): price self.cleaned_data.get(price) if price 0.01: raise forms.ValidationError(价格不能低于 0.01 元) return price def clean(self): cleaned_data super().clean() status cleaned_data.get(status) if status published and not self.instance.pk: # 新建商品设为已上架需触发审核流程此处简化为直接设 pending cleaned_data[status] pending return cleaned_dataproducts/views.py中处理发布逻辑含图片保存from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.core.files.storage import default_storage from .forms import ProductForm from .models import Product login_required def product_create(request): if request.method POST: form ProductForm(request.POST, request.FILES) if form.is_valid(): product form.save(commitFalse) product.owner request.user product.save() # 处理多图上传Django 4.2 支持 request.FILES.getlist for file in request.FILES.getlist(images): # 使用 default_storage 保存到 MEDIA_ROOT path default_storage.save(fproducts/{request.user.id}/{file.name}, file) # 此处应关联到 ProductImage 模型为简化暂存路径到 product.image_urls 字段 # 实际毕业设计中建议新建 ProductImage 模型外键关联 Product return redirect(products:detail, pkproduct.pk) else: form ProductForm() return render(request, products/product_form.html, {form: form})products/templates/products/product_form.html使用 crispy_forms 渲染{% load crispy_forms_tags %} form methodpost enctypemultipart/form-data {% csrf_token %} {{ form|crispy }} button typesubmit classbtn btn-primary发布商品/button /form提示enctypemultipart/form-data必不可少否则request.FILES为空crispy_forms的|crispy过滤器自动添加 Bootstrap 样式避免手写 CSS 影响答辩演示效果。3.2 用 django-filter 实现“价格区间关键词分类”三合一搜索products/filters.pyimport django_filters from .models import Product class ProductFilter(django_filters.FilterSet): min_price django_filters.NumberFilter(field_nameprice, lookup_exprgte, label最低价格) max_price django_filters.NumberFilter(field_nameprice, lookup_exprlte, label最高价格) search django_filters.CharFilter( field_nametitle, lookup_expricontains, label商品名称 ) category django_filters.ModelChoiceFilter( querysetCategory.objects.all(), label分类 ) class Meta: model Product fields [search, min_price, max_price, category]products/views.py中集成过滤器from django.shortcuts import render from django_filters.views import FilterView from .filters import ProductFilter from .models import Product class ProductListView(FilterView): model Product template_name products/product_list.html context_object_name products filterset_class ProductFilter paginate_by 12 # 每页 12 条符合毕业设计展示需求 def get_filterset_kwargs(self, filterset_class): kwargs super().get_filterset_kwargs(filterset_class) # 默认只显示已上架商品 kwargs[queryset] Product.objects.filter(statuspublished) return kwargsproducts/urls.pyfrom django.urls import path from . import views urlpatterns [ path(, views.ProductListView.as_view(), namelist), path(int:pk/, views.ProductDetailView.as_view(), namedetail), ]products/templates/products/product_list.html中渲染过滤表单!-- 过滤表单 -- form methodget {{ filter.form }} button typesubmit classbtn btn-outline-primary搜索/button a href{% url products:list %} classbtn btn-outline-secondary重置/a /form !-- 商品列表 -- div classrow {% for product in products %} div classcol-md-4 mb-4 div classcard img src{{ product.image_url|default:https://via.placeholder.com/300x200?textNoImage }} classcard-img-top alt{{ product.title }} div classcard-body h5 classcard-title{{ product.title }}/h5 p classcard-text{{ product.description|truncatewords:20 }}/p p classtext-danger¥{{ product.price }}/p a href{% url products:detail product.pk %} classbtn btn-sm btn-primary查看详情/a /div /div /div {% endfor %} /div注意filter.form自动渲染所有字段min_price/max_price生成input typenumbersearch生成input typetextcategory生成select。答辩时老师点“手机”分类“1000”最低价页面实时刷新证明你真懂查询逻辑。3.3 权限控制谁可以编辑商品谁能看到待审核商品Django 默认User模型权限粒度粗毕业设计不必接入django-guardian用UserPassesTestMixin足够products/views.py中限制编辑权限from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.views.generic import UpdateView class ProductUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): model Product form_class ProductForm template_name products/product_form.html success_url /products/ def test_func(self): product self.get_object() # 仅商品拥有者或超级用户可编辑 return product.owner self.request.user or self.request.user.is_superuser def handle_no_permission(self): from django.http import HttpResponseForbidden return HttpResponseForbidden(您没有权限编辑此商品)secondhand/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(users/, include(users.urls)), path(products/, include(products.urls)), path(orders/, include(orders.urls)), ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)products/admin.py中定制 Admin 界面答辩亮点from django.contrib import admin from .models import Product, Category admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display [title, price, category, owner, status, created_at] list_filter [status, category, created_at] search_fields [title, description] list_editable [status] # 直接在列表页修改状态 date_hierarchy created_at raw_id_fields [owner] # 避免用户多时下拉卡顿 def save_model(self, request, obj, form, change): # 保存时记录操作人 if not change: obj.created_by request.user super().save_model(request, obj, form, change) admin.register(Category) class CategoryAdmin(admin.ModelAdmin): prepopulated_fields {slug: (name,)} # 输入 name 自动填充 slug提示prepopulated_fields让分类名输入后自动生成 URL 友好 slug如“笔记本电脑”→bi-jing-ben-dian-nao答辩时老师输入中文看到 URL 变成拼音会觉得你考虑周全。4. 宝塔面板部署 Django 项目的实操步骤与避坑清单4.1 宝塔安装与 Python 项目环境配置登录宝塔面板后按顺序操作软件商店 → Python 项目管理 → 安装 Python 3.11勾选pip和setuptools网站 → 添加站点域名填your-domain.com根目录选/www/wwwroot/secondhandPHP 版本选“纯静态”Django 不用 PHPPython 项目 → 添加项目项目名称secondhand项目路径/www/wwwroot/secondhandPython 版本3.11启动文件/www/wwwroot/secondhand/manage.py启动命令gunicorn secondhand.wsgi:application -c /www/wwwroot/secondhand/gunicorn.conf.py运行目录/www/wwwroot/secondhand注意宝塔默认用gunicorn不是runserver。gunicorn.conf.py需手动创建内容见下节否则启动失败。4.2 gunicorn 配置文件与静态资源处理/www/wwwroot/secondhand/gunicorn.conf.pyimport multiprocessing bind 127.0.0.1:8000 # gunicorn 监听端口Nginx 反向代理至此 bind_address 127.0.0.1:8000 workers multiprocessing.cpu_count() * 2 1 worker_connections 1000 max_requests 1000 timeout 30 keepalive 2 preload True daemon False pidfile /www/wwwroot/secondhand/gunicorn.pid accesslog /www/wwwroot/secondhand/logs/access.log errorlog /www/wwwroot/secondhand/logs/error.log loglevel info执行收集静态文件关键否则 CSS/JS 404cd /www/wwwroot/secondhand source /www/server/python/v3.11/bin/activate python manage.py collectstatic --noinput此时STATIC_ROOT指向的/www/wwwroot/secondhand/staticfiles目录已生成全部静态资源。4.3 Nginx 反向代理配置与 media 文件直出宝塔网站设置 → 配置文件替换原有内容为# 代理 Django 应用 location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态文件由 Nginx 直出提升性能 location /static/ { alias /www/wwwroot/secondhand/staticfiles/; } # 用户上传图片直出 location /media/ { alias /www/wwwroot/secondhand/media/; }保存后重启 Nginx。此时访问http://your-domain.com应显示首页/admin/可登录后台。提示/media/路径必须与settings.py中MEDIA_URL一致且alias后路径末尾带/否则图片 404。答辩前务必上传一张测试图打开浏览器开发者工具 Network 标签确认media/xxx.jpg返回 200。4.4 常见报错与速查解决方案报错现象原因解决方案ModuleNotFoundError: No module named secondhand.wsgimanage.py所在目录未加入 Python path在宝塔 Python 项目设置中“运行目录”必须填/www/wwwroot/secondhand不是子目录502 Bad Gatewaygunicorn 未启动或端口冲突进入终端ps aux | grep gunicorn杀掉旧进程检查gunicorn.conf.py中bind端口是否被占用如netstat -tuln | grep 8000Static files not foundcollectstatic未执行或STATIC_ROOT路径错误进入宝塔终端cd /www/wwwroot/secondhand python manage.py collectstatic --noinput确认staticfiles目录存在且有文件Database connection failedsettings.py中DATABASES未切换为 MySQL安装宝塔 MySQL创建数据库修改settings.pyENGINE: django.db.backends.mysqlNAME: secondhand_dbUSER: rootPASSWORD: your_mysql_password5. 毕业设计答辩高频问题应答技巧与源码交付清单5.1 针对“Django ORM 查询优化”的三个必答话术老师常问“商品列表页如果数据量大怎么优化”标准答案结构承认瓶颈 给出 Django 原生解法 说明毕业设计取舍理由。“当前用Product.objects.filter(statuspublished)查询数据量小时无压力。若未来扩展我会① 在status字段加数据库索引db_indexTrue已在models.py中声明② 对分类、价格范围等高频筛选字段用django-filter的Index选项生成复合索引③ 分页用Paginator已实现避免LIMIT OFFSET深分页问题——但毕业设计数据量百条级OFFSET效率足够。”老师追问“为什么不用原生 SQL”回答“Django ORM 提供了extra()和raw()方法但会破坏模型抽象层。答辩材料中ER 图和models.py保持一致便于评审老师快速理解数据关系。若真需复杂统计我会用Product.objects.extra(tables[orders], where[products.id orders.product_id])而非脱离 ORM 写 SQL。”5.2 源码交付包必须包含的 7 个文件/目录毕业设计提交的.zip包不能只有manage.py必须结构完整让老师解压后pip install -r requirements.txt python manage.py migrate即可运行路径作用是否必须requirements.txtpip freeze requirements.txt生成含Django4.2.11等精确版本✅secondhand/settings.py注释掉DEBUGTrueALLOWED_HOSTS设为[*]本地测试或[your-domain.com]部署版✅db.sqlite3开发用 SQLite 数据库含测试数据避免老师装 MySQL✅media/目录存放 3 张测试商品图命名phone1.jpg,book2.jpg,laptop3.jpg证明图片上传功能✅templates/全目录含base.html、products/list.html等Bootstrap 5 CSS 已内联或 CDN 引入✅README.md写明① 环境要求Python 3.11② 启动步骤pip install -r req.txt→python manage.py migrate→python manage.py createsuperuser→python manage.py runserver③ 管理员账号admin/admin123✅ER_Diagram.png用 draw.io 画的实体关系图标注User-Product一对多、Product-Category多对一等关系✅注意requirements.txt中不要出现pkg-resources0.0.0Ubuntu 系统 bug用pip install --no-deps -r requirements.txt验证无报错。5.3 答辩现场演示的 3 个黄金动作打开 Admin 后台现场创建一个商品登录http://localhost:8000/admin→ 点击Products→ADD PRODUCT→ 填标题“二手 iPhone 13”、价格“3500.00”、选分类“手机”、状态“待审核” →SAVE→ 页面跳转到列表页看到新商品状态为“待审核”。目的证明模型、表单、权限全部打通。切换到前台搜索该商品打开http://localhost:8000/products/→ 在搜索框输入“iPhone” → 点搜索 → 列表只显示刚创建的商品因状态为 pending不显示→ 修改 Admin 中状态为“已上架” → 刷新前台商品出现。目的验证搜索逻辑与状态过滤联动。用另一个账号登录尝试编辑新开浏览器隐身窗口 →http://localhost:8000/users/login/→ 用测试账号testuser/test123登录 → 访问http://localhost:8000/products/1/update/ID 为 1 的商品→ 显示 403 Forbidden。目的证明权限控制生效不是摆设。这三个动作耗时不到 2 分钟却覆盖了模型、视图、模板、权限、数据库五大核心得分点。老师记笔记时写的往往是“演示流畅功能闭环”而不是“代码写了多少行”。本文还有配套的精品资源点击获取
返回列表