python学习之美多商城(十):用户模块--用户中心收货地址、省市区三级联动、使用缓存保存省市区、用户地址管理

收货地址:

在这里插入图片描述
在这个页面中,我们要实现用户地址的管理,主要的业务逻辑有:

  • 省市区地址的数据库建立与查询
  • 用户地址的增删改查处理
  • 设置默认地址
  • 设置地址标题

一、省市区三级联动:

在这里插入图片描述

1.三级联动:

在用户录入地址是,需要进行省市区的选择。在页面加载时,向后端请求省份数据,当用户选择确定省份后,向后端请求该省份下的城市数据;在用户选择确定城市数据后,向后端请求该城市的区县信息。我们把这个过程称为省市区三级联动。

2.数据库建表:

新建一个应用areas来实现省市区三级联动。

# /meiduo/meiduo_mall/meiduo_mall/apps
python ../../manage.py startapp area

在areas/models.py中,我们创建省市区数据表,采用自关联的方式。

# /meiduo_mall/apps/areas/models.py

from django.db import models

# Create your models here.
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

2.1自关联说明

  • 自关联字段的外键指向自身,所以ForeignKey(‘self’)
  • 需要使用related_name指明查询一个行政区划的所有下级行政区划时,使用哪种语法查询,如本模型类中指明通过Area模型类对象.subs查询所有下属行政区划,而不是使用Django默认的Area模型类对象.area_set语法。

2.2 迁移到数据库:

在配置文件中增加应用:

# meiduo_mall/settings/dev.py
...
INSTALLED_APPS = [
    ...
    'areas.apps.AreasConfig',
]
...

迁移到数据库:

python manage.py makemigrations
python manage.py migrate

3.导入数据到数据库:

迁移到数据库后,我们向数据库中添加全国省市区数据,将areas.sql导入数据库

# mysql -h数据库ip地址 -u数据库用户名 -p数据库密码 数据库 < areas.sql
mysql -h127.0.0.1 -uroot -pmysql meiduo_mall < areas.sql

4.后端实现:

4.1 请求省份数据

请求方式: GET areas/
请求参数:
**返回数据:**JSON

返回值类型是否必须说明
idint省份id
namestr省份名称
[
    {
        "id": 110000,
        "name": "北京市"
    },
    {
        "id": 120000,
        "name": "天津市"
    },
    {
        "id": 130000,
        "name": "河北省"
    },
    ...
]

业务逻辑:

  • 查询省信息
  • 结果返回

4.2 请求城市或区县的数据

请求方式: GET areas/(?P)/
请求参数: 路径传参

参数类型是否必传说明
idint父级行政区划id

返回数据: JSON

返回值类型是否必传说明
idint当前行政区划的id
namestr当前行政区划的名称
[
{"id":140100,"name":"太原市"},
{"id":140200,"name":"大同市"},
{"id":140300,"name":"阳泉市"},
...
]

业务逻辑:

  • 获取id
  • 查询市/区信息
  • 返回结果

4.3 后端代码实现:

序列化实现数据的序列化

# apps/areas/serializers.py
# -*-coding:utf-8-*-
from rest_framework import serializers
from areas.models import Areas


class AreasSerializer(serializers.ModelSerializer):
    """行政区划信息序列化器"""

    class Meta:
        model = Areas
        fields = ("id", "name")

视图类中的实现

# apps/areas/views.py

from django.shortcuts import render
# Create your views here.
from rest_framework.generics import ListAPIView
from areas.models import Areas
from areas.serializers import AreasSerializer


class ProvinceView(ListAPIView):
    """返回省份数据"""
    queryset = Areas.objects.filter(parent_id=None) # 过滤查询,返回父级为空的数据
    serializer_class = AreasSerializer


class AreasView(ListAPIView):
    """返回市、区镇数据"""
    serializer_class = AreasSerializer

    def get_queryset(self):
        """
        因为需要使用参数,所以构造一个方法
        :return:
        """
        id = self.kwargs["id"]
        return Areas.objects.filter(parent_id=id)

二、使用缓存:

省市区的数据是经常被用户查询使用的,而且数据基本不变化,所以我们可以将省市区数据进行缓存处理,减少数据库的查询次数。

在Django REST framework中使用缓存,可以通过drf-extensions扩展来实现。

关于扩展使用缓存的文档,可参考链接http://chibisov.github.io/drf-extensions/docs/#caching

1.安装:

pip install drf-extensions

2.使用方法:

2.1 直接添加装饰器:

可以在使用rest_framework_extensions.cache.decorators中的cache_response装饰器来装饰返回数据的类视图的对象方法,如:

