昨日内容回顾

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的区别?
- 双向绑定(数据变动,页面也随之更改)
- 单页面应用(切换页面,页面不刷新)

一、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

第一个就是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='')
conn.set('name','xiao') # 写入字符串
val = conn.get('name') # 获取字符串
print(val)

执行程序,输出如下:

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='')
conn.hset("dic_name","a1","aa") # 写入字典
val = conn.hget("dic_name","a1") # 获取key为a1的值
print(val)
print(val.decode('utf-8')) # 解码

执行输出:

b'aa'
aa

hgetall(name)

获取name对应hash的所有键值

举例:

import redis

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

执行输出:

{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='')
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)

执行输出:

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='')
conn.lpush("list_name",2)# 在list_name中增加一个值2 print(conn.llen("list_name")) # 获取列表元素的个数
val = conn.lindex("list_name",0) #根据索引获取列表内元素
print(val)

执行输出:

1
b''

集合(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='')
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)

执行输出:

{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='')
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)

执行输出:

3
2

总结:

a. 五大数据类型:
字符串,哈希,列表,集合,有序集合 b. 列举每种数据类型的操作
字符串:
set
get 字典:
hget
hgetall
hset
hmset
hdel 其他:
delete
expire
keys
flushall()

更多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'})),
]

修改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})

使用postman发送json数据

查看返回信息

查看Pycharm控制台输出:

要加入购物车了
b'{"courseid":"1","policyid":"2"}' <class 'bytes'>
{'courseid': '', 'policyid': ''} <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})

使用装饰器的视图函数:

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})

举例:

修改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})

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

<WSGIRequest: POST '/api/v1/shoppingcart/'> <class 'django.core.handlers.wsgi.WSGIRequest'>
b'{"courseid":"1","policyid":"2"}'
<QueryDict: {}>
{'policyid': '', 'courseid': ''} <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

看这一句,它默认会从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',
)
}

修改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})

使用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})

使用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})

使用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': '购买成功'})

发送一个正确的值

查看返回结果

使用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) ""
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) ""
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"

再购买一个课程

注意:价格策略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': '购买成功'})

使用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'})),
]

修改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)

使用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'})),
]

修改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)

使用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":"于超"}

作业:

1. 虚拟机安装上redis,redis服务启动
2. 购物车,完成以下功能:
- 添加到购物车
- 查看购物车信息
- 删除课程
- 修改课程

python 全栈开发,Day101(redis操作,购物车,DRF解析器)的更多相关文章

  1. python 全栈开发,Day99(作业讲解,DRF版本,DRF分页,DRF序列化进阶)

    昨日内容回顾 1. 为什么要做前后端分离? - 前后端交给不同的人来编写,职责划分明确. - API (IOS,安卓,PC,微信小程序...) - vue.js等框架编写前端时,会比之前写jQuery ...

  2. Python全栈开发之---redis数据库

    1.redis简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(s ...

  3. Python全栈开发-Day11-RabbitMQ/Redis

    本节内容 RabbitMQ——消息队列 Memcached & Redis使用 1.RabbitMQ——消息队列 RabbitMQ与Queue的关系 1.做的事情是一样的,两者都是队列. 2. ...

  4. python全栈开发day54-mysql库操作、表操作、数据类型、完整性约束

    一.昨日内容回顾 1.mysql的安装 1).解压文件 添加环境变量bin 2).初始化mysql生成数据data文件夹: mysqld --initialize-insecure 3).mysqld ...

  5. python全栈开发day62-两表操作增删改查,外键,if else模板语法

    一.今日内容总结: day62 内容回顾: 1. django有关所有命令: pip install django==1.11.14 django-admin startproject 项目名称 cd ...

  6. python 全栈开发,Day100(restful 接口,DRF组件,DRF跨域(cors组件))

    昨日内容回顾 1. 为什么要做前后端分离? - 前后端交给不同的人来编写,职责划分明确.方便快速开发 - 针对pc,手机,ipad,微信,支付宝... 使用同一个接口 2. 简述http协议? - 基 ...

  7. python全栈开发从入门到放弃之装饰器函数

    什么是装饰器#1 开放封闭原则:对扩展是开放的,对修改是封闭的#2 装饰器本身可以是任意可调用对象,被装饰的对象也可以是任意可调用对象#3 目的:''' 在遵循 1. 不修改被装饰对象的源代码 2. ...

  8. Win10构建Python全栈开发环境With WSL

    目录 Win10构建Python全栈开发环境With WSL 启动WSL 总结 对<Dev on Windows with WSL>的补充 Win10构建Python全栈开发环境With ...

  9. Python 全栈开发【第0篇】:目录

    Python 全栈开发[第0篇]:目录   第一阶段:Python 开发入门 Python 全栈开发[第一篇]:计算机原理&Linux系统入门 Python 全栈开发[第二篇]:Python基 ...

随机推荐

  1. 面向对象【day07】:类的属性(五)

    本节内容 概述 公有属性 一.概述 前面我们讲了类的私有属性,现在我们来说说类的公有属性,这边很容易被人弄混淆,有人觉的,在__init__()构造方法中,除了私有属性,其他的都是公有属性了,其实这是 ...

  2. fastjson基本使用 (待继续完善)【原】

    参考: http://blog.csdn.net/wx_962464/article/details/37612861 maven库下载 fastjson基本样例1 Cat.java package ...

  3. HDU - 3006 The Number of set(状态压缩位运算)

    http://acm.hdu.edu.cn/showproblem.php?pid=3006 题意 给定n个集合,每个集合都是由大于等于1小于等于m的数字组成,m最大为14.问由给出的集合可以组成多少 ...

  4. CSS魔法(四)常用属性

    元素的显示与隐藏 display.visibility.overflow 在CSS中有三个显示和隐藏的单词比较常见,我们要区分开,他们分别是 display.visibility 和 overflow ...

  5. 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常

    第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域  

  6. Netty入门(2) - 核心概念

    Netty Crash Course 一个Netty程序一般开始于Bootstrap类,通过设置程序后,使用Handlers来处理特定的event和设置Netty中的事件,从而处理多个协议数据,比如实 ...

  7. shell 终端常用插件

    参考链接: http://get.ftqq.com/992.get 1.zsh 2.autojump 3.apt-get install lamp-server^ 4.tldr 5.tree (显示目 ...

  8. Python 成仙之路

    这个部分的所有内容,都是我学习Python过程中的学习笔记. 这个部分的所有内容,都是我学习Python过程中的学习笔记. 这个部分的所有内容,都是我学习Python过程中的学习笔记. 第一部分  p ...

  9. Android UI组件之自定义控件实现IP地址控件

    http://www.cnblogs.com/razerlack/p/4273282.html

  10. Java编程:悲观锁、乐观锁的区别及使用场景

    定义: 悲观锁(Pessimistic Lock): 每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁.由于 ...