python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

时间:2023-03-09 06:59:51
python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

考试第二部分:MySQL数据库

6.  MySQL中char和varchar的区别(1分)

char是定长,varchar是变长。
char的查询速度比varchar要快。

7.   MySQL中varchar(50)的50表示什什么意思?(1分)

是字符长度。一个中文,也是一个字符。

8. left join、right join以及inner join的区别?(2分)

left join,表示左连接,以左表为基准,如果左表有不匹配的,显示为空
right join,表示右连接,以右表为基准,如果右表有不匹配的,显示为空
inner join,表示内连接,只显示2个表条件符合的记录,不匹配的不显示

9. MySQL组合索引(2分)
where⼦子句句中有a、b、c 三个查询条件,创建⼀一个组合索引 abc(a,b,c),那么如下那中情况会命 中索引:
a. where (a)
b. where (b)
c. where (c)
d. where (a,b)
e. where (b,c)
f. where (a,c)
g. where (a,b,c)

a,d,f,g 会命中索引

解释:

索引有2个功能:加快查询和约束。

这里的约束指的是唯一索引,联合唯一索引。

索引遵循的原则: 最左前缀原则

你可以认为联合索引是闯关游戏的设计

例如你这个联合索引是state/city/zipCode

那么state就是第一关 city是第二关, zipCode就是第三关

你必须匹配了第一关,才能匹配第二关,匹配了第一关和第二关,才能匹配第三关

你不能直接到第二关的

索引的格式就是第一层是state,第二层才是city

索引是因为B+树结构 所以查找快 如果单看第三列 是非排序的。
多列索引是先按照第一列进行排序,然后在第一列排好序的基础上再对第二列排序,如果没有第一列的话,直接访问第二列,那第二列肯定是无序的,直接访问后面的列就用不到索引了。
所以如果不是在前面列的基础上而是但看后面某一列,索引是失效的。

简而言之,只要where条件包含最左边的字段,那么它就会用到组合索引,反之亦然!

如果创建了组合索引,但是却没有命中,这是浪费磁盘空间。因为索引也占用磁盘!

10. 假设学⽣生Student和教师Teacher关系模型如下:(4分) Student(学号、姓名、性别、类型、身份证号) Teacher(教师号、姓名、性别、类型、身份证号、工资)
其中,学⽣生表中类别为“本科生”和“研究生”两类;性别为“男”和“女”两类。
a. 性别为女的所有学生。

select * from Student where 性别='女'

b. 学生表中类别分别对应的个数。

select 类型,count(1) from Student group by 类型

c.工资少于10000的女教师的身份证和姓名。

select 身份证,姓名 from Teacher where 性别= '女' and 类型='研究生' and 工资 < 10000

d.  研究生教师平均工资、最⾼高和最低工资。

select AVG(工资),MAX(工资),MIN(工资) from Teacher wherer 类型='研究生'

11. 根据如下表结构建表:(2分)

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`balance` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

12.    根据如下表查询每个⽤用户第⼀一次下订单的时间。(2分)

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

# 第一次下单时间,就是时间最早的
select name,MIN(order_time) from table group by name

13. 有⼀一个订单系统包含订单信息、商品信息、价格信息且还要⼀一些状态,如何设计表结构(2分)

#最简单的设计

商品表
- id
- 名称
- 价格
- 描述信息 订单表
- id
- 订单号(唯一)
- 商品id
- 用户id 用户表
- id
- username
- password

14. 有如下表:(3分)
products(商品表) columns为 id、name、price
orders(商城订单表) columns为 id、reservations_id、product_id、quantity(数量量)
reservations(酒店订单表) columns为 id、user_id、price、created_at

ps:这个一个真实面试题!

应用场景:比如万达酒店,需要订购商品,比如红酒,红木家具...

a. 各个商品的售卖情况,需要字段:商品名、购买总数、商品收⼊入(单价*数量量)

SELECT
products. NAME,
sum(orders.quantity),
products.price * sum(orders.quantity)
FROM
orders
LEFT JOIN products ON products.id = orders.product_id
GROUP BY
orders.product_id

b. 所有用户在2018-01-01至2018-02-01下单次数、下单金额、商城下单次数、商城下单金额

# 注意:最后的期限要加1天。因为23:59:59也是属于当天的
SELECT
count(1),
sum(reservations.price),
sum(orders.quantity),
products.price * sum(orders.quantity)
FROM
reservations
LEFT JOIN orders ON orders.reservations_id = reservations.id
LEFT JOIN products ON products.id = orders.product_id
WHERE
reservations.created_at BETWEEN 2018-01-01
AND reservations.created_at '2018-02-02'

c. 历月下单用户数:下单1次的用户数、下单2次的用户数、下单3次及以上的用户数

# 下单1次的用户数
select DATE_FORMAT(created_at,'%Y-%m'),user_id,count(1) from reservations group by DATE_FORMAT(created_at,'%Y-%m'),user_id having count(user_id) = 1; # 下单2次的用户数
select DATE_FORMAT(created_at,'%Y-%m'),user_id,count(1) from reservations group by DATE_FORMAT(created_at,'%Y-%m'),user_id having count(user_id) = 1; # 下单3次及以上的用户数
select DATE_FORMAT(created_at,'%Y-%m'),user_id,count(1) from reservations group by DATE_FORMAT(created_at,'%Y-%m'),user_id having count(user_id) >= 3;

15.  根据表写SQL语句句:(5分)

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

• 查询所有同学的学号、姓名、班级名称。(1分)

select student.sid,student.sname,class.caption from student left jon class on class.cid = student.class_id

• 查询没有学⽣生的所有班级。(2分)

select class.caption from student left jon class on class.cid = student.class_id where class.cid is null

• 查询有学⽣生的所有班级的名称和学数量量。(2分)

select class.caption,count(1) from student left jon class on class.cid = student.class_id

一、DRF用户认证

流程图

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

请求到达视图的时候,需要进行认证。

认证是在中间件之后的。如果一旦认证失败,则返回信息给用户

启动项目luffcity,访问购物车

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

注意:要启动redis,否则提示获取购物车数据失败

添加认证

购物车需要登录才能查看,登录成功后,返回一个token

修改views目录下的auth.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from api import models
from api.utils.response import BaseResponse
import uuid class AuthView(ViewSetMixin,APIView):
def login(self,request,*args,**kwargs):
"""
用户登陆认证
:param request:
:param args:
:param kwargs:
:return:
"""
response = BaseResponse() # 默认状态
try:
user = request.data.get('username')
pwd = request.data.get('password')
# 验证用户和密码
obj = models.Account.objects.filter(username=user,password=pwd).first()
if not obj:
response.code = 10002
response.error = '用户名或密码错误'
else:
uid = str(uuid.uuid4()) # 生成唯一id
response.code = 99999
response.data = uid except Exception as e:
response.code = 10005
response.error = '操作异常' return Response(response.dict)

请确保已经生成了表api_account,并添加了一条记录

如果没有,请参考昨天的文档!

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

测试用户和密码

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

查看返回结果

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

输入正确的用户名和密码

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

查看返回结果,返回一个随机码。这个就是token

9999表示登录成功

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

既然token已经生成了,并返回给了客户端。那么服务器如何验证客户端的token是否合法呢?

答案是,服务器需要保存token。推荐加一个有效期,比如微信公众号的token有效期为8个小时!

增加token表

修改models.py,增加token表