class AreasView(ListAPIView):
    """返回市、区镇数据"""
    serializer_class = AreasSerializer
    
	@cache_response()
    def get_queryset(self):
        """
        因为需要使用参数,所以构造一个方法
        :return:
        """
        ...

cache_response装饰器可以接受两个参数:

@cache_response(timeout=60*60, cache='default')
  • timeout: 缓存时间
  • default:缓存使用Django混村后端(即CACHES配置中的键名称)

如果在使用cache_response装饰器时未指明timeout或者cache参数,则会使用配置文件中的配置,可以通过如下方法指明:

# DRF扩展
REST_FRAMEWORK_EXTENSIONS = {
    # 缓存时间
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60,
    # 缓存存储
    'DEFAULT_USE_CACHE': 'default',
}
  • DEFAULT_CACHE_RESPONSE_TIMEOUT: 缓存有效时间, 单位是秒。
  • DEFAULT_USE_CACHE: 缓存的存储方式,与配置文件中CHCHES的键对应。
    注意:cache_response装饰器既可以装饰在视图类中的get方法上,也可以装饰在REST framework扩展类提供的list或retrieve方法上。使用cache_response装饰器无需使用method_decorator进行转换。

2.2 使用drf-extensions提供的扩展类:

drf-extensions扩展对于缓存提供了三个扩展类,三个扩展类都是在rest_framework_extensions.cache.mixins中:

  • ListCacheResponseMixin
    用于混村返回列表数据的视图,与ListModelMixin扩展类配合使用,实际是为list方法添加了cache_response装饰器。
  • RetrieveCacheResponseMixin
    用于混村返回单一数据的视图,与RetrieveModelMixin扩展类配合使用,实际是为了retrieve方法添加了chche_response装饰器。
  • CahceResponseMixin
    为视图集同时补充List和Retrieve两种缓存,与ListModelMixin一起配合使用。

3. 为省市区视图添加缓存:

因为省市区视图使用了视图集,并且视图集中有提供ListModel
Mixin和RetrieveModelMixin的扩展(由ReadOnlyModelViewSet提供 ),所哟可以直接添加CacheRespponseMixin扩展类。

修改返回省市区信息的视图:

# apps/areas/views.py
from django.shortcuts import render

# Create your views here.
from rest_framework.generics import ListAPIView
from rest_framework_extensions.cache.mixins import CacheResponseMixin

from areas.models import Areas
from areas.serializers import AreasSerializer


class ProvinceView(CacheResponseMixin, ListAPIView):
    """返回省份数据"""
    queryset = Areas.objects.filter(parent_id=None) # 过滤查询,返回父级为空的数据
    serializer_class = AreasSerializer

class AreasView(CacheResponseMixin, ListAPIView):
    """返回市、区镇数据"""
    serializer_class = AreasSerializer

    def get_queryset(self):
        """
        因为需要使用参数,所以构造一个方法
        :return:
        """
        id = self.kwargs["id"]
        return Areas.objects.filter(parent_id=id)

4.缓存数据保存位置与有效期的设置:

我们想把缓存在redis中,且设置有效期,可以在配置文件中定义的方式来实现。
在配置文件中增加:

# settings/dev.py

# DRF扩展
REST_FRAMEWORK_EXTENSIONS = {
    # 缓存时间
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60,
    # 缓存存储
    'DEFAULT_USE_CACHE': 'default',
}

三、用户地址管理:

我们为保存用户的地址信息,创建数据库表,在users/models.py中定义模型类:

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']

说明:

  • Address模型类中的外键指向Areas/models里面的Area,指明外键ForeignKey时,可以使用字符串应用名.模型类名来定义
  • related_name 在进行反向关联查询时使用的属性,如 city = models.ForeignKey(‘areas.Area’, related_name=‘city_addresses’)表示可以通过Area对象.city_addresses属性获取所有相关的city数据。
  • ordering 表名在进行Address查询时,默认使用的排序方式

为User模型类添加模型地址:

class User(AbstractUser):
	...
	default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='默认地址')
    ...

四 、用户地址管理代码

1.添加和修改用户收件地址:

1.1 接口分析:

请求方式: POST /addresses/
请求参数: json

参数类型是否必传说明
titlestr地址标题(同收件人)
receiverstr收件人
province_idint省份id
city_idint城市id
district_idint区县id
placestr详细地址
mobilestr手机号
emailstr邮箱
telstr固定电话

返回数据: JSON

返回值类型是否必传说明
idint收件地址id
titlestr地址标题(同收件人)
receiverstr收件人
province_idint省份id
city_idint城市id
district_idint区县id
placestr详细地址
mobilestr手机号
emailstr邮箱
telstr固定电话

1.2 业务逻辑:

  • 获取前端数据
  • 验证数据 ==> 定义序列化器 ==> 验证手机号
  • 保存数据 ==> create()
  • 返回结果

