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. 购物车,完成以下功能:
        - 添加到购物车
        - 查看购物车信息
        - 删除课程
        - 修改课程 
  • 相关阅读:
    hdu--2522--循环节
    hdu--2523--读懂题意....
    hdu--1073--字符串处理
    hdu--1114--完全背包
    C#写入对象到XML/从XML读取对象
    C#打开另一个窗体
    Intent启动照片或者相机
    ViewGroup
    上传文件的表单
    添加菜单到fragment
  • 原文地址:https://www.cnblogs.com/xiao987334176/p/9457338.html
Copyright © 2011-2022 走看看