它和用户表是一对一的关系!

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.utils.safestring import mark_safe
from django.db import models
import hashlib # ######################## 课程相关 ######################## class CourseCategory(models.Model):
"""课程大类, e.g 前端 后端..."""
name = models.CharField(max_length=64, unique=True) def __str__(self):
return "%s" % self.name class Meta:
verbose_name_plural = "01.课程大类" class CourseSubCategory(models.Model):
"""课程子类, e.g python linux """
category = models.ForeignKey("CourseCategory")
name = models.CharField(max_length=64, unique=True) def __str__(self):
return "%s" % self.name class Meta:
verbose_name_plural = "02.课程子类" class DegreeCourse(models.Model):
"""学位课程"""
name = models.CharField(max_length=128, unique=True)
course_img = models.CharField(max_length=255, verbose_name="缩略图")
brief = models.TextField(verbose_name="学位课程简介", )
total_scholarship = models.PositiveIntegerField(verbose_name="总奖学金(贝里)", default=40000) # 2000 2000
mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本课程的导师辅导费用(贝里)", default=15000)
period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=150) # 为了计算学位奖学金
prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师") # 用于GenericForeignKey反向查询, 不会生成表字段,切勿删除
# coupon = GenericRelation("Coupon") # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
degreecourse_price_policy = GenericRelation("PricePolicy") def __str__(self):
return self.name class Meta:
verbose_name_plural = "03.学位课" class Teacher(models.Model):
"""讲师、导师表"""
name = models.CharField(max_length=32)
role_choices = ((0, '讲师'), (1, '导师'))
role = models.SmallIntegerField(choices=role_choices, default=0)
title = models.CharField(max_length=64, verbose_name="职位、职称")
signature = models.CharField(max_length=255, help_text="导师签名", blank=True, null=True)
image = models.CharField(max_length=128)
brief = models.TextField(max_length=1024) def __str__(self):
return self.name class Meta:
verbose_name_plural = "04.导师或讲师" class Scholarship(models.Model):
"""学位课程奖学金"""
degree_course = models.ForeignKey("DegreeCourse")
time_percent = models.PositiveSmallIntegerField(verbose_name="奖励档位(时间百分比)", help_text="只填百分值,如80,代表80%")
value = models.PositiveIntegerField(verbose_name="奖学金数额") def __str__(self):
return "%s:%s" % (self.degree_course, self.value) class Meta:
verbose_name_plural = "05.学位课奖学金" class Course(models.Model):
"""专题课/学位课模块表"""
name = models.CharField(max_length=128, unique=True)
course_img = models.CharField(max_length=255)
sub_category = models.ForeignKey("CourseSubCategory")
course_type_choices = ((0, '付费'), (1, 'VIP专享'), (2, '学位课程'))
course_type = models.SmallIntegerField(choices=course_type_choices) # 不为空;学位课的某个模块
# 为空;专题课
degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, help_text="若是学位课程,此处关联学位表") brief = models.TextField(verbose_name="课程概述", max_length=2048)
level_choices = ((0, '初级'), (1, '中级'), (2, '高级'))
level = models.SmallIntegerField(choices=level_choices, default=1)
pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True)
period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=7) #
order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排")
attachment_path = models.CharField(max_length=128, verbose_name="课件路径", blank=True, null=True)
status_choices = ((0, '上线'), (1, '下线'), (2, '预上线'))
status = models.SmallIntegerField(choices=status_choices, default=0)
template_id = models.SmallIntegerField("前端模板id", default=1) coupon = GenericRelation("Coupon") # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
price_policy = GenericRelation("PricePolicy") asked_question = GenericRelation("OftenAskedQuestion") def __str__(self):
return "%s(%s)" % (self.name, self.get_course_type_display()) def save(self, *args, **kwargs):
if self.course_type == 2:
if not self.degree_course:
raise ValueError("学位课程必须关联对应的学位表")
super(Course, self).save(*args, **kwargs) class Meta:
verbose_name_plural = "06.专题课或学位课模块" class CourseDetail(models.Model):
"""课程详情页内容"""
course = models.OneToOneField("Course")
hours = models.IntegerField("课时")
course_slogan = models.CharField(max_length=125, blank=True, null=True)
video_brief_link = models.CharField(verbose_name='课程介绍', max_length=255, blank=True, null=True)
why_study = models.TextField(verbose_name="为什么学习这门课程")
what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容")
career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯")
prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True)
teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师") def __str__(self):
return "%s" % self.course class Meta:
verbose_name_plural = "07.课程或学位模块详细" class OftenAskedQuestion(models.Model):
"""常见问题"""
content_type = models.ForeignKey(ContentType) # 关联course or degree_course
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') question = models.CharField(max_length=255)
answer = models.TextField(max_length=1024) def __str__(self):
return "%s-%s" % (self.content_object, self.question) class Meta:
unique_together = ('content_type', 'object_id', 'question')
verbose_name_plural = "08. 常见问题" class CourseOutline(models.Model):
"""课程大纲"""
course_detail = models.ForeignKey("CourseDetail")
title = models.CharField(max_length=128)
# 前端显示顺序
order = models.PositiveSmallIntegerField(default=1) content = models.TextField("内容", max_length=2048) def __str__(self):
return "%s" % self.title class Meta:
unique_together = ('course_detail', 'title')
verbose_name_plural = "09. 课程大纲" class CourseChapter(models.Model):
"""课程章节"""
course = models.ForeignKey("Course")
chapter = models.SmallIntegerField(verbose_name="第几章", default=1)
name = models.CharField(max_length=128)
summary = models.TextField(verbose_name="章节介绍", blank=True, null=True)
pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True) class Meta:
unique_together = ("course", 'chapter')
verbose_name_plural = "10. 课程章节" def __str__(self):
return "%s:(第%s章)%s" % (self.course, self.chapter, self.name) class CourseSection(models.Model):
"""课时目录"""
chapter = models.ForeignKey("CourseChapter")
name = models.CharField(max_length=128)
order = models.PositiveSmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时")
section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频'))
section_type = models.SmallIntegerField(default=2, choices=section_type_choices)
section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link")
video_time = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32) # 仅在前端展示使用
pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True)
free_trail = models.BooleanField("是否可试看", default=False) class Meta:
unique_together = ('chapter', 'section_link')
verbose_name_plural = "11. 课时" def __str__(self):
return "%s-%s" % (self.chapter, self.name) class Homework(models.Model):
chapter = models.ForeignKey("CourseChapter")
title = models.CharField(max_length=128, verbose_name="作业题目")
order = models.PositiveSmallIntegerField("作业顺序", help_text="同一课程的每个作业之前的order值间隔1-2个数")
homework_type_choices = ((0, '作业'), (1, '模块通关考核'))
homework_type = models.SmallIntegerField(choices=homework_type_choices, default=0)
requirement = models.TextField(max_length=1024, verbose_name="作业需求")
threshold = models.TextField(max_length=1024, verbose_name="踩分点")
recommend_period = models.PositiveSmallIntegerField("推荐完成周期(天)", default=7)
scholarship_value = models.PositiveSmallIntegerField("为该作业分配的奖学金(贝里)")
note = models.TextField(blank=True, null=True)
enabled = models.BooleanField(default=True, help_text="本作业如果后期不需要了,不想让学员看到,可以设置为False") class Meta:
unique_together = ("chapter", "title")
verbose_name_plural = "12. 章节作业" def __str__(self):
return "%s - %s" % (self.chapter, self.title) # class CourseReview(models.Model):
# """课程评价"""
# enrolled_course = models.OneToOneField("EnrolledCourse")
# about_teacher = models.FloatField(default=0, verbose_name="讲师讲解是否清晰")
# about_video = models.FloatField(default=0, verbose_name="内容实用")
# about_course = models.FloatField(default=0, verbose_name="课程内容通俗易懂")
# review = models.TextField(max_length=1024, verbose_name="评价")
# disagree_number = models.IntegerField(default=0, verbose_name="踩")
# agree_number = models.IntegerField(default=0, verbose_name="赞同数")
# tags = models.ManyToManyField("Tags", blank=True, verbose_name="标签")
# date = models.DateTimeField(auto_now_add=True, verbose_name="评价日期")
# is_recommend = models.BooleanField("热评推荐", default=False)
# hide = models.BooleanField("不在前端页面显示此条评价", default=False)
#
# def __str__(self):
# return "%s-%s" % (self.enrolled_course.course, self.review)
#
# class Meta:
# verbose_name_plural = "13. 课程评价(购买课程后才能评价)"
#
#
# class DegreeCourseReview(models.Model):
# """学位课程评价
# 为了以后可以定制单独的评价内容,所以不与普通课程的评价混在一起,单独建表
# """
# enrolled_course = models.ForeignKey("EnrolledDegreeCourse")
# course = models.ForeignKey("Course", verbose_name="评价学位模块", blank=True, null=True,
# help_text="不填写即代表评价整个学位课程", limit_choices_to={'course_type': 2})
# about_teacher = models.FloatField(default=0, verbose_name="讲师讲解是否清晰")
# about_video = models.FloatField(default=0, verbose_name="视频质量")
# about_course = models.FloatField(default=0, verbose_name="课程")
# review = models.TextField(max_length=1024, verbose_name="评价")
# disagree_number = models.IntegerField(default=0, verbose_name="踩")
# agree_number = models.IntegerField(default=0, verbose_name="赞同数")
# tags = models.ManyToManyField("Tags", blank=True, verbose_name="标签")
# date = models.DateTimeField(auto_now_add=True, verbose_name="评价日期")
# is_recommend = models.BooleanField("热评推荐", default=False)
# hide = models.BooleanField("不在前端页面显示此条评价", default=False)
#
# def __str__(self):
# return "%s-%s" % (self.enrolled_course, self.review)
#
# class Meta:
# verbose_name_plural = "14. 学位课评价(购买课程后才能评价)" class PricePolicy(models.Model):
"""价格与有课程效期表"""
content_type = models.ForeignKey(ContentType) # 关联course or degree_course
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') # course = models.ForeignKey("Course")
valid_period_choices = ((1, '1天'), (3, '3天'),
(7, '1周'), (14, '2周'),
(30, '1个月'),
(60, '2个月'),
(90, '3个月'),
(180, '6个月'), (210, '12个月'),
(540, '18个月'), (720, '24个月'),
)
valid_period = models.SmallIntegerField(choices=valid_period_choices)
price = models.FloatField() class Meta:
unique_together = ("content_type", 'object_id', "valid_period")
verbose_name_plural = "15. 价格策略" def __str__(self):
return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price) # ################################### 优惠券相关 ################################# class Coupon(models.Model):
"""优惠券生成规则"""
name = models.CharField(max_length=64, verbose_name="活动名称")
brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍") coupon_type_choices = ((0, '立减'), (1, '满减券'), (2, '折扣券'))
coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型") money_equivalent_value = models.IntegerField(verbose_name="等值货币")
off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True)
minimum_consume = models.PositiveIntegerField("最低消费", default=0, help_text="仅在满减券时填写此字段") content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定")
content_object = GenericForeignKey('content_type', 'object_id') quantity = models.PositiveIntegerField("数量(张)", default=1)
open_date = models.DateField("优惠券领取开始时间")
close_date = models.DateField("优惠券领取结束时间")
valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True)
valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True)
# coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True,
# help_text="自券被领时开始算起")
date = models.DateTimeField(auto_now_add=True) class Meta:
verbose_name_plural = "31. 优惠券生成记录" def __str__(self):
return "%s(%s)" % (self.get_coupon_type_display(), self.name) class CouponRecord(models.Model):
"""优惠券发放、消费纪录"""
coupon = models.ForeignKey("Coupon")
account = models.ForeignKey("Account", verbose_name="拥有者") number = models.CharField(max_length=64, unique=True) status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'))
status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间") # order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单") # 一个订单可以有多个优惠券
order_id = models.IntegerField(verbose_name='关联订单ID') class Meta:
verbose_name_plural = "32. 用户优惠券" def __str__(self):
return '%s-%s-%s' % (self.account, self.number, self.status) class Account(models.Model):
username = models.CharField("用户名", max_length=64, unique=True)
email = models.EmailField(
verbose_name='邮箱',
max_length=255,
unique=True,
blank=True,
null=True
)
password = models.CharField('密码', max_length=128) class Meta:
verbose_name_plural = "33. 用户表" class UserToken(models.Model):
user = models.OneToOneField(to='Account')
token = models.CharField(max_length=36) class Meta:
verbose_name_plural = "34. token表"

