首页 > 其他分享 >【Django开发】前后端分离django美多商城项目第9篇:收货地址,1. 展示收货地址界面【附代码文档】

【Django开发】前后端分离django美多商城项目第9篇:收货地址,1. 展示收货地址界面【附代码文档】

时间:2024-09-04 19:52:38浏览次数:12  
标签:province name 收货 list 美多 地址 id sub

本教程的知识点为: 项目准备 项目准备 配置 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. 展示省市区数据
  2. 用户地址的增删改查处理
  3. 设置默认地址
  4. 设置地址标题

省市区三级联动

1. 展示收货地址界面

提示:

  • 省市区数据是在收货地址界面展示的,所以我们先渲染出收货地址界面。
  • 收货地址界面中基础的交互已经提前实现。
class AddressView(LoginRequiredMixin, View):
    """用户收货地址"""

    def get(self, request):
        """提供收货地址界面"""
        return render(request, 'user_center_site.html')

2. 准备省市区模型和数据

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

模型说明:

  • 自关联字段的外键指向自身,所以 models.ForeignKey('self')

  • 使用related_name指明父级查询子级数据的语法

    • 默认Area模型类对象.area_set语法
  • related_name='subs'

    • 现在Area模型类对象.subs语法

导入省市区数据

mysql -h数据库ip地址 -u数据库用户名 -p数据库密码 数据库 < areas.sql
mysql -h127.0.0.1 -uroot -pmysql meiduo_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,表示用户需要市或区数据
class AreasView(View):
    """省市区数据"""

    def get(self, request):
        """提供省市区数据"""
        area_id = request.GET.get('area_id')

        if not area_id:
            # 提供省份数据
            try:
                # 查询省份数据
                province_model_list = Area.objects.filter(parent__isnull=True)

                # 序列化省级数据
                province_list = []
                for province_model in province_model_list:
                    province_list.append({'id': province_model.id, 'name': province_model.name})
            except Exception as e:
                logger.error(e)
                return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '省份数据错误'})

            # 响应省份数据
            return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'province_list': province_list})
        else:
            # 提供市或区数据
            try:
                parent_model = Area.objects.get(id=area_id)  # 查询市或区的父级
                sub_model_list = parent_model.subs.all()

                # 序列化市或区数据
                sub_list = []
                for sub_model in sub_model_list:
                    sub_list.append({'id': sub_model.id, 'name': sub_model.name})

                sub_data = {
                    'id': parent_model.id,  # 父级pk
                    'name': parent_model.name,  # 父级name
                    'subs': sub_list  # 父级的子集
                }
            except Exception as e:
                logger.error(e)
                return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '城市或区数据错误'})

            # 响应市或区数据
            return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'sub_data': sub_data})

4. Vue渲染省市区数据

1.user_center_site.js

mounted() {
    // 获取省份数据
    this.get_provinces();
},
// 获取省份数据
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 = [];
        })
},
watch: {
    // 监听到省份id变化
    'form_address.province_id': function(){
        if (this.form_address.province_id) {
            let url = '/areas/?area_id=' + this.form_address.province_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;
            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 = [];
                })
        }
    }
},

2.user_center_site.html

<div class="form_group">
    <label>*所在地区:</label>
    <select v-model="form_address.province_id">
        <option v-for="province in provinces" :value="province.id">[[ province.name ]]</option>
    </select>
    <select v-model="form_address.city_id">
        <option v-for="city in cities" :value="city.id">[[ city.name ]]</option>
    </select>
    <select v-model="form_address.district_id">
        <option v-for="district in districts" :value="district.id">[[ district.name ]]</option>
    </select>
</div>

5. 缓存省市区数据

提示:

  • 省市区数据是我们动态查询的结果。
  • 但是省市区数据不是频繁变化的数据,所以没有必要每次都重新查询。
  • 所以我们可以选择对省市区数据进行缓存处理。

1.缓存工具

  • from django.core.cache import cache
  • 存储缓存数据:cache.set('key', 内容, 有效期)
  • 读取缓存数据:cache.get('key')
  • 删除缓存数据:cache.delete('key')
  • 注意:存储进去和读取出来的数据类型相同,所以读取出来后可以直接使用。

