python 全栈开发,Day101(redis操作,购物车,DRF解析器)

昨日内容回顾

1. django请求生命周期?
    - 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端
        请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post,体现在url之中.

    - url经过Django中的wsgi,再经过Django的中间件,最后url到过路由映射表,在路由中一条一条进行匹配,
        一旦其中一条匹配成功就执行对应的视图函数,后面的路由就不再继续匹配了.
    - 视图函数根据客户端的请求查询相应的数据.返回给Django,然后Django把客户端想要的数据做为一个字符串返回给客户端.
    - 客户端浏览器接收到返回的数据,经过渲染后显示给用户.

1. django请求生命周期?
    - 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端
    请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post,体现在url之中.

    - url经过Django中的wsgi,再经过Django的中间件,最后url到过路由映射表,在路由中一条一条进行匹配,
        一旦其中一条匹配成功就执行对应的视图函数,后面的路由就不再继续匹配了.
    - 视图函数根据客户端的请求查询相应的数据.返回给Django,然后Django把客户端想要的数据做为一个字符串返回给客户端.
    - 客户端浏览器接收到返回的数据,经过渲染后显示给用户.
    
2. django提供的功能 
    - 必备
        - 路由 
        - 视图
        - 模板渲染
    - django:
        - ORM:
            ...
            ...
        - 分页 
        - Form & ModelForm
        - admin 
        - auth
        - session 
        - 中间件 
        - contenttype
        - csrf
        - 缓存(速度块)
        
3. restful 
    - restful 规范 
    - django rest framwork 
    - 其他
        - 跨域
            a. 为什么出现跨域?
            b. 如何解决跨域?
                使用cors,即:设置响应头。
                简单请求:
                    响应头中设置一个允许域名访问
                复杂请求:
                    OPTIONS请求做预检,允许特殊请求方式和请求头 + 允许域名访问。
                    真正请求就可以发送过来进行处理 + 允许域名访问。
            c. 跨域 
                www.baidu.com         / www.luffycity.com 
                www.baidu.com         / api.luffycity.com 
                www.baidu.com:8001    / www.baidu.com:8002 
            
            d. 路飞线上代码无跨域(项目部署时,放在同一处)
    
        - vue.js 
            - 前端三大框架:react.js /angular.js / vue.js 
            - vue.js 2版本
            - 组件:
                - axios
                - vuex 
                - router
                
            - 你觉得vue和jQuery的区别?
                - 双向绑定(数据变动,页面也随之更改)
                - 单页面应用(切换页面,页面不刷新)
View Code

一、redis使用

redis介绍

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它的数据,存在内存中,读写速度快!也可以做持久化。

redis安装

使用centos系统安装

yum install -y redis

redis使用

注意:redis是安装在linux系统里面的,但是python程序是运行在windows系统中的。所以需要进行远程连接!

但是,redis默认使用127.0.0.1连接,端口为6379

修改配置

编辑配置文件

vim /etc/redis.conf

修改IP,关闭保护模式(否则无法远程操作redis)

bind 192.168.218.133
protected-mode no

启动redis

注意:必须指定配置文件

redis-server /etc/redis.conf

注意,此时终端不会有输出,再开一个窗口,查看端口

netstat -anpt

信息如下:

tcp        0      0 192.168.218.133:6379    0.0.0.0:*               LISTEN      3736/redis-server 1
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      995/sshd
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      2275/master
tcp        0      0 192.168.218.133:22      192.168.218.1:59646     ESTABLISHED 2575/sshd: root@not
tcp        0      0 192.168.218.133:22      192.168.218.1:58928     ESTABLISHED 2500/sshd: root@pts
tcp        0      0 192.168.218.133:22      192.168.218.1:55251     ESTABLISHED 3739/sshd: root@pts
tcp6       0      0 :::3306                 :::*                    LISTEN      2220/mysqld
tcp6       0      0 :::22                   :::*                    LISTEN      995/sshd
tcp6       0      0 ::1:25                  :::*                    LISTEN      2275/master
View Code

第一个就是redis,端口为6379

注意要关闭防火墙

/etc/init.d/iptables stop

redis相当于是一个在内存中的创建的大字典
redis的value有5大数据类型:字符串,哈希,列表,集合,有序集合

字符串(String)

Redis 字符串数据类型的相关命令用于管理 redis 字符串值,基本语法如下:

COMMAND KEY_NAME

set()

get(name)

分别表示设置和获取

举例:

写一个字符串,并获取

import redis

conn = redis.Redis(host='192.168.218.133',port='6379')
conn.set('name','xiao')  # 写入字符串
val = conn.get('name')  # 获取字符串
print(val)
View Code

执行程序,输出如下:

b'xiao'
xiao