使用2个命令,生成表

python manage.py makemigrations
python manage.py migrate

修改views目录下的auth.py,保存token到数据库中

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from api import models
from api.utils.response import BaseResponse
import uuid class AuthView(ViewSetMixin,APIView):
def login(self,request,*args,**kwargs):
"""
用户登陆认证
:param request:
:param args:
:param kwargs:
:return:
"""
response = BaseResponse() # 默认状态
try:
user = request.data.get('username')
pwd = request.data.get('password')
# 验证用户和密码
obj = models.Account.objects.filter(username=user,password=pwd).first()
if not obj:
response.code = 10002
response.error = '用户名或密码错误'
else:
uid = str(uuid.uuid4()) # 生成唯一id
# 保存到数据库中,update_or_create表示更新或者创建
# user=obj,这个是判断条件。当条件成立,更新token字段,值为uid
# 当条件不成立时,增加一条记录。注意:增加时,有2个字段,分别是user和token
models.UserToken.objects.update_or_create(user=obj, defaults={'token': uid})
response.code = 99999
response.data = uid except Exception as e:
response.code = 10005
response.error = '操作异常' return Response(response.dict)

再次发送POST请求,输入正确的用户名和密码

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

查看表api_usertoken,发现和返回结果是一样的!

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

再发送一次

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

表的数据随之更新

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

增加认证

不光购物车会用到用户认证,结算中心也需要用到认证,还有其他的视图,也同样需要登录才能使用。

所以,这个认证类需要放到utils里面

在utils目录中新建文件auth.py

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed from api import models class LuffyAuthentication(BaseAuthentication): def authenticate(self, request):
"""
用户认证
:param request:
:return:
"""
# 获取get参数中的token
token = request.query_params.get('token')
# 判断token是否在数据库中
token_obj = models.UserToken.objects.filter(token=token).first()
if not token_obj:
# 认证失败
raise AuthenticationFailed({'code':1008,'error':'认证失败'})
# 认证成功
# return 必须返回2个参数,请参考源码解析
# 这里的token_obj.user,表示UserToken表中的user字段
# token_obj就是UserToken表的一条记录,也就是一个object
return (token_obj.user,token_obj)

