第1步:定义地址模型,考虑到地址模型中的创建时间与更新时间在后续其它模型中也会用到,所以先定义一个包含创建时间和更新时间的基础模型BaseModel.py,其它模型通过继承它来获取创建时间和更新时间属性。
在utils包中创建models.py,并定义BaseModel模型
from django.db import models
class BaseModel(models.Model):
"""本项目中地址、商品后续涉及到创建时间和更新时间的模型的基类"""
create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
update_time = models.DateTimeField(verbose_name='更新时间', auto_now=True)
class Meta:
abstract = True # 不能实例化表
第2步:在users应用的下的models.py创建地址模型,此模型继承BaseModel,注意Address模型在放在User模型的下面,否则无法正确使用Use的模型作为外键
from xiaoyu_mall.utils.models import BaseModel
class Address(BaseModel):
"""收货地址"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='addresses', verbose_name='用户')
title = models.CharField(verbose_name='地址名称', max_length=20)
receiver = models.CharField(verbose_name='收货人', max_length=20)
province = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='province_addresses',
verbose_name='省')
city = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='city_addresses', verbose_name='市')
district = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='district_addresses',
verbose_name='区')
place = models.CharField(verbose_name='地址', max_length=50)
mobile = models.CharField(verbose_name='手机号', max_length=11)
tel = models.CharField(verbose_name='固定电话', max_length=20, null=True, blank=True, default='')
email = models.CharField(verbose_name='电子邮箱', max_length=30, null=True, blank=True, default='')
is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta:
db_table = 'tb_address'
verbose_name = '用户地址'
verbose_name_plural = verbose_name
ordering = ['-update_time'] # 按更新时间倒序排列
第3步:在User模型中添加默认收货地址的字段default_address
class User(AbstractUser):
...
# 新增字段
default_address = models.ForeignKey('Address', on_delete=models.SET_NULL, null=True, blank=True,
related_name='users', verbose_name='默认地址')
第4步:生成并执行迁移文件
python .\manage.py makemigrations
python .\manage.py makemigrations
python .\manage.py migrate
第5步:在users应用下定义展示地址的视图
class AddressView(LoginRequiredMixin, View):
"""展示地址"""
def get(self, request):
"""提供收货地址界面"""
login_user = request.user # 获取当前登录用户对象
addresses = Address.objects.filter(user=login_user, is_deleted=False)
address_list = [] # 将用户地址模型列表转字典列表
for address in addresses:
address_dict = {
"id": address.id, "title": address.title,
"receiver": address.receiver, "city": address.city.name,
"province": address.province.name, "place": address.place,
"district": address.district.name, "tel": address.tel,
"mobile": address.mobile, "email": address.email
}
address_list.append(address_dict)
context = {
'default_address_id': login_user.default_address_id or '0',
'addresses': address_list,
'address_num': constants.USER_ADDRESS_COUNTS_LIMIT
}
return render(request, 'user_center_site.html', context)
第6步:配置路由,users/urls.py
path('addresses/', views.AddressView.as_view(), name='address'),
第7步:导入模板user_center_site.html
{#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">#}
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>小鱼商城-用户中心</title>
<link rel="stylesheet" type="text/css" href="{{ static('css/reset.css') }}">
<link rel="stylesheet" type="text/css" href="{{ static('css/main.css') }}">
<script type="text/javascript" src="{{ static('js/vue-2.5.16.js') }}"></script>
<script type="text/javascript" src="{{ static('js/axios-0.18.0.min.js') }}"></script>
</head>
<body>
<div id="app">
<div class="header_con">
<div class="header" v-cloak>
<div class="welcome fl">欢迎来到小鱼商城!</div>
<div class="fr">
<div v-if="username" class="login_btn fl">
欢迎您:<em>[[ username ]]</em>
<span>|</span>
<a href="{{ url('users:logout') }}">退出</a>
</div>
<div v-else class="login_btn fl">
<a href="{{ url('users:login') }}">登录</a>
<span>|</span>
<a href="{{ url('users:register') }}">注册</a>
</div>
<div class="user_link fl">
<span>|</span>
<a href="{{ url('users:info') }}">用户中心</a>
<span>|</span>
{# <a href="{{ url('carts:info') }}">我的购物车</a>#}
<span>|</span>
{# <a href="{{ url('users:myorderinfo',args=(1,)) }}">我的订单</a>#}
</div>
</div>
</div>
</div>
<div class="search_bar clearfix">
<a href="{{ url('contents:index') }}" class="logo fl"><img src="{{ static('images/logo.png') }}"></a>
<div class="search_wrap fl">
<form method="get" action="/search/" class="search_con">
<input type="text" class="input_text fl" name="q" placeholder="搜索商品">
<input type="submit" class="input_btn fr" name="" value="搜索">
</form>
<ul class="search_suggest fl">
<li><a href="#">索尼微单</a></li>
<li><a href="#">优惠15元</a></li>
<li><a href="#">美妆个护</a></li>
<li><a href="#">买2免1</a></li>
</ul>
</div>
</div>
<div class="main_con clearfix">
<div class="left_menu_con clearfix">
<h3>用户中心</h3>
<ul>
<li><a href="{{ url('users:info') }}">· 个人信息</a></li>
<li><a href="{{ url('users:address') }}" class="active">· 收货地址</a></li>
{# <li><a href="{{ url('users:myorderinfo',args=(1,)) }}">· 全部订单</a></li>#}
{# <li><a href="{{ url('users:resetpwd') }}">· 修改密码</a></li>#}
</ul>
</div>
<div class="right_content clearfix" v-cloak>
<div class="site_top_con">
<a @click="show_add_site">新增收货地址</a>
<span>你已创建了<b>[[ addresses.length ]]</b>个收货地址,最多可创建<b>{{ address_num }}</b>个</span>
</div>
<div class="site_con" v-for="(address, index) in addresses">
<div class="site_title">
<div v-if="edit_title_index===index">
<input v-model="new_title" type="text" name="">
<input @click="save_title(index)" type="button" name="" value="保 存">
<input @click="cancel_title(index)" type="reset" name="" value="取 消">
</div>
<div>
<h3>[[ address.title ]]</h3>
<a @click="show_edit_title(index)" class="edit_title"></a>
</div>
<em v-if="address.id==default_address_id">默认地址</em>
<span @click="delete_address(index)">×</span>
</div>
<ul class="site_list">
<li><span>收货人:</span><b>[[ address.receiver ]]</b></li>
<li><span>所在地区:</span><b>[[ address.province ]] [[address.city]] [[ address.district ]]</b></li>
<li><span>地址:</span><b>[[ address.place ]]</b></li>
<li><span>手机:</span><b>[[ address.mobile ]]</b></li>
<li><span>固定电话:</span><b>[[ address.tel ]]</b></li>
<li><span>电子邮箱:</span><b>[[ address.email ]]</b></li>
</ul>
<div class="down_btn">
<a v-if="address.id!=default_address_id" @click="set_default(index)">设为默认</a>
<a @click="show_edit_site(index)" class="edit_icon">编辑</a>
</div>
</div>
</div>
</div>
<div class="footer">
<div class="foot_link">
<a href="#">关于我们</a>
<span>|</span>
<a href="#">联系我们</a>
<span>|</span>
<a href="#">招聘人才</a>
<span>|</span>
<a href="#">友情链接</a>
</div>
<p>CopyRight © 2024 北京小鱼商业股份有限公司 All Rights Reserved</p>
<p>电话:010-****888 京ICP备*******8号</p>
</div>
<div class="pop_con" v-show="is_show_edit" v-cloak>
<div class="site_con site_pop">
<div class="site_pop_title">
<h3>新增收货地址</h3>
<a @click="is_show_edit=false">×</a>
</div>
<form>
<div class="form_group">
<label>*收货人:</label>
<input v-model="form_address.receiver" @blur="check_receiver" type="text" class="receiver">
<span v-show="error_receiver" class="receiver_error">请填写收件人</span>
</div>
<div class="form_group">
<label>*所在地区:</label>
<select v-model="form_address.province_id">
<option v-for="province in provinces" :value="province.id">[[ province.name ]]</option>
</select>
<select v-model="form_address.city_id">
<option v-for="city in cities" :value="city.id">[[ city.name ]]</option>
</select>
<select v-model="form_address.district_id">
<option v-for="district in districts" :value="district.id">[[ district.name ]]</option>
</select>
</div>
<div class="form_group">
<label>*详细地址:</label>
<input v-model="form_address.place" @blur="check_place" type="text" class="place">
<span v-show="error_place" class="place_error">请填写地址信息</span>
</div>
<div class="form_group">
<label>*手机:</label>
<input v-model="form_address.mobile" @blur="check_mobile" type="text" class="mobile">
<span v-show="error_mobile" class="mobile_error">手机信息有误</span>
</div>
<div class="form_group">
<label>固定电话:</label>
<input v-model="form_address.tel" @blur="check_tel" type="text" class="tel">
<span v-show="error_tel" class="tel_error">固定电话有误</span>
</div>
<div class="form_group">
<label>邮箱:</label>
<input v-model="form_address.email" @blur="check_email" type="text" class="email">
<span v-show="error_email" class="email_error">邮箱信息有误</span>
</div>
<input @click="save_address" type="button" name="" value="新 增" class="info_submit">
<input @click="is_show_edit=false" type="reset" name="" value="取 消" class="info_submit info_reset">
</form>
</div>
<div class="mask"></div>
</div>
</div>
<script type="text/javascript">
let addresses = {{ addresses | safe }};
let default_address_id = "{{ default_address_id }}";
</script>
<script type="text/javascript" src="{{ static('js/common.js') }}"></script>
<script type="text/javascript" src="{{ static('js/user_center_site.js') }}"></script>
</body>
</html>
第8步:在users应用下定义AddressCreateView,用于实现新增地址的功能。
class AddressCreateView(LoginRequiredJSONMixin, View):
"""展示地址"""
def get(self, request):
"""提供收货地址界面"""
login_user = request.user # 获取当前登录用户对象
addresses = Address.objects.filter(user=login_user,
is_deleted=False)
address_list = [] # 将用户地址模型列表转字典列表
for address in addresses:
address_dict = {
"id": address.id, "title": address.title,
"receiver": address.receiver, "city": address.city.name,
"province": address.province.name, "place": address.place,
"district": address.district.name, "tel": address.tel,
"mobile": address.mobile, "email": address.email
}
address_list.append(address_dict)
context = {
'default_address_id': login_user.default_address_id or '0',
'addresses': address_list
}
return render(request, 'user_center_site.html', context)
def post(self, request):
"""新增收货地址"""
# 校验用户收货地址数量
count = request.user.addresses.filter(is_deleted__exact=False).count()
if count >= constants.USER_ADDRESS_COUNTS_LIMIT:
return JsonResponse({"code": RETCODE.THROTTLINGERR, 'errmsg': "超出用户地址数量上限"})
json_dict = json.loads(request.body.decode())
receiver = json_dict.get('receiver')
province_id = json_dict.get('province_id')
city_id = json_dict.get('city_id')
district_id = json_dict.get('district_id')
place = json_dict.get('place')
mobile = json_dict.get('mobile')
tel = json_dict.get('tel')
email = json_dict.get('email')
# 校验参数
if not all([receiver, province_id, city_id, district_id, place, mobile]):
return HttpResponseForbidden('缺少必传参数')
if not re.match(r'^1[3-9]\d{9}$', mobile):
return HttpResponseForbidden('参数mobile有误')
if tel:
if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
return HttpResponseForbidden('参数tel有误')
if email:
if not re.match(r'^[a-z0-9][\w\\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return HttpResponseForbidden('参数email有误')
# 保存收货地址
try:
address = Address.objects.create(
user=request.user, title=receiver, receiver=receiver,
province_id=province_id, place=place, tel=tel,
city_id=city_id, district_id=district_id,
mobile=mobile, email=email
)
# 设置默认收货地址
if not request.user.default_address:
request.user.default_address = address
request.user.save()
except Exception as e:
logger.error(e)
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '新增地址失败'})
# 返回响应,新增地址成功,将新增的地址响应给前端实现局部刷新 构造新增地址字典数据
address_dict = {
"id": address.id, "title": address.title,
"receiver": address.receiver, "province": address.province.name,
"city": address.city.name, "district": address.district.name,
"place": address.place, "mobile": address.mobile,
"tel": address.tel, "email": address.email
}
# 响应新增地址结果:需要将新增的地址返回给前端渲染
return JsonResponse({'code': RETCODE.OK,
'errmsg': '新增地址成功', 'address': address_dict})
第9步:在users应用下配置路由
path('addresses/create/', views.AddressCreateView.as_view()),