(根据居然老师直播课内容整理)
一、准备工作
D:\pythonprogram\django_project\lgshop\apps> python ..\manage.py startapp addresses
- 注册app
二、地址三级联动数据库设计
- 省、市、县三级地址保存方式有两种:
- 方式一:省、市、县存放在三个表中,根据外键进行关联
- 方式二:省、市、县存放在一个表中,利用外键关联自己实现
- 本次采用方式二:省市县存放一个表中实现
from django.db import models
class Area(models.Model):
name=models.CharField(max_length=20,verbose_name="名称")
parent=models.ForeignKey("self",on_delete=models.SET_NULL,null=True,blank=True,verbose_name="上级",related_name="subs")
class Meta:
db_table="tb_areas"
verbose_name="省市区"
verbose_name_plural=verbose_name
def __str__(self):
return self.name
- 数据迁移
python manage.py makemigrations
python manage.py migrate
三、实现方式
- 页面加载完成时,会自动向后端发起查询省份信息,并将结果插入省份下拉列表中
- 当省份信息变更时,通过ajax将省id发向后端发起市州查询,并将结果插入市州下拉列表中
- 当市州信息变更时,通过ajax将市id发向后端发起区县查询,并将结果插入区县下拉列表中
- 从而实现省市县三级联动
四、省份列表获取
1、前端实现
- 当页面加载完成时,自动向后端发起省份查询
2、接口设计和定义
2.1 请求方式:
选项 |
方案 |
请求方法 |
get |
请求地址 |
/areas/ |
2.2 请求参数 :
参数名 |
类型 |
是否必传 |
说明 |
area_id |
string |
否 |
空 |
2.3 响应结果 : json
响应结果 |
响应内容 |
code |
状态码 |
errmsg |
错误信息 |
province_list |
列表, 每个元素是一个字典 :{“id”:省份id, “name”:省份名称} |
{
"code":"0",
"errmsg":"OK",
"province_list":[
{
"id":110000,
"name":"北京市"
},
{
"id":120000,
"name":"天津市"
},
{
"id":130000,
"name":"河北省"
},
......
]
}
3、后端view实现
- 获取参数
- 如果参数为空,表示查询省份信息
- 通过Areas类,获取parent为空的数据集
- 循环读取数据集,生成省份信息列表
- 省份信息字典:{“id”:省份id,“name”:省份名称}
- 返回前端json数据
4、后端路由定义
4.1 总路由
- lgshop/urls.py
4.2 子路由
五、市列表获取
1、前端实现
- 选择的省份信息发生变化后,将省份id发向后端发起市查询
2、接口设计和定义
2.1 请求方式:
选项 |
方案 |
请求方法 |
get |
请求地址 |
/areas/ |
2.2 请求参数 :
参数名 |
类型 |
是否必传 |
说明 |
area_id |
string |
否 |
省id |
2.3 响应结果 : json
响应结果 |
响应内容 |
code |
状态码 |
errmsg |
错误信息 |
sub_data |
字典 |
{
"code":"0",
"errmsg":"OK",
"sub_data":{
"id":130000,
"name":"河北省",
"subs":[
{
"id":130100,
"name":"石家庄市"
},
......
]
}
}
3、后端view实现
- 获取参数
- 如果参数不为空,表示查询市或县信息
- 通过Areas类,获取id=省id的省信息
- 通过Areas类,获取parent__id=省id的市数据集
- 循环读取数据集,生成省份信息列表
- 市份信息字典:{“id”:市份id,“name”:市份名称}
- 生成subs_data数据
- 返回前端json数据
六、县列表获取
1、前端实现
- 选择的市信息发生变化后,将市id发向后端发起县查询
2、接口设计和定义
2.1 请求方式:
选项 |
方案 |
请求方法 |
get |
请求地址 |
/areas/ |
2.2 请求参数 :
参数名 |
类型 |
是否必传 |
说明 |
area_id |
string |
否 |
市id |
2.3 响应结果 : json
响应结果 |
响应内容 |
code |
状态码 |
errmsg |
错误信息 |
sub_data |
字典 |
{
"code": "0",
"errmsg": "OK",
"sub_data": {
"id": 130100,
"name": "石家庄市",
"subs": [
{
"id": 130102,
"name": "长安区"},
},
......
]
}
}
3、后端view实现
- 获取参数
- 如果参数不为空,表示查询市或县信息
- 通过Areas类,获取id=市id的市信息
- 通过Areas类,获取parent__id=市id的县数据集
- 循环读取数据集,生成县份信息列表
- 县份信息字典:{“id”:县id,“name”:县名称}
- 生成subs_data数据
- 返回前端json数据
- 代码与市代码复用
七、代码优化
- 省市县数据基本不会发生变化,可以放到缓存中保存起来,提高mysql数据读取次数,提高响应速度
- 异常捕获及处理
- 将省市县数据过期时间参数化
1、说明
- 获取参数
- 判断是参数是否为空
- 如果参数为空,即获取省份信息
- 如果参数不为空,即根据area_id获取市、县信息
- 获取省份信息:
- 从CACHES中读取 province_model_list
- 如果province_model_list为空,表求无省份信息或省份信息失效,需要从mysql数据库中读取
- 因需要访问数据库,可能存在读取失败或异常错误,需要异常捕获,
- 通过Areas类,获取parent为空的数据集
- 循环读取数据集,生成省份信息列表
- 将省信息份保存到CACHES中
- 此处不用返回,因province_model_list存在,也需要返回
- 如果发生异常,保存日志,返回含错误信息的JSON
- 返回包含省信息的JSON
- 获取市、县信息
- 从CACHES中读取 sub_data+area_id信息
- 如果sub_data+area_id信息为空,表求无市、县信息或市、县信息失效,需要从mysql数据库中读取
- 因需要访问数据库,可能存在读取失败或异常错误,需要异常捕获,
- 通过Areas类,获取id=省id的省信息
- 通过Areas类,获取parent__id=省id的市数据集
- 循环读取数据集,生成省份信息列表
- 生成subs_data数据
- 将市、县信息份保存到CACHES中: sub_data+area_id
- 如果发生异常,保存日志,返回含错误信息的JSON
- 返回包含省信息的JSON
2、 优化后的代码
logger = logging.getLogger('django')
class AreasView(View):
"""省市区三级联动"""
def get(self,request):
area_id=request.GET.get("area_id")
if not area_id:
province_list = cache.get('province_list')
if not province_list:
try:
province_model_list = Area.objects.filter(parent__isnull=True)
"""
# 返回省份json数据格式
{
"code":"0",
"errmsg":"OK",
"province_list":[
{
"id":110000,
"name":"北京市"
},
{
"id":120000,
"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, AREA_REDIS_EXPIRES)
except Exception as e:
logger.error(e)
return http.JsonResponse({
"code": RETCODE.DBERR, "errmsg": "查询省份数据错误"})
return http.JsonResponse({
"code": RETCODE.OK, "errmsg": "OK", "province_list": province_list})
else:
sub_data = cache.get("sub_data_" + area_id)
if not sub_data:
"""
# 返回市、县json数据格式
{
"code":"0",
"errmsg":"OK",
"sub_data":{
"id":130000,
"name":"河北省",
"subs":[
{
"id":130100,
"name":"石家庄市"
},
......
]
}
}
或
{
"code": "0",
"errmsg": "OK",
"sub_data": {
"id": 130100,
"name": "石家庄市",
"subs": [
{
"id": 130102,
"name": "长安区"},
},
......
]
}
}
"""
try:
parent_model=Area.objects.get(id=area_id)
sub_model_list = Area.objects.filter(parent__id=area_id)
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_" + area_id, sub_data, AREA_REDIS_EXPIRES)
except Exception as e:
logger.error(e)
return http.JsonResponse({
"code": RETCODE.DBERR, "errmsg": "查询城市或区县数据错误"})
return http.JsonResponse({
"code": RETCODE.OK, "errmsg": "OK", "sub_data": sub_data})