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

 

1、redis使用

redis介紹

Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。html

它的數據,存在內存中,讀寫速度快!也能夠作持久化。前端

redis安裝

使用centos系統安裝vue

yum install -y redis

redis使用

注意:redis是安裝在linux系統裏面的,可是python程序是運行在windows系統中的。因此須要進行遠程鏈接!python

可是,redis默認使用127.0.0.1鏈接,端口爲6379mysql

修改配置

編輯配置文件react

vim /etc/redis.conf

修改IP,關閉保護模式(不然沒法遠程操做redis)linux

bind 192.168.218.133
protected-mode no

啓動redis

注意:必須指定配置文件ios

redis-server /etc/redis.conf

注意,此時終端不會有輸出,再開一個窗口,查看端口git

netstat -anpt

信息以下:github

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

 

2、購物車

下載代碼:

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就自動幫你作了這件事情!詳情看下面的內容。

 

3、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) "Python\xe5\xbc\x80\xe5\x8f\x91\xe5\x85\xa5\xe9\x97\xa87\xe5\xa4\xa9\xe7\x89\xb9\xe8\xae\xad\xe8\x90\xa5"
 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) "Python\xe5\xbc\x80\xe5\x8f\x91\xe5\x85\xa5\xe9\x97\xa8"
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. 購物車,完成如下功能:
    - 添加到購物車
    - 查看購物車信息
    - 刪除課程
    - 修改課程 
相關文章
相關標籤/搜索