开源web框架django知识总结(十三)

Posted 主打Python

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开源web框架django知识总结(十三)相关的知识,希望对你有一定的参考价值。

开源web框架django知识总结(十三)

省市区三级联动

展示收货地址界面

提示:

  • 省市区数据是在收货地址界面展示的,所以我们先渲染出收货地址界面。
  • 收货地址界面中基础的交互已经提前实现。

1.新建app areas,新建子urls.py,同步,注册areas,

python ../../manage.py startapp areas

  1. 准备省市区模型和数据 areas.models.py
class Area(models.Model):
    """
    行政区划
    """
    # 创建 name 字段, 用户保存名称
    name = models.CharField(max_length=20,
                            verbose_name='名称')
    # 自关联字段 parent
    # 第一个参数是 self : parent关联自己.
    # on_delete=models.SET_NULL:  如果省删掉了,省内其他的信息为 NULL
    # related_name='subs': 设置之后
    # 我们就这样调用获取市: area.area_set.all() ==> area.subs.all()
    parent = models.ForeignKey('self',
                               on_delete=models.SET_NULL,
                               related_name='subs',
                               null=True,  #表示数据库创建时该字段可不填,用NULL填充
                               blank=True, # 表示代码中创建数据库记录时该字段可传空白(空串,空字符串).
                               verbose_name='上级行政区划')

    class Meta:
        db_table = 'tb_areas'
        verbose_name = '行政区划'
        verbose_name_plural = '行政区划'

    def __str__(self):
        return self.name

迁移模型类

python manage.py makemigrations
python manage.py migrate

模型说明:

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

  • 反向查询:没有外键属性一方,可以调用反向属性查询到关联的另一方。

    反向关联属性为“实例对象.引用类名(小写)”,使用

related_name

指明父级查询子级数据的语法

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

导入省市区数据:在项目根目录下建立scripts文件夹,将数据库文件areas.sql拷贝进来,在xshell进入到目录中执行下面语句。

# mysql -u数据库用户名 -p数据库密码  -D 数据库 < areas.sql  #注意要在数据库文件所在的目录内执行
mysql -usuifeng -p123456  -D aerf_mall < areas.sql

注意:如果出现错误信息:

ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)

mysql.service failed because the control process exited with error code问题

应该配置为bind-address=0.0.0.0,并且这行应该加在/etc/mysql/mysql.conf.d/mysqld.cnf 配置文件里.

也可以重启一下虚拟机试试,或者用下面的方法:

# mysql -h127.0.0.1 -u数据库用户名 -p数据库密码  -D 数据库 < areas.sql  #注意要在数据库文件所在的目录
mysql -h127.0.0.1 -usuifeng -p123456  -D aerf_mall < areas.sql

3. 查询省市区数据

1.请求方式

选项方案
请求方法GET
请求地址/areas/

2.请求参数:查询参数

  • 如果前端没有传入area_id,表示用户需要省份数据
  • 如果前端传入了area_id,表示用户需要市或区数据
参数名类型是否必传说明
area_idstring地区ID

3.响应结果:JSON

  • 省份数据

  "code":"0",
  "errmsg":"OK",
  "province_list":[
      
          "id":110000,
          "name":"北京市"
      ,
      
          "id":120000,
          "name":"天津市"
      ,
      
          "id":130000,
          "name":"河北省"
      ,
      ......
  ]

市或区数据


  "code":"0",
  "errmsg":"OK",
  "sub_data":
      "id":130000,
      "name":"河北省",
      "subs":[
          
              "id":130100,
              "name":"石家庄市"
          ,
          ......
      ]
  

4.查询省市区数据后端逻辑实现

  • 如果前端没有传入area_id,表示用户需要省份数据
  • 如果前端传入了area_id,表示用户需要市或区数据

获取可选省份信息、获取可选市区信息 areas.views.py

from django.views import View
from django.http import JsonResponse
from .models import Area
from django.core.cache import cache
# Create your views here.


# 获取可选省份信息
class ProvinceAreasView(View):

    def get(self, request):

        # 优先判断缓存中有没有数据
        p_list = cache.get('province_list')

        if not p_list:
            # 把省信息按照格式返回
            # 1、读取模型类查询集
            provinces = Area.objects.filter(
                parent=None
            )

            # 2、把所有的模型类对象,转化成字典id, name,json不认模型对象
            p_list = []
            for province in provinces:
                # province: 是省模型类对象
                p_list.append(
                    'id': province.id,
                    'name': province.name
                )

            # 读取mysql省数据之后,写入缓存
            # cache模块写入缓存是key-value形式
            cache.set('province_list', p_list, 3600)

        # 3、构建响应返回
        return JsonResponse(
            'code': 0,
            'errmsg': 'ok',
            'province_list': p_list
        )


