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

22 篇文章 19 订阅
19 篇文章 0 订阅

开源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]\d{9}$', 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': 'ok',
            'default_address_id': user.default_address_id,
            'addresses': address_list
        })

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

修改地址前后端逻辑

1. 修改地址接口设计和定义

1.请求方式

选项方案
请求方法delete、PUT
请求地址/addresses/(?P<address_id>\d+)/

2.请求参数:路径参数 和 JSON

参数名类型是否必传说明
address_idstring要修改的地址ID(路径参数)
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邮箱

2. 修改地址后端逻辑实现

提示

  • 删除地址后端逻辑和新增地址后端逻辑非常的相似。
  • 都是更新用户地址模型类,需要保存用户地址信息。
# 总结:相同的请求路径+不同的请求方法 = 统一类视图中
#re_path(r'^addresses/(?P<address_id>\d+)/$', UpdateDestroyAddressView.as_view()),
class UpdateDestroyAddressView(View):

    # 删除地址
    def delete(self, request, address_id):
        # 1、根据路径中的地址主键,获取地址对象
        try:
            address = Address.objects.get(pk=address_id)
        except Address.DoesNotExist as e:
            print(e)
            return JsonResponse({'code': 400, 'errmsg': '地址不存在'}, status=404)

        # 2、通过对象删除(真删除,逻辑删除)
        # 真删除: address.delete()
        # 逻辑删除
        address.is_deleted = True
        address.save()

        # 3、构建响应
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok'
        })


    # 更新地址接口
    def put(self, request, address_id):

        # 1、获取被更新的地址
        try:
            address = Address.objects.get(pk=address_id)
        except Address.DoesNotExist as e:
            print(e)
            return JsonResponse({'code': 400, 'errmsg': '资源未找到!'})

        # 2、提取参数
        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')

        # 3、校验参数
        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]\d{9}$', 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有误'})

		# 构造数据存储
        address.receiver = receiver
        address.province_id = province_id
        address.city_id = city_id
        address.district_id = district_id
        address.place = place
        address.mobile = mobile
        address.tel = tel
        address.email = email
        address.save()
		# 构造返回前端数据
        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
        }

        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'address': address_info
        })

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

设置默认地址

1. 设置默认地址接口设计和定义

1.请求方式

选项方案
请求方法PUT
请求地址/addresses/(?P<address_id>\d+)/default/

2.请求参数:路径参数

参数名类型是否必传说明
address_idstring要修改的地址ID(路径参数)

3.响应结果:JSON

字段说明
code状态码
errmsg错误信息

2. 设置默认地址后端逻辑实现

# 设置默认地址 re_path(r'^addresses/(?P<address_id>\d+)/default/$', DefaultAddressView.as_view()),
class DefaultAddressView(View):

    def put(self, request, address_id):
        # 修改当前登陆用户对象的default_address指向address_id的地址
        user = request.user

        # default_address是ForeignKey类型,是Address对象
        # user.default_address = <Address对象>
        # user.default_address = Address.objects.get(pk=address_id)

        # user.default_address_id = <Address对象的主键>
        user.default_address_id = address_id

        user.save()

        return JsonResponse({
            'code': 0,
            'errmsg': 'ok'
        })

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

修改地址标题

1. 修改地址标题接口设计和定义

1.请求方式

选项方案
请求方法PUT
请求地址/addresses/(?P<address_id>\d+)/title/

2.请求参数:路径参数

参数名类型是否必传说明
address_idstring要修改的地址ID(路径参数)

3.响应结果:JSON

字段说明
code状态码
errmsg错误信息

2. 修改地址标题后端逻辑实现

# 修改地址标题 re_path(r'^addresses/(?P<address_id>\d+)/title/$', UpdateTitleAddressView.as_view()),
class UpdateTitleAddressView(View):

    def put(self, request, address_id):
        # 1、获取更新数据
        data = json.loads(request.body.decode())
        title = data.get('title')

        # 2、获取被修改的地址对象
        address = Address.objects.get(pk=address_id)

        # 3、修改并返回响应
        address.title = title
        address.save()

        return JsonResponse({'code': 0, 'errmsg': 'ok'})

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

修改密码

1. 修改密码后端逻辑

提示:

  • 修改密码前需要校验原始密码是否正确,以校验修改密码的用户身份。
  • 如果原始密码正确,再将新的密码赋值给用户。
# 修改用户密码 re_path(r'^password/$', ChangePasswordView.as_view()),
class ChangePasswordView(View):

    def put(self, request):
        # 1、提取参数
        data = json.loads(request.body.decode())
        old_password = data.get('old_password')
        new_password = data.get('new_password')
        new_password2 = data.get('new_password2')

        # 2、校验参数
        if not all([old_password, new_password, new_password2]):
            return JsonResponse({'code':400, 'errmsg': '参数缺失'})

        # 新密码格式校验
        if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
            return JsonResponse({'code': 400,
                             'errmsg': '密码最少8位,最长20位'})
        # 两次输入是否一致校验
        if new_password != new_password2:
            return JsonResponse({'code': 400,
                             'errmsg': '两次输入密码不一致'})

        # 旧密码校验
        # User.set_password()
        # User.check_password()
        user = request.user
        if not user.check_password(old_password):
            return JsonResponse({'code': 400, 'errmsg': '旧密码有误!'}, status=400)


        # 3、更新数据
        user.set_password(new_password)
        user.save()

        # 补充逻辑:清楚登陆状态
        logout(request)

        # 4、返回响应
        response = JsonResponse({'code': 0, 'errmsg': 'ok'})
        response.delete_cookie('username')
        return response

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

补全users.urls.py中的urlpatterns:

# 新增收货地址
    re_path(r'^addresses/create/$', CreateAddressView.as_view()),
    # 展示地址
    re_path(r'^addresses/$', AddressView.as_view()),
    # 修改地址
    re_path(r'^addresses/(?P<address_id>\d+)/$', UpdateDestroyAddressView.as_view()),
    # 修改默认地址
    re_path(r'^addresses/(?P<address_id>\d+)/default/$', DefaultAddressView.as_view()),
    # 修改标题
    re_path(r'^addresses/(?P<address_id>\d+)/title/$', UpdateTitleAddressView.as_view()),
    # 修改密码
    re_path(r'^password/$', ChangePasswordView.as_view()),

替换user_center_site.js(其中有2个链接接口不对,一个参数,后端未做处理)

========================
在这里插入图片描述

祝大家学习python顺利!

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主打Python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值