修改views目录下的shoppingcart.py,导入utils下的auth模块

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
import json
import redis
from django.conf import settings
from api.utils.auth import LuffyAuthentication # redis连接
CONN = redis.Redis(host=settings.REDIS_SERVER.get('host'),port=settings.REDIS_SERVER.get('port')) # print(settings.REDIS_SERVER.get('host')) SHOPPING_CAR = {} USER_ID = 1 # 用户id # SHOPPING_CAR = {
# 1:{
# 2:{
# 'title':'xxxx',
# 'price':1,
# 'price_list':[
# {'id':11,},
# {'id':22},
# {'id':33},
# ]
# },
# 3:{},
# 5:{}
# },
# 2:{},
# 3:{},
# } class ShoppingCartView(ViewSetMixin,APIView):
# 开启认证,指定认证类
authentication_classes = [LuffyAuthentication,] def list(self, request, *args, **kwargs):
"""
查看购物车信息
:param request:
:param args:
:param kwargs:
:return:
"""
ret = {'code':10000,'data':None,'error':None}
try:
# request.user和request.auth是源码返回的
# 如果自定义认证类返回了一个元组,元组里面有2个值。
# 它会覆盖上面2个值,request.user和request.auth
print(request.user) # 认证类返回的第一个值
print(request.auth) # 认证类返回的第二个值
# 获取token
print('shopping',request.query_params.get('token')) shopping_car_course_list = [] # pattern = "shopping_car_%s_*" % (USER_ID,)
pattern = "shopping_car_%s_%s" % (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
except Exception as e:
# print(e)
ret['code'] = 10005
ret['error'] = '获取购物车数据失败' # print(ret)
# print(json.dumps(ret))
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
"""
相关问题:
a. 如果让你编写一个API程序,你需要先做什么?
- 业务需求
- 统一数据传输格式
- 表结构设计
- 程序开发
b. django restful framework的解析器的parser_classes的作用?
根据请求中Content-Type请求头的值,选择指定解析对请求体中的数据进行解析。
如:
请求头中含有Content-type: application/json 则内部使用的是JSONParser,JSONParser可以自动去请求体request.body中
获取请求数据,然后进行 字节转字符串、json.loads反序列化; c. 支持多个解析器(一般只是使用JSONParser即可) """
course_id = request.data.get('courseid')
policy_id = request.data.get('policyid') # 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,
'price': item.price,
'valid_period': item.valid_period,
'valid_period_display': item.get_valid_period_display()
}
price_policy_dict[item.id] = temp print(price_policy_dict,type(price_policy_dict))
print(policy_id,type(policy_id))
if policy_id not in price_policy_dict:
return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'}) # 3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
"""
购物车中要放:
课程ID
课程名称
课程图片
默认选中的价格策略
所有价格策略
{
shopping_car_1_1:{
id:课程ID
name:课程名称
img:课程图片
defaut:默认选中的价格策略
price_list:所有价格策略
}, } """ pattern = "shopping_car_%s_%s" % (USER_ID, '*',)
keys = CONN.keys(pattern)
if keys and len(keys) >= 1000:
return Response({'code': 10009, 'error': '购物车东西太多,先去结算再进行购买..'}) # key = "shopping_car_%s_%s" %(USER_ID,course_id,)
key = "shopping_car_%s_%s" % (USER_ID, course_id,)
print(key,'')
CONN.hset(key, 'id', course_id)
CONN.hset(key, 'name', course.name)
CONN.hset(key, 'img', course.course_img)
CONN.hset(key, 'default_price_id', policy_id)
CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict)) CONN.expire(key, 60 * 60 * 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')
courseid = request.data.get('courseid')
print(courseid)
# key = "shopping_car_%s_%s" % (USER_ID,courseid)
key = "shopping_car_%s_%s" % (USER_ID, courseid,) CONN.delete(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 = str(request.data.get('policyid')) if request.data.get('policyid') else None # key = 'shopping_car_%s_%s' %(USER_ID,course_id,)
key = "shopping_car_%s_%s" % (USER_ID, course_id,) if not CONN.exists(key):
response.code = 10007
response.error = '课程不存在'
return Response(response.dict) price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))
if policy_id not in price_policy_dict:
response.code = 10008
response.error = '价格策略不存在'
return Response(response.dict) CONN.hset(key,'default_price_id',policy_id)
CONN.expire(key, 20 * 60) # 有效期20分钟
response.data = '修改成功'
except Exception as e:
response.code = 10009
response.error = '修改失败' return Response(response.dict)

参数说明:

CONN.expire 表示设置有效期,单位是秒。60 * 60 * 24,表示一天

使用postman,发送get请求

提示认证失败

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

发送一个错误的token

提示认证失败!注意:这里直接被认证组件拦截了,并没有到达视图

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

发送一个正确的token,从数据库里面copy一下

返回code为10000,表示认证成功!

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

查看Pycharm控制台输出:

Account object
UserToken object
shopping c8aa8609-fb14-43ea-a6cf-96b2c2469b01

上面2个值,就被自定义类覆盖了!

既然得到了用户对象,那么常量USER_ID就可以删除了

修改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 api.utils.response import BaseResponse
import json
import redis
from django.conf import settings
from api.utils.auth import LuffyAuthentication # redis连接
CONN = redis.Redis(host=settings.REDIS_SERVER.get('host'),port=settings.REDIS_SERVER.get('port')) class ShoppingCartView(ViewSetMixin,APIView):
# 开启认证,指定认证类
authentication_classes = [LuffyAuthentication,] def list(self, request, *args, **kwargs):
"""
查看购物车信息
:param request:
:param args:
:param kwargs:
:return:
"""
ret = {'code':10000,'data':None,'error':None}
try:
# request.user和request.auth是源码返回的
# 如果自定义认证类返回了一个元组,元组里面有2个值。
# 它会覆盖上面2个值,request.user和request.auth
print(request.user) # 认证类返回的第一个值
print(request.auth) # 认证类返回的第二个值
# 获取token
print('shopping',request.query_params.get('token')) shopping_car_course_list = [] # pattern = "shopping_car_%s_*" % (request.user.id,)
pattern = "shopping_car_%s_%s" % (request.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
except Exception as e:
# print(e)
ret['code'] = 10005
ret['error'] = '获取购物车数据失败' # print(ret)
# print(json.dumps(ret))
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
"""
相关问题:
a. 如果让你编写一个API程序,你需要先做什么?
- 业务需求
- 统一数据传输格式
- 表结构设计
- 程序开发
b. django restful framework的解析器的parser_classes的作用?
根据请求中Content-Type请求头的值,选择指定解析对请求体中的数据进行解析。
如:
请求头中含有Content-type: application/json 则内部使用的是JSONParser,JSONParser可以自动去请求体request.body中
获取请求数据,然后进行 字节转字符串、json.loads反序列化; c. 支持多个解析器(一般只是使用JSONParser即可) """
course_id = request.data.get('courseid')
policy_id = request.data.get('policyid') # 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,
'price': item.price,
'valid_period': item.valid_period,
'valid_period_display': item.get_valid_period_display()
}
price_policy_dict[item.id] = temp print(price_policy_dict,type(price_policy_dict))
print(policy_id,type(policy_id))
if policy_id not in price_policy_dict:
return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'}) # 3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
"""
购物车中要放:
课程ID
课程名称
课程图片
默认选中的价格策略
所有价格策略
{
shopping_car_1_1:{
id:课程ID
name:课程名称
img:课程图片
defaut:默认选中的价格策略
price_list:所有价格策略
}, } """ pattern = "shopping_car_%s_%s" % (request.user.id, '*',)
keys = CONN.keys(pattern)
if keys and len(keys) >= 1000:
return Response({'code': 10009, 'error': '购物车东西太多,先去结算再进行购买..'}) # key = "shopping_car_%s_%s" %(request.user.id,course_id,)
key = "shopping_car_%s_%s" % (request.user.id, course_id,)
print(key,'')
CONN.hset(key, 'id', course_id)
CONN.hset(key, 'name', course.name)
CONN.hset(key, 'img', course.course_img)
CONN.hset(key, 'default_price_id', policy_id)
CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict)) CONN.expire(key, 60 * 12 * 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')
courseid = request.data.get('courseid')
print(courseid)
# key = "shopping_car_%s_%s" % (request.user.id,courseid)
key = "shopping_car_%s_%s" % (request.user.id, courseid,) CONN.delete(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 = str(request.data.get('policyid')) if request.data.get('policyid') else None # key = 'shopping_car_%s_%s' %(request.user.id,course_id,)
key = "shopping_car_%s_%s" % (request.user.id, course_id,) if not CONN.exists(key):
response.code = 10007
response.error = '课程不存在'
return Response(response.dict) price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))
if policy_id not in price_policy_dict:
response.code = 10008
response.error = '价格策略不存在'
return Response(response.dict) CONN.hset(key,'default_price_id',policy_id)
CONN.expire(key, 20 * 60)
response.data = '修改成功'
except Exception as e:
response.code = 10009
response.error = '修改失败' return Response(response.dict)

