开源web框架django知识总结(十三)
Posted 主打Python
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开源web框架django知识总结(十三)相关的知识,希望对你有一定的参考价值。
开源web框架django知识总结(十三)
省市区三级联动
展示收货地址界面
提示:
- 省市区数据是在收货地址界面展示的,所以我们先渲染出收货地址界面。
- 收货地址界面中基础的交互已经提前实现。
1.新建app areas,新建子urls.py,同步,注册areas,
python ../../manage.py startapp areas
- 准备省市区模型和数据 areas.models.py
class Area(models.Model):
"""
行政区划
"""
# 创建 name 字段, 用户保存名称
name = models.CharField(max_length=20,
verbose_name='名称')
# 自关联字段 parent
# 第一个参数是 self : parent关联自己.
# on_delete=models.SET_NULL: 如果省删掉了,省内其他的信息为 NULL
# related_name='subs': 设置之后
# 我们就这样调用获取市: area.area_set.all() ==> area.subs.all()
parent = models.ForeignKey('self',
on_delete=models.SET_NULL,
related_name='subs',
null=True, #表示数据库创建时该字段可不填,用NULL填充
blank=True, # 表示代码中创建数据库记录时该字段可传空白(空串,空字符串).
verbose_name='上级行政区划')
class Meta:
db_table = 'tb_areas'
verbose_name = '行政区划'
verbose_name_plural = '行政区划'
def __str__(self):
return self.name
迁移模型类
python manage.py makemigrations
python manage.py migrate
模型说明:
-
自关联字段的外键指向自身,所以
models.ForeignKey('self')
-
反向查询:没有外键属性一方,可以调用反向属性查询到关联的另一方。
反向关联属性为“实例对象.引用类名(小写)”,使用
related_name
指明父级查询子级数据的语法
- 默认
Area模型类对象.area_set
语法
related_name='subs'
-
- 现在
Area模型类对象.subs
语法
- 现在
导入省市区数据:在项目根目录下建立scripts文件夹,将数据库文件areas.sql拷贝进来,在xshell进入到目录中执行下面语句。
# mysql -u数据库用户名 -p数据库密码 -D 数据库 < areas.sql #注意要在数据库文件所在的目录内执行
mysql -usuifeng -p123456 -D aerf_mall < areas.sql
注意:如果出现错误信息:
ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)
mysql.service failed because the control process exited with error code问题
应该配置为bind-address=0.0.0.0,并且这行应该加在/etc/mysql/mysql.conf.d/mysqld.cnf
配置文件里.
也可以重启一下虚拟机试试,或者用下面的方法:
# mysql -h127.0.0.1 -u数据库用户名 -p数据库密码 -D 数据库 < areas.sql #注意要在数据库文件所在的目录
mysql -h127.0.0.1 -usuifeng -p123456 -D aerf_mall < areas.sql
3. 查询省市区数据
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /areas/ |
2.请求参数:查询参数
- 如果前端没有传入
area_id
,表示用户需要省份数据- 如果前端传入了
area_id
,表示用户需要市或区数据
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
area_id | string | 否 | 地区ID |
3.响应结果:JSON
- 省份数据
"code":"0",
"errmsg":"OK",
"province_list":[
"id":110000,
"name":"北京市"
,
"id":120000,
"name":"天津市"
,
"id":130000,
"name":"河北省"
,
......
]
市或区数据
"code":"0",
"errmsg":"OK",
"sub_data":
"id":130000,
"name":"河北省",
"subs":[
"id":130100,
"name":"石家庄市"
,
......
]
4.查询省市区数据后端逻辑实现
- 如果前端没有传入
area_id
,表示用户需要省份数据 - 如果前端传入了
area_id
,表示用户需要市或区数据
获取可选省份信息、获取可选市区信息 areas.views.py
from django.views import View
from django.http import JsonResponse
from .models import Area
from django.core.cache import cache
# Create your views here.
# 获取可选省份信息
class ProvinceAreasView(View):
def get(self, request):
# 优先判断缓存中有没有数据
p_list = cache.get('province_list')
if not p_list:
# 把省信息按照格式返回
# 1、读取模型类查询集
provinces = Area.objects.filter(
parent=None
)
# 2、把所有的模型类对象,转化成字典id, name,json不认模型对象
p_list = []
for province in provinces:
# province: 是省模型类对象
p_list.append(
'id': province.id,
'name': province.name
)
# 读取mysql省数据之后,写入缓存
# cache模块写入缓存是key-value形式
cache.set('province_list', p_list, 3600)
# 3、构建响应返回
return JsonResponse(
'code': 0,
'errmsg': 'ok',
'province_list': p_list
)
# 获取可选市区信息
class SubAreasView(View):
def get(self, request, pk):
# 路径中传入的pk
# 1、pk是省的主键,请求所有市信息
# 2、pk是市的主键,请求所有区信息
sub_data = cache.get('sub_area_%s'%pk)
if not sub_data:
# 当前pk过滤出的父级行政区对象
p_area = Area.objects.get(
pk=pk
)
# 当前父级行政区对象关联的多个子级行政区
subs = Area.objects.filter(
parent_id=pk
)
sub_list = []
for sub in subs:
# sub是子级行政区对象
sub_list.append(
'id': sub.id,
'name': sub.name
)
sub_data =
'id': p_area.id,
'name': p_area.name,
'subs': sub_list
cache.set('sub_area_%s'%pk, sub_data, 3600)
return JsonResponse(
'code': 0,
'errmsg': 'ok',
'sub_data': sub_data
)
5. areas.urls.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^areas/$', views.ProvinceAreasView.as_view()),
re_path(r'^areas/(?P<pk>[1-9]\\d+)/$', views.SubAreasView.as_view()),
]
收货地址
用户地址的主要业务逻辑有:
- 展示省市区数据
- 用户地址的增删改查处理
- 设置默认地址
- 设置地址标题
==============================
新增地址前后端逻辑
1. 定义用户地址模型类 user.models.py
1.用户地址模型类
from aerf_mall.utils.BaseModel import BaseModel
class Address(BaseModel):
"""
用户地址
"""
user = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='addresses',
verbose_name='用户')
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='区')
title = models.CharField(max_length=20, verbose_name='地址名称')
receiver = models.CharField(max_length=20, verbose_name='收货人')
place = models.CharField(max_length=50, verbose_name='地址')
mobile = models.CharField(max_length=11, verbose_name='手机')
tel = models.CharField(max_length=20,
null=True,
blank=True,
default='',
verbose_name='固定电话')
email = models.CharField(max_length=30,
null=True,
blank=True,
default='',
verbose_name='电子邮箱')
is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta:
db_table = 'tb_addresses'
verbose_name = '用户地址'
verbose_name_plural = verbose_name
# 定义默认查询集排序方式
ordering = ['-update_time']
2.Address模型类说明
-
Address
模型类中的外键指向areas/models
里面的Area
。指明外键时,可以使用应用名.模型类名
来定义。 -
ordering
表示在进行排序展示
Address
查询时,默认使用的排序方式。
ordering = ['-update_time']
: 根据更新的时间倒叙。
3.补充用户模型默认地址字段
class User(AbstractUser):
"""自定义用户模型类"""
mobile = models.CharField(
unique=True,
verbose_name='手机号',
null=True,
max_length=11
)
# 新增 email_active 字段
# 用于记录邮箱是否激活, 默认为 False: 未激活
email_active = models.BooleanField(default=False,verbose_name='邮箱验证状态')
# 外间关联字段,表示当前用户,采用的默认的收货地址是哪个
default_address = models.ForeignKey('Address',
related_name='users',
null=True,
blank=True,
on_delete=models.SET_NULL,
verbose_name='默认地址')
class Meta:
db_table = 'tb_users'
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
执行数据迁移:
python manage.py makemigrations
python manage.py migrate
2. 新增地址接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /addresses/create/ |
2.请求参数:JSON
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
receiver | string | 是 | 收货人 |
province_id | string | 是 | 省份ID |
city_id | string | 是 | 城市ID |
district_id | string | 是 | 区县ID |
place | string | 是 | 收货地址 |
mobile | string | 是 | 手机号 |
tel | string | 否 | 固定电话 |
string | 否 | 邮箱 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
id | 地址ID |
receiver | 收货人 |
province | 省份名称 |
city | 城市名称 |
district | 区县名称 |
place | 收货地址 |
mobile | 手机号 |
tel | 固定电话 |
邮箱 |
3. 新增地址后端逻辑实现 users.views.py
提示:
- 用户地址数量有上限,最多20个,超过地址数量上限就返回错误信息
from .models import Address
# 新增用户地址 re_path(r'^addresses/create/$', CreateAddressView.as_view()),
class CreateAddressView(View):
def post(self, request):
# 1、提取参数
data = json.loads(request.body.decode())
receiver = data.get('receiver')
province_id = data.get('province_id')
city_id = data.get('city_id')
district_id = data.get('district_id')
place = data.get('place') # 详细地址
mobile = data.get('mobile')
tel = data.get('tel')
email = data.get('email')
# 判断用户地址数量是否超过10个
user = request.user
count = Address.objects.filter(user=user).count()
if count >= 10:
return JsonResponse('code': 400, 'errmsg': '数量超限')
# 2、校验参数
if not all([receiver, province_id, city_id, district_id, place, mobile]):
return JsonResponse("code": 400, 'errmsg': '缺少参数!')
if not re.match(r'^1[3-9]\\d9$', mobile):
return JsonResponse('code': 400,
'errmsg': '参数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 JsonResponse('code': 400,
'errmsg': '参数tel有误')
if email:
if not re.match(r'^[a-z0-9][\\w\\.\\-]*@[a-z0-9\\-]+(\\.[a-z]2,5)1,2$', email):
return JsonResponse('code': 400,
'errmsg': '参数email有误')
# 3、新建用户地址
try:
address = Address.objects.create(
user=user,
province_id=province_id,
city_id=city_id,
district_id=district_id,
title=receiver, # 当前地址的标题,默认收货人名称就作为地址标题
receiver=receiver,
place=place,
mobile=mobile,
tel=tel
)
# 如果当前新增地址的时候,用户没有设置默认地址,那么
# 我们把当前新增的地址设置为用户的默认地址
if not user.default_address:
user.default_address = address
user.save()
except Exception as e:
print(e)
return JsonResponse('code': 400, 'errmsg': '新增地址失败!')
address_info =
"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
# 4、返回响应
return JsonResponse(
'code': 0,
'errmsg': 'ok',
'address': address_info
)
注意:循环调用问题。
===============================
展示地址前后端逻辑
1. 展示地址接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /addresses/ |
2.请求参数 无
2. 展示地址后端逻辑实现
# 网页地址展示接口 re_path(r'^addresses/$', AddressView.as_view()),
# 本质:把当前用户所有地址信息返回
class AddressView(View):
def get(self, request):
# 1、根据用户,过滤出当前用户的所有地址
user = request.user
addresses = Address.objects.filter(
user=user,
is_deleted=False # 没有逻辑删除的地址
)
# 2、把地址转化成字典
address_list = []
for address in addresses:
if address.id != user.default_address_id:
# address:每一个地址对象
address_list.append(
'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
)
else:
address_list.insert(0,
'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
)
# 3、构建响应返回
return JsonResponse(
'code'开源web框架django知识总结