拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Django电商项目实战:从数据模型到宝塔部署全解析

简介基于Django框架的完整电子商务网站项目源码面向Python Web初学者及需要搭建电商系统的开发者以真实业务串联前后端开发全流程。压缩包内共101个文件、约3.89MB包含17个Python源文件、15个HTML页面模板、多个商品图片与WebP素材以及SQLite数据库文件可直接对照运行调试。目前已有90人学习下载。项目完整覆盖Django MVT分层、ORM数据库建模、用户注册登录、商品分类展示、购物车与订单管理、支付网关接入等核心功能并通过DTL模板渲染动态页面。学习者能借此熟悉URL路由、视图逻辑、模板继承、CSRF防护等关键机制还可参考其缓存、静态文件管理与部署思路快速积累电商类Web应用的工程实践经验。 前段时间帮朋友搭了一个小型的电商站点技术栈直接选了Django。从需求确认、数据模型设计到部署上线前后差不多用了一个多月。项目不算大但商品展示、分类检索、购物车、订单、后台录入这套电商核心闭环全部跑通了目前已经在正常运营。如果你正打算用Django做一个电商网站想找一个完整项目把框架的各个知识点串起来或者你已经在开发但卡在购物车、订单、部署这些环节上那这篇内容应该能帮你省掉不少弯路的成本。我会把这套项目的完整思路拆开讲从为什么选Django、数据表怎么设计到核心业务逻辑的写法再到宝塔部署上线的完整流程最后是实际操作中踩过的坑。不是教科书式的概念罗列全是动手做出来的经验。1. 项目整体设计与技术选型1.1 为什么选Django而不是其他框架说实话做电商网站可选的方案很多Python里有Flask、FastAPIPHP里有LaravelJava里有Spring Boot。但我这次选Django主要还是看重几个别人替代不了的优势。第一是后台管理。电商网站需要频繁维护商品、价格、库存、分类Django自带的admin后台虽然谈不上多华丽但改一改就能用省掉了开发一整套管理端的工作量。在项目早期这套后台帮了大忙朋友那边运营人员自己就能录商品不用每次找我改数据库。第二是ORM和迁移机制。电商业务的数据关系比较复杂商品、分类、订单、用户之间关联很多。Django的ORM把大部分SQL操作封装好了而且makemigrations/migrate这套迁移流程在迭代改字段的时候特别舒服不需要手写一堆ALTER TABLE。如果学Django建议把ORM这块吃透日常开发90%的查询都能覆盖到。第三是用户认证和表单处理。登录、注册、密码重置、登录状态判断这些电商刚需功能Django自带auth应用直接扩一下就够用。表单的CSRF保护也是默认开启的对新手来说少踩很多安全上的坑。Flask当然也可以用但Flask太自由用户认证、ORM、表单都要自己选组件拼装。如果团队没有特别强的架构能力项目做到后面容易组件风格混乱。FastAPI更适合做前后端分离的API项目和异步高并发场景纯服务端渲染的电商站点没必要选它。所以最后我选了Django它对一个人短时间内把一个完整项目做成、上线这件事帮助是最大的。想系统学习Django的话《Django 5 By Example》这类书里的项目思路也很有参考价值不过看完书还是得自己动手完整做一个项目很多问题不亲自动手是遇不到的。1.2 核心模块与数据表设计动工之前我先画了一遍业务模块用户、商品分类、商品、购物车、订单、订单项。用户直接基于Django自带的User模型扩展没有单独建表。商品和分类是一对多关系一个分类下面多个商品。订单和用户是一对多订单和订单项是一对多。购物车我没有单独建表而是选择了存Session后面会详细说原因。下面是models.py里最核心的两张表商品和分类from django.db import models class Category(models.Model): name models.CharField(分类名称, max_length50, uniqueTrue) slug models.SlugField(URL别名, max_length60, uniqueTrue) class Meta: verbose_name 商品分类 verbose_name_plural 商品分类 def __str__(self): return self.name class Product(models.Model): category models.ForeignKey( Category, on_deletemodels.CASCADE, verbose_name分类, related_nameproducts ) name models.CharField(商品名称, max_length200) slug models.SlugField(URL别名, max_length200, uniqueTrue) description models.TextField(商品描述) price models.DecimalField(售价, max_digits10, decimal_places2) stock models.PositiveIntegerField(库存, default0) cover models.ImageField(封面图, upload_toproducts/%Y/%m/) is_active models.BooleanField(上架, defaultTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: ordering [-created_at] verbose_name 商品 verbose_name_plural 商品 def __str__(self): return self.name这里有两个新手经常踩的坑我提前说一下。第一个是价格字段一定要用DecimalField不要用FloatField。FloatField在计算金额的时候会有精度问题0.1加0.2可能等于0.30000000000000004这在电商订单金额计算里是绝对不能接受的。刚开始我图省事用了FloatField测试订单金额时发现多出来好几分钱后来全部改成DecimalField小数位用decimal_places2金额计算才稳下来。第二个是删除策略。分类用了on_deletemodels.CASCADE也就是删除分类会连带删除分类下的所有商品。这在电商系统里很危险万一运营误删了一个分类整个商品列表全没了。实际操作中我甚至不建议直接删除商品更推荐用is_active做软删除。下架商品只是把is_active改成False前端查询默认过滤掉但历史订单关联的数据还在后面统计报表也能查得到。真正的物理删除应该只留给超级管理员在后台操作。2. 核心业务模块的实现细节2.1 商品的展示与检索ListView 分页 Q对象商品展示页是用户进站看到的第一屏我用的是Django的类视图ListView配合分页器和Q对象做搜索代码量小逻辑也清楚。# views.py from django.views.generic import ListView from django.db.models import Q from .models import Product class ProductListView(ListView): model Product template_name shop/product_list.html context_object_name products paginate_by 10 def get_queryset(self): queryset Product.objects.filter(is_activeTrue) keyword self.request.GET.get(q, ) if keyword: queryset queryset.filter( Q(name__icontainskeyword) | Q(description__icontainskeyword) ) return queryset这里解释几个关键点。filter(is_activeTrue)保证了软删除或者未上架的商品不会出现在前台。paginate_by 10让列表页自动分页模板里直接使用page_obj就能渲染页码。Q对象的作用是拼接多个查询条件实现商品名和商品描述同时模糊搜索不区分大小写。在试着学习Django的时候类视图和函数视图到底该用哪个是我纠结过一阵子的问题。我的建议是纯查询的列表和详情页面直接用类视图省代码还自带分页涉及POST操作、表单处理复杂的场景比如下单、注册用函数视图或者FormView逻辑更直观调试也方便。不要为了炫技强行用类视图处理所有场景。2.2 购物车基于Session的轻量实现购物车是全项目里最容易让人纠结的部分。最开始的方案是建一张Cart表存数据库后来想了想对于不需要登录也能加购的访客来说Session方案更合适。购物车数据本质上是临时的用户关闭浏览器或者清掉会话之后基本就不需要了没必要长期占用数据库表。直接用request.session把商品ID和数量存起来读写都在内存/缓存里速度比查数据库快一个量级。而且访客不需要注册登录就能加购等下单的时候再引导登录购物车数据合入用户账户转化路径顺畅很多。下面是加购和购物车展示的辅助函数# cart.py def add_to_cart(request, product_id, quantity1): cart request.session.get(cart, {}) cart[str(product_id)] int(cart.get(str(product_id), 0)) quantity request.session[cart] cart request.session.modified True def get_cart_items(request): cart request.session.get(cart, {}) items [] total_price 0 for product_id, quantity in cart.items(): try: product Product.objects.get(pkint(product_id), is_activeTrue) except Product.DoesNotExist: continue subtotal product.price * quantity items.append({ product: product, quantity: quantity, subtotal: subtotal, }) total_price subtotal return items, total_price几个细节要注意。Session里拿出来的key一定是字符串所以存的时候用str(product_id)取的时候再用int(product_id)转回来类型不对很容易踩坑。每次修改购物车之后记得设置request.session.modified True确保Django知道session变了否则有些场景下浏览器刷新后数据不会更新。get_cart_items里用try...except Product.DoesNotExist过滤掉已经被删除或者下架的商品防止用户购物车里躺着空数据。输入product_id做归一化存储每个商品只存一个数量字段而不是存多行重复记录这样后续加购、修改数量直接对字典操作删除也只需要del cart[str(product_id)]一行代码比建表方案处理起来简单得多。2.3 订单创建与库存扣减事务保证一致性订单模块是整个项目里最不能出错的环节。用户点下单后台要做的事情很多创建订单、创建订单项、扣减库存、清空购物车。任何一个步骤失败都不能让数据变成半成品。这里必须用数据库事务来保证一致性。# services.py from django.db import transaction from django.shortcuts import get_object_or_404 from .models import Order, OrderItem, Product def create_order(request, user): cart_items, total_price get_cart_items(request) if not cart_items: raise ValueError(购物车为空) order None with transaction.atomic(): order Order.objects.create(useruser, total_pricetotal_price) for item in cart_items: product Product.objects.select_for_update().get(pkitem[product].pk) if product.stock item[quantity]: raise ValueError(f商品 {product.name} 库存不足) product.stock - item[quantity] product.save() OrderItem.objects.create( orderorder, productproduct, priceitem[product].price, quantityitem[quantity] ) request.session[cart] {} request.session.modified True return order这里有两个关键操作我重点说一下。select_for_update()是行级锁在同一事务里这条商品记录会被锁定其他并发事务必须等当前事务提交才能继续操作。电商秒杀、抢购场景里库存超卖问题就是这么解决的。如果没有这行代码两个人同时下单买最后一个库存两个请求都可能读到stock1然后同时扣减最后库存变成负数订单却都创建成功了。加了锁之后第二个请求必须等第一个事务结束才能读到最新库存发现不足就直接抛异常。事务中间抛出的ValueError会触发整个事务回滚之前创建好的订单、扣掉的库存全部还原。所以下单业务一定要放事务里这是保证数据一致性的底线。在实际项目里这部分逻辑我抽到了services.py而不是直接写在视图函数里这样以后写接口、写测试套件都可以直接复用视图层只负责接收请求和返回结果。订单和订单项的关系表是这样设计的class Order(models.Model): user models.ForeignKey(User, on_deletemodels.PROTECT, verbose_name用户) total_price models.DecimalField(订单总金额, max_digits10, decimal_places2) created_at models.DateTimeField(下单时间, auto_now_addTrue) status models.CharField(订单状态, max_length20, defaultpending) class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, verbose_name订单, related_nameitems) product models.ForeignKey(Product, on_deletemodels.PROTECT, verbose_name商品) price models.DecimalField(成交单价, max_digits10, decimal_places2) quantity models.PositiveIntegerField(数量)注意订单项里存了商品的price快照而不是下单时实时去查商品表。这个设计经验很重要因为商品价格是会变的用户下单后过几天后台调价了历史订单里的成交价不应该跟着变。Order和OrderItem的on_delete我特意用了PROTECT防止用户或商品被误删导致订单数据丢失。2.4 后台管理Django Admin的高效配置Django Admin如果只是默认注册模型确实很简陋但稍微配置几个字段就非常能打。我这里的配置是这样的# admin.py from django.contrib import admin from .models import Category, Product admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display (name, category, price, stock, is_active, created_at) list_filter (category, is_active) search_fields (name, description) prepopulated_fields {slug: (name,)} list_editable (price, stock, is_active) list_per_page 20prepopulated_fields可以根据商品名自动生成URL别名不用手动输入。list_editable让运营人员直接在列表页改价格、库存和上下架状态不用点进详情页效率高很多。商品描述字段如果想做得更好用可以集成一个富文本编辑器项目中我用的是django-wangeditorpip安装后后台表单里就能直接编辑富文本商品详情页就能渲染成带格式的图文描述比纯Textarea舒服很多。后台配置这块看起来不起眼但对电商项目来说运营效率就是成本。本来需要写一大堆前端管理页面才能实现的功能Admin里几行配置全搞定了。3. 从开发到上线Django项目的部署实践3.1 上线前的安全检查与配置本地开发的时候DEBUGTrue能帮我们显示详细报错信息但上线之前这行必须改成False否则任何访问者都能看到服务器堆栈信息和代码路径安全隐患非常大。下面是我每一次部署都会过一遍的检查清单。# settings.py 生产环境核心配置 DEBUG False # 通过环境变量读取不要写死在代码里 import os SECRET_KEY os.environ.get(DJANGO_SECRET_KEY) ALLOWED_HOSTS [www.example.com, example.com] STATIC_ROOT os.path.join(BASE_DIR, staticfiles) STATIC_URL /static/ MEDIA_ROOT os.path.join(BASE_DIR, media) MEDIA_URL /media/SECRET_KEY绝对不能硬编码在代码里。这个值一旦泄露攻击者就能伪造session、篡改签名数据。我上线的时候把SECRET_KEY写进了服务器环境变量代码仓库里只保留一个.env.example模板里面放占位符。数据库密码同理换成了从os.environ读取。开发阶段SQLite用着确实方便但上线我建议换成MySQL或者PostgreSQL。SQLite在高并发写入场景下会有锁冲突电商这种写入量稍大的业务很容易出问题。这次项目用的MySQL配置好字符集为utf8mb4之后基本不用操心。另外一个容易被忽略的是ALLOWED_HOSTS。开发的时候往往是空列表所有hostname都能访问。上线前如果还保持这样Django在非DEBUG模式下会拒绝所有请求。配置成自己域名的白名单也能顺带挡掉一部分直接用IP访问的恶意扫描。3.2 用宝塔面板部署Django项目部署这一块我直接用的宝塔面板对单人开发或者小团队来说效率是最高的。核心流程其实就四步代码上线、装依赖、跑迁移、配反向代理。下面是完整的操作路径。第一步在宝塔软件商店安装Python项目管理器这个工具能创建虚拟环境、安装依赖、管理进程和启动日志比直接手动敲gunicorn命令省心很多。然后把项目代码上传到服务器建议放在/www/wwwroot/下面和站点目录保持一致。第二步创建Python项目。选择项目路径、Python版本3.10以上填写启动方式。我用的启动命令是gunicorn ecommerce.wsgi:application --bind 127.0.0.1:8001 --workers 3workers数量一般设为CPU核心数×21小项目服务器2核4G的话起3个worker就够了。先在项目管理器里安装依赖requirements.txt要提前整理好Django5.0.6 gunicorn21.2.0 Pillow10.3.0 django-wangeditor4.3.1 pymysql1.4.6Pillow是Django处理ImageField的必装依赖不装的话图片上传直接报错。MySQL驱动如果用的是PyMySQL还需要在项目__init__.py里加一段初始化代码import pymysql pymysql.install_as_MySQLdb()第三步在宝塔的网站里添加一个纯静态站点或反向代理站点配置Nginx把80/443端口流量转发到本地的8001端口。这一步的核心配置是location / { proxy_pass http://127.0.0.1:8001; 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; } location /static/ { alias /www/wwwroot/ecommerce/staticfiles/; } location /media/ { alias /www/wwwroot/ecommerce/media/; }然后记得执行一次python manage.py collectstatic把项目里所有静态文件收集到STATIC_ROOT指定的目录。这里动作很快但很容易被漏掉漏掉的结果就是部署成功后页面CSS全部丢失下面第四节我会专门讲这个坑。第四步申请HTTPS证书并绑定域名在宝塔里一键开启强制HTTPS。Shop页面涉及登录和订单没有HTTPS等于明文传输密码和地址这对电商站来说是不可接受的。宝塔的Lets Encrypt证书申请流程是自动化的点几下就搞定。3.3 日志与备份上线之后更要盯着的事上线只是开始运营过程中最怕的是出问题你不知道。Django默认把日志打到标准输出但如果你用gunicorn部署最好把日志重定向到固定文件里。我在settings.py里加了这样的配置LOGGING { version: 1, disable_existing_loggers: False, handlers: { file: { level: ERROR, class: logging.FileHandler, filename: os.path.join(BASE_DIR, logs/error.log), }, }, loggers: { django: { handlers: [file], level: ERROR, propagate: True, }, }, }然后把logs/目录加到了.gitignore里服务器上保留日志文件。以后用户反馈出问题直接tail -f logs/error.log看报错比远程连数据库瞎猜快得多。备份这块我用的是宝塔的计划任务每天凌晨自动把MySQL数据库和media/目录分别打包上传。数据库备份用mysqldump图片文件用zip压缩。数据是一个人搭起来的项目里最容易忽视却最要命的事硬盘挂了或者手误删表没有备份就只能哭了。4. 常见问题与排查技巧实录4.1 部署后页面静态文件全部丢失这个问题我在第一次用宝塔部署Django的时候就遇到了打开页面发现HTML结构正常但是所有CSS样式全部失效页面跟裸奔一样。排查思路其实很简单按顺序查四个点。第一步确认运行collectstatic了没有。Django在开发模式下能自动处理静态文件但DEBUGFalse以后不再提供静态文件服务必须手动把分散在各个app里的静态文件收集到统一目录。第二步检查settings里的STATIC_URL和STATIC_ROOT配置以及STATIC_ROOT目录里是不是真的有文件。第三步确认Nginx里location /static/的alias路径和STATIC_ROOT完全一致最后别忘了改完配置执行nginx -s reload。多次遇到改完Nginx没重启以为没生效的情况也是新手最常见的问题。第四步媒体文件同理检查location /media/有没有配加上之后刷新就能看到商品图了。我把这个排查流程整理成了表格方便后面再遇到时直接照表查现象可能原因解决方法CSS/JS全部404没执行collectstaticpython manage.py collectstatic商品图片不显示Nginx没配/media路由添加location /media/并reload页面能开但样式错乱静态文件路径配置不对对比STATIC_URL与Nginx alias是否一致DEBUGFalse下个别页面报错依赖了Django静态文件服务全部通过Nginx处理静态资源4.2 DisallowedHost与CSRF相关的报错部署好后用域名访问突然页面弹出DisallowedHost at /这是最典型的ALLOWED_HOSTS没配置好。解决方案就一步把访问的域名加到ALLOWED_HOSTS列表里然后在项目管理器里重启gunicorn进程让新配置生效。注意重启的不只是NginxPython进程也得重启否则settings变更不会加载。另一个高频报错是CSRF验证失败。POST登录、提交订单时表单模板里忘记写{% csrf_token %}Django就会直接拒绝请求。本地开发时如果开了Chrome的插件改headers也可能触发。解决办法模板form标签下面第一行就写{% csrf_token %}如果是前后端分离的AJAX请求还需要在header里带X-CSRFToken。这部分Django做得比很多框架都严格理解了它是在保护你的业务不受跨站请求伪造攻击就不觉得烦了。4.3 删除对象与价格计算的坑Django执行删除操作说起来就是一行Model.objects.filter(...).delete()但坑往往藏在后面的级联行为里。我前面已经在模型设计里提过on_deletemodels.CASCADE的风险实际项目里还是继续奉行能软删不硬删的策略。用户账号、商品、分类这些核心数据都是先锁定is_activeFalse做逻辑删除只有确认不影响任何历史数据的情况下超级管理员才会做真正的物理删除。另外一个删除相关的常见异常是DoesNotExist。在get_cart_items函数里我已经用了try...except去兜底但在视图层直接get_object_or_404时如果商品没了会返回404页面而不是崩溃。这里要强调一下处理用户输入时异常捕获要做到位否则用户清掉session里的旧数据就会看到500错误页体验很差。价格计算那块再次强调用Decimal而不是float。Django的DecimalField取出来就是Decimal类型做乘法和累加也不会丢精度。如果你发现自己算出来的订单金额和数据库里查到的对不上第一怀疑对象就是某处用了float()强行转换或者用了Python内置的sum()对浮点列表做累加。单价乘以数量也要随手强转一下Decimal再算尽量别用float参与这个坑我替你们踩过了。4.4 时区和session登录失效的问题Django项目如果忘记设置时区用户看到的创建时间会比实际时间晚8个小时。部署国内的站点settiings里一定要确认TIME_ZONE Asia/ShanghaiUSE_TZ True保存到数据库的是UTC时间但模板渲染时会自动转换到当前时区。如果看到的时间不对就先查这两项配置基本都是这个问题。session登录失效最常见的原因是服务端重启后django.contrib.sessions表数据丢失或者浏览器清除了Cookie。如果用户反馈登录一会儿就掉线先查DEFAULT_AUTO_FIELD、SESSION_EXPIRE_AT_BROWSER_CLOSE等配置是否适合业务。默认的SESSION_ENGINE是存在数据库里的单机部署没问题多台机器负载均衡的时候就需要换成Redis存session了。这个项目规模还不大保持默认的数据库session就好后续如果流量上去了再迁移到Redis也很快。整个项目做完之后我最大的感受是Django确实能帮你省掉很多重复造轮子的时间但电商网站真正考验人的地方从来不是框架本身而是库存、价格、订单状态、数据一致性这些业务细节。一个“能跑”的电商demo和一个能正式上线的电商后台差别全在这些细节里。希望这篇整理能让你在自己的项目里少踩几个坑把精力花在真正重要的业务逻辑上。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门