#前言:
在最近十天我会用Python做一个购物类项目,会用到Django+Mysql+Redis+Vue等。
今天是第四天,主要负责撰写用户中心部分的收货地址部分。若是有不懂大家可以先阅读我的前三篇博客以能够顺承。
若是大家基础有不懂的,小编前面已经关于Django博客都更新完了,大家可以自行查看。若是有需要更改的地方欢迎大家指正,同时也欢迎大家关注点赞收藏等待后续,小编会尽力更新优质博客。
在此我会直接继承上一篇博客继续往下写。
九、用户中心(收货地址):
前提声明:
这是我们此篇幅博客要实现的终极目标,能够创建一个收获地址。
1、创捷视图接收路由:
在user应用下:
# 收货地址
path('address/' , views.AddressView.as_view() , name='address'),
class AddressView(View):
'''
用户收货地址
'''
def get(self , request):
return render(request , 'user_center_site.html')
2、创建应用:
当我们创建地址时候,面临的第一个问题就是我们要选择对应省——省对应的市——市对应的区。
因为,关于地区的信息和操作和其它操作关联性不强,所以我们单独创建注册一个areas应用。
(具体如何创建前面有很多很多次讲解,大家可以翻阅前几篇博客。)
3、创建模型类:
省——市——区。不难发现这其实是一个一对多的关系,但是由于数据量太庞大,创建多表的话占用内存太大,所以我们选择自关联,因为它省略磁盘空间,在一个表操作。
所以我们arear应用的模型类下创建:
from django.db import models
# Create your models here.
class Area(models.Model):
'''
实现省市区名称:
有个关联关系:省市区,分别都是一对多。
自关联:省略磁盘空间,在一个表操作
'''
name=models.CharField(max_length=20)
#SET_NULL:删除被关联的数据,对应的数据字段就会被自动设置为NULL
parent=models.ForeignKey('self',on_delete=models.SET_NULL,null=True,related_name='subs')#外键
class Meta:
db_table='areas'
这个表是什么意思,举个例子:
每个市、区都会有对应外键对应省、市。
'''
自关联
id name parent_id
1 广东省 null
2 湖北省 null
3 广州市 1
4 天河区 3
5 武汉市 2
6 深圳市 1
'''
而这个庞大的数据集要导入到数据库我已经将SQL语句写好了,大家可以到我网盘自行免费领取:
链接:https://pan.baidu.com/s/1A0wPS9cuPYG7zqM5wxau6w?pwd=ammd
提取码:ammd
这个数据集大家也可以一直留着,以后也可以经常使用。
注意这个数据集你在运行SQL语句时候一定要选择按语句的顺序开始运行,不然会出现很多错误。
4、读取地区数据:
对于我们的数据,因为都是在读,对地区该的频率低,所以我们优先考虑缓存Cache,具体来说:可以把这些数据缓存到内存,cache 可以连接到配置文件中设置的默认缓存内存数据库。
在areas应用的视图下:
主要思路就是,先从内存中访问省市,若没有去Mysql数据库中找到并且存入Cache,需要注意的是访问的为什么是province_list等,是因为Json数据响应的变量名称是他们,剩下的代码中都有详细解释。
from django.shortcuts import render
from django.views import View
from django.http import JsonResponse
from areas.models import Area
from utils.response_code import RETCODE
from django.core.cache import cache
class AreasView(View):
'''
读取省市区地区数据
省市区:读频率高 , 写改的频率低的数据;可以把这些数据缓存到内存
cache 可以连接到配置文件中设置的默认缓存内存数据库
'''
def get(self , request):
area_id = request.GET.get('area_id')
# 判断请求是否存在 area_id 的参数
if not area_id:
# 从内存中获取省份的数据
province_list = cache.get('province_list')
# 判断这个数据在内存中是否存在
if not province_list:
# 获取省份的名称数据(到MySQL数据库)
province_model_list = Area.objects.filter(parent_id__isnull=True)
'''
响应 json 数据
{
'code' : 200
'errmsg' : OK
'province_list' : [
{id:110000;name:北京市},
{id:120000;name:天津市},
{id:130000;name:河北省},
……
]
}
'''
province_list = []
for province_model in province_model_list:
province_dict = {
"id":province_model.id,
"name":province_model.name
}
province_list.append(province_dict)
# 将数据缓存到内存中
cache.set('province_list' , province_list , 3600)
return JsonResponse({'code':RETCODE.OK , 'errmsg':"OK" , 'province_list':province_list})
else:
# 获取市区的名称数据
# area_id 要么是省的 id(要获取到市的数据) 要么是市的 id(要获取到区的数据)
# 根据 area_id 的值进行获取对应关联该值的数据
'''
响应 json 数据
{
'code' : 200
'errmsg' : OK
sub_data = {
id: 110000
name: 北京市
subs : [
{id:110101;name:东城区},
{id:110102;name:西城区},
……
]
}
}
'''
sub_data = cache.get('sub_data_%s'%area_id)
if not sub_data:
# 获取 area_id 对应的数据
parent_model = Area.objects.get(id=area_id)
# 获取到关联 parent_model 这个对象的所有数据
sub_model_list = parent_model.subs.all()
subs = []
for sub_model in sub_model_list:
sub_dict = {
"id": sub_model.id,
"name": sub_model.name
}
subs.append(sub_dict)
sub_data = {
"id": parent_model.id,
"name" : parent_model.name,
"subs" : subs
}
cache.set('sub_data_%s'%area_id , sub_data , 3600)
return JsonResponse({'code': RETCODE.OK, 'errmsg': "OK", 'sub_data': sub_data})
5、响应到前端:
修改前端中 user_center_site.html 中对应标签的内容。
<div class="form_group">
<label>*所在地区:</label>
<select v-model="form_address.province_id">
<option value="0">请选择</option>
<option :value="province.id" v-for="province in provinces">[[ province.name ]]</option>
</select>
<select v-model="form_address.city_id">
<option value="0">请选择</option>
<option :value="city.id" v-for="city in cities">[[ city.name ]]</option>
</select>
<select v-model="form_address.district_id">
<option value="0">请选择</option>
<option :value="district.id" v-for="district in districts">[[ district.name ]]</option>
</select>
</div>
6、创建时间模型类:
关于创建地址,或者说后面对货物上架我们都会用到关于时间以及时间更新的数据,所以我们创建一个专门争对时间的model类。
让其他模型可以共用时间字段,在utils 包中创建一个 model 文件。
from django.db import models
class BaseModel(models.Model):
# 创建时间
create_time = models.DateTimeField(auto_now_add=True)
# 更新时间
update_time = models.DateTimeField(auto_now=True)
class Meta:
# 让该模型类在迁移数据库的时候不为其单独创建一张表,必须依赖于其它模型类
abstract = True
当其他模型类需要使用到该字段的时候,直接继承即可。
7、创建用户收货地址模型类:
在areas的模型下创建。
class Address(BaseModel):
# 用户收货地址
# 关联用户 , 要知道对应的数据是那个用户地址
user = models.ForeignKey(User , on_delete=models.CASCADE , related_name='address')
receiver = models.CharField(max_length=20)
# models.PROTECT:如果要删除的数据,有被其他数据关联,那么删除的操作失败。
province = models.ForeignKey('areas.Area' , on_delete=models.PROTECT , related_name='province_address')
city = models.ForeignKey('areas.Area' , on_delete=models.PROTECT , related_name='city_address')
district = models.ForeignKey('areas.Area' , on_delete=models.PROTECT , related_name='district_address')
palce = models.CharField(max_length=50) # 详细地址
mobile = models.CharField(max_length=11)
tel = models.CharField(max_length=20 , null=True , blank=True , default='')
email = models.CharField(max_length=20 , null=True , blank=True , default='')
class Meta:
db_table = 'address'
对标的正好是这些属性,值得一说的是关于固定电话以及邮箱这是非必填的内容,模型类中设置是可以为空。
而我们的用户地址到时候会有一个默认地址,所以我们是在用户个人数据模型类中保存默认收货地址 , 默认地址只能有一个。
注意:这是在原有模型类上面只加了一行代码,并不是重新定义的模型类。
class User(AbstractUser):
'''
用户数据认证模型类
'''
mobile=models.CharField(max_length=11,unique=True)
email = models.EmailField(blank=True, null=True) # 允许为空
#默认收获地址:关联收货地址模型类的数据
default_address=models.ForeignKey('Address',on_delete=models.SET_NULL,null=True,related_name='users')
class Meta:
db_table='user'
8、新增用户收货地址:
注意数据是Json数据类型的所以相对数据进行解码,然后接收数据,校验是否合法,然后将它保存到数据库(因为是地址,不具有唯一性,所以不进行数据库校验)。
# 新增收货地址
path('addresses/create/' , views.AddressCreateView.as_view()),
class AddressCreateView(View):
'''
用户新增收货地址
'''
def post(self , request):
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('手机号有误')
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('固定电话有误')
if email:
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$' , email):
return HttpResponseForbidden('邮箱有误')
# 将数据保存到数据库中
address = Address.objects.create(
user=request.user,
receiver=receiver,
province_id = province_id,
city_id = city_id,
district_id = district_id,
palce = place,
mobile = mobile,
tel = tel,
email = email
)
address_dict = {
"id" : address.id,
'receiver': address.receiver,
'province': address.province.name,
'city': address.city.name,
'district': address.district.name,
'place': address.palce,
'mobile': address.mobile,
'tel': address.tel,
'email': address.email,
}
return JsonResponse({"code":RETCODE.OK , 'errmsg':"新增地址成功" , "address":address_dict})
注意:address_dict
变量用于构造一个包含新创建的收货地址详细信息的字典。这些信息随后会作为 JSON 响应的一部分返回给前端。这个字典的结构便于前端展示新地址的详细信息,并且能够直接用于界面更新或数据处理。
9、渲染收货地址:
渲染收获地址说白了就是能将收货地址响应到页面:
我们 要做的就是获取用户登录信息,然后去数据库获取相应的用户信息,最后获取地址信息,然后由后端接收返回到前端页面。
class AddressView(View):
'''
用户收货地址
'''
def get(self , request):
# 获取当前登录的用户信息
login_user = request.user
# 根据当前登录的用户信息 , 获取对应的收货地址数据
addresses = Address.objects.filter(user=login_user)
address_list = []
for address in addresses:
address_dict = {
"id": address.id,
'receiver': address.receiver,
'province': address.province.name,
'city': address.city.name,
'district': address.district.name,
'place': address.palce,
'mobile': address.mobile,
'tel': address.tel,
'email': address.email,
}
address_list.append(address_dict)
context = {
'addresses' : address_list,
# 获取用户默认收货地址
'default_address_id': login_user.default_address_id,
# 用户收货地址个数
'count' : addresses.count()
}
return render(request , 'user_center_site.html' , context=context)
此时的前端:
修改 user_center_site.html 页面中对应标签的内容:
将数据接收到Js里面了,所以HTML的内容也要和JS对应。
<script type="text/javascript">
let addresses = {{ addresses | safe }};
let default_address_id = {{ default_address_id|default:0 }};
</script>
注意:我们按照JS不仅接收参数,而且得接收索引。也就是( v-for="(address , index) in addresses"),最后按照参数和索引一起来相应到前端。
<div class="right_content clearfix" v-cloak>
<div class="site_top_con">
<a @click="show_add_site">新增收货地址</a>
<span>你已创建了<b>{{ count }}</b>个收货地址,最多可创建<b>20</b>个</span>
</div>
<div class="site_con" v-for="(address , index) in addresses">
<div class="site_title">
<h3>[[ address.receiver ]]</h3>
<a @click="show_edit_title(index)" class="edit_icon"></a>
<em v-if="address.id === default_address_id">默认地址</em>
<span class="del_site" @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>
10、设置默认收货地址:
要设置默认收获地址前我们先用前端网页抓取来判断一下默认收货地址的请求类型和响应路由。
我们由此的得到是put请求和路由所以我们就可以开始写视图和路由。
# 默认收货地址
re_path('^addresses/(?P<address_id>\d+)/default/$' , views.DefaultAddressView.as_view()),
class DefaultAddressView(View):
'''
设置默认收获地址
'''
def put(self,request,address_id):
#去网页抓取一下就能发现接受的参数和为什么是put请求了
address=Address.objects.get(id=address_id)
request.user.default_address=address
request.user.save()#将数据保存到数据库中
return JsonResponse({'code':'RETCODE.OK','errmsg':"默认收获地址设置成功"})
前面HTML代码中已经包括了默认地址响应的代码。
11、删除/修改收货地址:
同样的要设前我们先用前端网页抓取来判断一下默认收货地址的请求类型和响应路由。
我们发现修改是delete请求、删除是put请求,而路由两个的一致,所以我们只需要创建一个分发路由即可。
和上面的操作一致,我们开始写类视图与分发路由。
写类视图的思路为:删除很简单,就是由地址id直接从数据库删除。而修改的思路和前面新增地址的基本一致,先解码json数据,再获取地址信息(自己输入前端页面的信息,也就是get请求),校验数据完整性,然后获取原先地址id,将原先输入的地址信息修改为刚刚输入的信息,最后将新地址信息保存到数据库。
# 修改/删除收货地址
re_path('^addresses/(?P<address_id>\d+)/$' , views.UpdateAddressView.as_view()),
class UpdateAddressView(View):
'''
修改/删除收货地址
'''
def delete(self , request , address_id):
# 删除收货地址
Address.objects.get(id=address_id).delete()
return JsonResponse({"code": RETCODE.OK, 'errmsg': "地址删除成功"})
def put(self , request , address_id):
# 修改收货地址
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('手机号有误')
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('固定电话有误')
if email:
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return HttpResponseForbidden('邮箱有误')
Address.objects.filter(id=address_id).update(
user=request.user,
receiver=receiver,
province_id=province_id,
city_id=city_id,
district_id=district_id,
palce=place,
mobile=mobile,
tel=tel,
email=email
)
address = Address.objects.get(id=address_id)
address_dict = {
"id": address.id,
'receiver': address.receiver,
'province': address.province.name,
'city': address.city.name,
'district': address.district.name,
'place': address.palce,
'mobile': address.mobile,
'tel': address.tel,
'email': address.email,
}
return JsonResponse({"code": RETCODE.OK, 'errmsg': "地址修改成功", "address": address_dict})
#总结:
该篇博客详细的介绍的Python项目中关于用户中心收货地址的讲解,主要是前后端和数据库来回获取数据,来回调用,响应,增删改查。内容完整详细,若是各位大佬发现需要修改的地方欢迎前来批评指正。同时,我后续还会继续更新后面项目内容,欢迎大家关注!您的关注与点赞将是小编变强路上最强的动力!