zoukankan      html  css  js  c++  java
  • 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. 购物车,完成以下功能:
        - 添加到购物车
        - 查看购物车信息
        - 删除课程
        - 修改课程 
  • 相关阅读:
    CSUFT 1002 Robot Navigation
    CSUFT 1003 All Your Base
    Uva 1599 最佳路径
    Uva 10129 单词
    欧拉回路
    Uva 10305 给任务排序
    uva 816 Abbott的复仇
    Uva 1103 古代象形文字
    Uva 10118 免费糖果
    Uva 725 除法
  • 原文地址:https://www.cnblogs.com/xiao987334176/p/9457338.html
Copyright © 2011-2022 走看看