注意:它的返回结果是bytes,那么使用decode('utf-8')解码之后,就会变成字符串

哈希(Hash)

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)

redis中的Hash 在内存中类似于一个name对应一个dic来存储 

hset(name, key, value)

name对应的hash中设置一个键值对(不存在,则创建,否则,修改)

hget(name,key)

在name对应的hash中根据key获取value

举例:

import redis

conn = redis.Redis(host='192.168.218.133',port='6379')
conn.hset("dic_name","a1","aa")  # 写入字典
val = conn.hget("dic_name","a1")  # 获取key为a1的值
print(val)
print(val.decode('utf-8'))  # 解码
View Code

执行输出:

b'aa'
aa

hgetall(name)

获取name对应hash的所有键值

举例:

import redis

conn = redis.Redis(host='192.168.218.133',port='6379')
val = conn.hgetall("dic_name")  # 获取dic_name的所有值
print(val)
View Code

执行输出:

{b'a1': b'aa'}

hmset(name, mapping)

在name对应的hash中批量设置键值对,mapping:字典

hmget(name, keys, *args)

在name对应的hash中获取多个key的值

举例:

import redis

conn = redis.Redis(host='192.168.218.133',port='6379')
dic={"a1":"aa","b1":"bb"}  # 定义一个字典
conn.hmset("dic_name",dic)  # 批量设置键值对
val_1 = conn.hget("dic_name","b1")  # 获取key为b1的值
val_2 = conn.hmget("dic_name","a1","b1")  # 获取多个值
print(val_1)
print(val_1.decode('utf-8'))  # 解码

print(val_2)
View Code

执行输出:

b'bb'
bb
[b'aa', b'bb']

列表(List)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

redis中的List在在内存中按照一个name对应一个List来存储 

lpush(name,values)

llen(name)

lindex(name, index)

举例:

import redis

conn = redis.Redis(host='192.168.142.129',port='6379')
conn.lpush("list_name",2)# 在list_name中增加一个值2

print(conn.llen("list_name"))  # 获取列表元素的个数
val = conn.lindex("list_name",0)  #根据索引获取列表内元素
print(val)
View Code

执行输出:

1
b'2'

集合(Set)

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

sadd(name,values)

smembers(name)

scard(name)

举例

import redis

conn = redis.Redis(host='192.168.142.129',port='6379')
conn.sadd("set_name","aa") # 在集合set_name中增加元素
conn.sadd("set_name","aa","bb")

print(conn.smembers("set_name"))  # 获取set_name集合的所有成员
val = conn.scard("set_name")  #获取set_name集合中的元素个数
print(val)
View Code

执行输出:

{b'bb', b'aa'}
2

有序集合(sorted set)

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

zadd(name, *args, **kwargs)

zcard(name)

zcount(name, min, max)

举例:

import redis

conn = redis.Redis(host='192.168.142.129',port='6379')
conn.zadd("zset_name", "a1", 6, "a2", 2,"a3",5) # 在有序集合zset_name中增加元素
# 或者使用下面的方式,效果同上!
# conn.zadd('zset_name1', b1=10, b2=5)

print(conn.zcard("zset_name"))  # 获取有序集合内元素的数量
val = conn.zcount("zset_name",1,5)  #获取有序集合中分数在[min,max]之间的个数
print(val)
View Code

执行输出:

3
2

总结:

a. 五大数据类型:
    字符串,哈希,列表,集合,有序集合

b. 列举每种数据类型的操作
字符串:
    set 
    get 
    
字典:
    hget
    hgetall
    hset
    hmset 
    hdel 

其他:
    delete 
    expire
    keys 
    flushall()
View Code

更多redis操作,请参考以下文章

http://www.runoob.com/redis/redis-lists.html

http://www.cnblogs.com/melonjiang/p/5342505.html

二、购物车

下载代码:

https://github.com/987334176/luffycity/archive/v1.3.zip

下载数据库使用(务必下载,上面的压缩包数据库是空的!!!)

https://github.com/987334176/luffycity/blob/master/db.sqlite3

进入api目录,务必删除views.py,它已经没有用了

先来看一个购物车步骤

1. 接受用户选中的课程ID和价格策略ID
2. 判断合法性
    - 课程是否存在?
    - 价格策略是否合法?
3. 把商品和价格策略信息放入购物车 SHOPPING_CAR

修改api_urls.py

from django.conf.urls import url
from api.views import course,degreecourse,auth,shoppingcart

urlpatterns = [
    url(r'auth/$', auth.AuthView.as_view({'post':'login'})),
    url(r'courses/$',course.CoursesView.as_view()),
    url(r'courses/(?P<pk>d+)/$',course.CourseDetailView.as_view()),

    url(r'shoppingcart/$', shoppingcart.ShoppingCartView.as_view({'get':'list','post':'create'})),
]
View Code

