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、首页广告数据库表分析

首页广告数据库表分析

虚拟化 商品 定义 电商 表结构 电商虚拟商品有什么_python_02

定义首页广告模型类

广告模型文件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

虚拟化 商品 定义 电商 表结构 电商虚拟商品有什么_django_03

8、商品信息数据库表分析

虚拟化 商品 定义 电商 表结构 电商虚拟商品有什么_python_04

虚拟化 商品 定义 电商 表结构 电商虚拟商品有什么_虚拟化 商品 定义 电商 表结构_05

定义商品信息模型类

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)