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

考试第二部分:MySQL数据库

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

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

 

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

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

 

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

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

 

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 会命中索引
View Code

解释:

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

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

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

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

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

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

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

你不能直接到第二关的

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

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

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

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

 

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

select * from Student where 性别=''
View Code

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

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

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

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

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

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

 

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;
View Code

 

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

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

 

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

#最简单的设计

商品表
- id
- 名称
- 价格
- 描述信息

订单表
- id
- 订单号(唯一)
- 商品id
- 用户id

用户表
- id
- username
- password
View Code

 

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
View Code

 

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'
View Code

 

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;
View Code

 

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

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

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

 

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

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

 

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

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

 

一、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)
View Code

请确保已经生成了表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表"
View Code

使用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)
View Code

再次发送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)
View Code

修改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,'1111111111')
        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)
View Code

参数说明:

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,'1111111111')
        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)
View Code

测试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',]
}
View Code

 

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

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

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

 

问题:认证类为什么要继承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
View Code

发现,只要执行了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':{
        ...
    },

}
View Code

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

结算中心 = {
    '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',
        }
    },
}
View Code

 

优惠券

优惠券分为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
        #       - 否则:非法请求
View Code

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,'1111111111')
        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)
View Code

使用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,默认修改之后才保存
View Code

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

比如session失效时间是2周

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

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

 

总结:

1. django-redis的作用
    - 连接redis并在redis中进行操作(含redis连接池)。

2. 帮助用户将session放到redis
    - django-redis的配置
    - session的配置
View Code

 

作业:

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

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)
View Code

修改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表"
View Code

执行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)
View Code

进入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
        #       - 否则:非法请求
View Code

使用postman发送GET请求

查看Pycharm控制台输出

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

 

posted @ 2018-08-14 15:13  肖祥  阅读(884)  评论(0编辑  收藏  举报