测试get请求

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

全局配置

假设有100个类,有98个视图要认证。可以加到全局里面,修改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_AUTHENTICATION_CLASSES':['api.utils.auth.LuffyAuthentication',]
}

那么登录和查看课程,是不需要认证的。怎么忽略呢?

修改views目录下的auth.py,定义认证类为空列表,表示不认证!

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from api import models
from api.utils.response import BaseResponse
import uuid class AuthView(ViewSetMixin,APIView):
authentication_classes = [] # 空列表表示不认证 def login(self,request,*args,**kwargs):
"""
用户登陆认证
:param request:
:param args:
:param kwargs:
:return:
"""
response = BaseResponse() # 默认状态
try:
user = request.data.get('username')
pwd = request.data.get('password')
# 验证用户和密码
obj = models.Account.objects.filter(username=user,password=pwd).first()
if not obj:
response.code = 10002
response.error = '用户名或密码错误'
else:
uid = str(uuid.uuid4()) # 生成唯一id
# 保存到数据库中,update_or_create表示更新或者创建
# user=obj,这个是判断条件。当条件成立,更新token字段,值为uid
# 当条件不成立时,增加一条记录。注意:增加时,有2个字段,分别是user和token
models.UserToken.objects.update_or_create(user=obj, defaults={'token': uid})
response.code = 99999
response.data = uid except Exception as e:
response.code = 10005
response.error = '操作异常' return Response(response.dict)

使用postman测试登录

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

查看返回结果

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

修改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_AUTHENTICATION_CLASSES':['api.utils.auth.LuffyAuthentication',]
}

问题:认证类为什么要继承BaseAuthentication?

查看源码BaseAuthentication

class BaseAuthentication(object):
"""
All authentication classes should extend BaseAuthentication.
""" def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
raise NotImplementedError(".authenticate() must be overridden.") def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
pass

发现,只要执行了authenticate方法,它会执行raise。它会主动报错

为了不让它报错,子类继承BaseAuthentication后,必须重写authenticate方法,才不会报错。

这样做的目的,是为了约束子类,哪些方法,必须要定义!

二、结算中心

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

点击去结算,会发送一次post请求。那么它该发送什么数据呢?

只需要发送课程id就可以了?为什么呢?
因为redis中有购物车相关数据!后台根据课程id去购物车中获取,要结算的课程就可以了!

结算中心和购物车一样,也是一个临时数据。它也需要放到redis中!

先来看购物车的数据结构

购物车 = {
'shopping_car_1_3':{
name:'',
src:'xx'
price_id:1,
price_dict = {
1:....
}
},
'shopping_car_1_1':{
...
},
'shopping_car_1_5':{
...
}, }

再来看结算中新的数据结构

结算中心 = {
'payment_1_3':{
id:3,
mame:Django框架学习,
price_id:1,
price_priod:30,
price:199,
defaul_coupon_id:0,
coupon_dict: { ----> 绑定了课程3的优惠券
0: '请选择课程优惠券',
1:'xxx',
2:'xxx',
3:'xxx',
4:'xxx',
}
},
'payment_1_1':{
id:1,
mame:Django框架学习,
price_id:1,
price_priod:30,
price:199,
defaul_coupon_id:0,
coupon_dict: { ----> 绑定了课程1的优惠券
0: '请选择课程优惠券',
1:'xxx',
2:'xxx',
3:'xxx',
4:'xxx',
}
},
}

优惠券

优惠券分为2大类:绑定课程和非绑定课程

点击去结算

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

在左下角,展示的是非绑定课程的优惠券。

在右边的下拉菜单中,展示的是绑定课程的优惠券

在views目录下,创建文件payment.py

