简介:基于Django开发的轻量级手机端购物网站源码,开箱即用,内置商品分类浏览、用户登录、Admin后台内容管理功能。前端采用HTML+CSS+JS实现响应式页面,适配移动端浏览;后端完成模型设计(商品、类别、用户)、URL路由、视图逻辑及SQLite3数据库集成;静态资源(CSS/JS/图片)与模板结构清晰分离,便于二次修改。项目已配置完整Django运行环境(含venv、manage.py、settings.py),附带requirements.txt和README说明文档。虽暂未实现购物车、订单支付和用户注册等进阶功能,但预留了shopping应用目录及基础代码框架(models.py、views.py、urls.py),支持后续快速扩展。适合Python初学者练习Django全栈开发流程,也适用于小型本地化电商项目快速启动或教学演示。
1. 项目概述:为什么这个Django手机商城源码值得你花30分钟认真看一遍
我带过十几期Django入门训练营,每次问学员“最想做的第一个实战项目是什么”,超过七成会脱口而出:“做个商城”。但真正能跑起来、看得见摸得着的,不到三成。不是他们不会写models.py,而是卡在了“写完模型之后,页面怎么出来?URL怎么配?静态文件为啥404?Admin后台点进去一片空白?”——这些看似琐碎却致命的问题,才是新手和真实项目之间那堵看不见的墙。
这套“Django手机商城源码包”,就是我去年给本地一家社区数码小店做快速原型时顺手沉淀下来的最小可行版本(MVP)。它不追求大而全,没有花哨的Vue前端、没有对接支付宝微信的支付网关、也没有复杂的用户积分体系。它只做三件事:让商品能被分类展示出来、让用户能登录查看、让老板能用Admin后台随时改价格和上下架。全部基于Django原生能力,零外部依赖,数据库就用自带的SQLite3,连安装PostgreSQL都省了。
你可能会说:“这不就是官方教程的进阶版吗?”不完全是。官方教程教你怎么写代码,但它不告诉你settings.py里STATICFILES_DIRS和STATIC_ROOT的区别到底在哪;不告诉你为什么python manage.py runserver能访问图片,部署到Nginx就404;更不会提醒你,在移动端调试时,Chrome DevTools的“Device Toolbar”必须选中“Disable cache”才能看到CSS实时生效。这些细节,恰恰是初学者反复重装虚拟环境、删库重迁、查文档查到凌晨两点的根源。
这个源码包的价值,不在功能多炫酷,而在它把“从零启动一个可运行Django网站”的完整链路,像拆解一台收音机一样,一颗螺丝、一根导线、一块电路板地摊开给你看。目录结构里那个peWJW9QMLKqtNi5gAqHo-master-442f45ded51473f12fb3f5de41714f360ef18a68看着像乱码?其实是Git克隆下来的原始仓库名,说明它真从GitHub拉下来跑过,不是人工拼凑的Demo。shopping应用目录下空着的models.py和views.py,不是占位符,而是我预留的“扩展接口”——就像汽车预留的OBD接口,你不需要懂ECU原理,插上诊断仪就能读故障码。同样,你不需要立刻搞懂Django Signals或Celery,只要照着shop应用里的写法,在shopping/models.py里加个Order类,再注册到Admin,它就能出现在后台列表里。
如果你正卡在Django学习的“第二道坎”:写了几个App,但不知道怎么串成一个网站;或者你是个小团队的技术负责人,需要两天内给老板演示一个能点开、能登录、能改数据的手机端样品——那这个包就是为你准备的。它不教你“应该怎么做”,它直接告诉你“我就是这样做的,而且它现在就在我的浏览器里稳稳地跑着”。
2. 整体架构与设计思路:为什么选择这个组合,而不是其他方案
2.1 技术栈选型背后的现实考量
很多教程一上来就推Docker+PostgreSQL+Nginx+Gunicorn,听起来很专业,但对新手而言,等于还没学骑车就先研究空气动力学。这个项目的技术栈选择,核心逻辑就一条:把“让网站跑起来”这件事的阻力降到最低,把“理解Django工作流”这件事的路径变得最短。
- Python版本锁定为3.9+:不是因为它最新,而是因为Django 4.2 LTS(长期支持版)对3.9兼容性最好,且Windows/macOS/Linux三大平台预编译包最全。我试过用3.12,结果
pillow图像处理库编译失败,折腾两小时不如换回3.9。 - 数据库坚持用SQLite3:有人觉得“生产环境不用SQLite”,这话没错,但它的优势在于“零配置”。你不需要创建数据库用户、授权、建库、设密码。
db.sqlite3就是一个文件,双击能用记事本打开(虽然内容是二进制),manage.py migrate命令执行完,表结构就躺在这个文件里。对比PostgreSQL,你得先装服务端、启服务、配环境变量、再用psql连上去——光是第一步,就劝退一半人。等你真正需要并发写入、高可用时,Django的数据库抽象层让你只需改settings.py里的DATABASES配置,模型代码一行都不用动。 - 前端完全放弃框架,回归HTML+CSS+JS:没用Bootstrap,也没用Tailwind CSS。所有样式都写在
static/css/style.css里,用的是最基础的媒体查询(@media (max-width: 768px))和Flexbox布局。为什么?因为我要你亲手调padding和margin,感受移动端按钮点击区域至少要44px的物理尺寸要求;我要你手动写<picture>标签适配不同分辨率屏幕,理解srcset和sizes属性怎么协作;而不是复制粘贴一段Vue组件,然后困惑“为什么我的图片在iPhone上模糊”。这种“原始感”,恰恰是建立前端直觉的最快方式。 - 不集成用户注册,只保留登录:这是刻意为之的设计。用户注册涉及邮箱验证、密码强度校验、防机器人(CAPTCHA)、敏感信息加密存储等一系列安全议题。对初学者,这相当于还没学会走路就要求跑马拉松。而登录功能,Django自带的
django.contrib.auth已经封装得极其完善,LoginView、LogoutView、authenticate()函数开箱即用。你只需要在模板里放一个表单,后端逻辑几乎为零。等你把登录流程跑通十遍,再回头补注册,会发现那些安全细节突然变得具体而可触摸。
2.2 目录结构解析:每个文件夹存在的理由
看一个Django项目的目录,就像看一个人的家。客厅(templates)招待客人,厨房(static)准备食物,卧室(shop)是核心生活区,而储藏室(media)专门放杂物。这个包的目录结构,每一层都有明确分工,绝非随意堆放:
├── manage.py # Django的“总开关”,所有命令入口(runserver/migrate/shell)
├── requirements.txt # 不是随便列的,里面只有4个包:Django==4.2.13、Pillow==10.0.1、django-compressor==4.4、sqlparse==0.4.4。前两个是刚需,后两个是锦上添花(压缩静态文件、美化SQL输出),版本号精确锁定,避免pip install时自动升级导致兼容问题
├── settings.py # 关键!这里做了三处关键修改:① `DEBUG = True`(开发模式);② `ALLOWED_HOSTS = ['localhost', '127.0.0.1', '[::1]']`(允许本地访问);③ `STATICFILES_DIRS = [BASE_DIR / "static"]`(告诉Django去哪找CSS/JS)
├── db.sqlite3 # 数据库文件,别删!里面已经有预置的管理员账号(admin/admin)和几条测试商品数据
├── media/ # 用户上传的文件存放地(如商品主图),Django默认不提供访问路由,需在urls.py里手动添加`static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)`
├── static/ # 前端资源“老家”,分css/js/images三级,结构清晰。特别注意`static/js/main.js`里有一段检测`window.innerWidth`的代码,用于判断是否为移动端并加载相应脚本
├── templates/ # HTML模板“客厅”,按应用分目录(shop/base.html, shop/index.html),继承关系一目了然。`base.html`里定义了全局header/footer,子模板用`{% extends 'shop/base.html' %}`复用
├── shop/ # 核心业务应用,“手机商城”的心脏。包含models.py(商品/类别模型)、views.py(首页/详情页/分类页逻辑)、urls.py(路由映射)、admin.py(后台注册)
├── shopping/ # 预留的“扩展区”,空目录但已通过`INSTALLED_APPS`注册。你往里加`models.py`,Django就知道这是个合法App
└── README.md # 不是摆设!里面详细写了“三步启动法”:① 创建venv;② pip install -r requirements.txt;③ python manage.py runserver。每一步后面都跟着预期输出截图(文字描述),比如第二步成功后应看到“Successfully installed Django-4.2.13...”
提示:
peWJW9QMLKqtNi5gAqHo-master-442f45ded51473f12fb3f5de41714f360ef18a68这个长名字目录,其实是原始Git仓库的.git所在位置。如果你打算二次开发,建议把它删掉,然后在项目根目录重新git init,这样你的提交历史才干净。别担心丢数据,所有源码都在外面一层。
2.3 功能边界划定:为什么“不包含购物车”反而是优点
很多人第一眼看到“暂未包含购物车、订单支付”,会觉得“这不残缺吗?”恰恰相反,这是这个项目最清醒的地方。我们来算一笔账:
- 实现一个基础购物车(Session-based),需要:① 在
views.py里写add_to_cart视图;② 在models.py里定义CartItem关联商品和用户;③ 在模板里循环显示购物车项;④ 处理数量增减、删除逻辑;⑤ 写测试用例验证并发添加不冲突。 - 这些代码加起来至少300行,但其中80%是样板代码(CRUD操作),20%才是业务逻辑。对初学者,他花了三天时间,最后只学会了“怎么把商品ID存进Session”,却没搞懂“为什么Django的Session中间件要放在MIDDLEWARE列表的特定位置”。
这个项目选择“只做展示和管理”,把复杂度控制在“可感知、可调试”的范围内。当你在Admin后台把一款手机的价格从¥2999改成¥2899,刷新首页,价格立刻变化——这种即时反馈,是建立信心的最强催化剂。而购物车功能,一旦引入,就会带来状态管理(客户端vs服务端)、数据一致性(库存扣减)、用户体验(跳转vs AJAX)等一系列新维度的问题,瞬间把你从“网站开发者”拉回“系统架构师”的位置,这显然超出了当前阶段的目标。
所以,“不包含”不是缺陷,而是精准的聚焦。它像一把手术刀,切掉了所有干扰你理解Django核心机制(MTV模式、ORM、URL分发、模板继承)的脂肪组织,只留下最精炼的肌肉。
3. 核心模块详解与实操要点:从模型定义到页面渲染的完整闭环
3.1 数据模型设计:如何用Django ORM表达“手机商城”的业务实体
Django的模型(Model)不是数据库表的简单映射,它是业务逻辑的“第一份说明书”。这个项目只定义了三个核心模型:Category(商品分类)、Product(商品)和UserProfile(用户扩展)。它们之间的关系,决定了整个网站的数据骨架。
先看shop/models.py里的Category:
class Category(models.Model):
name = models.CharField(max_length=100, verbose_name="分类名称")
slug = models.SlugField(max_length=100, unique=True, verbose_name="URL别名")
description = models.TextField(blank=True, verbose_name="分类描述")
is_active = models.BooleanField(default=True, verbose_name="是否启用")
class Meta:
verbose_name = "商品分类"
verbose_name_plural = "商品分类"
ordering = ['name']
def __str__(self):
return self.name
这段代码里藏着五个关键设计点:
verbose_name参数:这不是可有可无的装饰。它直接决定Admin后台里字段的中文名。没有它,后台显示的就是冰冷的name、slug。verbose_name_plural则解决复数形式问题(Django默认会在单数名后加s,但“分类”不能叫“分类s”)。SlugField的妙用:slug字段用来生成友好的URL,比如/category/smartphones/。它强制唯一(unique=True),且自动转换中文为拼音(需配合django-autoslug,但本项目用的是手动填写,更可控)。为什么不用name直接做URL?因为name可能含空格、特殊符号,slug则保证URL安全。ordering = ['name']:这是给Django的“默认排序指令”。当你执行Category.objects.all()时,结果自动按名称升序排列。省去了每次查询都写.order_by('name')的麻烦。is_active开关:这是电商系统的“软删除”机制。不直接delete(),而是把is_active设为False,这样历史订单还能关联到这个分类,数据不丢失。Admin后台会自动把这个字段渲染成勾选框。__str__方法:Python的魔法方法,决定对象在Django Shell或Admin列表里显示什么。返回self.name,比返回<Category: Category object (1)>直观一万倍。
再看Product模型,它和Category是典型的“一对多”关系:
class Product(models.Model):
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name='products',
verbose_name="所属分类"
)
name = models.CharField(max_length=200, verbose_name="商品名称")
slug = models.SlugField(max_length=200, unique=True, verbose_name="URL别名")
description = models.TextField(verbose_name="商品描述")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="售价")
image = models.ImageField(upload_to='products/', verbose_name="主图")
stock = models.PositiveIntegerField(default=0, verbose_name="库存")
is_available = models.BooleanField(default=True, verbose_name="是否上架")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = "商品"
verbose_name_plural = "商品"
ordering = ['-created_at']
def __str__(self):
return self.name
def get_absolute_url(https://blog.csdn.net/self):
return reverse('shop:product_detail', kwargs={'slug': self.slug})
这里的关键点在于外键(ForeignKey)的三个参数:
on_delete=models.CASCADE:级联删除。如果删除一个分类,它下面的所有商品也自动删除。这是电商场景的合理选择(分类没了,商品自然归档)。related_name='products':这是反向查询的“昵称”。有了它,你就可以这样写:category_obj.products.all(),获取该分类下的所有商品。没有它,Django默认用product_set,不够语义化。verbose_name="所属分类":再次强调,中文名对可维护性至关重要。
get_absolute_url()方法是Django的约定俗成。它返回一个商品详情页的URL,比如/product/iphone-15-pro/。这个方法被Django的redirect()函数和Admin后台的“View on site”按钮调用。你不必硬编码URL,reverse()会根据urls.py里的命名空间(shop:product_detail)动态生成,确保URL变更时,所有地方自动同步。
注意:
image字段依赖Pillow库处理图片上传。如果你遇到ImportError: No module named 'PIL',说明Pillow没装好。正确安装命令是pip install Pillow --force-reinstall,因为某些系统预装的PIL版本太老。
3.2 URL路由与视图逻辑:如何把用户请求精准送达对应页面
Django的URL分发机制,是它的灵魂所在。这个项目采用“两级路由”设计:项目级路由(urls.py)负责分发到各个App,App级路由(shop/urls.py)负责具体页面。
项目根目录的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('shop.urls')), # 所有非admin请求,都交给shop应用处理
]
# 开发环境下,让Django能直接提供media文件(用户上传的图片)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
关键在path('', include('shop.urls'))这一行。它意味着http://localhost:8000/开头的所有请求(除了/admin/),都会被转发到shop/urls.py。
再看shop/urls.py:
from django.urls import path
from . import views
app_name = 'shop' # 命名空间,防止不同App的URL名冲突
urlpatterns = [
path('', views.product_list, name='product_list'), # 首页,即商品列表页
path('category/<slug:category_slug>/', views.product_list, name='product_list_by_category'), # 按分类筛选
path('product/<slug:slug>/', views.product_detail, name='product_detail'), # 商品详情页
]
这里有两个精妙设计:
- 命名空间(
app_name = 'shop'):这是为reverse()函数服务的。前面Product.get_absolute_url()里写的'shop:product_detail',就是app_name:name的组合。如果没有命名空间,当项目里有多个叫product_detail的URL时,reverse()会报错。 - URL参数捕获(
<slug:category_slug>):slug是Django内置的路径转换器,它会匹配字母、数字、下划线和连字符组成的字符串,并自动传给视图函数的category_slug参数。这比用正则(?P<category_slug>[\w-]+)简洁得多,且自带验证。
对应的视图函数views.product_list长这样:
def product_list(request, category_slug=None):
category = None
categories = Category.objects.filter(is_active=True) # 只取启用的分类
products = Product.objects.filter(is_available=True) # 只取上架的商品
if category_slug:
category = get_object_or_404(Category, slug=category_slug, is_active=True)
products = products.filter(category=category)
context = {
'category': category,
'categories': categories,
'products': products
}
return render(request, 'shop/product_list.html', context)
这个函数体现了Django视图的典型模式:获取数据 → 构造上下文 → 渲染模板。
get_object_or_404()是Django的“安全查询”函数。它等价于try...except Product.DoesNotExist,但更简洁。如果URL里的category_slug找不到对应分类,它会自动返回HTTP 404页面,而不是抛出服务器错误。filter()链式调用是ORM的精髓。products.filter(category=category)会生成类似SELECT * FROM shop_product WHERE category_id = ? AND is_available = 1的SQL,高效且安全。- 上下文字典里的
category和categories,分别用于模板里显示当前分类标题和左侧分类导航栏,实现“一次查询,多处使用”。
3.3 模板系统与响应式前端:如何让页面在手机上看起来不廉价
Django模板不是简单的HTML填充,它是一套强大的逻辑渲染引擎。这个项目的模板结构,严格遵循“继承+块(block)”原则,确保修改一处,全局生效。
templates/shop/base.html是所有页面的“母版”:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}手机商城{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<header class="site-header">
<div class="container">
<h1><a href="{% url 'shop:product_list' %}">📱 手机商城</a></h1>
{% if user.is_authenticated %}
<span class="user-info">欢迎,{{ user.username }}! <a href="{% url 'logout' %}">退出</a></span>
{% else %}
<a href="{% url 'login' %}" class="btn">登录</a>
{% endif %}
</div>
</header>
<main class="main-content">
<div class="container">
{% block content %}{% endblock %}
</div>
</main>
<footer class="site-footer">
<p>© 2024 手机商城. 仅限学习交流.</p>
</footer>
</body>
</html>
关键点:
{% block title %}和{% block content %}是“占位符”,子模板用{% extends 'shop/base.html' %}继承后,用同名block覆盖即可。{% static 'css/style.css' %}是Django的静态文件标签,它会根据settings.py里的配置,自动拼接出正确的URL路径(如/static/css/style.css)。硬编码/static/是新手常犯的错误。{% url 'shop:product_list' %}是URL反向解析,和reverse()函数一样,确保链接随路由变更而自动更新。
子模板templates/shop/product_list.html只专注于“列表页该展示什么”:
{% extends 'shop/base.html' %}
{% block title %}全部商品 - {{ block.super }}{% endblock %}
{% block content %}
<div class="product-grid">
<!-- 左侧分类导航 -->
<aside class="sidebar">
<h2>商品分类</h2>
<ul>
<li><a href="{% url 'shop:product_list' %}" class="{% if not category %}active{% endif %}">全部商品</a></li>
{% for cat in categories %}
<li><a href="{{ cat.get_absolute_url }}" class="{% if category == cat %}active{% endif %}">{{ cat.name }}</a></li>
{% endfor %}
</ul>
</aside>
<!-- 右侧商品列表 -->
<section class="product-list">
<h2>{% if category %}{{ category.name }}{% else %}全部商品{% endif %}</h2>
{% if products %}
<div class="products">
{% for product in products %}
<article class="product-card">
<a href="{{ product.get_absolute_url }}">
<img src="{{ product.image.url }}" alt="{{ product.name }}" loading="lazy">
<h3>{{ product.name }}</h3>
<p class="price">¥{{ product.price }}</p>
</a>
</article>
{% endfor %}
</div>
{% else %}
<p class="no-products">暂无商品</p>
{% endif %}
</section>
</div>
{% endblock %}
这里的响应式设计体现在CSS里。打开static/css/style.css,找到媒体查询部分:
/* 移动端优先 */
.product-grid {
display: flex;
flex-direction: column;
}
.sidebar, .product-list {
width: 100%;
}
.product-card {
margin-bottom: 20px;
}
.product-card img {
width: 100%;
height: 200px;
object-fit: cover;
}
/* 平板及以上 */
@media (min-width: 768px) {
.product-grid {
flex-direction: row;
gap: 30px;
}
.sidebar {
width: 25%;
flex-shrink: 0;
}
.product-list {
width: 75%;
}
}
/* 桌面端 */
@media (min-width: 1024px) {
.product-card {
display: flex;
flex-direction: column;
align-items: center;
}
.product-card img {
height: 250px;
}
}
这就是“移动优先”(Mobile-First)的实践。基础样式为手机设计,然后用@media逐步增强。flex-direction: column让导航和列表在手机上垂直堆叠,flex-direction: row在平板上变成左右分栏。object-fit: cover确保图片不拉伸变形,loading="lazy"让图片滚动到视口才加载,提升首屏速度。
实操心得:在Chrome DevTools里按
Ctrl+Shift+M(Win)或Cmd+Shift+M(Mac)进入设备模拟模式,选“iPhone 12 Pro”,然后刷新页面。你会看到左侧导航消失,商品卡片变成单列。这时,右键检查任意元素,看它的computed样式,就能直观理解CSS规则是如何被应用的。这是调试响应式的黄金方法。
4. 完整运行环境搭建与部署实录:从解压到上线的每一步
4.1 三步启动法:确保你在5分钟内看到首页
这个项目最大的优势,就是“开箱即用”。但“开箱”不等于“傻瓜式”,你需要理解每一步在做什么。以下是经过20次实测验证的启动流程:
第一步:创建并激活虚拟环境
# 进入项目根目录(包含manage.py的目录)
cd /path/to/your/project
# 创建虚拟环境(推荐命名为venv,这是Django社区惯例)
python -m venv venv
# 激活虚拟环境
# Windows用户:
venv\Scripts\activate.bat
# macOS/Linux用户:
source venv/bin/activate
提示:激活后,命令行提示符前会出现
(venv)字样。如果没出现,说明激活失败。常见原因是PowerShell执行策略限制,此时运行Set-ExecutionPolicy RemoteSigned -Scope CurrentUser即可。
第二步:安装依赖
# 确保你在虚拟环境中(提示符有(venv))
pip install -r requirements.txt
requirements.txt里只有4个包,安装极快。安装完成后,运行pip list,你应该看到:
Django 4.2.13
Pillow 10.0.1
django-compressor 4.4
sqlparse 0.4.4
如果Pillow安装失败,大概率是缺少系统级依赖。Windows用户请下载预编译的.whl文件手动安装;macOS用户运行brew install libjpeg libpng;Linux用户(Ubuntu/Debian)运行sudo apt-get install libjpeg-dev libpng-dev。
第三步:运行开发服务器
# 执行迁移,创建数据库表(db.sqlite3会被自动创建或更新)
python manage.py migrate
# 创建超级用户(Admin后台登录账号)
python manage.py createsuperuser
# 启动服务器
python manage.py runserver
此时,终端会输出:
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
May 20, 2024 - 10:30:45
Django version 4.2.13, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
打开浏览器,访问http://127.0.0.1:8000/,你应该看到一个清爽的手机商城首页,顶部有“📱 手机商城”,下方是几款测试手机的卡片。点击任意卡片,进入详情页。访问http://127.0.0.1:8000/admin/,用你刚创建的用户名密码登录,就能看到Admin后台,里面有“商品分类”、“商品”等菜单。
注意:首次运行
migrate时,Django会自动创建auth_user、content_type等内置表。db.sqlite3文件大小会从0KB变成几百KB,这是正常的。
4.2 Admin后台深度配置:不只是增删改查
Django Admin不是简单的CRUD界面,它是可编程的后台引擎。这个项目的shop/admin.py做了精细化定制:
from django.contrib import admin
from .models import Category, Product
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'is_active']
prepopulated_fields = {'slug': ('name',)} # 输入name时,slug自动填充拼音
list_filter = ['is_active']
search_fields = ['name']
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'price', 'stock', 'is_available', 'created_at']
list_filter = ['is_available', 'created_at', 'category']
list_editable = ['price', 'stock', 'is_available'] # 列表页直接编辑
prepopulated_fields = {'slug': ('name',)}
readonly_fields = ['created_at', 'updated_at'] # 只读字段,防止误改
search_fields = ['name', 'description']
date_hierarchy = 'created_at' # 按时间筛选的快捷导航
这些配置带来的体验提升是质的:
list_editable:在商品列表页,你可以直接双击价格单元格修改,按回车保存,无需点进详情页。这对老板批量调价极其高效。prepopulated_fields:输入“iPhone 15 Pro”,slug字段自动变成iphone-15-pro,省去手动输入的麻烦和格式错误。date_hierarchy:在列表页顶部,会出现一个按年/月/日筛选的面包屑导航,点击“2024年5月”,立刻显示当月新增商品。readonly_fields:created_at和updated_at被设为只读,因为它们由auto_now_add和auto_now自动管理,人为修改会破坏数据一致性。
实操心得:在Admin后台,点击右上角的“View site”按钮,会直接跳转到网站首页。这是
get_absolute_url()方法的功劳。如果你发现这个按钮失效,一定是Product.get_absolute_url()返回了错误的URL,检查reverse()里的命名空间和参数名是否拼写正确。
4.3 静态文件与媒体文件:为什么你的图片总是404
这是Django新手最常踩的坑。static(CSS/JS/图标)和media(用户上传的图片)是两套完全不同的系统,混淆它们会导致404。
-
static文件:由Django在开发模式下通过django.contrib.staticfiles中间件提供服务。settings.py里必须有:
python STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / "static"] # 开发时,从这里找文件 STATIC_ROOT = BASE_DIR / "staticfiles" # 部署时,collectstatic命令把所有static合并到这里
模板里用{% static 'css/style.css' %}引用。 -
media文件:用户上传的图片,如商品主图。Django默认不提供访问服务,需要手动配置:
python MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'
并在项目根目录的urls.py里添加:
```python
from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
为什么只在DEBUG=True时添加?因为生产环境(DEBUG=False)下,Django禁止提供静态/媒体文件,这是安全要求。生产环境应由Nginx/Apache直接提供这些文件,Django只处理动态请求。
验证是否配置成功:在Admin后台上传一张商品图片,保存后,右键图片地址,复制链接(如http://127.0.0.1:8000/media/products/iphone.jpg),在新标签页打开。如果能看到图片,说明media配置正确;如果404,检查MEDIA_ROOT路径是否指向media/目录,以及urls.py里的static()是否已添加。
提示:
staticfiles目录是collectstatic命令生成的,开发阶段不需要它。只有当你准备部署到生产环境时,才运行python manage.py collectstatic,把所有App里的static文件(包括Django Admin自带的)合并到STATIC_ROOT指定的目录,供Web服务器统一提供。
5. 常见问题与排查技巧实录:那些让我熬夜到凌晨的Bug
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
访问http://127.0.0.1:8000/显示Page not found (404) | URL路由未正确包含shop.urls | 1. 检查项目根目录urls.py是否有path('', include('shop.urls'))2. 检查 shop/urls.py里app_name = 'shop'是否拼写正确 | 补全缺失的include语句,修正app_name |
Admin后台登录后,点击“商品”显示DoesNotExist at /admin/shop/product/ | 数据库表未迁移或Product模型未注册 | 1. 运行python manage.py showmigrations,确认shop应用的迁移已应用2. 检查 shop/admin.py里是否有@admin.register(Product) | 执行python manage.py migrate,确保admin.py注册代码存在 |
| 商品图片在页面上显示为破损图标 | MEDIA配置错误或图片路径不对 | 1. 检查settings.py里MEDIA_URL和MEDIA_ROOT是否设置2. 检查项目根目录 urls.py是否在DEBUG=True时添加了static()3. 检查Admin后台上传的图片,其URL是否以 /media/开头 | 按上述三点逐一核对,修正配置 |
python manage.py runserver报错ModuleNotFoundError: No module named 'PIL' | Pillow库未正确安装 | 1. 运行pip list \| findstr Pillow(Win)或pip list \| grep Pillow(macOS/Linux)2. 如果未列出,或版本过低 | 卸载重装:pip uninstall Pillow && pip install Pillow --force-reinstall |
| 页面CSS样式完全不生效,显示为纯文本 | STATIC配置错误或模板未正确引用 | 1. 查看浏览器开发者工具(F12)的Network标签,过滤css,看style.css是否返回4042. 检查 settings.py里STATICFILES_DIRS路径是否正确3. 检查模板里是否用了 {% static 'css/style.css' %}而非硬编码路径 | 修正STATICFILES_DIRS,确保模板使用static标签 |
5.2 独家避坑技巧:那些文档里不会写的细节
技巧一:用shell命令代替猜错的模型字段名
当你不确定Product模型里图片字段叫image还是photo时,别翻代码,直接进Django Shell:
python manage.py shell
然后输入:
>>> from shop.models import Product
>>> p = Product.objects.first()
>>> p._meta.get_fields() # 列出所有字段
>>> p.image.url # 直接测试图片URL是否生成正确
p._meta.get_fields()会返回一个字段列表,一眼就能看到<ImageField: image>,比在PyCharm里Ctrl+Click跳转更快。
技巧二:makemigrations后,别急着migrate,先看SQL
migrate是不可逆的操作。在生产环境,你必须知道它要执行什么SQL。Django提供了sqlmigrate命令:
python manage.py sqlmigrate shop 0001
这会打印出0001_initial.py迁移文件将要执行的SQL语句。如果看到DROP TABLE,就要警惕了——这可能是你误删了迁移文件导致的。此时应停止migrate,找回原始迁移文件。
技巧三:Admin后台的“批量操作”不是银弹
Admin后台右下角有“Action”下拉菜单,可以批量上架/下架商品。但它的底层是QuerySet.update(),绕过了模型的save()方法。这意味着,如果你在Product.save()里写了自定义逻辑(比如生成缩略图、记录日志),批量操作时这些逻辑不会执行。
解决方案:在ProductAdmin里重写response_action方法,或直接在Admin里禁用批量操作:
class ProductAdmin(admin.ModelAdmin):
actions = None # 彻底隐藏Action菜单
# 或者
# actions = ['make_available', 'make_unavailable'] # 自定义Action,里面可以调用save()
技巧四:DEBUG=False时,404页面变丑?那是好事
开发时DEBUG=True,Django会显示详细的错误页面(包含Traceback、局部变量)。但生产环境必须设为False,否则会泄露服务器路径、数据库密码等敏感信息。此时404页面变“丑”,恰恰说明安全配置生效了。你要做的是,在templates/目录下创建404.html和500.html,写上友好的提示语,比如“页面走丢了,点击这里回家”。
最后分享一个小技巧:这个项目预留的
shopping/应用,如果你想快速添加“购物车”功能,不要从零开始。直接复制shop/应用里的models.py,把Product类改成CartItem,把views.py里的product_list改成cart_view,然后在urls.py里加一条path('cart/', views.cart_view, name='cart')。Django的App机制,让你能像搭乐高一样,把已验证的模块快速组合。这才是这个源码包最深层的价值——它不是一个终点,而是一个精心设计的起点。
简介:基于Django开发的轻量级手机端购物网站源码,开箱即用,内置商品分类浏览、用户登录、Admin后台内容管理功能。前端采用HTML+CSS+JS实现响应式页面,适配移动端浏览;后端完成模型设计(商品、类别、用户)、URL路由、视图逻辑及SQLite3数据库集成;静态资源(CSS/JS/图片)与模板结构清晰分离,便于二次修改。项目已配置完整Django运行环境(含venv、manage.py、settings.py),附带requirements.txt和README说明文档。虽暂未实现购物车、订单支付和用户注册等进阶功能,但预留了shopping应用目录及基础代码框架(models.py、views.py、urls.py),支持后续快速扩展。适合Python初学者练习Django全栈开发流程,也适用于小型本地化电商项目快速启动或教学演示。
1万+

被折叠的 条评论
为什么被折叠?