2.缓存逻辑

3.缓存逻辑实现

  • 省份缓存数据

    • cache.set('province_list', province_list, 3600)
  • 市或区缓存数据

    • cache.set('sub_area_' + area_id, sub_data, 3600)
class AreasView(View):
    """省市区数据"""

    def get(self, request):
        """提供省市区数据"""
        area_id = request.GET.get('area_id')

        if not area_id:
            # 读取省份缓存数据
            province_list = cache.get('province_list')

            if not province_list:
                # 提供省份数据
                try:
                    # 查询省份数据
                    province_model_list = Area.objects.filter(parent__isnull=True)

                    # 序列化省级数据
                    province_list = []
                    for province_model in province_model_list:
                        province_list.append({'id': province_model.id, 'name': province_model.name})
                except Exception as e:
                    logger.error(e)
                    return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '省份数据错误'})

                # 存储省份缓存数据
                cache.set('province_list', province_list, 3600)

            # 响应省份数据
            return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'province_list': province_list})
        else:
            # 读取市或区缓存数据
            sub_data = cache.get('sub_area_' + area_id)

            if not sub_data:
                # 提供市或区数据
                try:
                    parent_model = Area.objects.get(id=area_id)  # 查询市或区的父级
                    sub_model_list = parent_model.subs.all()

                    # 序列化市或区数据
                    sub_list = []
                    for sub_model in sub_model_list:
                        sub_list.append({'id': sub_model.id, 'name': sub_model.name})

                    sub_data = {
                        'id': parent_model.id,  # 父级pk
                        'name': parent_model.name,  # 父级name
                        'subs': sub_list  # 父级的子集
                    }
                except Exception as e:
                    logger.error(e)
                    return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '城市或区数据错误'})

                # 储存市或区缓存数据
                cache.set('sub_area_' + area_id, sub_data, 3600)

            # 响应市或区数据
            return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'sub_data': sub_data})

新增地址前后端逻辑

1. 定义用户地址模型类

1.用户地址模型类

class Address(BaseModel):
    """用户地址"""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='addresses', verbose_name='用户')
    title = models.CharField(max_length=20, verbose_name='地址名称')
    receiver = models.CharField(max_length=20, 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='区')
    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_address'
        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(max_length=11, unique=True, verbose_name='手机号')
    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

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. 新增地址后端逻辑实现

提示:

  • 用户地址数量有上限,最多20个,超过地址数量上限就返回错误信息
class CreateAddressView(LoginRequiredJSONMixin, View):
    """新增地址"""

    def post(self, request):
        """实现新增地址逻辑"""
        # 判断是否超过地址上限:最多20个
        # Address.objects.filter(user=request.user).count()
        count = request.user.addresses.count()
        if count >= constants.USER_ADDRESS_COUNTS_LIMIT:
            return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '超过地址数量上限'})

        # 接收参数
        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 = Address.objects.create(
                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
            )

            # 设置默认地址
            if not request.user.default_address:
                request.user.default_address = address
                request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '新增地址失败'})

        # 新增地址成功,将新增的地址响应给前端实现局部刷新
        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})

4. 新增地址前端逻辑实现

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 {
        // 新增地址
        let url = '/addresses/create/';
        axios.post(url, this.form_address, {
            headers: {
                'X-CSRFToken':getCookie('csrftoken')
            },
            responseType: 'json'
        })
            .then(response => {
                if (response.data.code == '0') {
                    // 局部刷新界面:展示所有地址信息

                } else if (response.data.code == '4101') {
                    location.href = '/login/?next=/addresses/';
                } else {
                    alert(response.data.errmsg);
                }
            })
            .catch(error => {
                console.log(error.response);
            })
    }
},

标签:province,name,收货,list,美多,地址,id,sub
From: https://blog.51cto.com/u_16958431/11919818

