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分)
- 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分)
- # 第一次下单时间,就是时间最早的
- 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分)
• 查询所有同学的学号、姓名、班级名称。(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用户认证
流程图
请求到达视图的时候,需要进行认证。
认证是在中间件之后的。如果一旦认证失败,则返回信息给用户
启动项目luffcity,访问购物车
注意:要启动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,并添加了一条记录
如果没有,请参考昨天的文档!
测试用户和密码
查看返回结果
输入正确的用户名和密码
查看返回结果,返回一个随机码。这个就是token
9999表示登录成功
既然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请求,输入正确的用户名和密码
查看表api_usertoken,发现和返回结果是一样的!
再发送一次
表的数据随之更新
增加认证
不光购物车会用到用户认证,结算中心也需要用到认证,还有其他的视图,也同样需要登录才能使用。
所以,这个认证类需要放到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请求
提示认证失败
发送一个错误的token
提示认证失败!注意:这里直接被认证组件拦截了,并没有到达视图
发送一个正确的token,从数据库里面copy一下
返回code为10000,表示认证成功!
查看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请求
全局配置
假设有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测试登录
查看返回结果
修改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方法,才不会报错。
这样做的目的,是为了约束子类,哪些方法,必须要定义!
二、结算中心
点击去结算,会发送一次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大类:绑定课程和非绑定课程
点击去结算
在左下角,展示的是非绑定课程的优惠券。
在右边的下拉菜单中,展示的是绑定课程的优惠券
在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
访问正常
作为 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
为用户加点钱
修改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后台,添加几条优惠券,并绑定用户
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请求
查看Pycharm控制台输出
- 课程id 1
- 结算课程 Python开发入门7天特训营
- 用户id 1
- 立减10
- 用户贝里 100.0
python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)的更多相关文章
- python 全栈开发,Day98(路飞学城背景,django ContentType组件,表结构讲解)
昨日内容回顾 1. 为什么要做前后端分离? - 前后端交给不同的人来编写,职责划分明确. - API (IOS,安卓,PC,微信小程序...) - vue.js等框架编写前端时,会比之前写jQuery ...
- python 全栈开发,Day97(Token 认证的来龙去脉,DRF认证,DRF权限,DRF节流)
昨日内容回顾 1. 五个葫芦娃和三行代码 APIView(views.View) 1. 封装了Django的request - request.query_params --> 取URL中的参数 ...
- python 全栈开发,Day95(RESTful API介绍,基于Django实现RESTful API,DRF 序列化)
昨日内容回顾 1. rest framework serializer(序列化)的简单使用 QuerySet([ obj, obj, obj]) --> JSON格式数据 0. 安装和导入: p ...
- 巨蟒python全栈开发django10:ajax&&登录认证
通过题目进行知识点回顾: 聚合查询 From django.db.models import Avg,Min,Max,F,Q,Count,Sum #查询书籍的平均值 Ret= Models.Book. ...
- Python 全栈开发【第0篇】:目录
Python 全栈开发[第0篇]:目录 第一阶段:Python 开发入门 Python 全栈开发[第一篇]:计算机原理&Linux系统入门 Python 全栈开发[第二篇]:Python基 ...
- python 全栈开发,Day99(作业讲解,DRF版本,DRF分页,DRF序列化进阶)
昨日内容回顾 1. 为什么要做前后端分离? - 前后端交给不同的人来编写,职责划分明确. - API (IOS,安卓,PC,微信小程序...) - vue.js等框架编写前端时,会比之前写jQuery ...
- python全栈开发-Day2 布尔、流程控制、循环
python全栈开发-Day2 布尔 流程控制 循环 一.布尔 1.概述 #布尔值,一个True一个False #计算机俗称电脑,即我们编写程序让计算机运行时,应该是让计算机无限接近人脑,或者说人 ...
- python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)
python全栈开发笔记第二模块 第四章 :常用模块(第二部分) 一.os 模块的 详解 1.os.getcwd() :得到当前工作目录,即当前python解释器所在目录路径 impor ...
- Python全栈开发【面向对象进阶】
Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...
随机推荐
- MySQL中的编码问题
1.查看MySQL数据库的默认编码 (1).使用status命令 mysql> status -------------- mysql Ver 14.14 Distrib 5.5.28, for ...
- JVM总结(四):JVM类加载机制
这一节我们来总结一下JVM类加载机制.具体目录如下: 类加载的过程 类加载过程概括 说说引用 详解类加载全过程: 加载 验证 准备 解析 初始化 虚拟机把描述类的数据从Class文件加载到内存,并对数 ...
- MVC中常用的跳转方法
MVC中常用的跳转方法 这里总结了几种MVC中的跳转方式,主要汇总了前端页面跳转,后台的跳转,和过滤器中的跳转方式. 1.在前端页面的跳转方式 <!--属性形式---> <a hre ...
- SqlServer中的临时表
一.什么是临时表 临时表属于会话级的,会话结束的时候,临时表被释放,其创建.使用.删除都和普通表一样,临时表空间一般利用虚拟内存,不必进行磁盘I/O,因此效率较高..临时表有两种:普通临时表 (#Tb ...
- linux 进程 ctrl-c,ctrl-z,ctrl-d
linux下: ctrl-c 发送 SIGINT 信号给前台进程组中的所有进程.常用于终止正在运行的程序. ctrl-z 发送 SIGTSTP 信号给前台进程组中的所有进程,常用于挂起一个进程. ct ...
- 转--python 面试题
# 每一题都值得好好琢磨钻透 [原文地址](http://www.cnblogs.com/Allen-rg/p/7693394.html)1.Python是如何进行内存管理的? 答:从三个方面来说,一 ...
- 由-webkit-transform-style:preserve-3d;所想
看一个用css3写幻灯片的demo用到了这么几个属性 .demo{ -webkit-transform-style:preserve-3d; -webkit-perspective:800px; -w ...
- UEditor百度编辑器,工具栏自定义添加一个普通按钮
根据网上前辈提供的,还真的不错,下面也整理一下 添加一个名叫“macros”的普通按钮在工具栏上: 第一步:找到ueditor.config.js文件中的toolbars数组,增加一个“macros” ...
- sql 把多列内容合并
这个语句不完整.应该是这样:stuff(select ',' + fieldname from tablename for xml path('')),1,1,'') as ’别名‘这一整句的作用是 ...
- 第3月第1天 GCDAsyncSocket dispatch_source_set_event_handler runloop
+ (void)startCFStreamThreadIfNeeded { LogTrace(); static dispatch_once_t predicate; dispatch_once(&a ...