import json
import redis
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api.utils.auth import LuffyAuthentication
from api import models
from api.utils.response import BaseResponse # redis连接
CONN = redis.Redis(host=settings.REDIS_SERVER.get('host'),port=settings.REDIS_SERVER.get('port')) class PaymentView(ViewSetMixin, APIView):
authentication_classes = [LuffyAuthentication, ] def create(self, request, *args, **kwargs):
"""
在结算中添加课程
:param request:
:param args:
:param kwargs:
:return:
"""
# 1.接受用户选择的要结算的课程ID列表 # 2.清空当前用户request.user.id结算中心的数据
# key = payment_1* # 3.循环要加入结算中的所有课程ID列表 """
for course_id in 用户提交课程ID列表:
3.1 根据course_id,request.user.id去购物车中获取商品信息:商品名称、图片、价格(id,周期,显示周期,价格)
3.2 根据course_id,request.user.id获取
- 当前用户
- 当前课程
- 可用的优惠券 加入结算中心 提示:可以使用contenttypes
""" # 4.获取当前用户所有未绑定课程优惠券
# - 未使用
# - 有效期内
# - 加入结算中心:glocal_coupon_用户ID def list(self, request, *args, **kwargs):
"""
查看结算中心
:param request:
:param args:
:param kwargs:
:return:
""" # 1. 根据用户ID去结算中心获取该用户所有要结算课程 # 2. 根据用户ID去结算中心获取该用户所有可用未绑定课程的优惠券 # 3. 用户表中获取贝里余额 # 4. 以上数据构造成一个字典
return Response('...') def update(self, request, *args, **kwargs):
"""
更新优惠券
:param request:
:param args:
:param kwargs:
:return:
"""
# 1. 获取用户提交:
# course_id=1,coupon_id=3
# course_id=0,coupon_id=6 # 2. course_id=1 --> 去结算中心获取当前用户所拥有的绑定当前课程优惠,并进行校验
# - 成功:defaul_coupon_id=3
# - 否则:非法请求 # 2. course_id=0 --> 去结算中心获取当前用户所拥有的未绑定课程优惠,并进行校验
# - 成功:defaul_coupon_id=3
# - 否则:非法请求

course_id为空,表示 未绑定课程,否则为绑定课程

这里面展示的是一些业务逻辑,需要自己用代码来填充

提示你的代码编写能力!

三、django-redis

介绍

django-redis 基于 BSD 许可, 是一个使 Django 支持 Redis cache/session 后端的全功能组件

django-redis 中文文档,请参考

http://django-redis-chs.readthedocs.io/zh_CN/latest/

为何要用 django-redis ?

因为:

  • 持续更新
  • 本地化的 redis-py URL 符号连接字符串
  • 可扩展客户端
  • 可扩展解析器
  • 可扩展序列器
  • 默认客户端主/从支持
  • 完善的测试
  • 已在一些项目的生产环境中作为 cache 和 session 使用
  • 支持永不超时设置
  • 原生进入 redis 客户端/连接池支持
  • 高可配置 ( 例如仿真缓存的异常行为 )
  • 默认支持 unix 套接字
  • 支持 Python 2.7, 3.4, 3.5 以及 3.6

安装

安装 django-redis 最简单的方法就是用 pip :

pip install django-redis

作为 cache backend 使用配置

为了使用 django-redis , 你应该将你的 django cache setting 改成这样:

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}

举例:

在上面购物车中,使用了缓存。结算中心也需要使用缓存,那么就可以定义一个全局配置。当需要使用时,导入一下配置即可!

修改settings.py,最后一行添加

# ######django-redis的配置 #################
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.218.140:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100},
# "PASSWORD": "密码",
}
}
}

参数解释:

BACKEND 表示后台连接

OPTIONS 表示参数

CONNECTION_POOL_KWARGS 表示连接池。max_connections表示最大连接数

连接池,请参考链接:

https://baike.baidu.com/item/%E8%BF%9E%E6%8E%A5%E6%B1%A0%E6%8A%80%E6%9C%AF/523659?fr=aladdin

上面定义了100个连接池,假设100进程,都在使用连接池。当地101个访问时,会等待。直到有空闲的进程时,才处理!

不过redis的处理是很快的,很少会出现等待的情况!

使用连接池,有很多优点:

1.减少连接创建时间
2.简化的编程模式
3.受控的资源使用

使用连接池,性能会更高好!

视图中使用

加上2行代码,就可以了

from django_redis import get_redis_connection
CONN = get_redis_connection("default")

这里的default指的是settings.py中CACHES配置项的default

修改views目录下的shoppingcar.py

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
import json
from api.utils.auth import LuffyAuthentication
from django_redis import get_redis_connection CONN = get_redis_connection("default") # 使用redis连接池 class ShoppingCartView(ViewSetMixin,APIView):
# 开启认证,指定认证类
authentication_classes = [LuffyAuthentication,] def list(self, request, *args, **kwargs):
"""
查看购物车信息
:param request:
:param args:
:param kwargs:
:return:
"""
ret = {'code':10000,'data':None,'error':None}
try:
# request.user和request.auth是源码返回的
# 如果自定义认证类返回了一个元组,元组里面有2个值。
# 它会覆盖上面2个值,request.user和request.auth
print(request.user) # 认证类返回的第一个值
print(request.auth) # 认证类返回的第二个值
# 获取token
print('shopping',request.query_params.get('token')) shopping_car_course_list = [] # pattern = "shopping_car_%s_*" % (request.user.id,)
pattern = "shopping_car_%s_%s" % (request.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
except Exception as e:
# print(e)
ret['code'] = 10005
ret['error'] = '获取购物车数据失败' # print(ret)
# print(json.dumps(ret))
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
"""
相关问题:
a. 如果让你编写一个API程序,你需要先做什么?
- 业务需求
- 统一数据传输格式
- 表结构设计
- 程序开发
b. django restful framework的解析器的parser_classes的作用?
根据请求中Content-Type请求头的值,选择指定解析对请求体中的数据进行解析。
如:
请求头中含有Content-type: application/json 则内部使用的是JSONParser,JSONParser可以自动去请求体request.body中
获取请求数据,然后进行 字节转字符串、json.loads反序列化; c. 支持多个解析器(一般只是使用JSONParser即可) """
course_id = request.data.get('courseid')
policy_id = request.data.get('policyid') # 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,
'price': item.price,
'valid_period': item.valid_period,
'valid_period_display': item.get_valid_period_display()
}
price_policy_dict[item.id] = temp print(price_policy_dict,type(price_policy_dict))
print(policy_id,type(policy_id))
if policy_id not in price_policy_dict:
return Response({'code': 10002, 'error': '傻×,价格策略别瞎改'}) # 3. 把商品和价格策略信息放入购物车 SHOPPING_CAR
"""
购物车中要放:
课程ID
课程名称
课程图片
默认选中的价格策略
所有价格策略
{
shopping_car_1_1:{
id:课程ID
name:课程名称
img:课程图片
defaut:默认选中的价格策略
price_list:所有价格策略
}, } """ pattern = "shopping_car_%s_%s" % (request.user.id, '*',)
keys = CONN.keys(pattern)
if keys and len(keys) >= 1000:
return Response({'code': 10009, 'error': '购物车东西太多,先去结算再进行购买..'}) # key = "shopping_car_%s_%s" %(request.user.id,course_id,)
key = "shopping_car_%s_%s" % (request.user.id, course_id,)
print(key,'')
CONN.hset(key, 'id', course_id)
CONN.hset(key, 'name', course.name)
CONN.hset(key, 'img', course.course_img)
CONN.hset(key, 'default_price_id', policy_id)
CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict)) CONN.expire(key, 60 * 12 * 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')
courseid = request.data.get('courseid')
print(courseid)
# key = "shopping_car_%s_%s" % (request.user.id,courseid)
key = "shopping_car_%s_%s" % (request.user.id, courseid,) CONN.delete(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 = str(request.data.get('policyid')) if request.data.get('policyid') else None # key = 'shopping_car_%s_%s' %(request.user.id,course_id,)
key = "shopping_car_%s_%s" % (request.user.id, course_id,) if not CONN.exists(key):
response.code = 10007
response.error = '课程不存在'
return Response(response.dict) price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))
if policy_id not in price_policy_dict:
response.code = 10008
response.error = '价格策略不存在'
return Response(response.dict) CONN.hset(key,'default_price_id',policy_id)
CONN.expire(key, 20 * 60)
response.data = '修改成功'
except Exception as e:
response.code = 10009
response.error = '修改失败' return Response(response.dict)

使用postman测试访问,要带上正确的token

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

访问正常

作为 session backend 使用配置

Django 默认可以使用任何 cache backend 作为 session backend, 将 django-redis 作为 session 储存后端不用安装任何额外的 backend

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

举例:

修改settings.py

# ######django-redis的配置 #################
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.218.140:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100},
# "PASSWORD": "密码",
}
}
} ###使用redis缓存session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存

简单来讲,加上2行就可以了。下面的那些配置,是参考源码设置的。

比如session失效时间是2周

如果需要修改,在这里指定一下,就可以了!

注意:里面的defalut就是redis配置的defalut,名字是一一对应的!

总结:

1. django-redis的作用
- 连接redis并在redis中进行操作(含redis连接池)。 2. 帮助用户将session放到redis
- django-redis的配置
- session的配置

作业:

完整结算中心的代码,实现以下功能:

1. 添加

2. 查看

3. 修改

注意:使用认证+django-redis

修改utils目录下的auth.py

当为GET请求时,从url中取token,否则从请求体中获取token

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed from api import models class LuffyAuthentication(BaseAuthentication): def authenticate(self, request):
"""
用户认证
:param request:
:return:
"""
# print(request.method)
# 判断请求方式
if request.method == "GET":
token = request.query_params.get('token')
else:
token = request.data.get('token') # print('auth',token)
token_obj = models.UserToken.objects.filter(token=token).first()
if not token_obj:
# 认证失败
raise AuthenticationFailed({'code':1008,'error':'认证失败'})
# 认证成功
# return (token_obj.user,token_obj)
return (token_obj.user,token_obj)

修改models.py,在用户表增加字段

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.utils.safestring import mark_safe
from django.db import models
import hashlib # ######################## 课程相关 ######################## class CourseCategory(models.Model):
"""课程大类, e.g 前端 后端..."""
name = models.CharField(max_length=64, unique=True) def __str__(self):
return "%s" % self.name class Meta:
verbose_name_plural = "01.课程大类" class CourseSubCategory(models.Model):
"""课程子类, e.g python linux """
category = models.ForeignKey("CourseCategory")
name = models.CharField(max_length=64, unique=True) def __str__(self):
return "%s" % self.name class Meta:
verbose_name_plural = "02.课程子类" class DegreeCourse(models.Model):
"""学位课程"""
name = models.CharField(max_length=128, unique=True)
course_img = models.CharField(max_length=255, verbose_name="缩略图")
brief = models.TextField(verbose_name="学位课程简介", )
total_scholarship = models.PositiveIntegerField(verbose_name="总奖学金(贝里)", default=40000) # 2000 2000
mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本课程的导师辅导费用(贝里)", default=15000)
period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=150) # 为了计算学位奖学金
prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师") # 用于GenericForeignKey反向查询, 不会生成表字段,切勿删除
# coupon = GenericRelation("Coupon") # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
degreecourse_price_policy = GenericRelation("PricePolicy") def __str__(self):
return self.name class Meta:
verbose_name_plural = "03.学位课" class Teacher(models.Model):
"""讲师、导师表"""
name = models.CharField(max_length=32)
role_choices = ((0, '讲师'), (1, '导师'))
role = models.SmallIntegerField(choices=role_choices, default=0)
title = models.CharField(max_length=64, verbose_name="职位、职称")
signature = models.CharField(max_length=255, help_text="导师签名", blank=True, null=True)
image = models.CharField(max_length=128)
brief = models.TextField(max_length=1024) def __str__(self):
return self.name class Meta:
verbose_name_plural = "04.导师或讲师" class Scholarship(models.Model):
"""学位课程奖学金"""
degree_course = models.ForeignKey("DegreeCourse")
time_percent = models.PositiveSmallIntegerField(verbose_name="奖励档位(时间百分比)", help_text="只填百分值,如80,代表80%")
value = models.PositiveIntegerField(verbose_name="奖学金数额") def __str__(self):
return "%s:%s" % (self.degree_course, self.value) class Meta:
verbose_name_plural = "05.学位课奖学金" class Course(models.Model):
"""专题课/学位课模块表"""
name = models.CharField(max_length=128, unique=True)
course_img = models.CharField(max_length=255)
sub_category = models.ForeignKey("CourseSubCategory")
course_type_choices = ((0, '付费'), (1, 'VIP专享'), (2, '学位课程'))
course_type = models.SmallIntegerField(choices=course_type_choices) # 不为空;学位课的某个模块
# 为空;专题课
degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, help_text="若是学位课程,此处关联学位表") brief = models.TextField(verbose_name="课程概述", max_length=2048)
level_choices = ((0, '初级'), (1, '中级'), (2, '高级'))
level = models.SmallIntegerField(choices=level_choices, default=1)
pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True)
period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=7) #
order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排")
attachment_path = models.CharField(max_length=128, verbose_name="课件路径", blank=True, null=True)
status_choices = ((0, '上线'), (1, '下线'), (2, '预上线'))
status = models.SmallIntegerField(choices=status_choices, default=0)
template_id = models.SmallIntegerField("前端模板id", default=1) coupon = GenericRelation("Coupon") # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
price_policy = GenericRelation("PricePolicy") asked_question = GenericRelation("OftenAskedQuestion") def __str__(self):
return "%s(%s)" % (self.name, self.get_course_type_display()) def save(self, *args, **kwargs):
if self.course_type == 2:
if not self.degree_course:
raise ValueError("学位课程必须关联对应的学位表")
super(Course, self).save(*args, **kwargs) class Meta:
verbose_name_plural = "06.专题课或学位课模块" class CourseDetail(models.Model):
"""课程详情页内容"""
course = models.OneToOneField("Course")
hours = models.IntegerField("课时")
course_slogan = models.CharField(max_length=125, blank=True, null=True)
video_brief_link = models.CharField(verbose_name='课程介绍', max_length=255, blank=True, null=True)
why_study = models.TextField(verbose_name="为什么学习这门课程")
what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容")
career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯")
prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True)
teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师") def __str__(self):
return "%s" % self.course class Meta:
verbose_name_plural = "07.课程或学位模块详细" class OftenAskedQuestion(models.Model):
"""常见问题"""
content_type = models.ForeignKey(ContentType) # 关联course or degree_course
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') question = models.CharField(max_length=255)
answer = models.TextField(max_length=1024) def __str__(self):
return "%s-%s" % (self.content_object, self.question) class Meta:
unique_together = ('content_type', 'object_id', 'question')
verbose_name_plural = "08. 常见问题" class CourseOutline(models.Model):
"""课程大纲"""
course_detail = models.ForeignKey("CourseDetail")
title = models.CharField(max_length=128)
# 前端显示顺序
order = models.PositiveSmallIntegerField(default=1) content = models.TextField("内容", max_length=2048) def __str__(self):
return "%s" % self.title class Meta:
unique_together = ('course_detail', 'title')
verbose_name_plural = "09. 课程大纲" class CourseChapter(models.Model):
"""课程章节"""
course = models.ForeignKey("Course")
chapter = models.SmallIntegerField(verbose_name="第几章", default=1)
name = models.CharField(max_length=128)
summary = models.TextField(verbose_name="章节介绍", blank=True, null=True)
pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True) class Meta:
unique_together = ("course", 'chapter')
verbose_name_plural = "10. 课程章节" def __str__(self):
return "%s:(第%s章)%s" % (self.course, self.chapter, self.name) class CourseSection(models.Model):
"""课时目录"""
chapter = models.ForeignKey("CourseChapter")
name = models.CharField(max_length=128)
order = models.PositiveSmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时")
section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频'))
section_type = models.SmallIntegerField(default=2, choices=section_type_choices)
section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link")
video_time = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32) # 仅在前端展示使用
pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True)
free_trail = models.BooleanField("是否可试看", default=False) class Meta:
unique_together = ('chapter', 'section_link')
verbose_name_plural = "11. 课时" def __str__(self):
return "%s-%s" % (self.chapter, self.name) class Homework(models.Model):
chapter = models.ForeignKey("CourseChapter")
title = models.CharField(max_length=128, verbose_name="作业题目")
order = models.PositiveSmallIntegerField("作业顺序", help_text="同一课程的每个作业之前的order值间隔1-2个数")
homework_type_choices = ((0, '作业'), (1, '模块通关考核'))
homework_type = models.SmallIntegerField(choices=homework_type_choices, default=0)
requirement = models.TextField(max_length=1024, verbose_name="作业需求")
threshold = models.TextField(max_length=1024, verbose_name="踩分点")
recommend_period = models.PositiveSmallIntegerField("推荐完成周期(天)", default=7)
scholarship_value = models.PositiveSmallIntegerField("为该作业分配的奖学金(贝里)")
note = models.TextField(blank=True, null=True)
enabled = models.BooleanField(default=True, help_text="本作业如果后期不需要了,不想让学员看到,可以设置为False") class Meta:
unique_together = ("chapter", "title")
verbose_name_plural = "12. 章节作业" def __str__(self):
return "%s - %s" % (self.chapter, self.title) # class CourseReview(models.Model):
# """课程评价"""
# enrolled_course = models.OneToOneField("EnrolledCourse")
# about_teacher = models.FloatField(default=0, verbose_name="讲师讲解是否清晰")
# about_video = models.FloatField(default=0, verbose_name="内容实用")
# about_course = models.FloatField(default=0, verbose_name="课程内容通俗易懂")
# review = models.TextField(max_length=1024, verbose_name="评价")
# disagree_number = models.IntegerField(default=0, verbose_name="踩")
# agree_number = models.IntegerField(default=0, verbose_name="赞同数")
# tags = models.ManyToManyField("Tags", blank=True, verbose_name="标签")
# date = models.DateTimeField(auto_now_add=True, verbose_name="评价日期")
# is_recommend = models.BooleanField("热评推荐", default=False)
# hide = models.BooleanField("不在前端页面显示此条评价", default=False)
#
# def __str__(self):
# return "%s-%s" % (self.enrolled_course.course, self.review)
#
# class Meta:
# verbose_name_plural = "13. 课程评价(购买课程后才能评价)"
#
#
# class DegreeCourseReview(models.Model):
# """学位课程评价
# 为了以后可以定制单独的评价内容,所以不与普通课程的评价混在一起,单独建表
# """
# enrolled_course = models.ForeignKey("EnrolledDegreeCourse")
# course = models.ForeignKey("Course", verbose_name="评价学位模块", blank=True, null=True,
# help_text="不填写即代表评价整个学位课程", limit_choices_to={'course_type': 2})
# about_teacher = models.FloatField(default=0, verbose_name="讲师讲解是否清晰")
# about_video = models.FloatField(default=0, verbose_name="视频质量")
# about_course = models.FloatField(default=0, verbose_name="课程")
# review = models.TextField(max_length=1024, verbose_name="评价")
# disagree_number = models.IntegerField(default=0, verbose_name="踩")
# agree_number = models.IntegerField(default=0, verbose_name="赞同数")
# tags = models.ManyToManyField("Tags", blank=True, verbose_name="标签")
# date = models.DateTimeField(auto_now_add=True, verbose_name="评价日期")
# is_recommend = models.BooleanField("热评推荐", default=False)
# hide = models.BooleanField("不在前端页面显示此条评价", default=False)
#
# def __str__(self):
# return "%s-%s" % (self.enrolled_course, self.review)
#
# class Meta:
# verbose_name_plural = "14. 学位课评价(购买课程后才能评价)" class PricePolicy(models.Model):
"""价格与有课程效期表"""
content_type = models.ForeignKey(ContentType) # 关联course or degree_course
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id') # course = models.ForeignKey("Course")
valid_period_choices = ((1, '1天'), (3, '3天'),
(7, '1周'), (14, '2周'),
(30, '1个月'),
(60, '2个月'),
(90, '3个月'),
(180, '6个月'), (210, '12个月'),
(540, '18个月'), (720, '24个月'),
)
valid_period = models.SmallIntegerField(choices=valid_period_choices)
price = models.FloatField() class Meta:
unique_together = ("content_type", 'object_id', "valid_period")
verbose_name_plural = "15. 价格策略" def __str__(self):
return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price) # ################################### 优惠券相关 ################################# class Coupon(models.Model):
"""优惠券生成规则"""
name = models.CharField(max_length=64, verbose_name="活动名称")
brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍") coupon_type_choices = ((0, '立减'), (1, '满减券'), (2, '折扣券'))
coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型") money_equivalent_value = models.IntegerField(verbose_name="等值货币")
off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True)
minimum_consume = models.PositiveIntegerField("最低消费", default=0, help_text="仅在满减券时填写此字段") content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定")
content_object = GenericForeignKey('content_type', 'object_id') quantity = models.PositiveIntegerField("数量(张)", default=1)
open_date = models.DateField("优惠券领取开始时间")
close_date = models.DateField("优惠券领取结束时间")
valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True)
valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True)
# coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True,
# help_text="自券被领时开始算起")
date = models.DateTimeField(auto_now_add=True) class Meta:
verbose_name_plural = "31. 优惠券生成记录" def __str__(self):
return "%s(%s)" % (self.get_coupon_type_display(), self.name) class CouponRecord(models.Model):
"""优惠券发放、消费纪录"""
coupon = models.ForeignKey("Coupon")
account = models.ForeignKey("Account", verbose_name="拥有者") number = models.CharField(max_length=64, unique=True) status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'))
status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间") # order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单") # 一个订单可以有多个优惠券
order_id = models.IntegerField(verbose_name='关联订单ID') class Meta:
verbose_name_plural = "32. 用户优惠券" def __str__(self):
return '%s-%s-%s' % (self.account, self.number, self.status) class Account(models.Model):
username = models.CharField("用户名", max_length=64, unique=True)
email = models.EmailField(
verbose_name='邮箱',
max_length=255,
unique=True,
blank=True,
null=True
)
password = models.CharField('密码', max_length=128)
balance = models.FloatField('贝里',default=0) class Meta:
verbose_name_plural = "33. 用户表" class UserToken(models.Model):
user = models.OneToOneField(to='Account')
token = models.CharField(max_length=36) class Meta:
verbose_name_plural = "34. token表"