修改views目录下的shoppingcart.py

from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models

class ShoppingCartView(ViewSetMixin,APIView):

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        return Response('ok')

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        print('要加入购物车了')
        print(request.body,type(request.body))
        print(request.data, type(request.data))

        return Response({'code':1000})
View Code

使用postman发送json数据

查看返回信息

查看Pycharm控制台输出:

要加入购物车了
b'{"courseid":"1","policyid":"2"}' <class 'bytes'>
{'courseid': '1', 'policyid': '2'} <class 'dict'>

可以发现body的数据是bytes类型的。那么request.data的数据,怎么就成字典了呢?

假设抛开request.data。使用request.body的数据,解析成字典。需要经历2个步骤:

1.将数据使用decode('utf-8'),进行解码得到字符串

2.将字符串使用json.load('value'),反序列化成字典。

那么rest framework就自动帮你做了这件事情!详情看下面的内容。

三、DRF解析器

1.Parser对象

REST框架提供了一系列的内建Parser对象来对不同的媒体类型进行解析,也支持为API接口灵活的自定义Parser

如何选择合适的Parser

通常为一个viewset定义一个用于解析的Parser对象列表 
当接收到request.data时,REST框架首先检查请求头的Content-Type字段,然后决定使用哪种解析器来处理请求内容

注意: 
当你编写客户端应用程序时,发送HTTP请求时,一定要在请求头中设置Content-Type。 
如果你没有设置这个属性,大多数客户端默认使用’application/x-www-form-urlencoded’,但这有时并不是你想要的。 
例如当你用jQuery的ajax方法发送一个json编码的数据时,应该确保包含contentType: ‘application/json’设置。

设置默认的解析器

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
    )
}

也可以为基于APIView的单个视图类或者视图集合设置自己的Parser

from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    """
    一个能处理post提交的json数据的视图类
    """
    parser_classes = (JSONParser,)

    def post(self, request, format=None):
        return Response({'received data': request.data})
View Code

使用装饰器的视图函数:

from rest_framework.decorators import api_view
from rest_framework.decorators import parser_classes

# 注意装饰器顺序
@api_view(['POST'])
@parser_classes((JSONParser,))
def example_view(request, format=None):
    """
    A view that can accept POST requests with JSON content.
    """
    return Response({'received data': request.data})
View Code

举例:

修改views目录下的shoppingcart.py,使用解析器

JSONParser对应的数据类型为application/json

FormParser对应的数据类型为application/x-www-form-urlencoded

from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models
from rest_framework.parsers import JSONParser,FormParser

class ShoppingCartView(ViewSetMixin,APIView):
    parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        return Response('ok')

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        print('要加入购物车了')
        # print(request.body,type(request.body))
        print(request._request, type(request._request))  # 原生django的request
        print(request._request.body)  # 获取body
        print(request._request.POST)  # 获取post
        print(request.data, type(request.data))  # 封装后的数据

        return Response({'code':1000})
View Code

使用postman再次发送,查看Pycharm控制台输出:

<WSGIRequest: POST '/api/v1/shoppingcart/'> <class 'django.core.handlers.wsgi.WSGIRequest'>
b'{"courseid":"1","policyid":"2"}'
<QueryDict: {}>
{'policyid': '2', 'courseid': '1'} <class 'dict'>

从上面的信息中,可以看出。原生的django通过body可以获取数据,但是post的数据是空的。因为客户端的请求数据类型不是

application/x-www-form-urlencoded

而经过rest framework封装之后,可以从data中获取数据,并解析成字典了!

查看APIView源码

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
View Code

看这一句,它默认会从settings.py中查找解析器

parser_classes = api_settings.DEFAULT_PARSER_CLASSES

如果需要指定默认的解析器,修改settings.py

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
    'VERSION_PARAM':'version',
    'DEFAULT_VERSION':'v1',
    'ALLOWED_VERSIONS':['v1','v2'],
    'PAGE_SIZE':20,
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
    )
}
View Code

修改views目录下的shoppingcart.py,注释掉解析器

from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models
# from rest_framework.parsers import JSONParser,FormParser

class ShoppingCartView(ViewSetMixin,APIView):
    # parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        return Response('ok')

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        print('要加入购物车了')
        # print(request.body,type(request.body))
        print(request._request, type(request._request))  # 原生django的request
        print(request._request.body)  # 获取body
        print(request._request.POST)  # 获取post
        print(request.data, type(request.data))  # 封装后的数据

        return Response({'code':1000})
View Code

使用postman再次发送,效果同上!

关于DRF解析器的源码解析,请参考文章

http://www.cnblogs.com/derek1184405959/p/8724455.html