2.获取用户收件地址:

2.1 接口分析:

请求方式: GET /addresses/
请求参数: headers传参jwt

参数类型是否必传说明

返回数据: JSON

返回值类型是否必传说明
idint收件地址id
titlestr地址标题(同收件人)
receiverstr收件人
province_idint省份id
city_idint城市id
district_idint区县id
placestr详细地址
mobilestr手机号
emailstr邮箱
telstr固定电话

数据示例:

{
	"addresses":
	[
		{
			"id":3,
			"province":"山西省",
			"city":"阳泉市",
			"district":"郊区",
			"title":"zzz",
			"receiver":"zzz",
			"place":"哈哈哈哈哈",
			"mobile":"13211111111",
			"tel":"",
			"email":"",
			"default_address":null
		},
		{
			"id":1,
			"province":"山西省",
			"city":"晋城市",
			"district":"陵川县",
			"title":"python",
			"receiver":"python",
			"place":"tttttttttttt",
			"mobile":"13211111111",
			"tel":"",
			"email":"",
			"default_address":null
		}
	]
}

2.2 业务逻辑:

  • 查询数据
  • 序列化返回数据

3. 删除收货地址:

3.1 接口分析:

请求方式: GET /addresses/
请求参数: 路径传参

参数类型是否必传说明
pkint收货地址的id

返回数据: JSON

返回值类型是否必传说明
“ok”str成功状态

3.2业务逻辑:

  • 获取前端传来的pk
  • 查询对应的地址
  • 将逻辑删除属性置为True
  • 返回成功状态

4.修改收货地址标题:

4.1 接口分析:

请求方式: PUT addresses//title/
请求参数: 路径传参和请求体传参

参数类型是否必传说明
pkint收货地址的id
titlestr新收货地址标签

返回数据: JSON

返回值类型是否必传说明
“ok”str成功状态

4.2业务逻辑:

  • 获取前端传来的pk
  • 查询对应的地址
  • 将title属性修改为
  • 返回成功状态

5.代码实现:

视图类:

# /apps/areas/views.py
# url(r"^addresses/(?P<pk>\d+)/$",views.AddressesView().as_view()),

class AddressesView(CreateAPIView, ListAPIView, DestroyAPIView, UpdateAPIView):
    """用户地址管理"""

    """序列化器实现保存和修改收货地址"""
    serializer_class = AddressSerializers
    permissions = [IsAuthenticated]

    def get_queryset(self):
        """
        重写方法,制定查询集
        :return:
        """
        return Address.objects.filter(user = self.request.user,
                                      is_deleted=False)

    def list(self,request, *args, **kwargs):
        """
        返回所有地址
        根据前端需求,重写list方法
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        queryset = self.filter_queryset(self.get_queryset())
        serializer = self.get_serializer(queryset, many=True)
        return Response({"addresses":serializer.data})

    def destroy(self, request, *args, **kwargs):
        """
        重写删除方法,实现逻辑删除
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        instance = self.get_object()
        instance.is_deleted = True
        instance.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

# url(r"^addresses/(?P<pk>\d+)/title/$", views.AddressesChangeTitleView.as_view()),
class AddressesChangeTitleView(APIView):
    """修改收货地址的标题"""
    def put(self,request,pk):
        """
        修改收货地址标题
        :param requset:
        :param pk:
        :return:
        """

        address = Address.objects.filter(id=pk)[0]
        new_title = request.data["title"]
        address.title = new_title
        address.save()

        return Response("ok")

序列化器:

# /apps/areas/serializers.py

class AddressSerializers(serializers.ModelSerializer):
    """收货地址序列化器"""
    """显示指名地址的id"""
    province_id = serializers.IntegerField(label='省ID', required=True,write_only= True)
    city_id = serializers.IntegerField(label='市ID', required=True,write_only= True)
    district_id = serializers.IntegerField(label='区ID', required=True,write_only= True)

    """序列化输出"""
    province = serializers.StringRelatedField(read_only=True)
    city = serializers.StringRelatedField(read_only=True)
    district = serializers.StringRelatedField(read_only=True)


    class Meta:
        model = Address
        exclude = ('user',)

    def validated_mobile(self, value):
        """
        验证手机号格式
        :param value:
        :return:
        """
        if not re.match(r'1[3-9]\d{9}$', value):
            raise serializers.ValidationError("手机号格式错误")
        return value


    def create(self, validated_data):
        """
        重写父类方法
        需要在验证后的数据添加user
        :param validated_data:
        :return:
        """
        user = self.context['request'].user
        validated_data['user'] = user
        address = super().create(validated_data)
        return address
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

浅弋、璃鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值