# 获取可选市区信息
class SubAreasView(View):

    def get(self, request, pk):
        # 路径中传入的pk
        # 1、pk是省的主键,请求所有市信息
        # 2、pk是市的主键,请求所有区信息

        sub_data = cache.get('sub_area_%s'%pk)

        if not sub_data:
            # 当前pk过滤出的父级行政区对象
            p_area = Area.objects.get(
                pk=pk
            )

            # 当前父级行政区对象关联的多个子级行政区
            subs = Area.objects.filter(
                parent_id=pk
            )

            sub_list = []
            for sub in subs:
                # sub是子级行政区对象
                sub_list.append(
                    'id': sub.id,
                    'name': sub.name
                )

            sub_data = 
                    'id': p_area.id,
                    'name': p_area.name,
                    'subs': sub_list
            

            cache.set('sub_area_%s'%pk, sub_data, 3600)

        return JsonResponse(
            'code': 0,
            'errmsg': 'ok',
            'sub_data': sub_data
        )

5. areas.urls.py

from django.urls import re_path
from . import views

urlpatterns = [
    re_path(r'^areas/$', views.ProvinceAreasView.as_view()),
    re_path(r'^areas/(?P<pk>[1-9]\\d+)/$', views.SubAreasView.as_view()),
]

收货地址

用户地址的主要业务逻辑有:

  1. 展示省市区数据
  2. 用户地址的增删改查处理
  3. 设置默认地址
  4. 设置地址标题

==============================

新增地址前后端逻辑

1. 定义用户地址模型类 user.models.py

1.用户地址模型类

from aerf_mall.utils.BaseModel import BaseModel
class Address(BaseModel):
    """
    用户地址
    """
    user = models.ForeignKey(User,
                             on_delete=models.CASCADE,
                             related_name='addresses',
                             verbose_name='用户')

    province = models.ForeignKey('areas.Area',
                                 on_delete=models.PROTECT,
                                 related_name='province_addresses',
                                 verbose_name='省')

    city = models.ForeignKey('areas.Area',
                             on_delete=models.PROTECT,
                             related_name='city_addresses',
                             verbose_name='市')

    district = models.ForeignKey('areas.Area',
                                 on_delete=models.PROTECT,
                                 related_name='district_addresses',
                                 verbose_name='区')

    title = models.CharField(max_length=20, verbose_name='地址名称')
    receiver = models.CharField(max_length=20, verbose_name='收货人')
    place = models.CharField(max_length=50, verbose_name='地址')
    mobile = models.CharField(max_length=11, verbose_name='手机')
    tel = models.CharField(max_length=20,
                           null=True,
                           blank=True,
                           default='',
                           verbose_name='固定电话')

    email = models.CharField(max_length=30,
                             null=True,
                             blank=True,
                             default='',
                             verbose_name='电子邮箱')

    is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')

    class Meta:
        db_table = 'tb_addresses'
        verbose_name = '用户地址'
        verbose_name_plural = verbose_name

        # 定义默认查询集排序方式
        ordering = ['-update_time']

注释1:Django中related_name作用

2.Address模型类说明

  • Address模型类中的外键指向areas/models里面的Area。指明外键时,可以使用应用名.模型类名来定义。

  • ordering
    

    表示在进行排序展示

    Address
    

    查询时,默认使用的排序方式。

    • ordering = ['-update_time'] : 根据更新的时间倒叙。

3.补充用户模型默认地址字段

class User(AbstractUser):
    """自定义用户模型类"""
    mobile = models.CharField(
        unique=True,
        verbose_name='手机号',
        null=True,
        max_length=11
    )

    # 新增 email_active 字段
    # 用于记录邮箱是否激活, 默认为 False: 未激活
    email_active = models.BooleanField(default=False,verbose_name='邮箱验证状态')

    # 外间关联字段,表示当前用户,采用的默认的收货地址是哪个
    default_address = models.ForeignKey('Address',
                                        related_name='users',
                                        null=True,
                                        blank=True,
                                        on_delete=models.SET_NULL,
                                        verbose_name='默认地址')


    class Meta:
        db_table = 'tb_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username

执行数据迁移:

python manage.py makemigrations
python manage.py migrate

2. 新增地址接口设计和定义

1.请求方式