注意:一般在前后端分离的架构中,前端约定俗成发送json数据,后端接收并解析数据!

解析器到这里就结束了,下面继续讲购物车

判断课程id是否合法

修改views目录下的shoppingcart.py,修改post方法

import redis
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models

CONN = redis.Redis(host='192.168.142.129',port=6379)

class ShoppingCartView(ViewSetMixin,APIView):
    # parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        return Response('ok')

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        course_id = request.data.get('courseid')
        policy_id = request.data.get('policyid')

        if course_id.isdigit():  # 判断是否为数字
            policy_id = int(policy_id)
        else:
            return Response({'code': 10001, 'error': '课程非法'})

        # 2. 判断合法性
        #   - 课程是否存在?
        #   - 价格策略是否合法?

        # 2.1 课程是否存在?
        course = models.Course.objects.filter(id=course_id).first()
        if not course:
            return Response({'code': 10001, 'error': '课程不存在'})

        return Response({'code':1000})
View Code

使用postman发送json数据

查看返回信息

数据放入redis

为什么要将购物车数据,放到redis中呢?

因为购物车的操作比较频繁,它是一个临时数据。用户付款后,数据就删除了。

如果使用数据库,速度太慢,影响用户体验!

购物车数据结构

shopping_car_用户id_课程id:{
    id:课程ID
    name:课程名称
    img:课程图片
    defaut:默认选中的价格策略
    # 所有价格策略
    price_list:[
        {'策略id':'价格'},
        {'策略id':'价格'},
        ...
    ]
},

为什么要这么设计呢?

其中我们可以使用3层字典嵌套,来展示用户-->课程id-->价格策略

但是redis不支持字典嵌套,所以这样设计,是为了减少字典嵌套。注意:所有价格策略,存的是json数据

判断价格策略id是否合法

修改views目录下的shoppingcart.py,修改post方法

import redis
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models

CONN = redis.Redis(host='192.168.142.129',port=6379)

class ShoppingCartView(ViewSetMixin,APIView):
    # parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        return Response('ok')

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        course_id = request.data.get('courseid')
        policy_id = request.data.get('policyid')

        if course_id.isdigit():  # 判断是否为数字
            policy_id = int(policy_id)
        else:
            return Response({'code': 10001, 'error': '课程非法'})

        # 2. 判断合法性
        #   - 课程是否存在?
        #   - 价格策略是否合法?

        # 2.1 课程是否存在?
        course = models.Course.objects.filter(id=course_id).first()
        if not course:
            return Response({'code': 10001, 'error': '课程不存在'})

        # 2.2 价格策略是否合法?
        # 查看当前课程所有价格策略
        price_policy_queryset = course.price_policy.all()
        price_policy_dict = {}  # 空字典
        for item in price_policy_queryset:
            temp = {
                'id': item.id,  # 价格策略id
                'price': item.price,  # 价格
                'valid_period': item.valid_period,  # 有效期
                'valid_period_display': item.get_valid_period_display()  # 有效期中文
            }
            price_policy_dict[item.id] = temp  # 循环加入到空字典中

        # policy_id类型必须为数字,否则即使存在,这里也会提示价格策略不存在
        if policy_id not in price_policy_dict:  # 判断价格策略是否存在
            return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'})

        return Response({'code':1000})
View Code

使用postman再次发送,效果同上!

发送一个不存在的价格策略id

查看返回值

商品信息存入redis

修改views目录下的shoppingcart.py

import redis
import json
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models

CONN = redis.Redis(host='192.168.142.129',port=6379)
USER_ID = 1 # 固定用户id

class ShoppingCartView(ViewSetMixin,APIView):
    # parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        return Response('ok')

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        course_id = request.data.get('courseid')
        policy_id = request.data.get('policyid')

        if course_id.isdigit():  # 判断是否为数字
            policy_id = int(policy_id)
        else:
            return Response({'code': 10001, 'error': '课程非法'})

        # 2. 判断合法性
        #   - 课程是否存在?
        #   - 价格策略是否合法?

        # 2.1 课程是否存在?
        course = models.Course.objects.filter(id=course_id).first()
        if not course:
            return Response({'code': 10001, 'error': '课程不存在'})

        # 2.2 价格策略是否合法?
        # 查看当前课程所有价格策略
        price_policy_queryset = course.price_policy.all()
        price_policy_dict = {}  # 空字典
        for item in price_policy_queryset:
            temp = {
                'id': item.id,  # 价格策略id
                'price': item.price,  # 价格
                'valid_period': item.valid_period,  # 有效期
                'valid_period_display': item.get_valid_period_display()  # 有效期中文
            }
            price_policy_dict[item.id] = temp  # 循环加入到空字典中

        # policy_id类型必须为数字,否则即使存在,这里也会提示价格策略不存在
        if policy_id not in price_policy_dict:  # 判断价格策略是否存在
            return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'})

        # 3. 把商品和价格策略信息放入购物车
        pattern = 'shopping_car_%s_%s' % (USER_ID, '*',)  # key的格式
        keys = CONN.keys(pattern)  # 搜索key,比如:shopping_car_1_*   *表示模糊匹配
        if keys and len(keys) >= 1000:  # 如果key的长度大于1000。意思就是买了1000门课程
            return Response({'code': 10009, 'error': '购物车东西太多,先去结算再进行购买..'})

        key = "shopping_car_%s_%s" %(USER_ID,course_id,)  # 单个课程

        CONN.hset(key, 'id', course_id)  # 存入课程id
        CONN.hset(key, 'name', course.name)
        CONN.hset(key, 'img', course.course_img)
        CONN.hset(key, 'default_price_id', policy_id)
        # 由于价格策略有很多个,需要json一下
        CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict))

        CONN.expire(key, 60*60*24)  # key的有效期为24小时

        return Response({'code': 10000, 'data': '购买成功'})
