1、修改地址前后端逻辑
修改地址接口设计和定义
- 请求方式
- 请求参数:路径参数 和 JSON
- 响应结果:JSON
修改地址后端逻辑实现
- 删除地址后端逻辑和新增地址后端逻辑非常的相似。
- 都是更新用户地址模型类,需要保存用户地址信息。
class UpdateDestroyAddressView(LoginRequiredJSONMixin, View):
"""修改和删除地址"""
def put(self, request, address_id):
"""修改地址"""
# 接收参数
# 校验参数
# 判断地址是否存在,并更新地址信息
# 构造响应数据
# 响应更新地址结果
修改地址前端逻辑实现
添加修改地址的标记
- 新增地址和修改地址的交互不同。
- 为了区分用户是新增地址还是修改地址,我们可以选择添加一个变量,作为标记。
- 为了方便得到正在修改的地址信息,我们可以选择展示地址时对应的序号作为标记。
data: {
editing_address_index: '',
},
实现编辑按钮对应的事件
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>
展示要重新编辑的数据
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]));
},
发送修改地址请求
-
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);
})
}
项目实例代码
apps/users/views.py
文件,用户后端验证视图文件
# 更新地址类定义 更新收货地址
class UpdateAddressView(LoginRequiredJsonMixin, View):
"""更新收货地址"""
def put(self, request, address_id): # address_id参数根据请求地址url的定义来确定,
# 表单数据存储在body属性中,无法通过get获取,request.body.decode()获取表单的地址信息
json_dict = json.loads(request.body.decode()) # json.loads是转换数据类型 接收参数
# json_dict接收到的参数
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 re.match(r"^1[3-9]\d{9}$", mobile): # re.match正则匹配,^开始符号,$结束符号
return http.HttpResponseForbidden('参数mobile错误')
# 修改用户的收货地址
try:
# address = Address.objects.filter(id=address_id) # 查询address_id字段的数据
# address.receiver = receiver # 修改数据库中的参数(一种方式)
Address.objects.filter(id=address_id).update( # 修改数据库中的参数(另一种简单方式)
user=request.user,
title=receiver, # 收货人
receiver=receiver,
province_id=province_id, # province是外键,根据数据库中的字段进行添加
city_id=city_id, # city是外键,根据数据库中的字段进行添加
district_id=district_id, # district是外键,根据数据库中的字段进行添加
place=place,
mobile=mobile,
tel=tel,
email=email,
# is_deleted= # 使用默认值
)
# print(address)
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, # 数据库中新增收货地址的id,address是数据库的对象
"receiver": address.receiver,
"province": address.province.name, # address.province显示的是省份代码,.name是外键,能够显示省份的字符串
"city": address.city.name, # address.city显示的是城市代码,.name是外键
"district": address.district.name, # 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})
个人用户中心地址界面静态文件:static/js/user_center_site.js
// 个人用户中心地址界面静态文件:static/js/user_center_site.js
let vm = new Vue({
el: '#app',
delimiters: ['[[', ']]'],
data: {
username: getCookie('username'),
is_show_edit: false,
form_address: {
receiver: '',
province_id: '',
city_id: '',
district_id: '',
place: '',
mobile: '',
tel: '',
email: '',
},
provinces: [],
cities: [],
districts: [],
addresses: JSON.parse(JSON.stringify(addresses)),
default_address_id: default_address_id,
editing_address_index: '',
edit_title_index: '',
new_title: '',
error_receiver: false,
error_place: false,
error_mobile: false,
error_tel: false,
error_email: false,
},
mounted() {
// 获取省份数据 页面加载完成再执行
this.get_provinces();
},
watch: {
// 监听到省份id变化
'form_address.province_id': function(){
if (this.form_address.province_id) {
let url = '/areas/?area_id=' + this.form_address.province_id; // 监听area_id的数据
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.cities = response.data.sub_data.subs;
} else {
console.log(response.data);
this.cities = [];
}
})
.catch(error => {
console.log(error.response);
this.cities = [];
})
}
},
// 监听到城市id变化
'form_address.city_id': function(){
if (this.form_address.city_id){
let url = '/areas/?area_id='+ this.form_address.city_id; // 监听area_id的数据
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.districts = response.data.sub_data.subs;
} else {
console.log(response.data);
this.districts = [];
}
})
.catch(error => {
console.log(error.response);
this.districts = [];
})
}
}
},
methods: {
// 展示新增地址弹框
show_add_site(){
this.is_show_edit = true;
// 清空错误提示信息
this.clear_all_errors();
// 清空原有数据
this.form_address.receiver = '';
this.form_address.province_id = '';
this.form_address.city_id = '';
this.form_address.district_id = '';
this.form_address.place = '';
this.form_address.mobile = '';
this.form_address.tel = '';
this.form_address.email = '';
this.editing_address_index = '';
},
// 展示编辑地址弹框
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]));
},
// 校验收货人
check_receiver(){
if (!this.form_address.receiver) {
this.error_receiver = true;
} else {
this.error_receiver = false;
}
},
// 校验收货地址
check_place(){
if (!this.form_address.place) {
this.error_place = true;
} else {
this.error_place = false;
}
},
// 校验手机号
check_mobile(){
let re = /^1[3-9]\d{9}$/;
if(re.test(this.form_address.mobile)) {
this.error_mobile = false;
} else {
this.error_mobile = true;
}
},
// 校验固定电话
check_tel(){
if (this.form_address.tel) {
let re = /^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$/;
if (re.test(this.form_address.tel)) {
this.error_tel = false;
} else {
this.error_tel = true;
}
} else {
this.error_tel = false;
}
},
// 校验邮箱
check_email(){
if (this.form_address.email) {
let re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;
if(re.test(this.form_address.email)) {
this.error_email = false;
} else {
this.error_email = true;
}
} else {
this.error_email = false;
}
},
// 清空错误提示信息
clear_all_errors(){
this.error_receiver = false;
this.error_mobile = false;
this.error_place = false;
this.error_tel = false;
this.error_email = false;
},
// 获取省份数据
get_provinces(){
let url = '/areas/';
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.provinces = response.data.province_list;
} else {
console.log(response.data);
this.provinces = [];
}
})
.catch(error => {
console.log(error.response);
this.provinces = [];
})
},
// 新增地址
save_address(){
if (this.error_receiver || this.error_place || this.error_mobile || this.error_email || !this.form_address.province_id || !this.form_address.city_id || !this.form_address.district_id ) {
alert('信息填写有误!');
} else {
// 注意:0 == '';返回true; 0 === '';返回false;
if (this.editing_address_index === '') {
// 新增地址
let url = '/users/addresses/create/';
axios.post(url, this.form_address, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 局部刷新界面:展示所有地址信息,将新的地址添加到头部
this.addresses.splice(0, 0, response.data.address);
this.is_show_edit = false;
} else if (response.data.code == '4101') {
location.href = '/users/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
} else {
// 修改地址
let url = '/users/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 = '/users/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
alert(error.response);
})
}
}
},
// 删除地址
delete_address(index){
let url = '/users/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);
})
},
// 设置默认地址
set_default(index){
let url = '/users/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 = '/users/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
},
// 展示地址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 = '/users/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 = '/users/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
}
},
}
});
2、删除地址前后端逻辑
删除地址接口设计和定义
- 请求方式
- 请求参数:路径参数
- 响应结果:JSON
删除地址后端逻辑实现
删除地址不是物理删除,是逻辑删除。
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': '删除地址成功'})
删除地址前端逻辑实现
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>
项目实例代码
apps/users/views.py
文件,用户后端验证视图文件
# 更新地址类定义 更新收货地址,删除地址信息功能
class UpdateAddressView(LoginRequiredJsonMixin, View):
"""更新收货地址"""
def put(self, request, address_id): # address_id参数根据请求地址url的定义来确定,
# 表单数据存储在body属性中,无法通过get获取,request.body.decode()获取表单的地址信息
json_dict = json.loads(request.body.decode()) # json.loads是转换数据类型 接收参数
# json_dict接收到的参数
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 re.match(r"^1[3-9]\d{9}$", mobile): # re.match正则匹配,^开始符号,$结束符号
return http.HttpResponseForbidden('参数mobile错误')
# 修改用户的收货地址
try:
# address = Address.objects.filter(id=address_id) # 查询address_id字段的数据
# address.receiver = receiver # 修改数据库中的参数(一种方式)
Address.objects.filter(id=address_id).update( # 修改数据库中的参数(另一种简单方式)
user=request.user,
title=receiver, # 收货人
receiver=receiver,
province_id=province_id, # province是外键,根据数据库中的字段进行添加
city_id=city_id, # city是外键,根据数据库中的字段进行添加
district_id=district_id, # district是外键,根据数据库中的字段进行添加
place=place,
mobile=mobile,
tel=tel,
email=email,
# is_deleted= # 使用默认值
)
# print(address)
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, # 数据库中新增收货地址的id,address是数据库的对象
"receiver": address.receiver,
"province": address.province.name, # address.province显示的是省份代码,.name是外键,能够显示省份的字符串
"city": address.city.name, # address.city显示的是城市代码,.name是外键
"district": address.district.name, # 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})
# 删除地址信息
def delete(self, request, address_id):
try:
addresses = Address.objects.get(id=address_id)
# 假删除
addresses.is_deleted = True # 修改is_deleted字段变成True
addresses.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({"code": RETCODE.DBERR, "errmsg": "删除地址失败"})
# 删除功能正常
return http.JsonResponse({"code": RETCODE.OK, "errmsg": "删除地址成功"})
3、设置默认地址
设置默认地址接口设计和定义
- 请求方式
- 请求参数:路径参数
- 响应结果:JSON
设置默认地址后端逻辑实现
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': '设置默认地址成功'})
设置默认地址前端逻辑实现
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>
项目实例代码
apps/users/urls.py
子路由,users模块的路由
注意: 更新收货地址路由
和设置默认的收货地址路由
的相似,就需要用正则中的$
符号来区分
# -*- encoding: utf-8 -*-
"""
@File : urls.py
@Time : 2020/7/26 20:34
@Author : chen
apps/users/urls.py 子路由,users模块的路由
"""
from django.urls import path, include, re_path
from . import views
app_name = 'users'
urlpatterns = [
# 注册路由
path('register/', views.RegisterView.as_view(), name='register'),
# 判断用户名是否存在 /usernames/(?P<username>[a-zA-Z0-9_-]{5,20})/count/
# ?P<username> 拼接用户名,[a-zA-Z0-9_-]{5,20}正则匹配
re_path("usernames/(?P<username>[a-zA-Z0-9_-]{5,20})/count/", views.UsernameExists.as_view()),
# 登陆界面url绑定
path("login/", views.LoginView.as_view(), name='login'),
# 用户退出登陆
path("logout/", views.LogoutView.as_view(), name='logout'),
# 用户个人中心路由
path("info/", views.UserInfoView.as_view(), name='info'),
# 用户添加邮箱路由
path("emails/", views.EmailView.as_view()),
# 用户验证邮箱路由 www.meiduo.site:8000/users/emails/verification/?token=xxxxx
path("emails/verification/", views.VerifyEmailView.as_view()),
# 用户收货地址路由
path("addresses/", views.AddressView.as_view(), name='address'),
# 用户新增收货地址路由
path("addresses/create/", views.CreateAddressView.as_view()),
# 更新收货地址路由 re_path支持正则
re_path(r"addresses/(?P<address_id>\d+)/$", views.UpdateAddressView.as_view()),
# 设置默认的收货地址
re_path(r"addresses/(?P<address_id>\d+)/default/$", views.DefaultAddressView.as_view()),
]
apps/users/views.py
文件,用户后端验证视图文件
# 定义默认地址类
class DefaultAddressView(LoginRequiredJsonMixin, View):
""" 设置默认地址 """
def put(self, request, address_id):
try:
address = Address.objects.get(id=address_id) # 通过地址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": "设置默认收货地址成功"})
个人用户中心地址界面静态文件:static/js/user_center_site.js
// 个人用户中心地址界面静态文件:static/js/user_center_site.js
let vm = new Vue({
el: '#app',
delimiters: ['[[', ']]'],
data: {
username: getCookie('username'),
is_show_edit: false,
form_address: {
receiver: '',
province_id: '',
city_id: '',
district_id: '',
place: '',
mobile: '',
tel: '',
email: '',
},
provinces: [],
cities: [],
districts: [],
addresses: JSON.parse(JSON.stringify(addresses)),
default_address_id: default_address_id,
editing_address_index: '',
edit_title_index: '',
new_title: '',
error_receiver: false,
error_place: false,
error_mobile: false,
error_tel: false,
error_email: false,
},
mounted() {
// 获取省份数据 页面加载完成再执行
this.get_provinces();
},
watch: {
// 监听到省份id变化
'form_address.province_id': function(){
if (this.form_address.province_id) {
let url = '/areas/?area_id=' + this.form_address.province_id; // 监听area_id的数据
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.cities = response.data.sub_data.subs;
} else {
console.log(response.data);
this.cities = [];
}
})
.catch(error => {
console.log(error.response);
this.cities = [];
})
}
},
// 监听到城市id变化
'form_address.city_id': function(){
if (this.form_address.city_id){
let url = '/areas/?area_id='+ this.form_address.city_id; // 监听area_id的数据
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.districts = response.data.sub_data.subs;
} else {
console.log(response.data);
this.districts = [];
}
})
.catch(error => {
console.log(error.response);
this.districts = [];
})
}
}
},
methods: {
// 展示新增地址弹框
show_add_site(){
this.is_show_edit = true;
// 清空错误提示信息
this.clear_all_errors();
// 清空原有数据
this.form_address.receiver = '';
this.form_address.province_id = '';
this.form_address.city_id = '';
this.form_address.district_id = '';
this.form_address.place = '';
this.form_address.mobile = '';
this.form_address.tel = '';
this.form_address.email = '';
this.editing_address_index = '';
},
// 展示编辑地址弹框
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]));
},
// 校验收货人
check_receiver(){
if (!this.form_address.receiver) {
this.error_receiver = true;
} else {
this.error_receiver = false;
}
},
// 校验收货地址
check_place(){
if (!this.form_address.place) {
this.error_place = true;
} else {
this.error_place = false;
}
},
// 校验手机号
check_mobile(){
let re = /^1[3-9]\d{9}$/;
if(re.test(this.form_address.mobile)) {
this.error_mobile = false;
} else {
this.error_mobile = true;
}
},
// 校验固定电话
check_tel(){
if (this.form_address.tel) {
let re = /^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$/;
if (re.test(this.form_address.tel)) {
this.error_tel = false;
} else {
this.error_tel = true;
}
} else {
this.error_tel = false;
}
},
// 校验邮箱
check_email(){
if (this.form_address.email) {
let re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;
if(re.test(this.form_address.email)) {
this.error_email = false;
} else {
this.error_email = true;
}
} else {
this.error_email = false;
}
},
// 清空错误提示信息
clear_all_errors(){
this.error_receiver = false;
this.error_mobile = false;
this.error_place = false;
this.error_tel = false;
this.error_email = false;
},
// 获取省份数据
get_provinces(){
let url = '/areas/';
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.provinces = response.data.province_list;
} else {
console.log(response.data);
this.provinces = [];
}
})
.catch(error => {
console.log(error.response);
this.provinces = [];
})
},
// 新增地址
save_address(){
if (this.error_receiver || this.error_place || this.error_mobile || this.error_email || !this.form_address.province_id || !this.form_address.city_id || !this.form_address.district_id ) {
alert('信息填写有误!');
} else {
// 注意:0 == '';返回true; 0 === '';返回false;
if (this.editing_address_index === '') {
// 新增地址
let url = '/users/addresses/create/';
axios.post(url, this.form_address, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 局部刷新界面:展示所有地址信息,将新的地址添加到头部
this.addresses.splice(0, 0, response.data.address);
this.is_show_edit = false;
} else if (response.data.code == '4101') {
location.href = '/users/login/?next=/addresses/'; // 注意此时的路由/users/
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
} else {
// 修改地址
let url = '/users/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 = '/users/login/?next=/addresses/'; // 注意此时的路由/users/
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
alert(error.response);
})
}
}
},
// 删除地址 addresses[index].id 意思为[{}, {},{}...]----> {}.id代表字典中的属性的数据
delete_address(index){ // index是apps/users/views.py中地址信息的下标
let url = '/users/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 = '/users/login/?next=/addresses/'; // 注意此时的路由/users/
}else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
},
// 设置默认地址
set_default(index){ // index是apps/users/views.py中地址信息的下标
let url = '/users/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 = '/users/login/?next=/addresses/'; // 注意此时的路由/users/
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
},
// 展示地址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 = '/users/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 = '/users/login/?next=/addresses/'; // 注意此时的路由/users/
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
}
},
}
});
4、修改密码
修改密码业务逻辑分析
修改密码后端逻辑
- 修改密码前需要校验原始密码是否正确,以校验修改密码的用户身份。
- 如果原始密码正确,再将新的密码赋值给用户。
class ChangePasswordView(LoginRequiredMixin, View):
"""修改密码"""
def get(self, request):
"""展示修改密码界面"""
return render(request, 'user_center_pass.html')
def post(self, request):
"""实现修改密码逻辑"""
# 接收参数
# 校验参数
# 修改密码
# 清理状态保持信息
# 响应密码修改结果:重定向到登录界面
5、虚拟机安装docker/FastDFS
6、电商-商品知识
SPU和SKU
在电商中对于商品,有两个重要的概念:SPU和SKU
SPU介绍
- SPU =
Standard Product Unit
(标准产品单位)
• SPU是商品信息聚合的最小单位,是一组可服用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
• 通俗的讲,属性值、特性相同的商品就可以归类到一类SPU。
• 例如: iPhone X 就是一个SPU,与商家、颜色、款式、规格、套餐等都无关。
SKU介绍
- SKU =
Stock Keeping Unit
(库存量单位)
• SKU即库存进出计量的单位,可以是以件、盒等为单位,是物理上不可分割的最小存货单元。
• 通俗的讲,SKU是指一款商品,每款都有一个SKU,便于电商品牌识别商品。
• 例如:iPhone X 全网通 黑色 256G 就是一个SKU,表示了具体的规格、颜色等信息。
SPU和SKU是一对多的对应关系
7、首页广告数据库表分析
首页广告数据库表分析
定义首页广告模型类
广告模型文件:apps/contents/models.py
class ContentCategory(BaseModel):
"""广告内容类别"""
name = models.CharField(max_length=50, verbose_name='名称')
key = models.CharField(max_length=50, verbose_name='类别键名')
class Meta:
db_table = 'tb_content_category'
verbose_name = '广告内容类别'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Content(BaseModel):
"""广告内容"""
category = models.ForeignKey(ContentCategory, on_delete=models.PROTECT, verbose_name='类别')
title = models.CharField(max_length=100, verbose_name='标题')
url = models.CharField(max_length=300, verbose_name='内容链接')
image = models.ImageField(null=True, blank=True, verbose_name='图片')
text = models.TextField(null=True, blank=True, verbose_name='内容')
sequence = models.IntegerField(verbose_name='排序')
status = models.BooleanField(default=True, verbose_name='是否展示')
class Meta:
db_table = 'tb_content'
verbose_name = '广告内容'
verbose_name_plural = verbose_name
def __str__(self):
return self.category.name + ': ' + self.title
8、商品信息数据库表分析
定义商品信息模型类
class GoodsCategory(BaseModel):
"""商品类别"""
name = models.CharField(max_length=10, verbose_name='名称')
parent = models.ForeignKey('self', related_name='subs', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父类别')
class Meta:
db_table = 'tb_goods_category'
verbose_name = '商品类别'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsChannelGroup(BaseModel):
"""商品频道组"""
name = models.CharField(max_length=20, verbose_name='频道组名')
class Meta:
db_table = 'tb_channel_group'
verbose_name = '商品频道组'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsChannel(BaseModel):
"""商品频道"""
group = models.ForeignKey(GoodsChannelGroup, verbose_name='频道组名')
category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='顶级商品类别')
url = models.CharField(max_length=50, verbose_name='频道页面链接')
sequence = models.IntegerField(verbose_name='组内顺序')
class Meta:
db_table = 'tb_goods_channel'
verbose_name = '商品频道'
verbose_name_plural = verbose_name
def __str__(self):
return self.category.name
class Brand(BaseModel):
"""品牌"""
name = models.CharField(max_length=20, verbose_name='名称')
logo = models.ImageField(verbose_name='Logo图片')
first_letter = models.CharField(max_length=1, verbose_name='品牌首字母')
class Meta:
db_table = 'tb_brand'
verbose_name = '品牌'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class SPU(BaseModel):
"""商品SPU"""
name = models.CharField(max_length=50, verbose_name='名称')
brand = models.ForeignKey(Brand, on_delete=models.PROTECT, verbose_name='品牌')
category1 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat1_spu', verbose_name='一级类别')
category2 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat2_spu', verbose_name='二级类别')
category3 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat3_spu', verbose_name='三级类别')
sales = models.IntegerField(default=0, verbose_name='销量')
comments = models.IntegerField(default=0, verbose_name='评价数')
desc_detail = models.TextField(default='', verbose_name='详细介绍')
desc_pack = models.TextField(default='', verbose_name='包装信息')
desc_service = models.TextField(default='', verbose_name='售后服务')
class Meta:
db_table = 'tb_spu'
verbose_name = '商品SPU'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class SKU(BaseModel):
"""商品SKU"""
name = models.CharField(max_length=50, verbose_name='名称')
caption = models.CharField(max_length=100, verbose_name='副标题')
spu = models.ForeignKey(SPU, on_delete=models.CASCADE, verbose_name='商品')
category = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, verbose_name='从属类别')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')
cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='进价')
market_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='市场价')
stock = models.IntegerField(default=0, verbose_name='库存')
sales = models.IntegerField(default=0, verbose_name='销量')
comments = models.IntegerField(default=0, verbose_name='评价数')
is_launched = models.BooleanField(default=True, verbose_name='是否上架销售')
default_image_url = models.CharField(max_length=200, default='', null=True, blank=True, verbose_name='默认图片')
class Meta:
db_table = 'tb_sku'
verbose_name = '商品SKU'
verbose_name_plural = verbose_name
def __str__(self):
return '%s: %s' % (self.id, self.name)
class SKUImage(BaseModel):
"""SKU图片"""
sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku')
image = models.ImageField(verbose_name='图片')
class Meta:
db_table = 'tb_sku_image'
verbose_name = 'SKU图片'
verbose_name_plural = verbose_name
def __str__(self):
return '%s %s' % (self.sku.name, self.id)
class SPUSpecification(BaseModel):
"""商品SPU规格"""
spu = models.ForeignKey(SPU, on_delete=models.CASCADE, related_name='specs', verbose_name='商品SPU')
name = models.CharField(max_length=20, verbose_name='规格名称')
class Meta:
db_table = 'tb_spu_specification'
verbose_name = '商品SPU规格'
verbose_name_plural = verbose_name
def __str__(self):
return '%s: %s' % (self.spu.name, self.name)
class SpecificationOption(BaseModel):
"""规格选项"""
spec = models.ForeignKey(SPUSpecification, related_name='options', on_delete=models.CASCADE, verbose_name='规格')
value = models.CharField(max_length=20, verbose_name='选项值')
class Meta:
db_table = 'tb_specification_option'
verbose_name = '规格选项'
verbose_name_plural = verbose_name
def __str__(self):
return '%s - %s' % (self.spec, self.value)
class SKUSpecification(BaseModel):
"""SKU具体规格"""
sku = models.ForeignKey(SKU, related_name='specs', on_delete=models.CASCADE, verbose_name='sku')
spec = models.ForeignKey(SPUSpecification, on_delete=models.PROTECT, verbose_name='规格名称')
option = models.ForeignKey(SpecificationOption, on_delete=models.PROTECT, verbose_name='规格值')
class Meta:
db_table = 'tb_sku_specification'
verbose_name = 'SKU规格'
verbose_name_plural = verbose_name
def __str__(self):
return '%s: %s - %s' % (self.sku, self.spec.name, self.option.value)