相关文章

  • pbootcms模板如何输出当前页面的完整url地址
    在PbootCMS中,如果你需要在模板的内容页中调用当前页面的完整URL,可以结合使用 {pboot:httpurl} 和 {content:link} 来实现。这样可以确保生成的URL是完整的,并且包含了当前页面的路径。示例代码假设你需要在模板的内容页中调用当前页面的完整URL,可以使用以下代码:html......
  • IP地址基础知识科普
    IP地址是分配给连接到互联网上的每一台设备的唯一数字标识,用于网络之间相互连通。在互联网上,只有输入正确的IP地址,才能获得准确的信息。通常IP地址在计算机网络中用数字形式体现。IP地址的构成通常IP地址是由网络地址和主机地址两部分构成的。网络地址:用于标识某个IP地址所......
  • pom.xml里配置私服发布地址并总是取最新快照版本
    在工程pom.xml下配置:<distributionManagement><snapshotRepository><id>maven-snapshots</id><url>http://your_host:port/repository/maven-snapshots/</url><snapshots>......
  • crash查看percpu变量在每个cpu上的基地址和内容
    查看percpu变量在每个cpu上的基地址 crash>kmem-oPER-CPUOFFSETVALUES:CPU0:ffff88807f600000CPU1:ffff88807fa00000CPU2:ffff88813d600000CPU3:ffff88813da00000CPU4:ffff8881bd600000CPU5:ffff8881bda00000CPU6:ffff88823d600000CPU......
  • Jforum论坛项目,用Jemter调发布帖子接口,响应结果返回的重定向地址为error...html,导致一
    1.Jmeter的发布帖子接口,使用Jemter代理录制工具进行录制的,发布帖子的参数和接口,都是通过录制得来,如下图,action是insertSave 2.因为需要先登录,再能进行发帖,所以根据项目的cookie保存,增加一个cookie管理器,用来自动保存登录成功后的cookie认证 3.等脚本参数都配置好了之后,......
  • 【Azure Policy】添加策略用于审计Azure 网络安全组(NSG)规则 -- 只能特定的IP地址允
    问题描述对Azure上的虚拟机资源,需要进行安全管理。只有指定的IP地址才能够通过RDP/SSH远程到虚拟机上,有如下几点考虑:1)使用AzurePolicy服务,扫描订阅中全部的网络安全组(NSG:NetworkSecurityGroup)资源2)判断入站规则,判断是否是3389,22端口3)判断源地址是否是被允许的IP4)对......
  • 根据地址获取经纬度
      1:安装依赖与引入调用腾讯地图api前需要先去注册并申请key在vue项目中的/public文件夹中的index.html的head中写入安装lodash:npminstall--savelodash安装jsonp:npminstallvue-jsonp--savemain.js引入jsonpimportVuefrom'vue'import{VueJsonp}from'vue-js......
  • LiveGBS GB28181/GB35114流媒体平台中关于接口鉴权和流媒体地址鉴权的配置和使用
    @目录1、安全控制1.1、HTTP接口鉴权1.2、流地址鉴权2、401Unauthorized2.1、携带token调用接口2.1.1、获取鉴权token2.1.2、调用其它接口2.1.2.1、携带CookieToken2.1.2.2、携带URLToken2.2、play页面携带token2.3、携带StreamToken播放视频流2.3.1、获取视频流地址2.3.2、获取......
  • IP地址提示不是私密连接如何解决
    IP地址提示“不是私密链接”或“连接不是私密的”通常是由以下几个原因导致的:一、SSL/TLS证书问题证书过期:网站的SSL证书如果超过了有效期,浏览器会认为它不再可信,从而显示连接不安全的警告。证书不受信任:证书可能由不受浏览器信任的颁发机构(CA)签署,或者根本就没有被任何受信......
  • 11.吐血整理sed入门到精通,sed语法,脚本命令,打印,替换,删除,插入,行替换,字符替换,保
    文章目录前言sed介绍1.sed介绍2.sed语法介绍3.sed脚本命令1.打印2.s替换3.删除脚本命令d3.插入脚本命令a/i4.行替换脚本命令c4.字符替换脚本y5.保存内容脚本w6.插入其他文本r6.中断退出脚本命令q脚本命令当中的地址[address]正则表达式sed[选项]1.sed-i选项2.sed-e......