View Code

发送一个正确的值

查看返回结果

使用xhsell登录redis,查看所有的key

使用命令:keys *

127.0.0.1:6379> keys *
1) "shopping_car_1_1"

查看key的所有信息

使用命令: hgetall shopping_car_1_1

127.0.0.1:6379> hgetall shopping_car_1_1
 1) "default_price_id"
 2) "2"
 3) "name"
 4) "Pythonxe5xbcx80xe5x8fx91xe5x85xa5xe9x97xa87xe5xa4xa9xe7x89xb9xe8xaexadxe8x90xa5"
 5) "id"
 6) "1"
 7) "price_policy_dict"
 8) "{"1": {"valid_period_display": "1\u5468", "valid_period": 7, "price": 10.0, "id": 1}, "2": {"valid_period_display": "1\u4e2a\u6708", "valid_period": 30, "price": 50.0, "id": 2}}"
 9) "img"
10) "Pythonxe5xbcx80xe5x8fx91xe5x85xa5xe9x97xa8"
View Code

再购买一个课程

注意:价格策略id是唯一的,看价格策略表

这里展示的价格策略id,就是价格策略表的主键id

object_id  表示course表的主键id,表示具体哪门课程。

content_type_id为8,表示course表。为什么8就是course表呢?

查看django_content_type表,因为主键id为8的。就是course表!

查看所有key

127.0.0.1:6379> keys *
1) "shopping_car_1_2"
2) "shopping_car_1_1"

查看购物车记录

修改views目录下的shoppingcart.py

import redis
import json
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models
from api.utils.serialization_general import SerializedData

CONN = redis.Redis(host='192.168.142.129',port=6379)
USER_ID = 1 # 固定用户id
KEY_prefix = 'shopping_car'  # 购物车key的前缀

class ShoppingCartView(ViewSetMixin,APIView):
    # parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        ret = {'code': 10000, 'data': None, 'error': None}  # 状态字典
        try:
            shopping_car_course_list = []  # 空列表

            pattern = "%s_%s_*" % (KEY_prefix,USER_ID,)  # 默认匹配用户的购物车

            user_key_list = CONN.keys(pattern)
            for key in user_key_list:
                temp = {
                    'id': CONN.hget(key, 'id').decode('utf-8'),  # 解码
                    'name': CONN.hget(key, 'name').decode('utf-8'),
                    'img': CONN.hget(key, 'img').decode('utf-8'),
                    'default_price_id': CONN.hget(key, 'default_price_id').decode('utf-8'),
                    # 先解码,再反序列化
                    'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))
                }
                shopping_car_course_list.append(temp)

            ret['data'] = shopping_car_course_list  # 状态字典增加key

        except Exception as e:
            ret['code'] = 10005
            ret['error'] = '获取购物车数据失败'

        return Response(ret)

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        course_id = request.data.get('courseid')
        policy_id = request.data.get('policyid')

        if course_id.isdigit():  # 判断是否为数字
            policy_id = int(policy_id)
        else:
            return Response({'code': 10001, 'error': '课程非法'})

        # 2. 判断合法性
        #   - 课程是否存在?
        #   - 价格策略是否合法?

        # 2.1 课程是否存在?
        course = models.Course.objects.filter(id=course_id).first()
        if not course:
            return Response({'code': 10001, 'error': '课程不存在'})

        # 2.2 价格策略是否合法?
        # 查看当前课程所有价格策略
        price_policy_queryset = course.price_policy.all()
        price_policy_dict = {}  # 空字典
        for item in price_policy_queryset:
            temp = {
                'id': item.id,  # 价格策略id
                'price': item.price,  # 价格
                'valid_period': item.valid_period,  # 有效期
                'valid_period_display': item.get_valid_period_display()  # 有效期中文
            }
            price_policy_dict[item.id] = temp  # 循环加入到空字典中

        # policy_id类型必须为数字,否则即使存在,这里也会提示价格策略不存在
        if policy_id not in price_policy_dict:  # 判断价格策略是否存在
            return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'})

        # 3. 把商品和价格策略信息放入购物车
        pattern = '%s_%s_%s' % (KEY_prefix,USER_ID, '*',)  # key的格式
        keys = CONN.keys(pattern)  # 搜索key,比如:shopping_car_1_*   *表示模糊匹配
        if keys and len(keys) >= 1000:  # 如果key的长度大于1000。意思就是买了1000门课程
            return Response({'code': 10009, 'error': '购物车东西太多,先去结算再进行购买..'})

        key = "%s_%s_%s" %(KEY_prefix,USER_ID,course_id,)  # 单个课程

        CONN.hset(key, 'id', course_id)  # 存入课程id
        CONN.hset(key, 'name', course.name)
        CONN.hset(key, 'img', course.course_img)
        CONN.hset(key, 'default_price_id', policy_id)
        # 由于价格策略有很多个,需要json一下
        CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict))

        CONN.expire(key, 60*60*24)  # key的有效期为24小时

        return Response({'code': 10000, 'data': '购买成功'})
