开源web框架django知识总结(十三)
省市区三级联动
展示收货地址界面
提示:
- 省市区数据是在收货地址界面展示的,所以我们先渲染出收货地址界面。
- 收货地址界面中基础的交互已经提前实现。
1.新建app areas,新建子urls.py,同步,注册areas,
python ../../manage.py startapp areas
- 准备省市区模型和数据 areas.models.py
class Area(models.Model):
"""
行政区划
"""
name = models.CharField(max_length=20,
verbose_name='名称')
parent = models.ForeignKey('self',
on_delete=models.SET_NULL,
related_name='subs',
null=True,
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 -usuifeng -p123456 -D aerf_mall < areas.sql
注意:如果出现错误信息:
ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)
应该配置为 bind-address=0.0.0.0,并且这行应该加在 /etc/mysql/mysql.conf.d/mysqld.cnf
配置文件里.
也可以重启一下虚拟机试试,或者用下面的方法:
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
class ProvinceAreasView(View):
def get(self, request):
p_list = cache.get('province_list')
if not p_list:
provinces = Area.objects.filter(
parent=None
)
p_list = []
for province in provinces:
p_list.append({
'id': province.id,
'name': province.name
})
cache.set('province_list', p_list, 3600)
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'province_list': p_list
})
class SubAreasView(View):
def get(self, request, pk):
sub_data = cache.get('sub_area_%s'%pk)
if not sub_data:
p_area = Area.objects.get(
pk=pk
)
subs = Area.objects.filter(
parent_id=pk
)
sub_list = []
for sub in subs:
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[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 = 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否固定电话
email
string否邮箱
3.响应结果:JSON
字段说明
code
状态码
errmsg
错误信息
id
地址ID
receiver
收货人
province
省份名称
city
城市名称
district
区县名称
place
收货地址
mobile
手机号
tel
固定电话
email
邮箱
3. 新增地址后端逻辑实现 users.views.py
提示:
- 用户地址数量有上限,最多20个,超过地址数量上限就返回错误信息
from .models import Address
class CreateAddressView(View):
def post(self, request):
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')
user = request.user
count = Address.objects.filter(user=user).count()
if count >= 10:
return JsonResponse({'code': 400, 'errmsg': '数量超限'})
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]\d{9}$', 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有误'})
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
}
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'address': address_info
})
注意:循环调用问题。
===============================
展示地址前后端逻辑
1. 展示地址接口设计和定义
1.请求方式
选项方案
请求方法
GET
请求地址
/addresses/
2.请求参数 无
2. 展示地址后端逻辑实现
class AddressView(View):
def get(self, request):
user = request.user
addresses = Address.objects.filter(
user=user,
is_deleted=False
)
address_list = []
for address in addresses:
if address.id != user.default_address_id:
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
})
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'default_address_id': user.default_address_id,
'addresses': address_list
})
========================
修改地址前后端逻辑
1. 修改地址接口设计和定义
1.请求方式
选项方案
请求方法
delete、PUT
请求地址
/addresses/(?P
2.请求参数:路径参数 和 JSON
参数名类型是否必传说明
address_id
string是要修改的地址ID(路径参数)
receiver
string是收货人
province_id
string是省份ID
city_id
string是城市ID
district_id
string是区县ID
place
string是收货地址
mobile
string是手机号
tel
string否固定电话
email
string否邮箱
3.响应结果:JSON
字段说明
code
状态码
errmsg
错误信息
id
地址ID
receiver
收货人
province
省份名称
city
城市名称
district
区县名称
place
收货地址
mobile
手机号
tel
固定电话
email
邮箱
2. 修改地址后端逻辑实现
提示
- 删除地址后端逻辑和新增地址后端逻辑非常的相似。
- 都是更新用户地址模型类,需要保存用户地址信息。
class UpdateDestroyAddressView(View):
def delete(self, request, address_id):
try:
address = Address.objects.get(pk=address_id)
except Address.DoesNotExist as e:
print(e)
return JsonResponse({'code': 400, 'errmsg': '地址不存在'}, status=404)
address.is_deleted = True
address.save()
return JsonResponse({
'code': 0,
'errmsg': 'ok'
})
def put(self, request, address_id):
try:
address = Address.objects.get(pk=address_id)
except Address.DoesNotExist as e:
print(e)
return JsonResponse({'code': 400, 'errmsg': '资源未找到!'})
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')
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]\d{9}$', 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有误'})
address.receiver = receiver
address.province_id = province_id
address.city_id = city_id
address.district_id = district_id
address.place = place
address.mobile = mobile
address.tel = tel
address.email = email
address.save()
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
}
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'address': address_info
})
================================
设置默认地址
1. 设置默认地址接口设计和定义
1.请求方式
选项方案
请求方法
PUT
请求地址
/addresses/(?P
2.请求参数:路径参数
参数名类型是否必传说明
address_id
string是要修改的地址ID(路径参数)
3.响应结果:JSON
字段说明
code
状态码
errmsg
错误信息
2. 设置默认地址后端逻辑实现
class DefaultAddressView(View):
def put(self, request, address_id):
user = request.user
user.default_address_id = address_id
user.save()
return JsonResponse({
'code': 0,
'errmsg': 'ok'
})
==========================
修改地址标题
1. 修改地址标题接口设计和定义
1.请求方式
选项方案
请求方法
PUT
请求地址
/addresses/(?P
2.请求参数:路径参数
参数名类型是否必传说明
address_id
string是要修改的地址ID(路径参数)
3.响应结果:JSON
字段说明
code
状态码
errmsg
错误信息
2. 修改地址标题后端逻辑实现
class UpdateTitleAddressView(View):
def put(self, request, address_id):
data = json.loads(request.body.decode())
title = data.get('title')
address = Address.objects.get(pk=address_id)
address.title = title
address.save()
return JsonResponse({'code': 0, 'errmsg': 'ok'})
==============================
修改密码
1. 修改密码后端逻辑
提示:
- 修改密码前需要校验原始密码是否正确,以校验修改密码的用户身份。
- 如果原始密码正确,再将新的密码赋值给用户。
class ChangePasswordView(View):
def put(self, request):
data = json.loads(request.body.decode())
old_password = data.get('old_password')
new_password = data.get('new_password')
new_password2 = data.get('new_password2')
if not all([old_password, new_password, new_password2]):
return JsonResponse({'code':400, 'errmsg': '参数缺失'})
if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
return JsonResponse({'code': 400,
'errmsg': '密码最少8位,最长20位'})
if new_password != new_password2:
return JsonResponse({'code': 400,
'errmsg': '两次输入密码不一致'})
user = request.user
if not user.check_password(old_password):
return JsonResponse({'code': 400, 'errmsg': '旧密码有误!'}, status=400)
user.set_password(new_password)
user.save()
logout(request)
response = JsonResponse({'code': 0, 'errmsg': 'ok'})
response.delete_cookie('username')
return response
========================
补全users.urls.py中的urlpatterns:
re_path(r'^addresses/create/$', CreateAddressView.as_view()),
re_path(r'^addresses/$', AddressView.as_view()),
re_path(r'^addresses/(?P\d+)/$', UpdateDestroyAddressView.as_view()),
re_path(r'^addresses/(?P\d+)/default/$', DefaultAddressView.as_view()),
re_path(r'^addresses/(?P\d+)/title/$', UpdateTitleAddressView.as_view()),
re_path(r'^password/$', ChangePasswordView.as_view()),
替换user_center_site.js(其中有2个链接接口不对,一个参数,后端未做处理)
========================
祝大家学习python顺利!
Original: https://blog.csdn.net/weixin_54733110/article/details/121913293
Author: 主打Python
Title: 开源web框架django知识总结(十三)
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/737467/
转载文章受原作者版权保护。转载请注明原作者出处!