选项方案
请求方法POST
请求地址/addresses/create/

2.请求参数:JSON

参数名类型是否必传说明
receiverstring收货人
province_idstring省份ID
city_idstring城市ID
district_idstring区县ID
placestring收货地址
mobilestring手机号
telstring固定电话
emailstring邮箱

3.响应结果:JSON

字段说明
code状态码
errmsg错误信息
id地址ID
receiver收货人
province省份名称
city城市名称
district区县名称
place收货地址
mobile手机号
tel固定电话
email邮箱

3. 新增地址后端逻辑实现 users.views.py

提示:

  • 用户地址数量有上限,最多20个,超过地址数量上限就返回错误信息
from .models import Address
# 新增用户地址 re_path(r'^addresses/create/$', CreateAddressView.as_view()),
class CreateAddressView(View):

    def post(self, request):
        # 1、提取参数
        data = json.loads(request.body.decode())
        receiver = data.get('receiver')
        province_id = data.get('province_id')
        city_id = data.get('city_id')
        district_id = data.get('district_id')
        place = data.get('place') # 详细地址
        mobile = data.get('mobile')
        tel = data.get('tel')
        email = data.get('email')

        # 判断用户地址数量是否超过10个
        user = request.user
        count = Address.objects.filter(user=user).count()
        if count >= 10:
            return JsonResponse('code': 400, 'errmsg': '数量超限')

        # 2、校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return JsonResponse("code": 400, 'errmsg': '缺少参数!')

        if not re.match(r'^1[3-9]\\d9$', mobile):
            return JsonResponse('code': 400,
                                 'errmsg': '参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]2,3-)?([2-9][0-9]6,7)+(-[0-9]1,4)?$', tel):
                return JsonResponse('code': 400,
                                     'errmsg': '参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\\w\\.\\-]*@[a-z0-9\\-]+(\\.[a-z]2,5)1,2$', email):
                return JsonResponse('code': 400,
                                     'errmsg': '参数email有误')

        # 3、新建用户地址
        try:
            address = Address.objects.create(
                user=user,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                title=receiver, # 当前地址的标题,默认收货人名称就作为地址标题
                receiver=receiver,
                place=place,
                mobile=mobile,
                tel=tel
            )

            # 如果当前新增地址的时候,用户没有设置默认地址,那么
            # 我们把当前新增的地址设置为用户的默认地址
            if not user.default_address:
                user.default_address = address
                user.save()

        except Exception as e:
            print(e)
            return JsonResponse('code': 400, 'errmsg': '新增地址失败!')

        address_info = 
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,

            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,

            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        

        # 4、返回响应
        return JsonResponse(
            'code': 0,
            'errmsg': 'ok',
            'address': address_info
        )

注意:循环调用问题。

===============================

展示地址前后端逻辑

1. 展示地址接口设计和定义

1.请求方式

选项方案
请求方法GET
请求地址/addresses/

2.请求参数

2. 展示地址后端逻辑实现

# 网页地址展示接口 re_path(r'^addresses/$', AddressView.as_view()),
# 本质:把当前用户所有地址信息返回
class AddressView(View):

    def get(self, request):
        # 1、根据用户,过滤出当前用户的所有地址
        user = request.user
        addresses = Address.objects.filter(
            user=user,
            is_deleted=False # 没有逻辑删除的地址
        )

        # 2、把地址转化成字典
        address_list = []
        for address in addresses:
            if address.id != user.default_address_id:
                # address:每一个地址对象
                address_list.append(
                    'id': address.id,
                    'title': address.title,
                    'receiver': address.receiver,
                    'province': address.province.name,
                    'city': address.city.name,
                    'district': address.district.name,
                    'place': address.place,
                    'mobile': address.mobile,
                    'tel': address.tel,
                    'email': address.email
                )
            else:
                address_list.insert(0, 
                    'id': address.id,
                    'title': address.title,
                    'receiver': address.receiver,
                    'province': address.province.name,
                    'city': address.city.name,
                    'district': address.district.name,
                    'place': address.place,
                    'mobile': address.mobile,
                    'tel': address.tel,
                    'email': address.email
                )

        # 3、构建响应返回
        return JsonResponse(
            'code': 0,
            'errmsg'以上是关于开源web框架django知识总结(十三)的主要内容,如果未能解决你的问题,请参考以下文章

开源web框架django知识总结

开源web框架django知识总结

开源web框架django知识总结

开源web框架django知识总结

开源web框架django知识总结(十八)

开源web框架django知识总结(十八)