View Code

使用postman发送get请求,不需要参数

购物车删除

删除购物车,需要传入一个课程id。通过url传参就可以了

修改api_urls.py,增加delete

from django.conf.urls import url
from api.views import course,degreecourse,auth,shoppingcart

urlpatterns = [
    url(r'auth/$', auth.AuthView.as_view({'post':'login'})),
    url(r'courses/$',course.CoursesView.as_view()),
    url(r'courses/(?P<pk>d+)/$',course.CourseDetailView.as_view()),

    url(r'shoppingcart/$', shoppingcart.ShoppingCartView.as_view({'get':'list','post':'create','delete':'destroy'})),
]
View Code

修改views目录下的shoppingcart.py

import redis
import json
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models
from api.utils.response import BaseResponse

CONN = redis.Redis(host='192.168.142.129',port=6379)
USER_ID = 1 # 固定用户id
KEY_PREFIX = 'shopping_car'  # 购物车key的前缀

class ShoppingCartView(ViewSetMixin,APIView):
    # parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        ret = {'code': 10000, 'data': None, 'error': None}  # 状态字典
        try:
            shopping_car_course_list = []  # 空列表

            pattern = "%s_%s_*" % (KEY_PREFIX,USER_ID,)  # 默认匹配用户的购物车

            user_key_list = CONN.keys(pattern)
            for key in user_key_list:
                temp = {
                    'id': CONN.hget(key, 'id').decode('utf-8'),  # 解码
                    'name': CONN.hget(key, 'name').decode('utf-8'),
                    'img': CONN.hget(key, 'img').decode('utf-8'),
                    'default_price_id': CONN.hget(key, 'default_price_id').decode('utf-8'),
                    # 先解码,再反序列化
                    'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))
                }
                shopping_car_course_list.append(temp)

            ret['data'] = shopping_car_course_list  # 状态字典增加key

        except Exception as e:
            ret['code'] = 10005
            ret['error'] = '获取购物车数据失败'

        return Response(ret)

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        course_id = request.data.get('courseid')
        policy_id = request.data.get('policyid')

        if course_id.isdigit():  # 判断是否为数字
            policy_id = int(policy_id)
        else:
            return Response({'code': 10001, 'error': '课程非法'})

        # 2. 判断合法性
        #   - 课程是否存在?
        #   - 价格策略是否合法?

        # 2.1 课程是否存在?
        course = models.Course.objects.filter(id=course_id).first()
        if not course:
            return Response({'code': 10001, 'error': '课程不存在'})

        # 2.2 价格策略是否合法?
        # 查看当前课程所有价格策略
        price_policy_queryset = course.price_policy.all()
        price_policy_dict = {}  # 空字典
        for item in price_policy_queryset:
            temp = {
                'id': item.id,  # 价格策略id
                'price': item.price,  # 价格
                'valid_period': item.valid_period,  # 有效期
                'valid_period_display': item.get_valid_period_display()  # 有效期中文
            }
            price_policy_dict[item.id] = temp  # 循环加入到空字典中

        # policy_id类型必须为数字,否则即使存在,这里也会提示价格策略不存在
        if policy_id not in price_policy_dict:  # 判断价格策略是否存在
            return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'})

        # 3. 把商品和价格策略信息放入购物车
        pattern = '%s_%s_%s' % (KEY_PREFIX,USER_ID, '*',)  # key的格式
        keys = CONN.keys(pattern)  # 搜索key,比如:shopping_car_1_*   *表示模糊匹配
        if keys and len(keys) >= 1000:  # 如果key的长度大于1000。意思就是买了1000门课程
            return Response({'code': 10009, 'error': '购物车东西太多,先去结算再进行购买..'})

        key = "%s_%s_%s" %(KEY_PREFIX,USER_ID,course_id,)  # 单个课程

        CONN.hset(key, 'id', course_id)  # 存入课程id
        CONN.hset(key, 'name', course.name)
        CONN.hset(key, 'img', course.course_img)
        CONN.hset(key, 'default_price_id', policy_id)
        # 由于价格策略有很多个,需要json一下
        CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict))

        CONN.expire(key, 60*60*24)  # key的有效期为24小时

        return Response({'code': 10000, 'data': '购买成功'})

    def destroy(self, request, *args, **kwargs):
        """
        删除购物车中的某个课程
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        response = BaseResponse()
        try:
            courseid = request.GET.get('courseid')  # 获取课程id
            key = "%s_%s_%s" % (KEY_PREFIX,USER_ID,courseid)  # 获取redis中的课程id

            CONN.delete(key)  # 删除单个key
            response.data = '删除成功'
            
        except Exception as e:
            response.code = 10006
            response.error = '删除失败'
            
        return Response(response.dict)
View Code

使用postman,发送带参数的get请求

提示删除成功

查看购物车,发现只有一个课程

修改价格策略

这里只要选择了一个价格策略,会发送一个ajax请求。后端会修改redis中的数据

修改用户购物车的默认价格策略id

修改api_urls.py,增加put

from django.conf.urls import url
from api.views import course,degreecourse,auth,shoppingcart

urlpatterns = [
    url(r'auth/$', auth.AuthView.as_view({'post':'login'})),
    url(r'courses/$',course.CoursesView.as_view()),
    url(r'courses/(?P<pk>d+)/$',course.CourseDetailView.as_view()),

    url(r'shoppingcart/$', shoppingcart.ShoppingCartView.as_view({'get':'list','post':'create','delete':'destroy','put':'update'})),
]
View Code

修改views目录下的shoppingcart.py

import redis
import json
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api import models
from api.utils.response import BaseResponse

CONN = redis.Redis(host='192.168.142.129',port=6379)
USER_ID = 1 # 固定用户id
KEY_PREFIX = 'shopping_car'  # 购物车key的前缀

class ShoppingCartView(ViewSetMixin,APIView):
    # parser_classes = [JSONParser,FormParser]  # 指定解析器

    def list(self, request, *args, **kwargs):
        """
        查看购物车信息
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        ret = {'code': 10000, 'data': None, 'error': None}  # 状态字典
        try:
            shopping_car_course_list = []  # 空列表

            pattern = "%s_%s_*" % (KEY_PREFIX,USER_ID,)  # 默认匹配用户的购物车

            user_key_list = CONN.keys(pattern)
            for key in user_key_list:
                temp = {
                    'id': CONN.hget(key, 'id').decode('utf-8'),  # 解码
                    'name': CONN.hget(key, 'name').decode('utf-8'),
                    'img': CONN.hget(key, 'img').decode('utf-8'),
                    'default_price_id': CONN.hget(key, 'default_price_id').decode('utf-8'),
                    # 先解码,再反序列化
                    'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))
                }
                shopping_car_course_list.append(temp)

            ret['data'] = shopping_car_course_list  # 状态字典增加key

        except Exception as e:
            ret['code'] = 10005
            ret['error'] = '获取购物车数据失败'

        return Response(ret)

    def create(self,request,*args,**kwargs):
        """
        加入购物车
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 接受用户选中的课程ID和价格策略ID
        2. 判断合法性
            - 课程是否存在?
            - 价格策略是否合法?
        3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
        
        注意:用户ID=1
        """

        # 1. 接受用户选中的课程ID和价格策略ID
        course_id = request.data.get('courseid')
        policy_id = request.data.get('policyid')

        if course_id.isdigit():  # 判断是否为数字
            policy_id = int(policy_id)
        else:
            return Response({'code': 10001, 'error': '课程非法'})

        # 2. 判断合法性
        #   - 课程是否存在?
        #   - 价格策略是否合法?

        # 2.1 课程是否存在?
        course = models.Course.objects.filter(id=course_id).first()
        if not course:
            return Response({'code': 10001, 'error': '课程不存在'})

        # 2.2 价格策略是否合法?
        # 查看当前课程所有价格策略
        price_policy_queryset = course.price_policy.all()
        price_policy_dict = {}  # 空字典
        for item in price_policy_queryset:
            temp = {
                'id': item.id,  # 价格策略id
                'price': item.price,  # 价格
                'valid_period': item.valid_period,  # 有效期
                'valid_period_display': item.get_valid_period_display()  # 有效期中文
            }
            price_policy_dict[item.id] = temp  # 循环加入到空字典中

        # policy_id类型必须为数字,否则即使存在,这里也会提示价格策略不存在
        if policy_id not in price_policy_dict:  # 判断价格策略是否存在
            return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'})

        # 3. 把商品和价格策略信息放入购物车
        pattern = '%s_%s_%s' % (KEY_PREFIX,USER_ID, '*',)  # key的格式
        keys = CONN.keys(pattern)  # 搜索key,比如:shopping_car_1_*   *表示模糊匹配
        if keys and len(keys) >= 1000:  # 如果key的长度大于1000。意思就是买了1000门课程
            return Response({'code': 10009, 'error': '购物车东西太多,先去结算再进行购买..'})

        key = "%s_%s_%s" %(KEY_PREFIX,USER_ID,course_id,)  # 单个课程

        CONN.hset(key, 'id', course_id)  # 存入课程id
        CONN.hset(key, 'name', course.name)
        CONN.hset(key, 'img', course.course_img)
        CONN.hset(key, 'default_price_id', policy_id)
        # 由于价格策略有很多个,需要json一下
        CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict))

        CONN.expire(key, 60*60*24)  # key的有效期为24小时

        return Response({'code': 10000, 'data': '购买成功'})

    def destroy(self, request, *args, **kwargs):
        """
        删除购物车中的某个课程
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        response = BaseResponse()
        try:
            courseid = request.GET.get('courseid')  # 获取课程id
            key = "%s_%s_%s" % (KEY_PREFIX,USER_ID,courseid)  # 获取redis中的课程id

            CONN.delete(key)  # 删除单个key
            response.data = '删除成功'

        except Exception as e:
            response.code = 10006
            response.error = '删除失败'

        return Response(response.dict)

    def update(self, request, *args, **kwargs):
        """
        修改用户选中的价格策略
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        """
        1. 获取课程ID、要修改的价格策略ID
        2. 校验合法性(去redis中)
        """
        response = BaseResponse()
        try:
            course_id = request.data.get('courseid')
            policy_id = request.data.get('policyid')

            key = '%s_%s_%s' %(KEY_PREFIX,USER_ID,course_id,)  # 获取用户购物车中的单个课程

            if not CONN.exists(key):  # 判断key是否存在
                response.code = 10007
                response.error = '课程不存在'
                return Response(response.dict)
            
            # 获取所有的价格策略。先解码,再反序列化。最终是一个字典
            price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))
            # 由于反序列化之后,字段的key-value都强制转换为字符串了
            # 所以上面获取到的价格策略id必须转换为字符串,才能使用下面的not in 判断
            policy_id = str(policy_id)
            if policy_id not in price_policy_dict:  # 判断价格策略id是否存在
                response.code = 10008
                response.error = '价格策略不存在'
                return Response(response.dict)

            CONN.hset(key, 'default_price_id', policy_id)  # 修改默认的价格策略id
            CONN.expire(key, 60*60*24)  # 重新设置有效期为24小时,之前的有效期会被覆盖!
            response.data = '修改成功'
            
        except Exception as e:
            response.code = 10009
            response.error = '修改失败'

        return Response(response.dict)
