Django二手交易网站开发:毕业设计全流程实战 📅 发布时间:2026/9/15 17:27:02 👁 浏览次数: 简介这是一套基于Python与Django框架开发的二手商品交易网站完整源码面向高校计算机专业学生及初学者适用于毕业设计、课程设计与Web项目实践解决从零构建用户注册登录、商品发布管理、搜索浏览到交易记录查询等核心电商功能的学习需求。资源包共98个文件含30个核心Python业务逻辑文件如views.py、models.py、controller.py、9个HTML模板页含index.html、goods_detail.html等完整前端视图、10张商品示例图片及6个JS/CSS静态资源结构清晰分层明确涵盖login、trade、goodsIssue等多个Django应用模块压缩包仅673KB轻量易部署。已有228人学习下载提供可直接运行的测试通过版本附README.md说明文档与典型目录结构便于理解MVT架构落地细节、快速二次开发或功能扩展。1. 为什么用 Django 搭建二手商品交易网站是毕业设计里「稳中带亮」的硬核选择很多计算机专业同学在选毕业设计题时卡在第一步既要体现技术深度又不能脱离课程基础既要功能完整可演示又得在两个月内能跑通上线。这时候“基于 Python Django 开发的二手商品交易网站”就成了一类高频、高通过率、高展示度的选题——它不是简单 CRUD 的博客系统而是天然包含用户认证、商品发布、图片上传、状态流转上架/下架/已售、订单关联、搜索过滤等真实业务模块Django 自带 Admin 后台、ORM、表单验证、CSRF 防护和中间件机制让开发者能把精力聚焦在业务逻辑而非轮子重造上。更重要的是它不依赖前端框架就能交付可用界面也兼容 Vue/React 前后端分离演进后续扩展性强。对指导老师而言项目结构清晰、代码可读性高、数据库 ER 图易绘制、部署路径明确Nginx Gunicorn SQLite/MySQL答辩时能讲清 MVC 分层、QuerySet 优化、权限控制粒度、缓存策略等关键点比纯爬虫或算法题更易体现工程能力。2. 从零初始化 Django 项目结构规划、核心 App 划分与数据库建模二手商品交易网站不是单个模型堆砌而是多个实体间存在明确关系约束的业务系统。盲目开干容易后期重构成本陡增。我一般会先画出最小可行域MVP的实体关系草图用户User→ 商品Item→ 分类Category→ 订单Order→ 订单项OrderItem→ 图片Image。其中 User 复用 Django 自带AbstractUser避免重复实现登录注册Item 必须关联发布者ForeignKey to User、所属分类ForeignKey to Category、状态字段choices: on_sale, sold, off_shelfOrder 需记录买家、下单时间、总金额、支付状态OrderItem 则解耦商品与订单数量关系支持同一订单含多件不同商品。2.1 创建项目骨架与基础 App# 创建虚拟环境推荐使用 venv避免全局污染 python -m venv env source env/bin/activate # Linux/macOS # env\Scripts\activate # Windows # 安装 Django建议固定版本如 4.2.13避免新版 API 变更影响答辩 pip install django4.2.13 # 初始化项目项目名建议用英文小写如 secondhand_shop django-admin startproject secondhand_shop . python manage.py startapp users python manage.py startapp items python manage.py startapp orders提示startproject后加.表示当前目录为项目根目录避免生成嵌套文件夹。App 命名按职责拆分不叫app或main方便后期模块化维护。2.2 定义核心模型models.py以items/models.py为例定义商品主模型# items/models.py from django.db import models from django.contrib.auth import get_user_model from django.core.validators import MinValueValidator, MaxValueValidator User get_user_model() class Category(models.Model): name models.CharField(max_length50, uniqueTrue, verbose_name分类名称) slug models.SlugField(max_length60, uniqueTrue, blankTrue) def __str__(self): return self.name class Meta: verbose_name 商品分类 verbose_name_plural 商品分类 class Item(models.Model): STATUS_CHOICES [ (on_sale, 在售), (sold, 已售), (off_shelf, 下架), ] title models.CharField(max_length120, verbose_name标题) description models.TextField(verbose_name描述) price models.DecimalField( max_digits10, decimal_places2, validators[MinValueValidator(0.01)], verbose_name价格 ) seller models.ForeignKey( User, on_deletemodels.CASCADE, related_nameitems, verbose_name卖家 ) category models.ForeignKey( Category, on_deletemodels.PROTECT, # 防止误删分类导致商品归属丢失 related_nameitems, verbose_name分类 ) status models.CharField( max_length20, choicesSTATUS_CHOICES, defaulton_sale, verbose_name状态 ) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) updated_at models.DateTimeField(auto_nowTrue, verbose_name更新时间) def __str__(self): return f{self.title} ({self.get_status_display()}) class Meta: verbose_name 商品 verbose_name_plural 商品 ordering [-created_at]关键参数说明on_deletemodels.PROTECT比CASCADE更安全删除分类前会抛出ProtectedError强制开发者显式处理依赖related_nameitems反向查询时可用user.items.all()语义清晰verbose_name中文字段名直接影响 Admin 界面和表单标签ordering [-created_at]默认按时间倒序列表页无需额外.order_by()。2.3 注册 App 并执行迁移修改secondhand_shop/settings.py中的INSTALLED_APPSINSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 自定义 App顺序重要靠前的 App 可被后置 App 引用 users, items, orders, ]然后执行python manage.py makemigrations python manage.py migrate注意makemigrations会生成migrations/0001_initial.py文件记录模型变更migrate才真正写入数据库。若中途修改模型需再次makemigrations—— 不要手动编辑 migration 文件除非你清楚--fake和--squash的作用。3. 实现用户中心与商品发布流程表单验证、图片上传与权限控制二手交易的核心动作是“用户发布商品”这涉及身份校验、文件上传、数据一致性保障三个层面。Django 提供了ModelForm、FileField、login_required装饰器等原生支持但需注意细节配置否则会出现上传失败、空字段绕过、跨用户编辑等问题。3.1 扩展 User 模型并构建用户资料页Django 默认 User 缺少头像、电话、学校等字段。直接修改auth.User不推荐应继承AbstractUser# users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): avatar models.ImageField( upload_toavatars/, blankTrue, nullTrue, verbose_name头像 ) phone models.CharField( max_length11, blankTrue, verbose_name手机号 ) school models.CharField( max_length100, blankTrue, verbose_name学校 ) class Meta: verbose_name 用户 verbose_name_plural 用户并在settings.py中指定AUTH_USER_MODEL users.CustomUser警告此设置必须在首次运行migrate前完成若已有迁移文件需先清空数据库或使用--fake-initial仅限开发环境。3.2 构建商品发布表单forms.py# items/forms.py from django import forms from .models import Item, Category class ItemCreateForm(forms.ModelForm): # 显式声明字段便于定制 widget 和验证逻辑 images forms.ImageField( requiredFalse, widgetforms.ClearableFileInput(attrs{multiple: True}), label商品图片最多3张 ) class Meta: model Item 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_images(self): images self.files.getlist(images) if len(images) 3: raise forms.ValidationError(最多上传 3 张图片) return images3.3 视图层实现发布逻辑views.py# items/views.py from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.http import HttpResponseForbidden from .forms import ItemCreateForm from .models import Item login_required def item_create(request): if request.method POST: form ItemCreateForm(request.POST, request.FILES) if form.is_valid(): item form.save(commitFalse) item.seller request.user # 绑定当前用户 item.save() # 处理多图上传假设 Image 模型已定义 from .models import Image images request.FILES.getlist(images) for img in images: Image.objects.create(itemitem, imageimg) return redirect(items:item_detail, pkitem.pk) else: form ItemCreateForm() return render(request, items/item_form.html, {form: form})关键逻辑说明login_required未登录用户访问自动跳转/accounts/login/form.save(commitFalse)暂不写入 DB以便插入seller字段request.FILES.getlist(images)获取多文件列表配合ClearableFileInput(attrs{multiple: True})使用redirect(items:item_detail, pkitem.pk)使用命名 URL 反向解析避免硬编码路径。3.4 配置媒体文件上传路径在settings.py中添加import os MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media)并在secondhand_shop/urls.py中追加开发环境路由from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), # ... 其他路由 ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)注意生产环境DEBUGFalse必须由 Nginx/Apache 直接服务MEDIA_ROOTDjango 不应处理静态/媒体文件。4. 实现商品列表页与搜索过滤QuerySet 优化、分页与关键词匹配二手网站首页必须支持按分类筛选、价格区间、关键词模糊搜索并保证响应速度。Django ORM 提供了链式 QuerySet但不当使用会导致 N1 查询或全表扫描。毕业设计中常被忽略的性能点恰恰是答辩加分项。4.1 构建高效商品列表视图# items/views.py from django.core.paginator import Paginator from django.db.models import Q from django.shortcuts import render def item_list(request): # 获取查询参数 category_id request.GET.get(category) min_price request.GET.get(min_price) max_price request.GET.get(max_price) q request.GET.get(q, ).strip() # 构建基础 QuerySet只查必要字段 queryset Item.objects.select_related(category, seller).filter(statuson_sale) # 分类筛选 if category_id: queryset queryset.filter(category_idcategory_id) # 价格区间 if min_price: queryset queryset.filter(price__gtefloat(min_price)) if max_price: queryset queryset.filter(price__ltefloat(max_price)) # 关键词搜索标题 描述使用 OR 逻辑 if q: queryset queryset.filter( Q(title__icontainsq) | Q(description__icontainsq) ) # 分页每页 12 条 paginator Paginator(queryset, 12) page_number request.GET.get(page) page_obj paginator.get_page(page_number) # 传递分类列表供模板筛选 categories Category.objects.all() return render(request, items/item_list.html, { page_obj: page_obj, categories: categories, query_params: request.GET.dict(), # 保留当前筛选条件用于分页链接 })QuerySet 优化要点select_related(category, seller)对 ForeignKey 字段做 JOIN避免循环中多次查询filter(statuson_sale)前置过滤减少后续计算量Q(title__icontainsq) | Q(description__icontainsq)icontains不区分大小写适合中文搜索若需更高性能可引入django.contrib.postgres.searchPostgreSQL或django-haystackElasticsearchrequest.GET.dict()将 GET 参数转为字典模板中可直接{% url items:item_list %}?{{ query_params|urlencode }}保持筛选状态。4.2 模板中实现筛选表单与分页导航!-- items/item_list.html -- form methodget classmb-4 div classrow g-2 div classcol-md-3 select namecategory classform-select option value全部分类/option {% for cat in categories %} option value{{ cat.id }} {% if request.GET.category cat.id|stringformat:s %}selected{% endif %} {{ cat.name }} /option {% endfor %} /select /div div classcol-md-2 input typenumber namemin_price classform-control placeholder最低价 value{{ request.GET.min_price }} /div div classcol-md-2 input typenumber namemax_price classform-control placeholder最高价 value{{ request.GET.max_price }} /div div classcol-md-3 input typetext nameq classform-control placeholder搜索商品... value{{ request.GET.q }} /div div classcol-md-2 button typesubmit classbtn btn-primary w-100搜索/button /div /div /form !-- 商品列表 -- div classrow {% for item in page_obj %} div classcol-md-4 mb-4 div classcard h-100 img src{{ item.image.url }} classcard-img-top alt{{ item.title }} height180 div classcard-body h5 classcard-title{{ item.title|truncatechars:20 }}/h5 p classcard-text text-muted small{{ item.description|truncatewords:20 }}/p div classd-flex justify-content-between align-items-center span classbadge bg-success¥{{ item.price }}/span small classtext-muted{{ item.category.name }}/small /div /div /div /div {% endfor %} /div !-- 分页 -- nav aria-labelPage navigation ul classpagination justify-content-center {% if page_obj.has_previous %} li classpage-itema classpage-link href?page1{{ query_params|urlencode }}首页/a/li li classpage-itema classpage-link href?page{{ page_obj.previous_page_number }}{{ query_params|urlencode }}上一页/a/li {% endif %} {% for num in page_obj.paginator.page_range %} {% if page_obj.number num %} li classpage-item activespan classpage-link{{ num }}/span/li {% elif num page_obj.number|add:-2 and num page_obj.number|add:2 %} li classpage-itema classpage-link href?page{{ num }}{{ query_params|urlencode }}{{ num }}/a/li {% endif %} {% endfor %} {% if page_obj.has_next %} li classpage-itema classpage-link href?page{{ page_obj.next_page_number }}{{ query_params|urlencode }}下一页/a/li li classpage-itema classpage-link href?page{{ page_obj.paginator.num_pages }}{{ query_params|urlencode }}末页/a/li {% endif %} /ul /nav提示{{ query_params|urlencode }}是关键它把所有筛选参数如?category3min_price50编码进分页链接避免翻页后筛选失效。5. 部署到宝塔面板Gunicorn Nginx 配置、静态文件收集与常见 502 排查毕业设计最终要能本地或服务器演示而宝塔面板是国内学生最常用的可视化部署工具。它简化了 Nginx 配置、Python 环境管理但 Django 项目需额外配置STATIC_ROOT、ALLOWED_HOSTS和进程守护否则极易出现 502 Bad Gateway 或静态文件 404。5.1 Django 项目部署前必改配置修改secondhand_shop/settings.py# 生产环境开关宝塔中设为 False DEBUG False # 允许访问的域名宝塔站点域名如 yoursite.com ALLOWED_HOSTS [yoursite.com, www.yoursite.com, 127.0.0.1] # 静态文件配置CSS/JS/Images STATIC_URL /static/ STATICFILES_DIRS [os.path.join(BASE_DIR, static)] STATIC_ROOT os.path.join(BASE_DIR, collected_static) # collectstatic 输出目录 # 媒体文件用户上传图片 MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media) # 数据库宝塔 MySQL DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: secondhand_db, USER: db_user, PASSWORD: db_pass, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }5.2 在宝塔中创建 Python 项目站点新建站点域名填yoursite.com根目录选项目所在路径如/www/wwwroot/secondhand_shopPython 版本安装 Python 3.9宝塔软件管理 → Python 项目 → 安装设置运行目录站点设置 → Python 项目 → 运行目录填/www/wwwroot/secondhand_shop启动命令gunicorn secondhand_shop.wsgi:application --bind 127.0.0.1:8000 --workers 2 --timeout 30静态文件在站点设置 → 静态文件 → 添加URL/static/→ 路径/www/wwwroot/secondhand_shop/collected_static/URL/media/→ 路径/www/wwwroot/secondhand_shop/media/5.3 执行部署命令SSH 登录服务器后cd /www/wwwroot/secondhand_shop source /www/server/python/bin/activate # 宝塔 Python 环境路径 # 安装依赖确保 requirements.txt 包含 gunicorn、mysqlclient pip install -r requirements.txt # 收集静态文件必须否则 CSS/JS 不加载 python manage.py collectstatic --noinput # 创建数据库宝塔 MySQL 中新建数据库 secondhand_db用户 db_user python manage.py migrate # 创建超级用户用于 Admin 登录 python manage.py createsuperuser # 测试 Gunicorn 是否能启动不加 --daemon看日志 gunicorn secondhand_shop.wsgi:application --bind 127.0.0.1:8000 --workers 2 --timeout 305.4 Nginx 配置关键段站点设置 → 配置文件# 在 server {} 块内添加 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; proxy_set_header X-Forwarded-Proto $scheme; } # 静态文件由 Nginx 直接服务提升性能 location /static/ { alias /www/wwwroot/secondhand_shop/collected_static/; } location /media/ { alias /www/wwwroot/secondhand_shop/media/; }常见 502 排查清单现象检查点解决方法访问首页显示 502Gunicorn 进程未启动进入宝塔 Python 项目 → 重启项目或 SSH 手动执行gunicorn ...看报错Admin 登录页样式错乱collectstatic未执行或路径错误检查STATIC_ROOT路径是否与 Nginxalias一致确认collectstatic输出文件数 0上传图片失败MEDIA_ROOT权限不足chown -R www:www /www/wwwroot/secondhand_shop/media/数据库连接失败mysqlclient未安装或 MySQL 服务未运行pip install mysqlclient宝塔 → 数据库 → 启动 MySQL注意宝塔 Python 项目插件会自动生成supervisor配置守护 Gunicorn 进程无需手动配置 systemd。若需调整 worker 数直接在宝塔界面修改启动命令即可。本文还有配套的精品资源点击获取