Django 从 0 到 1 打造完整电商平台:收货地址管理

Django 从 0 到 1 打造完整电商平台:收货地址管理 IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我也会在其它的平台持续发布最新文章助你少走弯路。前面几篇我们完成了用户注册、登录、个人中心等功能用户体系已经基本成型。但电商平台还有一个绕不开的基础模块——收货地址。用户下单时总得告诉商家“送到哪儿”所以今天我们就来实现收货地址的完整增删改查并支持设置默认地址。在第 2 篇设计数据库时我们已经建好了Address模型现在只需补上视图、表单、模板和路由。全程代码量不大但有几个业务细节值得注意默认地址的唯一性切换、地址数量上限、以及删除时的软提示。一、需求分析收货地址模块的功能点地址列表展示当前用户所有收货地址默认地址置顶且高亮。新增地址表单填写收件人、手机号、省市区、详细地址可设为默认。编辑地址修改已有地址的信息。删除地址删除指定地址若删除的是默认地址则将最近更新的地址设为默认。设置默认在列表页一键设置默认地址或通过编辑页勾选实现。权限控制仅登录用户可操作且只能操作自己的地址。二、模型回顾我们在apps/users/models.py中已定义Address核心字段user外键关联用户related_nameaddressesreceiver、phone、province、city、district、detailis_default布尔字段标记默认地址create_time、update_time模型无需修改直接用。三、编写表单在apps/users/forms.py中追加地址表单from .modelsimportAddress class AddressForm(forms.ModelForm): class Meta: modelAddress fields[receiver,phone,province,city,district,detail,is_default]widgets{receiver:forms.TextInput(attrs{class:form-control,placeholder:收件人姓名}),phone:forms.TextInput(attrs{class:form-control,placeholder:手机号}),province:forms.TextInput(attrs{class:form-control,placeholder:省份}),city:forms.TextInput(attrs{class:form-control,placeholder:城市}),district:forms.TextInput(attrs{class:form-control,placeholder:区/县}),detail:forms.Textarea(attrs{class:form-control,rows:3,placeholder:详细地址街道、门牌号等}),is_default:forms.CheckboxInput(attrs{class:form-check-input}),}labels{is_default:设为默认地址,}def clean_phone(self): phoneself.cleaned_data.get(phone)ifphone and not phone.isdigit()or len(phone)!11: raise forms.ValidationError(请输入有效的11位手机号)returnphone地址表单直接绑定 ModelForm省去大量重复字段定义。手机号做了简单的格式校验。四、编写视图打开apps/users/views.py在文件顶部确保导入Address模型和新表单from .modelsimportUser, Address from .formsimport(RegisterForm, LoginForm, UpdateProfileForm, ChangePasswordForm, AddressForm)4.1 地址列表login_required(login_urlusers:login)def address_list(request): addressesrequest.user.addresses.all()returnrender(request,users/address_list.html,{addresses:addresses})利用related_nameaddresses反向查询一行拿到所有地址。4.2 新增地址login_required(login_urlusers:login)def address_create(request):ifrequest.methodPOST:formAddressForm(request.POST)ifform.is_valid(): addressform.save(commitFalse)address.userrequest.user# 如果勾选了默认先取消该用户其他默认地址ifaddress.is_default: request.user.addresses.filter(is_defaultTrue).update(is_defaultFalse)address.save()messages.success(request,收货地址添加成功。)returnredirect(users:address_list)else: formAddressForm()returnrender(request,users/address_form.html,{form:form,title:新增收货地址})关键点commitFalse让你先不存入数据库绑上user后再保存。设置默认地址时必须先将其它地址的is_default置为False保证默认地址唯一。4.3 编辑地址login_required(login_urlusers:login)def address_edit(request, pk): addressget_object_or_404(Address,pkpk,userrequest.user)ifrequest.methodPOST:formAddressForm(request.POST,instanceaddress)ifform.is_valid(): updated_addressform.save(commitFalse)# 如果本次设为默认同样取消其他默认ifupdated_address.is_default: request.user.addresses.filter(is_defaultTrue).exclude(pkaddress.pk).update(is_defaultFalse)updated_address.save()messages.success(request,地址修改成功。)returnredirect(users:address_list)else: formAddressForm(instanceaddress)returnrender(request,users/address_form.html,{form:form,title:编辑收货地址})用get_object_or_404确保只能编辑自己的地址通过userrequest.user过滤。4.4 删除地址login_required(login_urlusers:login)def address_delete(request, pk): addressget_object_or_404(Address,pkpk,userrequest.user)ifrequest.methodPOST:was_defaultaddress.is_default address.delete()# 如果删除的是默认地址尝试将最新的地址设为默认ifwas_default: latest_addressrequest.user.addresses.first()iflatest_address: latest_address.is_defaultTrue latest_address.save(update_fields[is_default])messages.success(request,地址已删除。)returnredirect(users:address_list)returnrender(request,users/address_confirm_delete.html,{address:address})删除默认地址后自动将剩余地址中最新的一条Meta.ordering我们设为了[-is_default, -create_time]所以first()是最近创建的设置为默认。4.5 快捷设置默认地址我们也可以在列表页直接加一个“设为默认”按钮用一个小视图处理login_required(login_urlusers:login)require_POST def address_set_default(request, pk): addressget_object_or_404(Address,pkpk,userrequest.user)# 取消所有默认request.user.addresses.filter(is_defaultTrue).update(is_defaultFalse)# 设置当前地址为默认address.is_defaultTrue address.save(update_fields[is_default])messages.success(request, f已将“{address.receiver}”的地址设为默认。)returnredirect(users:address_list)五、配置 URL在apps/users/urls.py中添加新路由urlpatterns[# ... 已有的路由 ...path(address/, views.address_list,nameaddress_list), path(address/create/, views.address_create,nameaddress_create), path(address/edit/int:pk/, views.address_edit,nameaddress_edit), path(address/delete/int:pk/, views.address_delete,nameaddress_delete), path(address/set_default/int:pk/, views.address_set_default,nameaddress_set_default),]六、设计模板6.1 地址列表页面创建apps/users/templates/users/address_list.html{% extendsbase.html%}{% block title %}我的收货地址{% endblock %}{% block content %}divclassd-flex justify-content-between align-items-center mb-3h3 收货地址管理/h3ahref{% url users:address_create %}classbtn btn-primary 新增地址/a/div{%ifaddresses %}divclassrow{%foraddrinaddresses %}divclasscol-md-6 mb-3divclasscard shadow-sm {% if addr.is_default %}border-primary{% endif %}divclasscard-body{%ifaddr.is_default %}spanclassbadge bg-primary float-end默认/span{% endif %}h5classcard-title{{addr.receiver}}/h5pclasscard-text mb-1{{addr.phone}}/ppclasscard-text text-muted{{addr.province}}{{addr.city}}{{addr.district}}br{{addr.detail}}/pdivclassmt-2ahref{% url users:address_edit addr.pk %}classbtn btn-sm btn-outline-secondary编辑/a{%ifnot addr.is_default %}formaction{% url users:address_set_default addr.pk %}methodpoststyledisplay:inline;{% csrf_token %}buttontypesubmitclassbtn btn-sm btn-outline-primary设为默认/button/form{% endif %}buttontypebuttonclassbtn btn-sm btn-outline-dangeronclickconfirmDelete({{ addr.pk }}, {{ addr.receiver }})删除/button/div/div/div/div{% endfor %}/div{%else%}divclassalert alert-info你还没有添加收货地址ahref{% url users:address_create %}去添加/a。/div{% endif %}!-- 隐藏的删除确认表单 --formiddelete-formmethodpostaction{% url users:address_delete 0 %}styledisplay:none;{% csrf_token %}/form{% endblock %}{% block extra_js %}scriptfunctionconfirmDelete(pk, receiver){if(confirm(确定要删除“ receiver ”的收货地址吗)){const formdocument.getElementById(delete-form);form.actionform.action.replace(/0/,/ pk /);form.submit();}}/script{% endblock %}说明每个地址卡片中默认地址有蓝色边框和徽章。删除通过 JS 弹出确认框再用隐藏表单提交 POST 请求符合 RESTful 原则。6.2 新增/编辑地址表单页面创建apps/users/templates/users/address_form.html{% extendsbase.html%}{% block title %}{{title}}{% endblock %}{% block content %}divclassrow justify-content-centerdivclasscol-md-8 col-lg-6divclasscard shadow-smdivclasscard-body p-4h3classtext-center mb-4{%if新增intitle %}➕{%else%}✏️{% endif %}{{title}}/h3formmethodpostnovalidate{% csrf_token %}divclassrowdivclasscol-md-6 mb-3labelclassform-label收件人/label{{form.receiver}}{{form.receiver.errors}}/divdivclasscol-md-6 mb-3labelclassform-label手机号/label{{form.phone}}{{form.phone.errors}}/div/divdivclassrowdivclasscol-md-4 mb-3labelclassform-label省份/label{{form.province}}{{form.province.errors}}/divdivclasscol-md-4 mb-3labelclassform-label城市/label{{form.city}}{{form.city.errors}}/divdivclasscol-md-4 mb-3labelclassform-label区/县/label{{form.district}}{{form.district.errors}}/div/divdivclassmb-3labelclassform-label详细地址/label{{form.detail}}{{form.detail.errors}}/divdivclassmb-3 form-check{{form.is_default}}labelclassform-check-labelfor{{ form.is_default.id_for_label }}{{form.is_default.label}}/label/div{%ifform.non_field_errors %}divclassalert alert-danger{{form.non_field_errors.0}}/div{% endif %}buttontypesubmitclassbtn btn-primary w-100保存/buttonahref{% url users:address_list %}classbtn btn-outline-secondary w-100 mt-2取消/a/form/div/div/div/div{% endblock %}6.3 删除确认页面创建apps/users/templates/users/address_confirm_delete.html{% extendsbase.html%}{% block title %}删除地址{% endblock %}{% block content %}divclassrow justify-content-centerdivclasscol-md-6divclasscard shadow-smdivclasscard-body text-center p-4h5classmb-3确定要删除以下收货地址吗/h5pstrong{{address.receiver}}/strong{{address.phone}}/ppclasstext-muted{{address.province}}{{address.city}}{{address.district}}{{address.detail}}/pformmethodpost{% csrf_token %}buttontypesubmitclassbtn btn-danger确认删除/buttonahref{% url users:address_list %}classbtn btn-outline-secondary取消/a/form/div/div/div/div{% endblock %}注意我们在地址列表页已经通过 JS 直接提交了删除请求实际上可以不经过这个确认页。保留它作为备用或者你可以改成重定向到删除视图的 GET 请求显示此页本例中地址列表的删除按钮直接走 JS 提交 POST不会渲染该页此处提供是为了演示另一种模式实际部署时可以去掉。七、更新个人中心侧边栏打开apps/users/templates/users/center.html找到“收货地址”那一行把href#替换为ahref{% url users:address_list %}classtext-decoration-none收货地址/a八、完整流程测试启动服务器python manage.py runserver测试步骤登录用手机号13800138000登录。进入地址列表导航栏点击个人中心 → 侧边栏“收货地址”或直接访问/users/address/。初始没有地址显示提示信息。新增地址点击“新增地址”填写收件人“张三”、手机号 13800000001、省份广东、城市深圳、区/县南山区、详细地址“科技园路1号”勾选“设为默认地址”提交。终端输出[22/May/2026 09:10:45]POST /users/address/create/ HTTP/1.13020[22/May/2026 09:10:45]GET /users/address/ HTTP/1.12002987地址卡片中显示“张三”并有蓝色“默认”徽章。新增第二个地址新增收件人“李四”、手机号 13800000002、北京市朝阳区、详细地址“望京 SOHO”不勾选默认。提交后列表页展示两个地址卡片默认地址依然是张三。设为默认在“李四”的卡片上点击“设为默认”按钮。页面刷新后“李四”变为默认地址带蓝框和徽章张三不再有默认标记。终端[22/May/2026 09:12:33]POST /users/address/set_default/2/ HTTP/1.13020编辑地址点击张三的“编辑”修改手机号为 13900000000勾选设为默认。保存后张三重新变成默认地址李四取消默认标记。删除地址点击李四的“删除”按钮弹出确认框确定后李四的地址消失。张三仍是默认。终端[22/May/2026 09:15:00]POST /users/address/delete/2/ HTTP/1.13020删除默认地址现在只剩下张三一个默认地址点击删除。页面显示“你还没有添加收货地址”并在后台自动将无地址了前面逻辑中如果删完没有地址则不设默认。重新新增一个地址应该没有默认标记手动设置为默认正常。九、数据验证用dbshell查询地址表python manage.py dbshell sqliteSELECT id, receiver, phone, is_default FROM tb_address WHERE user_id用户ID;可以根据操作看到is_default的切换逻辑完全正确。十、总结与下集预告今天我们把收货地址的完整增删改查以及默认地址切换功能全部实现了关键点回顾利用 ModelForm 快速搭建地址编辑表单视图层严格控制只能操作自己的地址userrequest.user默认地址唯一性保障新增/编辑时取消其他默认删除默认时自动替补前端卡片式布局交互流畅AJAX 已内置于删除确认。到这篇为止用户模块的全部基础功能都齐了——注册、登录、个人中心、地址管理。下一站我们将深入研究 Django 的消息框架与用户权限系统的初步应用把前端的用户体验和后台的操作日志再提升一个档次。第 10 篇使用 Django 消息框架与用户权限初步我们不见不散想了解更多也可以去其它平台搜索「IT策士」一起升级 IT 思维 本文为《Django 从 0 到 1 打造完整电商平台》系列第 9 篇。