View Code

使用postman发送put请求,注意带上参数

查看返回值

总结:

a. 为什么要把购物车信息放到redis中?
    - 查询频繁
        - 课程是否存在?
        - 价格策略是否合法?
    - 中间状态 
        - 购买成功之后,需要删除。
        - 购物车信息删除
        
b. 购物车有没有数量限制?
    使用 keys 查看个数做判断,限制为1000。
    如果不限制,会导致redis内存占满,导致内存溢出!
    
c. 购物车的结构 
    shopping_car_用户id_课程id:{
        id:课程ID
        name:课程名称
        img:课程图片
        defaut:默认选中的价格策略
        # 所有价格策略
        price_list:[
            {'策略id':'价格'},
            {'策略id':'价格'},
            ...
        ]
    },
d. 对于字典的key,序列化会将数字转换成字符串
比如:
info = {1:'xiao',2:'zhang'}
new = json.dumps(info)  # 结果为 '{"1":"alex", "2":"于超"}'
data = json.loads(new)  # 结果为 {"1":"alex", "2":"于超"}
View Code

作业:

1. 虚拟机安装上redis,redis服务启动
2. 购物车,完成以下功能:
    - 添加到购物车
    - 查看购物车信息
    - 删除课程
    - 修改课程 
原文地址:https://www.cnblogs.com/xiao987334176/p/9457338.html