本教程的知识点为: 项目准备 项目准备 配置 1. 修改settings/dev.py 文件中的路径信息 2. INSTALLED_APPS 3. 数据库 用户部分 图片 1. 后端接口设计: 视图原型 2. 具体视图实现 用户部分 使用Celery完成发送 判断帐号是否存在 1. 判断用户名是否存在 后端接口设计: 用户部分 JWT 什么是JWT 起源 传统的session认证 用户部分 登录 1. 业务说明 2. 后端接口设计 3. 后端实现 登录 使用登录的流程 创建模型类 urllib使用说明 登录回调处理 登录 使用登录的流程 创建模型类 urllib使用说明 绑定用户身份接口 邮件与验证 学习目标: 业务说明: 技术说明: 保存邮箱并发送验证邮件 省市区地址查询 数据库建表 说明 页面静态化 注意 定时任务 安装 部分 详情页 异步任务的触发 。 后端接口设计 收货地址 使用缓存 安装 使用方法 为省市区视图添加缓存 数据库表设计 表结构 数据表结构 首页数据表结构 Docker使用 Docker简介 用户浏览历史记录 1. 保存 后端接口设计 后端实现 搜索 1. 需求分析 2. 搜索引擎原理 3. Elasticsearch 部分 业务需求分析 技术实现 数据存储设计 1. Redis保存已登录用户 商品部分 业务需求分析 技术实现 查询数据 1. 后端接口设计 部分 业务需求分析 技术实现 登录合并 修改登录视图 部分 保存 1. 后端接口设计 2. 后端实现 保存的思路 创建数据库模型类 接入 开发平台登录 沙箱环境 Xadmin 1. 安装 2. 使用 站点的全局配置 站点Model管理。 在Ubuntu中安装 2. 启动与停止 3. 镜像操作 端与自定义文件存储系统 1. 的Python客户端 安装 使用。
完整笔记资料代码:https://gitee.com/yinuo112/Backend/tree/master/Django/前后端分离django美多商城项目/note.md
感兴趣的小伙伴可以自取哦~
全套教程部分目录:
部分文件图片:
收货地址
用户地址的主要业务逻辑有:
- 展示省市区数据
- 用户地址的增删改查处理
- 设置默认地址
- 设置地址标题
展示地址前后端逻辑
1. 展示地址接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /addresses/ |
> | |
2.请求参数 |
无
3.响应结果:HTML
user_center_site.html
2. 展示地址后端逻辑实现
class AddressView(LoginRequiredMixin, View):
"""用户收货地址"""
def get(self, request):
"""提供收货地址界面"""
# 获取用户地址列表
login_user = request.user
addresses = Address.objects.filter(user=login_user, is_deleted=False)
address_dict_list = []
for address in addresses:
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
}
context = {
'default_address_id': login_user.default_address_id,
'addresses': address_dict_list,
}
return render(request, 'user_center_site.html', context)
3. 展示地址前端逻辑实现
1.将后端模板数据传递到Vue.js
<script type="text/javascript">
let addresses = {{ addresses | safe }};
let default_address_id = "{{ default_address_id }}";
</script>
data: {
addresses: JSON.parse(JSON.stringify(addresses)),
default_address_id: default_address_id,
},
2.
user_center_site.html
中渲染地址信息
<div class="right_content clearfix" v-cloak>
<div class="site_top_con">
<a @click="show_add_site">新增收货地址</a>
<span>你已创建了<b>[[ addresses.length ]]</b>个收货地址,最多可创建<b>20</b>个</span>
</div>
<div class="site_con" v-for="(address, index) in addresses">
<div class="site_title">
<h3>[[ address.title ]]</h3>
<a href="javascript:;" class="edit_icon"></a>
<em v-if="address.id===default_address_id">默认地址</em>
<span class="del_site">×</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">设为默认</a>
<a href="javascript:;" class="edit_icon">编辑</a>
</div>
</div>
</div>
3.完善
user_center_site.js
中成功新增地址后的局部刷新
if (response.data.code == '0') {
// 局部刷新界面:展示所有地址信息,将新的地址添加到头部
this.addresses.splice(0, 0, response.data.address);
this.is_show_edit = false;
}
修改地址前后端逻辑
1. 修改地址接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /addresses/(?P\d+)/ |
> | |
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 | 否 | 固定电话 |
string | 否 | 邮箱 | |
> | |||
3.响应结果:JSON |
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
id | 地址ID |
receiver | 收货人 |
province | 省份名称 |
city | 城市名称 |
district | 区县名称 |
place | 收货地址 |
mobile | 手机号 |
tel | 固定电话 |
邮箱 |
2. 修改地址后端逻辑实现
提示
- 删除地址后端逻辑和新增地址后端逻辑非常的相似。
- 都是更新用户地址模型类,需要保存用户地址信息。
class UpdateDestroyAddressView(LoginRequiredJSONMixin, View):
"""修改和删除地址"""
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 http.HttpResponseForbidden('缺少必传参数')
if not re.match(r'^1[3-9]\d{9}$', mobile):
return http.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 http.HttpResponseForbidden('参数tel有误')
if email:
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return http.HttpResponseForbidden('参数email有误')
# 判断地址是否存在,并更新地址信息
try:
Address.objects.filter(id=address_id).update(
user = request.user,
title = receiver,
receiver = receiver,
province_id = province_id,
city_id = city_id,
district_id = district_id,
place = place,
mobile = mobile,
tel = tel,
email = email
)
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '更新地址失败'})
# 构造响应数据
address = Address.objects.get(id=address_id)
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 http.JsonResponse({'code': RETCODE.OK, 'errmsg': '更新地址成功', 'address': address_dict})
3. 修改地址前端逻辑实现
1.添加修改地址的标记
- 新增地址和修改地址的交互不同。
- 为了区分用户是新增地址还是修改地址,我们可以选择添加一个变量,作为标记。
- 为了方便得到正在修改的地址信息,我们可以选择展示地址时对应的序号作为标记。
data: {
editing_address_index: '',
},
2.实现
编辑
按钮对应的事件
show_edit_site(index){
this.is_show_edit = true;
this.clear_all_errors();
this.editing_address_index = index.toString();
},
<div class="down_btn">
<a v-if="address.id!=default_address_id">设为默认</a>
<a href="javascript:;" class="edit_icon">编辑</a>
</div>
3.展示要重新编辑的数据
show_edit_site(index){
this.is_show_edit = true;
this.clear_all_errors();
this.editing_address_index = index.toString();
// 只获取要编辑的数据
this.form_address = JSON.parse(JSON.stringify(this.addresses[index]));
},
4.发送修改地址请求
-
重要提示:
-
0 == ''
返回true
0 === ''
返回false
- 为了避免第0个索引出错,我们选择
this.editing_address_index === ''
的方式进行判断
if (this.editing_address_index === '') {
// 新增地址
......
} else {
// 修改地址
let url = '/addresses/' + this.addresses[this.editing_address_index].id + '/';
axios.put(url, this.form_address, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.addresses[this.editing_address_index] = response.data.address;
this.is_show_edit = false;
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
alert(error.response);
})
}
删除地址前后端逻辑
1. 删除地址接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | DELETE |
请求地址 | /addresses/(?P\d+)/ |
> | |
2.请求参数:路径参数 |
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | string | 是 | 要修改的地址ID(路径参数) |
> | |||
3.响应结果:JSON |
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
2. 删除地址后端逻辑实现
提示:
- 删除地址不是物理删除,是逻辑删除。
class UpdateDestroyAddressView(LoginRequiredJSONMixin, View):
"""修改和删除地址"""
def put(self, request, address_id):
"""修改地址"""
......
def delete(self, request, address_id):
"""删除地址"""
try:
# 查询要删除的地址
address = Address.objects.get(id=address_id)
# 将地址逻辑删除设置为True
address.is_deleted = True
address.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '删除地址失败'})
# 响应删除地址结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})
3. 删除地址前端逻辑实现
delete_address(index){
let url = '/addresses/' + this.addresses[index].id + '/';
axios.delete(url, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 删除对应的标签
this.addresses.splice(index, 1);
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
}else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
},
<div class="site_title">
<h3>[[ address.title ]]</h3>
<a href="javascript:;" class="edit_icon"></a>
<em v-if="address.id===default_address_id">默认地址</em>
<span @click="delete_address(index)" class="del_site">×</span>
</div>
设置默认地址
1. 设置默认地址接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /addresses/(?P\d+)/default/ |
> | |
2.请求参数:路径参数 |
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | string | 是 | 要修改的地址ID(路径参数) |
> | |||
3.响应结果:JSON |
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
2. 设置默认地址后端逻辑实现
class DefaultAddressView(LoginRequiredJSONMixin, View):
"""设置默认地址"""
def put(self, request, address_id):
"""设置默认地址"""
try:
# 接收参数,查询地址
address = Address.objects.get(id=address_id)
# 设置地址为默认地址
request.user.default_address = address
request.user.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置默认地址失败'})
# 响应设置默认地址结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置默认地址成功'})
3. 设置默认地址前端逻辑实现
set_default(index){
let url = '/addresses/' + this.addresses[index].id + '/default/';
axios.put(url, {}, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 设置默认地址标签
this.default_address_id = this.addresses[index].id;
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
},
<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>
修改地址标题
1. 修改地址标题接口设计和定义
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /addresses/(?P\d+)/title/ |
> | |
2.请求参数:路径参数 |
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | string | 是 | 要修改的地址ID(路径参数) |
> | |||
3.响应结果:JSON |
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
2. 修改地址标题后端逻辑实现
class UpdateTitleAddressView(LoginRequiredJSONMixin, View):
"""设置地址标题"""
def put(self, request, address_id):
"""设置地址标题"""
# 接收参数:地址标题
json_dict = json.loads(request.body.decode())
title = json_dict.get('title')
try:
# 查询地址
address = Address.objects.get(id=address_id)
# 设置新的地址标题
address.title = title
address.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置地址标题失败'})
# 4.响应删除地址结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置地址标题成功'})
3. 修改地址标题前端逻辑实现
<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>
data: {
edit_title_index: '',
new_title: '',
},
// 展示地址title编辑框
show_edit_title(index){
this.edit_title_index = index;
},
// 取消保存地址title
cancel_title(){
this.edit_title_index = '';
this.new_title = '';
},
// 修改地址title
save_title(index){
if (!this.new_title) {
alert("请填写标题后再保存!");
} else {
let url = '/addresses/' + this.addresses[index].id + '/title/';
axios.put(url, {
title: this.new_title
}, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 更新地址title
this.addresses[index].title = this.new_title;
this.cancel_title();
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
}
},
修改密码
1. 修改密码后端逻辑
提示:
- 修改密码前需要校验原始密码是否正确,以校验修改密码的用户身份。
- 如果原始密码正确,再将新的密码赋值给用户。
class ChangePasswordView(LoginRequiredMixin, View):
"""修改密码"""
def get(self, request):
"""展示修改密码界面"""
return render(request, 'user_center_pass.html')
def post(self, request):
"""实现修改密码逻辑"""
# 接收参数
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
new_password2 = request.POST.get('new_password2')
# 校验参数
if not all([old_password, new_password, new_password2]):
return http.HttpResponseForbidden('缺少必传参数')
try:
request.user.check_password(old_password)
except Exception as e:
logger.error(e)
return render(request, 'user_center_pass.html', {'origin_pwd_errmsg':'原始密码错误'})
if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
return http.HttpResponseForbidden('密码最少8位,最长20位')
if new_password != new_password2:
return http.HttpResponseForbidden('两次输入的密码不一致')
# 修改密码
try:
request.user.set_password(new_password)
request.user.save()
except Exception as e:
logger.error(e)
return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '修改密码失败'})
# 清理状态保持信息
logout(request)
response = redirect(reverse('users:login'))
response.delete_cookie('username')
# # 响应密码修改结果:重定向到登录界面
return response