执行2个命令,生成字段

python manage.py makemigrations
python manage.py migrate

为用户加点钱

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

修改admin.py,注册所有表

from django.contrib import admin

# Register your models here.
from api import models
admin.site.register(models.CourseCategory)
admin.site.register(models.CourseSubCategory)
admin.site.register(models.DegreeCourse)
admin.site.register(models.Teacher)
admin.site.register(models.Scholarship)
admin.site.register(models.Course)
admin.site.register(models.CourseDetail)
admin.site.register(models.OftenAskedQuestion)
admin.site.register(models.CourseOutline)
admin.site.register(models.CourseChapter)
admin.site.register(models.CourseSection)
admin.site.register(models.Homework)
admin.site.register(models.PricePolicy)
admin.site.register(models.Coupon)
admin.site.register(models.CouponRecord)
admin.site.register(models.Account)

进入admin后台,添加几条优惠券,并绑定用户

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

list

修改payment.py,先做get请求的

import json
import redis
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api.utils.auth import LuffyAuthentication
from api import models
from api.utils.response import BaseResponse from django_redis import get_redis_connection CONN = get_redis_connection("default") # 使用redis连接池 class PaymentView(ViewSetMixin, APIView):
authentication_classes = [LuffyAuthentication, ] def create(self, request, *args, **kwargs):
"""
在结算中添加课程
:param request:
:param args:
:param kwargs:
:return:
"""
# 1.接收用户选择的要结算的课程ID列表 # 2.清空当前用户request.user.id结算中心的数据
# key = payment_1* # 3.循环要加入结算中的所有课程ID列表 """
for course_id in 用户提交课程ID列表:
3.1 根据course_id,request.user.id去购物车中获取商品信息:商品名称、图片、价格(id,周期,显示周期,价格)
3.2 根据course_id,request.user.id获取
- 当前用户
- 当前课程
- 可用的优惠券 加入结算中心 提示:可以使用contenttypes
""" # 4.获取当前用户所有未绑定课程优惠券
# - 未使用
# - 有效期内
# - 加入结算中心:glocal_coupon_用户ID def list(self, request, *args, **kwargs):
"""
查看结算中心
:param request:
:param args:
:param kwargs:
:return:
""" # 1. 根据用户ID去结算中心获取该用户所有要结算课程
course_id = request.query_params.get('course_id')
print('课程id',course_id)
obj = models.Course.objects.filter(id=course_id).first()
print('结算课程',obj.name)
# 2. 根据用户ID去结算中心获取该用户所有可用未绑定课程的优惠券
user_id =request.user.id
print('用户id', user_id)
obj2 = models.CouponRecord.objects.filter(account=user_id, coupon__object_id__isnull=True).first()
# print(obj2.coupon.get_coupon_type_display()) if obj2.coupon.coupon_type == 0:
print('{}{}'.format(obj2.coupon.get_coupon_type_display(),obj2.coupon.money_equivalent_value))
elif obj2.coupon.coupon_type == 1:
print('满{}减{}'.format(obj2.coupon.minimum_consume,obj2.coupon.money_equivalent_value))
else:
print(obj2.coupon.id)
print('{}折'.format(obj2.coupon.off_percent)) # 3. 用户表中获取贝里余额
beili = models.Account.objects.filter(id=user_id).first()
print('用户贝里',beili.balance) # 4. 以上数据构造成一个字典 return Response('...') def update(self, request, *args, **kwargs):
"""
更新优惠券
:param request:
:param args:
:param kwargs:
:return:
"""
# 1. 获取用户提交:
# course_id=1,coupon_id=3
# course_id=0,coupon_id=6 # 2. course_id=1 --> 去结算中心获取当前用户所拥有的绑定当前课程优惠,并进行校验
# - 成功:defaul_coupon_id=3
# - 否则:非法请求 # 3. course_id=0 --> 去结算中心获取当前用户所拥有的未绑定课程优惠,并进行校验
# - 成功:defaul_coupon_id=3
# - 否则:非法请求

使用postman发送GET请求

python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

查看Pycharm控制台输出

课程id 1
结算课程 Python开发入门7天特训营
用户id 1
立减10
用户贝里 100.0