Django logging配置

from django.db import models

# Create your models here.

from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation

# __all__ = ['CourseCategory', 'Course', 'CourseDetail']

# ####################################### 课程表 ########################################


class CourseCategory(models.Model):
    name = models.CharField(max_length=64, unique=True)

    def __str__(self):
        return "%s" % self.name

    class Meta:
        verbose_name = "课程分类"
        verbose_name_plural = "课程分类"


class Course(models.Model):
    """
    专题课程
    """
    name = models.CharField(max_length=128, unique=True, verbose_name="模块")
    course_img = models.CharField(max_length=255)
    course_type_choices = ((0, '付费'), (1, 'VIP专享'), (2, '学位课程'))
    course_type = models.SmallIntegerField(choices=course_type_choices)
    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)
    course_category = models.ForeignKey("CourseCategory", on_delete=models.CASCADE, null=True, blank=True)
    order_details = GenericRelation("OrderDetail", related_query_name="course")
    coupon = GenericRelation("Coupon")
    price_policy = GenericRelation("PricePolicy")  # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除,如有疑问请联系老村长

    def __str__(self):
        return "%s(%s)" % (self.name, self.get_course_type_display())


class CourseDetail(models.Model):
    """课程详情页内容"""

    course = models.OneToOneField("Course", on_delete=models.CASCADE)
    hours = models.IntegerField("课时")
    course_slogan = models.CharField(max_length=125, blank=True, null=True)
    video_brief_link = models.CharField(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 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 PricePolicy(models.Model):
    """价格与有课程效期表"""
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)  # 关联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个月'),
                            (120, '4个月'),
                            (180, '6个月'), (365, '12个月'),
                            (540, '18个月'), (720, '24个月'),
                            (722, '24个月'), (723, '24个月'),
                            (999, '永久有效'),
                            )
    valid_period = models.SmallIntegerField(choices=valid_period_choices)
    price = models.FloatField()

    class Meta:
        unique_together = ("content_type", 'object_id', "valid_period")

    def __str__(self):
        return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)


class CourseChapter(models.Model):
    """课程章节"""
    course = models.ForeignKey("Course", related_name='coursechapters', on_delete=models.CASCADE)
    chapter = models.SmallIntegerField(verbose_name="第几章", default=1)
    name = models.CharField(max_length=128)
    summary = models.TextField(verbose_name="章节介绍", blank=True, null=True)
    is_create = models.BooleanField(verbose_name="是否创建题库进度", default=True)
    pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True)

    class Meta:
        unique_together = ("course", 'chapter')

    def __str__(self):
        return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)


class CourseSection(models.Model):
    """课时目录"""
    chapter = models.ForeignKey("CourseChapter", related_name='coursesections', on_delete=models.CASCADE)
    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)
    is_flash = models.BooleanField(verbose_name="是否使用FLASH播放", default=False)
    player_choices = ((0, "CC"), (1, "POLYV"), (2, "ALI"))
    player = models.SmallIntegerField(choices=player_choices, default=1, help_text="视频播放器选择")

    def course_chapter(self):
        return self.chapter.chapter

    def course_name(self):
        return self.chapter.course.name

    class Meta:
        unique_together = ('chapter', 'section_link')

    def __str__(self):
        return "%s-%s" % (self.chapter, self.name)


class OftenAskedQuestion(models.Model):
    """常见问题"""
    content_type = models.ForeignKey(ContentType, limit_choices_to={'model__contains': 'course'},
                                     on_delete=models.CASCADE)  # 关联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 = "常见问题"


# ####################################### 优惠券 ########################################

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.FloatField(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, on_delete=models.CASCADE)
    # 不绑定课程就是通用优惠券
    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="自券被领时开始算起")
    status_choices = ((0, "上线"), (1, "下线"))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return "%s(%s)" % (self.get_coupon_type_display(), self.name)


class CouponRecord(models.Model):
    """优惠券发放、消费纪录"""
    coupon = models.ForeignKey("Coupon", on_delete=models.CASCADE)
    account = models.ForeignKey("UserInfo", blank=True, null=True, verbose_name="使用者", on_delete=models.CASCADE)
    status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'), (3, '未领取'))
    status = models.SmallIntegerField(choices=status_choices, default=3)
    get_time = models.DateTimeField(blank=True, null=True, verbose_name="领取时间", help_text="用户领取时间")
    used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间")
    order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单",
                              on_delete=models.CASCADE)  # 一个订单可以有多个优惠券
    date = models.DateTimeField(auto_now_add=True, verbose_name="生成时间")

    # _coupon = GenericRelation("Coupon")

    def __str__(self):
        return self.coupon.name + "优惠券记录"


# ####################################### 订单表 ########################################

class Order(models.Model):
    """订单"""
    payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里'), (4, '银联'))
    payment_type = models.SmallIntegerField(choices=payment_type_choices)
    payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True)
    order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True)  # 考虑到订单合并支付的问题
    account = models.ForeignKey("UserInfo", on_delete=models.CASCADE)
    actual_amount = models.FloatField(verbose_name="实付金额")
    # coupon = models.OneToOneField("Coupon", blank=True, null=True, verbose_name="优惠码") #一个订单可以有多个优惠券
    status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消'))
    status = models.SmallIntegerField(choices=status_choices, verbose_name="状态")
    order_type_choices = ((0, '用户下单'), (1, '线下班创建'),)
    order_type = models.SmallIntegerField(choices=order_type_choices, default=0, verbose_name="订单类型")
    date = models.DateTimeField(auto_now_add=True, verbose_name="订单生成时间")
    pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款时间")
    cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="订单取消时间")

    def __str__(self):
        return "%s" % self.order_number


class OrderDetail(models.Model):
    """订单详情"""
    order = models.ForeignKey("Order", on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)  # 可关联普通课程或学位
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    original_price = models.FloatField("课程原价")
    price = models.FloatField("折后价格")
    content = models.CharField(max_length=255, blank=True, null=True)  #
    valid_period_display = models.CharField("有效期显示", max_length=32)  # 在订单页显示
    valid_period = models.PositiveIntegerField("有效期(days)")  # 课程有效期
    memo = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return "%s - %s - %s" % (self.order, self.content_type, self.price)

    class Meta:
        # unique_together = ("order", 'course')
        unique_together = ("order", 'content_type', 'object_id')


# ####################################### 用户表 ########################################

from django.utils.safestring import mark_safe
from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    username = models.CharField("用户名", max_length=64, unique=True)
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
        blank=True,
        null=True
    )
    uid = models.CharField(max_length=64, unique=True)  # 与第3方交互用户信息时,用这个uid,以避免泄露敏感用户信息
    mobile = models.BigIntegerField(verbose_name="手机", unique=True, help_text="用于手机验证码登录", null=True)
    qq = models.CharField(verbose_name="QQ", max_length=64, blank=True, null=True, db_index=True)
    weixin = models.CharField(max_length=128, blank=True, null=True, db_index=True, verbose_name="微信")
    signature = models.CharField('个人签名', blank=True, null=True, max_length=255)
    brief = models.TextField("个人介绍", blank=True, null=True)
    openid = models.CharField(max_length=128, blank=True, null=True)
    alipay_card = models.CharField(max_length=128, blank=True, null=True, verbose_name="支付宝账户")
    gender_choices = ((0, '保密'), (1, ''), (2, ''))
    gender = models.SmallIntegerField(choices=gender_choices, default=0, verbose_name="性别")
    id_card = models.CharField(max_length=32, blank=True, null=True, verbose_name="身份证号或护照号")
    password = models.CharField('password', max_length=128,
                                help_text=mark_safe('''<a class='btn-link' href='password'>重置密码</a>'''))
    is_active = models.BooleanField(default=True, verbose_name="账户状态")
    is_staff = models.BooleanField(verbose_name='staff status', default=False, help_text='决定着用户是否可登录管理后台')
    name = models.CharField(max_length=32, default="", verbose_name="真实姓名")
    head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png',
                                verbose_name="个人头像")
    role_choices = ((0, '学员'), (1, '导师'), (2, '讲师'), (3, '管理员'), (4, '班主任'), (5, '线下班主任'))
    role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="角色")
    # #此处通过transaction_record表就可以查到,所以不用写在这了
    memo = models.TextField('备注', blank=True, null=True, default=None, help_text="json格式存储")
    date_joined = models.DateTimeField(auto_now_add=True, verbose_name="注册时间")

    beli = models.IntegerField(default=100)

    class Meta:
        verbose_name = '账户信息'
        verbose_name_plural = "账户信息"

    def __str__(self):
        return "%s(%s)" % (self.username, self.get_role_display())


class Token(models.Model):
    """
    The default authorization token model.
    """
    key = models.CharField(max_length=40)
    user = models.OneToOneField(
        UserInfo, related_name='auth_token',
        on_delete=models.CASCADE, verbose_name="关联用户"
    )
    created = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)

    def __str__(self):
        return self.key
api.models.py
#settings.py中配置
BASE_LOG_DIR=os.path.join(BASE_DIR,"logs")  #在根目录下新建logs文件夹

LOGGING = {
    'version': 1,  # 保留字
    'disable_existing_loggers': False,  # 是否禁用已经存在的日志实例
    'formatters': {  # 定义日志的格式
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    'filters': {  # 定义日志的过滤器
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {  # 日志处理程序
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],  # 只有在Django debug为True时才在屏幕打印日志
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'SF': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,根据文件大小自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 500,  # 日志大小 500M(最好不要超过1G)
            'backupCount': 3,  # 备份数为3  xx.log --> xx.log.1 --> xx.log.2 --> xx.log.3
            'formatter': 'standard',
            'encoding': 'utf-8',  # 文件记录的编码格式
        },
        'TF': {
            'level': 'INFO',
            'class': 'logging.handlers.TimedRotatingFileHandler',  # 保存到文件,根据时间自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'backupCount': 3,  # 备份数为3  xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ...
            'when': 'D',  # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {  # 日志实例
        '': {  # 默认的logger应用如下配置
            'handlers': ['SF', 'console', 'error'],  # 上线之后可以把'console'移除
            'level': 'DEBUG',
            'propagate': True,  # 是否向上一级logger实例传递日志信息
        },
        'collect': {  # 名为 'collect' 的logger还单独处理
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}


#############view中使用
import logging
logger = logging.getLogger(__name__)

logger.info('bubuinfo')
logger.debug('bubuinfo')
logger.error('bubuinfo')




####使用####
import logging

# 实例化log对象 --> logger
# 以当前文件的名字作为logger实例的名字
logger = logging.getLogger(__name__)
# 生成一个 名字叫 collect 的日志实例
logger_c = logging.getLogger('collect')


from rest_framework.generics import ListAPIView, GenericAPIView
from api import models
from api.serializers.course import CourseModelSerializer, CourseCategoryModelSerializer
from rest_framework.response import Response
from utils.response import BaseResponse
from api.filter import MyFilter
from api.auth import LoginAuth
from api.permission import LoginRequirePermission

class CourseListView(ListAPIView):
    queryset = models.Course.objects.all()
    serializer_class = CourseModelSerializer
    filter_backends = [MyFilter, ]
    # authentication_classes = [LoginAuth, ]
    # 课程列表需要登录才能访问
    # permission_classes = [LoginRequirePermission, ]

    def get(self, request, *args, **kwargs):
        res_obj = BaseResponse()
        logger.debug(request.data)                  ###########################
        try:
            queryset = self.filter_queryset(self.get_queryset())

            # 手动过滤
            # 拿到过滤的条件
            # queryset = self.get_queryset()
            # category_id = str(request.query_params.get('category', ''))
            # logger.debug(f'category_id:{category_id}')                   ###########################
            # # 如果category_id是0或者不是数字 我们就返回所有的课程
            # if category_id != '0' and category_id.isdigit():
            #     # 按照条件去过滤
            #     queryset = queryset.filter(course_category_id=category_id)

            page = self.paginate_queryset(queryset)
            if page is not None:
                serializer = self.get_serializer(page, many=True)
                return self.get_paginated_response(serializer.data)

            serializer = self.get_serializer(queryset, many=True)
            # 因为要排序的字段是我们序列化的时候自己加的字段,不能使用内置的order_by
            data = serializer.data
            ordering_key = request.query_params.get('ordering', '')

            if ordering_key:
                if ordering_key.startswith('-'):
                    ordering_key = ordering_key[1:]
                    is_reverse = True
                elif ordering_key == 'learn_num':
                    is_reverse = True
                else:
                    is_reverse = False
                # 对返回的数据做排序
                data = sorted(data, key=lambda item: item.get(ordering_key, 0), reverse=is_reverse)
                print(data)
            res_obj.data = data
        except Exception as e:
            logger.error(str(e))          ###########################
            res_obj.code = 1
            res_obj.msg = str(e)
        return Response(res_obj.dict)


# 课程分类
class CourseCategoryView(ListAPIView):
    queryset = models.CourseCategory.objects.all()
    serializer_class = CourseCategoryModelSerializer

    def get(self, request, *args, **kwargs):
        res_obj = BaseResponse()
        queryset = self.get_queryset()
        ser_obj = self.get_serializer(queryset, many=True)
        res_obj.data = ser_obj.data
        return Response(res_obj.dict)



class CourseCategoryView(APIView):
    """课程分类接口"""
    def get(self, request):
        # 通过ORM操作获取所有分类数据
        queryset = models.Category.objects.all()
        # 利用序列化器去序列化我们的数据
        ser_obj = serializer.CourseCategorySerializer(queryset, many=True)
        # 返回
        return Response({"code":0,"data":ser_obj.data})



#api下的filter.py
from rest_framework.filters import BaseFilterBackend
import logging
logger = logging.getLogger(__name__)



class MyFilter(BaseFilterBackend):

    def filter_queryset(self, request, queryset, view):
        category_id = str(request.query_params.get('category', ''))
        logger.debug(f'category_id:{category_id}')
        # 如果category_id是0或者不是数字 我们就返回所有的课程
        if category_id != '0' and category_id.isdigit():
            # 按照条件去过滤
            queryset = queryset.filter(course_category_id=category_id)
        return queryset
View Code
from rest_framework import serializers
from api import models


class CourseModelSerializer(serializers.ModelSerializer):
    # price = serializers.SerializerMethodField()
    # learn_num = serializers.SerializerMethodField()
    learn_num = serializers.IntegerField(source='order_details.count')
    course_detail_id = serializers.IntegerField(source='coursedetail.id')

    # def get_price(self, obj):
    #     # 把所有课程永久有效的价格拿出来
    #     price_obj = obj.price_policy.all().filter(valid_period=999).first()
    #     return price_obj.price

    # def get_learn_num(self, obj):
    #     return obj.order_details.count()

    # 修改序列化结果的终极方法
    def to_representation(self, instance):
        # 调用父类的同名方法把序列化的结果拿到
        data = super().to_representation(instance)
        # 针对序列化的结果做一些自定制操作
        # 判断当前这个课程是否有永久有效的价格
        price_obj = instance.price_policy.all().filter(valid_period=999).first()
        if price_obj:
            # 有永久有效的价格
            data['has_price'] = True
            data['price'] = price_obj.price
        else:
            # 没有永久有效的价格策略
            data['has_price'] = False
        return data

    class Meta:
        model = models.Course
        fields = '__all__'


class CourseCategoryModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.CourseCategory
        fields = '__all__'
api.serializers.course.py
import logging
from rest_framework.views import APIView
from rest_framework.response import Response
from utils.response import BaseResponse
from api import models
from django.contrib.auth import authenticate
from utils.geetest import GeetestLib
import uuid
import datetime
from django.core.cache import cache
from django.shortcuts import HttpResponse
from django.conf import settings

logger = logging.getLogger(__name__)


pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"


# 初始化滑动验证码的接口
def init_geetest(request):
    user_id = 'test'
    gt = GeetestLib(pc_geetest_id, pc_geetest_key)
    status = gt.pre_process(user_id)
    # request.session[gt.GT_STATUS_SESSION_KEY] = status
    # request.session["user_id"] = user_id
    response_str = gt.get_response_str()
    return HttpResponse(response_str)


# 登录的接口
class LoginView(APIView):

    def post(self, request, *args, **kwargs):
        logger.debug(str(request.data))
        res_obj = BaseResponse()

        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.data.get(gt.FN_CHALLENGE, '')
        validate = request.data.get(gt.FN_VALIDATE, '')
        seccode = request.data.get(gt.FN_SECCODE, '')

        status = True
        if status:
            # 调用极验科技的接口检验是不是真人
            result = gt.success_validate(challenge, validate, seccode, None)
        else:
            # 本地校验是不是正经人
            result = gt.failback_validate(challenge, validate, seccode)

        if result:
            username = request.data.get('username')
            password = request.data.get('password')  # admin1234
            # 使用内置的auth模块提供的authenticate方法校验用户名密码是否正确
            user_obj = authenticate(username=username, password=password)
            # models.UserInfo.objects.filter(username=username, password=password)  # 机密的密码
            logger.debug('username:{}'.format(username))
            logger.debug('password:{}'.format(password))
            if user_obj:
                logger.debug('用户名和密码正确')
                # 创建Token
                token = uuid.uuid1().hex
                # 将Token存到数据库中
                now = datetime.datetime.now()
                # ? 不是简单地创建,如果当前用户有token就更新 没有才创建新的
                # models.Token.objects.create(key=token, user=user_obj, created=now)
                _, created = models.Token.objects.update_or_create(user=user_obj, defaults={'key': token, 'created': now})
                # if created:
                #     # 新创建的Token
                # else:
                #     # 更新的Token
                # 把Token存在缓存中
                cache.set(token, user_obj, settings.AUTH_TOKEN_TIMEOUT)

                # 给用户返回token
                res_obj.data = token
            else:
                logger.debug('用户名和密码错误')
                res_obj.code = 1
                res_obj.msg = '用户名和密码错误'
        else:
            res_obj.code = 1
            res_obj.msg = '请滑动验证码进行验证'
        return Response(res_obj.dict)
views.login.py
from rest_framework.authentication import BaseAuthentication
from api.models import Token
from rest_framework.exceptions import AuthenticationFailed
from django.conf import settings
import datetime
from django.core.cache import cache

# cache.set(key, value)
# cache.get(key)


class LoginAuth(BaseAuthentication):

    def authenticate(self, request):
        # 从请求头中拿到用户传过来的token
        # print(request.META.get('HTTP_AUTHORIZATION', ''))
        token = request.META.get('HTTP_AUTHORIZATION', '')
        # 放到缓存
        # 先从缓存中取
        user_obj = cache.get(token)
        # 缓存中能取到当前登录的用户就直接从缓存取
        if user_obj:
            return user_obj, token

        token_obj = Token.objects.filter(key=token).first()
        if token_obj:
            # token存在说明这个人是登陆过的
            # 判断token是否有效
            # 拿到过期时间
            timeout_seconds = settings.AUTH_TOKEN_TIMEOUT  # 秒数
            # 拿到当前时间
            # 不同时区不能直接相减
            now = datetime.datetime.now(tz=datetime.timezone.utc)
            # Token创建时间
            created = token_obj.created
            delta = now - (created + datetime.timedelta(seconds=timeout_seconds))
            is_ok = delta.total_seconds() < 0
            if is_ok:
                # 没过期
                return token_obj.user, token
            else:
                # 过期了
                raise AuthenticationFailed('token已过期,请重新登陆')
        raise AuthenticationFailed('无效的token')
        
        
        
# settings.py中

STATIC_URL = '/static/'

# 告诉Django用UserInfo表代替内置的 User 表
AUTH_USER_MODEL = 'api.UserInfo'  # app名.表名


# corsheaders配置项
CORS_ORIGIN_ALLOW_ALL = True

# Token有效期
# AUTH_TOKEN_TIMEOUT = 7
AUTH_TOKEN_TIMEOUT = 60*60*24*7
api.auth.py
<template>
    <div>
        <!--课程的分类或筛选-->
        <el-row style="margin-top: 20px">
            <el-col :span="16" :offset="4">
                <el-card class="box-card">
                    <el-row type="flex">
                        <span class="category-title">课程分类:</span>
                        <el-button
                                v-for="item in categoryList"
                                :key="item.id"
                                :autofocus="true"
                                @click="clickCategoty(item.id)"
                        >
                            {{ item.name }}
                        </el-button>
                    </el-row>
                    <div class="line"></div>
                    <el-row type="flex">
                        <span class="filter-title">筛&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</span>
                        <el-button
                                v-for="item in filterList"
                                :key="item.id"
                                type="text"
                                :style="{color: currentFilter===item.id?'#E6A23C':''}"
                                @click="clickFilter(item.id, item.ordering)"
                        >
                            {{ item.name }}
                            <i v-if="item.id === 2" class="el-icon-d-caret"></i>
                        </el-button>
                    </el-row>
                </el-card>
            </el-col>
        </el-row>
        <!--课程列表-->
        <div class="course-list" v-loading="loading">
            <el-row v-for="item in courseList" :key="item.id" style="margin: 20px 0" @click.native="goDetail(item.id)">
            <el-col :span="16" :offset="4">
                <el-card>
                    <div>
                        <img :src="item.course_img" alt="" class="course-img">
                    </div>
                    <h1>{{item.name}}</h1>
                    <h2>{{item.price}}</h2>
                </el-card>
            </el-col>
        </el-row>
        </div>
    </div>


</template>

<script>
    export default {
        name: "course",
        data() {
            return {
                loading: true,  // 课程列表页面的加载动画
                currentOrdering: '',   // 默认的排序参数
                categoryList: [],  // 课程分类列表
                filterList: [
                    {"id": 0, "name": "默认", "hide": false, ordering: ''},
                    {"id": 1, "name": "人气", "hide": false, ordering: '-learn_number'},
                    {"id": 2, "name": "价格", "hide": false, ordering: 'price'},
                ],
                currentCategory: 0,  // 默认选中 全部
                currentFilter: 0,  // 默认选中 默认
                courseList: [],  // 课程列表
            }
        },
        methods: {
            clickFilter(id, ordering) {
                this.currentFilter = id;
                // 1. 先判断一下当前的排序条件是不是 -price或price
                if (this.currentOrdering.endsWith('price')) {
                    // 如果满足上面的条件
                    //     原来是  price  --> -price
                    if (this.currentOrdering === 'price'){
                        this.currentOrdering = '-price'
                    }else {
                        this.currentOrdering = 'price'
                    }
                    //     原本始 -price  --> price
                    // 不满足上面的条件,就直接赋值了
                }else {
                    this.currentOrdering = ordering;
                }

                this.getCourseList()
            },
            getCategoryList(){
                // 1. 发请求到后端请求数据
            this.$axios.get('courses/category/')
                .then((res) => {
                    // console.log(res)
                    if (res.data.code === 0) {
                        // 请求数据成功
                        // console.log(res.data.data)
                        // 2. 把数据赋值给组件的数据属性
                        // console.log(this);
                        this.categoryList = res.data.data;
                        // 3. 在前面加一个全部的默认选项
                        let obj = {
                            id: 0,
                            name: '全部',
                            category: 0,
                            hide: false
                        };
                        this.categoryList.unshift(obj)
                    }
                })
            },
            getCourseList(){
                this.$axios.get(`courses/?category=${this.currentCategory}&ordering=${this.currentOrdering}`)
                    .then((res)=>{
                        console.log(res);
                        if (res.data.code === 0){
                            this.courseList = res.data.data;
                            // 去掉加载动画
                            this.loading = false
                        }
                    })
            },
            clickCategoty(id){
                console.log('要请求的章节是', id);
                this.currentCategory = id;
                // 重新请求一下 课程列表
                this.getCourseList()
            },
            goDetail(courseId){
                console.log(1111);
                // 编程式导航
                this.$router.push({name: 'courser-detail', params: {id: courseId}})
            },

        },
        created() {
            console.log(this);
            this.getCategoryList();
            this.getCourseList()

        }
    }
</script>

<style scoped>
    .line {
        height: 1px;
        background-color: #eee;
        margin: 10px 0;
    }

    .category-title,
    .filter-title {
        line-height: 40px;
    }

    .course-img {
         423px;
        height: 210px;
        float: left;
    }
</style>
Course.vue
<template>
   <el-row v-show="!$route.meta.notShowHeader">
      <el-col :span="24">
          <el-menu
                  class="el-menu-demo"
                  mode="horizontal"
                  background-color="#545c64"
                  text-color="#fff"
                  active-text-color="#ffd04b"
                  :router="true"
                  :default-active="$route.path"
                  >
                  <el-menu-item :index="item.path" v-for="(item, index) in navLinks" :key="index">{{ item.title }}</el-menu-item>

                </el-menu>
          <!--
            饿了么UI中 nav-menu 组件中 当开启:router="true" 配置项的时候 启动了 router-link
            :index="item.name"  --》 指向的是路由的 path
           -->
      </el-col>
   </el-row>
</template>

<script>
    export default {
        name: "my-header",
        data(){
            return {
                navLinks: [
                    {path: '/home', name: 'home', title: '首页'},
                    {path: '/course', name: 'course', title: '免费课程'},
                    {path: '/lite-course', name: 'lite-course', title: '轻课'},
                    {path: '/degree', name: 'degree-course', title: '学位课程'},
                ]
            }
        }
    }
</script>

<style scoped>

</style>
Header.vue
"""
结算相关的接口
"""

from rest_framework.views import APIView
from utils.response import BaseResponse
from rest_framework.response import Response
from api import models
from api.auth import LoginAuth
from api.permission import LoginRequirePermission
import django_redis
import json
import datetime
cache = django_redis.get_redis_connection()


class BuyView(APIView):
    """结算"""

    authentication_classes = [LoginAuth, ]
    permission_classes = [LoginRequirePermission, ]

    def _get_coupon_list(self, request, course_id=None):
        now = datetime.datetime.utcnow()
        coupon_list_queryset = models.CouponRecord.objects.filter(
            account=request.user,
            status=0,
            coupon__content_type__model='course',  # 限定要查那张表
            coupon__object_id=course_id,  # 限定哪一个课程
            coupon__valid_begin_date__lte=now,
            coupon__valid_end_date__gte=now,
        )
        coupon_list = []  # 该课程优惠券列表
        for coupon_obj in coupon_list_queryset:
            coupon_dict = {  # 优惠券信息字典
                'id': coupon_obj.id,
                'name': coupon_obj.coupon.name,
                'coupon_type': coupon_obj.coupon.get_coupon_type_display(),
                'money_equivalent_value': coupon_obj.coupon.money_equivalent_value,
                'off_percent': coupon_obj.coupon.off_percent,
                'minimum_consume': coupon_obj.coupon.minimum_consume,
                'valid_begin_date': datetime.datetime.strftime(coupon_obj.coupon.valid_begin_date, '%Y-%m-%d %H:%M'),
                'valid_end_date': datetime.datetime.strftime(coupon_obj.coupon.valid_end_date, '%Y-%m-%d %H:%M')
            }
            coupon_list.append(coupon_dict)
        return coupon_list

    def get(self, request, *args, **kwargs):
        res_obj = BaseResponse()
        user_id = request.user.id
        # 从缓存取结算的课程列表数据
        course_list_value = cache.get(f'BUY_{user_id}')
        if not course_list_value:
            res_obj.code = 3003
            res_obj.msg = '暂无结算数据'
            return Response(res_obj.dict)
        buy_list = json.loads(course_list_value)
        # 从缓存取通用优惠券数据
        common_coupon_value = cache.get(f'COMMON_COUPON_LIST_{user_id}')
        if common_coupon_value:
            common_coupon_list = json.loads(common_coupon_value)
        else:
            common_coupon_list = []
        # 拼接返回的数据
        res_obj.data = {
            "course_list": buy_list,
            "common_coupon_list": common_coupon_list
        }
        return Response(res_obj.dict)

    # 1. 在购物车页勾选课程和价格策略去结算
    # 2. 直接在商品页面点击 立即购买
    def post(self, request, *args, **kwargs):
        """
        向后端提交要结算的课程
        1. 课程详情页面点击 立即购买
        2. 购物车页面 勾选 批量结算

        POST 数据格式:
        {
            "course_list": [
                {"course_id": 1, "price_policy_id": 2},
                {"course_id": 2, "price_policy_id": 4}
            ]
        }

        需要获取的数据:
         课程图片
         课程名称
         课程的价格策略
         课程的优惠券

         该用户的通用优惠券
        """
        res_obj = BaseResponse()
        user_id = request.user.id
        course_list = request.data.get('course_list')
        if not course_list:
            res_obj.code = 3001
            res_obj.msg = '缺少course_list参数'
            return Response(res_obj.dict)
        course_info_list = []  # 结算页面for循环的列表(课程字典的列表)  --》 课程字典里包含需要展示的课程相关信息
        # 遍历 要结算的课程列表
        for item in course_list:
            course_id = item.get('course_id')  # 拿到课程
            course_obj = models.Course.objects.filter(id=course_id).first()
            if not course_obj:
                res_obj.code = 3002
                res_obj.msg = '无效的课程id'
                return Response(res_obj.dict)
            # 取出课程相关的信息
            course_info = {}
            course_info['id'] = course_obj.id
            course_info['img'] = course_obj.course_img
            course_info['name'] = course_obj.name
            # 取价格策略
            # 取到当前课程的所有价格策略
            course_price_policy = course_obj.price_policy.all()
            price_policy_id = item.get('price_policy_id')  # 拿到当前价格策略id
            price_policy_obj = models.PricePolicy.objects.filter(id=price_policy_id).first()  # 拿到当前价格策略对象
            if not (price_policy_obj and price_policy_obj in course_price_policy):
                res_obj.code = 3002
                res_obj.msg = '无效的价格策略id'
                return Response(res_obj.dict)
            course_info['price_policy_id'] = price_policy_obj.id  # 价格策略id
            course_info['price'] = price_policy_obj.price  # 结算页面显示的价格
            course_info['price_policy_text'] = price_policy_obj.get_valid_period_display()  # 结算页面展示该课程的有效时间

            # 查询该课程的优惠券(还在for循环中)
            # 查询的关键点 1. 当前用户 2. 当前课程  3. 未使用 4. 有效期内
            course_coupon_list = self._get_coupon_list(request, course_id=course_id)
            # 将该课程的优惠券信息保存到课程字典中
            course_info['course_coupon_list'] = course_coupon_list
            # 将该课程加入结算列表
            course_info_list.append(course_info)
            # 到此 一个for循环走完了

        # 2. 通用优惠券
        common_coupon_list = self._get_coupon_list(request)
        print('通用优惠券:', common_coupon_list)
        # 将当前用户的通用优惠券缓存起来
        cache.delete(f'COMMON_COUPON_LIST_{user_id}')
        cache.set(f'COMMON_COUPON_LIST_{user_id}', json.dumps(common_coupon_list))
        # 存到缓存等 get 来获取
        cache.delete(f'BUY_{user_id}')  # 先删除之前缓存的结算信息
        cache.set(f'BUY_{user_id}', json.dumps(course_info_list))  # 缓存此次的结算列表
        res_obj.msg = '创建结算记录成功'
        return Response(res_obj.dict)
views.buy.py
"""
购物车接口
"""
from rest_framework.views import APIView
from api.auth import LoginAuth
from api.permission import LoginRequirePermission
from api import models
from utils.response import BaseResponse
import logging
from rest_framework.response import Response
import django_redis
import json
cache = django_redis.get_redis_connection()
logger = logging.getLogger(__name__)


class ShoppingCartView(APIView):

    authentication_classes = [LoginAuth, ]
    permission_classes = [LoginRequirePermission, ]

    def get(self, request, *args, **kwargs):
        user_id = request.user.id
        # 从缓存中取购物车数据
        shopping_cart_value = cache.get(f'SHOPPING_CART_{user_id}')
        if shopping_cart_value:

            shopping_cart_list = json.loads(shopping_cart_value)
        else:
            shopping_cart_list = []
        res_obj = BaseResponse()
        res_obj.data = shopping_cart_list
        return Response(res_obj.dict)

    def post(self, request, *args, **kwargs):
        res_obj = BaseResponse()
        # 1. 获取提交的数据
        # 提交的数据: 课程id 课程名称 课程图片 价格策略 价格 用户id
        # 只需要提供那些数据: 课程id  选中的价格策略id  用户id
        course_id = request.data.get('course_id')
        price_policy_id = request.data.get('price_policy_id')
        user_id = request.user.id
        # 2. 校验数据的有效性
        # 2.1 校验课程id是否存在
        course_obj = models.Course.objects.filter(id=course_id).first()
        if not course_obj:
            res_obj.code = 2001
            res_obj.msg = '无效的课程id'
            return Response(res_obj.dict)
        # 2.2 校验价格策略id是否有效 和 价格策略id是否存在
        price_policy_obj = models.PricePolicy.objects.filter(id=price_policy_id).first()
        # 拿到当前课程的所有价格策略
        course_price_policy = course_obj.price_policy.all()
        if not (price_policy_obj and price_policy_obj in course_price_policy):
            res_obj.code = 2002
            res_obj.msg = '无效的价格策略id'
            return Response(res_obj.dict)
        # 先获取当前用户购物车中已有的数据
        shopping_cart_value = cache.get(f'SHOPPING_CART_{user_id}')
        if shopping_cart_value:
            shopping_cart_list = json.loads(shopping_cart_value)  # 能取到就把redis中存储的数据反序列化成列表
        else:
            shopping_cart_list = []  # 取不到就生成一个空列表

        # 3. 把购物车页面用到的数据都查出来
        # 3.1 把课程所有的价格策略取出来
        price_list = []
        for item in course_price_policy:
            price_list.append({
                'id': item.id,
                'valid_period': item.valid_period,
                'valid_period_text': item.get_valid_period_display(),
                # 'selected': True if item.id == price_policy_id else False,  # 耻辱
                'selected': str(item.id) == price_policy_id,
                'prcie': item.price
            })
        course_info = {}
        course_info['id'] = course_id
        course_info['default_price_period_id'] = price_policy_id  # 价格策略id
        course_info['default_price_period'] = price_policy_obj.valid_period  # 价格策略有效时间
        course_info['default_price'] = price_policy_obj.price  # 价格策略的具体价格
        course_info['relate_price_policy'] = price_list
        logger.debug(course_info)

        # 4. 存入缓存
        shopping_cart_list.append(course_info)  # 把当前这个课程加入购物车列表
        cache.set(f'SHOPPING_CART_{user_id}', json.dumps(shopping_cart_list))
        res_obj.msg = '加入购物车成功'
        return Response(res_obj.dict)

    def put(self, request, *args, **kwargs):
        """更新购物车 修改课程的价格策略"""
        res_obj = BaseResponse()
        # 1. 课程id 和价格策略id
        course_id = request.data.get('course_id')
        price_policy_id = request.data.get('price_policy_id')
        user_id = request.user.id
        if not(course_id and price_policy_id):
            res_obj.code = 2003
            res_obj.msg = '没有参数'
            logger.warning('shopping cart put without course_id and price_policy_id.')
            return Response(res_obj.dict)
        # 判断是否存在
        course_obj = models.Course.objects.filter(id=course_id).first()
        price_policy_obj = models.PricePolicy.objects.filter(id=price_policy_id).first()
        # # 从缓存中取出购物车列表
        shopping_cart_value = cache.get(f'SHOPPING_CART_{user_id}')
        shopping_cart_list = json.loads(shopping_cart_value)
        # 更新缓存中的购物车列表
        # 遍历整个购物车列表 找到要修改的课程 修改其默认的价格策略
        for item in shopping_cart_list:
            if str(item['id']) == course_id:
                # 这个课程就是我要修改的课程
                # 遍历该课程的所有价格策略,修改默认选中
                # 拿到当前课程的所有的价格策略id
                valid_price_id = [str(i['id']) for i in item['relate_price_policy']]
                if price_policy_id not in valid_price_id:
                    res_obj.code = 2004
                    res_obj.msg = '无效的价格策略id'
                    return Response(res_obj.dict)
                # 修改该课程的默认选中的价格策略
                item['default_price_period_id'] = price_policy_obj.id
                item['default_price_period'] = price_policy_obj.valid_period
                item['default_price'] = price_policy_obj.price
                for i in item['relate_price_policy']:
                    if str(i['id']) == price_policy_id:
                        i['selected'] = True
                    else:
                        i['selected'] = False
                res_obj.msg = '修改成功'
                logger.debug('修改购物车成功')
                cache.set(f'SHOPPING_CART_{user_id}', json.dumps(shopping_cart_list))
                return Response(res_obj.dict)
        else:
            res_obj.code = 2007
            res_obj.msg = '要修改的课程购物车中不存在'
            logger.debug('要修改的课程购物车中不存在')
            return Response(res_obj.dict)




#######数据库中判断

    def put(self, request, *args, **kwargs):
        """更新购物车 修改课程的价格策略"""
        res_obj = BaseResponse()
        # 1. 课程id 和价格策略id
        course_id = request.data.get('course_id')
        price_policy_id = request.data.get('price_policy_id')
        user_id = request.user.id
        if not(course_id and price_policy_id):
            res_obj.code = 2003
            res_obj.msg = '没有参数'
            logger.warning('shopping cart put without course_id and price_policy_id.')
            return Response(res_obj.dict)
        # 判断是否存在
        course_obj = models.Course.objects.filter(id=course_id).first()
        if not course_obj:
            res_obj.code = 2004
            res_obj.msg = '无效的课程id'
            logger.warning('给shoppingcart接口发put请求却携带了一个无效的课程id')
            return Response(res_obj.dict)
        course_price_policy = course_obj.price_policy.all()  # 拿到当前课程的所有价格策略
        price_policy_obj = models.PricePolicy.objects.filter(id=price_policy_id).first()
        # 该价格策略对象必须存在并且在我当前课程的价格策略中
        if not(price_policy_obj and price_policy_obj in course_price_policy):
            res_obj.code = 2005
            res_obj.msg = '无效的价格策略id'
            logger.warning('给shoppingcart接口发put请求却携带了一个无效的价格策略id')
            return Response(res_obj.dict)
        # 从缓存中取出购物车列表
        shopping_cart_value = cache.get(f'SHOPPING_CART_{user_id}')
        if not shopping_cart_value:
            res_obj.code = 2006
            res_obj.msg = '购物车是空的'
            logger.warning('给shoppingcart接口发put请求 但是购物车是空的没法改')
            return Response(res_obj.dict)
        shopping_cart_list = json.loads(shopping_cart_value)
        # 更新缓存中的购物车列表
        # 遍历整个购物车列表 找到要修改的课程 修改其默认的价格策略
        for item in shopping_cart_list:
            if str(item['id']) == course_id:
                # 这个课程就是我要修改的课程
                item['default_price_period_id'] = price_policy_obj.id
                item['default_price_period'] = price_policy_obj.valid_period
                item['default_price'] = price_policy_obj.price
                # 遍历该课程的所有价格策略,修改默认选中
                for i in item['relate_price_policy']:
                    if str(i['id']) == price_policy_id:
                        i['selected'] = True
                    else:
                        i['selected'] = False
                res_obj.msg = '修改成功'
                logger.debug('修改购物车成功')
                return Response(res_obj.dict)
        else:
            res_obj.code = 2007
            res_obj.msg = '要修改的课程购物车中不存在'
            logger.debug('要修改的课程购物车中不存在')
            return Response(res_obj.dict)






    def delete(self, request, *args, **kwargs):
        """删除课程 从购物车列表中把某个课程移除"""
        res_obj = BaseResponse()
        user_id = request.user.id
        course_id = request.data.get('course_id')
        if not course_id:
            res_obj.code = 2008
            res_obj.msg = '去烧课程id参数'
            logger.warning('要删除购物车的课程却没传课程id')
            return Response(res_obj.dict)
        # 先从缓存中把购物车列表取出来
        shopping_cart_value = cache.get(f'SHOPPING_CART_{user_id}')
        if shopping_cart_value:
            shopping_cart_list = json.loads(shopping_cart_value)
        else:
            shopping_cart_list = []
        # 遍历购物车列表 找到 course_id 对应的课程 把它从购物车列表删掉
        shopping_cart_num = len(shopping_cart_list)
        for i in range(shopping_cart_num-1,-1,-1):
            if shopping_cart_list[i]['id'] == course_id:
                # 这个课程就是要删除的课程
                shopping_cart_list.pop(i)
                # 删除成功 更新缓存
                res_obj.msg = '删除课程成功'
                cache.set(f'SHOPPING_CART_{user_id}', json.dumps(shopping_cart_list))
                return Response(res_obj.dict)
        else:
            # 要删除的课程不在购物车里面
            res_obj.code = 2009
            res_obj.msg = '要删除的课程不在购物车中'
            logger.warning('要删除的课程不在购物车中')
            return Response(res_obj.dict)



"""

{
    "id": 2,  # 课程id
    "default_price_period": 14,  # 默认选中的价格策略
    "default_price": 200.0  # 默认价格
    "relate_price_policy": {  # 课程所有的价格策略
        "1": {
            "valid_period": 7,
            "valid_period_text": "1周",
            "default": false,
            "prcie": 100.0
        },
        "2": {
            "valid_period": 14,
            "valid_period_text": "2周",
            "default": true,
            "prcie": 200.0
        },
        "3": {
            "valid_period": 30,
            "valid_period_text": "1个月",
            "default": false,
            "prcie": 300.0
        }
    },
    "name": "Django框架学习",  # 课程名称
    "course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png",  # 课程图片  
}
"""


"""
[
        {
            "id": "3",
            "default_price_period": "5",
            "default_price": 9.9,
            "relate_price_policy": [
                {
                    "id": 5,
                    "valid_period": 999,
                    "valid_period_text": "永久有效",
                    "selected": true,
                    "prcie": 9.9
                }
            ]
        },
        {
            "id": "1",
            "default_price_period": "1",
            "default_price": 99,
            "relate_price_policy": [
                {
                    "id": 1,
                    "valid_period": 7,
                    "valid_period_text": "1周",
                    "selected": true,
                    "prcie": 99
                },
                {
                    "id": 2,
                    "valid_period": 90,
                    "valid_period_text": "3个月",
                    "selected": false,
                    "prcie": 1999
                },
                {
                    "id": 3,
                    "valid_period": 999,
                    "valid_period_text": "永久有效",
                    "selected": false,
                    "prcie": 9999
                }
            ]
        }
    ]
"""
view.shoppingcart.py
class BaseResponse(object):
    __slots__ = ('code', 'data', 'msg')

    def __init__(self):
        self.code = 0
        self.data = None
        self.msg = ''

    @property
    def dict(self):
        return {'code': self.code, 'data': self.data, 'msg': self.msg}


if __name__ == '__main__':

    res_obj = BaseResponse()
    res_obj.data = [11, 22, 33]
    res_obj.code = 10003
    res_obj.msg = '用户名或密码错误'

    print(res_obj.dict)
    # 设置了__slots__之后,实例对象就没有__dict__了
    # print(res_obj.__dict__)
    # res_obj.name = 'alex'

    # __slots__不能限制子类
    class Son(BaseResponse):
        pass

    s1 = Son()
    print(s1.__dict__)
    s1.name = 'Alex'
    print(s1.name)
    print(s1.__dict__)
utils.response.py
"""
自定义异常 合集
"""


class LuffyPayException(Exception):

    def __init__(self, code: int, msg: str):
        self.code = code
        self.msg = msg

    def __str__(self):
        return self.msg
utils.exception.py
class PayException(Exception):

    def __init__(self, code: int, msg: str):
        self.code = code
        self.msg = msg

    def __str__(self):
        return self.msg

class BuyException(Exception):

    def __init__(self, code: int, msg: str):
        self.code = code
        self.msg = msg

    def __str__(self):
        return self.msg



class BaseResponse(object):
    __slots__ = ('code', 'data', 'msg')

    def __init__(self):
        self.code = 0
        self.data = None
        self.msg = ''

    @property
    def dict(self):
        return {'code': self.code, 'data': self.data, 'msg': self.msg}



def test():
    res_obj=BaseResponse()
    try:
        for i in range(3):
            if i==1:
                # res_obj.code = 2000
                # res_obj.msg = 'Pay错误'
                # return res_obj.dict

                raise PayException(2000, 'Pay错误')  # 少些几个return
            if i==2:
                raise BuyException(3000, 'Buy错误')
    except PayException as e:
        res_obj.code=e.code
        res_obj.msg=e.msg
    except BuyException as e:
        res_obj.code = e.code
        res_obj.msg = e.msg
    except Exception as e:
        res_obj.code = 500
        res_obj.msg = str(e)
    return res_obj.dict

print(test())

"""
{'data': None, 'code': 2000, 'msg': 'Pay错误'}
"""
异常
"""
支付校验


不管前段传过来应收多少钱 我都要后台自己算一遍 确保钱是对的

"""

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.response import BaseResponse
from utils.exception import LuffyPayException
from api import models
from api.auth import LoginAuth
from api.permission import LoginRequirePermission
import logging
import datetime
import time
import random
import django_redis
cache = django_redis.get_redis_connection()
from utils.ali.api import ali_api
logger = logging.getLogger(__name__)


class PayView(APIView):
    """支付校验"""
    authentication_classes = [LoginAuth, ]
    permission_classes = [LoginRequirePermission, ]

    def _clac_coupon_price(self, o_price, coupon_obj):
        """
        计算券后价格
        :param o_price: 原价格
        :param coupon_obj: 优惠券对象(课程优惠券/通用优惠券)
        :return: 券后价格
        """
        if coupon_obj.coupon.coupon_type == 0:
            # 立减券
            course_coupon_price = o_price - coupon_obj.coupon.money_equivalent_value
            if course_coupon_price < 0:
                course_coupon_price = 0
        elif coupon_obj.coupon.coupon_type == 1:
            # 满减券
            if o_price < coupon_obj.coupon.minimum_consume:
                raise LuffyPayException(4004, '课程价格不满足满减条件')
            course_coupon_price = o_price - coupon_obj.coupon.money_equivalent_value
        elif coupon_obj.coupon.coupon_type == 2:
            # 折扣券
            course_coupon_price = o_price * coupon_obj.coupon.off_percent / 100
        else:
            logger.error('本不应该走到这步田地!')
            raise LuffyPayException(500, '内部错误')

        return course_coupon_price

    def post(self, request, *args, **kwargs):
        """
        对前端传过来的数据进行校验
        {
            "use_beli":false,
            "course_list":[{
                "course_id":1,
                "price_policy_id":1,
                "coupon_record_id":2
            },
            {
                "course_id":2,
                "price_policy_id":4
            }],
            "common_coupon_id":3,
            "pay_money":298
        }

        """

        res_obj = BaseResponse()
        # 1. 获取数据
        user_id = request.user.id
        course_list = request.data.get('course_list')
        common_coupon_id = request.data.get('common_coupon_id')
        use_beli = request.data.get('use_beli')  # 'false'
        pay_money = request.data.get('pay_money')
        now = datetime.datetime.utcnow()
        print(use_beli, type(use_beli))
        try:
            # 1.1 在最开始把用到的数据都校验一下是否存在,
            if not (course_list and common_coupon_id and f'{use_beli}' and pay_money):
                raise LuffyPayException(4000, '请求的数据格式有误')
            course_coupon_price_list = []
            # 2. 校验数据(存在、合法)
            for course in course_list:
                # 分别取出课程相关的数据
                course_id = course.get('course_id')
                price_policy_id = course.get('price_policy_id')
                coupon_record_id = course.get('coupon_record_id')
                # 2.1 校验课程
                course_obj = models.Course.objects.filter(id=course_id).first()
                # if not course_obj:
                #     res_obj.code = 4001
                #     res_obj.msg = '要支付的课程不存在!'
                #     logger.warning(f'用户:{user_id} 支付 课程:{course_id} 不存在')
                #     return Response(res_obj.dict)
                if not course_obj:
                    logger.warning(f'用户:{user_id} 支付 课程:{course_id} 不存在')
                    raise LuffyPayException(4001, '要支付的课程不存在!')
                # 2.2 校验课程的价格策略
                price_policy_obj = models.PricePolicy.objects.filter(
                    id=price_policy_id,  # 价格策略id
                    content_type__model='course',  # 课程表
                    object_id=course_id  # 课程id
                ).first()
                if not price_policy_obj:
                    logger.warning(f'用户:{user_id} 支付 课程:{course_id} 价格策略:{price_policy_id} 不存在')
                    raise LuffyPayException(4002, '价格策略不合法')

                course['valid_period'] = price_policy_obj.valid_period  # 把课程的有效期 加入到course字典中
                course['valid_period_display'] = price_policy_obj.get_valid_period_display()  # 显示的有效期
                # 2.3 校验课程的优惠券(计算券后价格)
                # 只有勾选课程优惠券才走下面的判断及结算课程券后价格
                course_origin_price = price_policy_obj.price  # 拿到课程的原价
                course_coupon_price = course_origin_price  # 课程的券后价格
                if coupon_record_id:
                    coupon_record_obj = models.CouponRecord.objects.filter(
                        id=coupon_record_id,
                        account=request.user,
                        status=0,
                        coupon__content_type__model='course',  # 限定要查那张表
                        coupon__object_id=course_id,  # 限定哪一个课程
                        coupon__valid_begin_date__lte=now,
                        coupon__valid_end_date__gte=now,
                    ).first()
                    if not coupon_record_obj:
                        logger.warning(f'用户:{user_id} 支付 课程:{course_id} 优惠券:{coupon_record_id} 不存在')
                        raise LuffyPayException(4003, '课程优惠券不合法')
                    # 使用了课程的优惠券 还需要计算课程券后价格
                    # 1. 根据优惠券的类型不同 按照不同的规则计算券后价格
                    course_coupon_price = self._clac_coupon_price(course_origin_price, coupon_record_obj)
                    course['origin_price'] = course_origin_price  # 把课程原价格放入course字典中
                    course['coupon_price'] = course_coupon_price  # 把课程券后价格放入course字典中
                    # 还在for循环内
                    print('#' * 60)
                    print(f'课程名称:{course_obj.name} 原价:{course_origin_price} 券后价:{course_coupon_price}')
                # 不管有没有使用课程优惠券 都要计算课程总价
                course_coupon_price_list.append(course_coupon_price)
            # 所有课程的总券后价
            sum_course_price = sum(course_coupon_price_list)
            # 2.4 校验通用优惠券
            if common_coupon_id:
                # 只有使用了通用优惠券才走下面的判断
                common_coupon_obj = models.CouponRecord.objects.filter(
                    id=common_coupon_id,
                    account=request.user,
                    status=0,
                    coupon__content_type__model='course',  # 限定要查那张表
                    coupon__object_id=None,  # 不限定哪一个课程就是通用优惠券
                    coupon__valid_begin_date__lte=now,
                    coupon__valid_end_date__gte=now,
                ).first()
                if not common_coupon_obj:
                    logger.warning(f'用户:{user_id} 支付 通用优惠券:{common_coupon_id} 不存在')
                    raise LuffyPayException(4005, '通用优惠券无效!')
                common_price = self._clac_coupon_price(sum_course_price, common_coupon_obj)  # 把课程的券后总价按照通用优惠券再计算券后价
            else:
                common_price = sum_course_price
            print('*' * 120)
            print(f'初始总价:{sum_course_price} 券后总价:{common_price}')
            # 2.5 贝里
            if use_beli:  # ?
                my_beli = request.user.beli / 10  # 10贝里 = 1元人民币
                if my_beli > common_price:  # 如果贝里数大于要支付的课程的价格   beli: 5000 课程价格:100元
                    request.user.beli = (my_beli - common_price) * 10
                    result_price = 0
                    cost_beli = common_price * 10  # 记录消费了多少贝里,如果订单取消或超时未支付再返回来
                else:  # 如果贝里数小于要支付的课程的价格 贝里:500  课程价格:100元
                    result_price = common_price - my_beli
                    request.user.beli = 0
                    cost_beli = my_beli * 10  # 记录消费了多少贝里,如果订单取消或超时未支付再返回来
                request.user.save()
            else:
                result_price = common_price
                cost_beli = 0
            # 2.6 总的价格是否一致
            if pay_money != result_price:
                logger.warning(f'用户:{user_id} 支付价格异常!!!')
                raise LuffyPayException(4006, '支付价格异常!')

            # 3. 生成订单
            # 商城的订单号
            order_num = self._get_order_number()
            # 将订单号和贝里关联起来保存到缓存, 30分钟过期
            cache.set(f'{order_num}|{cost_beli}', '', 30 * 60)

            # 清除缓存中结算数据和通用优惠券数据
            cache.delete(f'BUY_{user_id}')
            cache.delete(f'COMMON_COUPON_LIST_{user_id}')

            # 生成订单和订单详情,(未支付状态)
            # 1. 先创建订单
            order_obj = models.Order.objects.create(
                payment_type=1,
                order_number=order_num,  # 订单号
                account=request.user,  # 用户
                status=1,  # 状态(待支付)
                order_type=0,  # '用户下单'
                actual_amount=result_price,  # 最终要支付的价格
            )
            # 2. 在创建订单详情
            for course_item in course_list:
                models.OrderDetail.objects.create(
                    order=order_obj,  # 关联的订单

                    # content_type__model='course',
                    # object_id=course_item.get("course_id"),
                    content_object=models.Course.objects.get(id=course_item.get("course_id")),
                    # ? 注意数据的来源!!!
                    original_price=course_item.get("origin_price"),
                    price=course_item.get("coupon_price"),
                    valid_period=course_item.get("valid_period"),
                    valid_period_display=course_item.get("valid_period_display"),
                )
            # 4. 拼接URL
            url = self._get_pay_url(request, order_num, result_price)
            res_obj.data = url

        except LuffyPayException as e:
            res_obj.code = e.code
            res_obj.msg = e.msg
        except Exception as e:
            res_obj.code = 500
            res_obj.msg = str(e)
            logger.error(str(e))
        return Response(res_obj.dict)

    def _get_order_number(self, order_type='1'):

        """生成订单号"""
        now_str = str(round(time.time() * 1000))  # 13位时间戳
        random_str = str(random.randint(1000, 9999))
        return f'{order_type}{now_str}{random_str}'

    def _get_pay_url(self, request, order_number, final_price):
        """生成支付宝支付二维码页面URL"""
        if request.META["HTTP_USER_AGENT"]:
            pay_api = ali_api.pay.pc
        elif request == "APP":
            pay_api = ali_api.pay.app
        else:
            pay_api = ali_api.pay.wap
        pay_url = pay_api.direct(
            subject="路飞学城",  # 商品简单描述
            out_trade_no=order_number,  # 商户订单号
            total_amount=final_price,  # 交易金额(单位: 元 保留俩位小数)
        )
        print("pay_url", pay_url)
        return pay_url
views.pay.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-


"""
Redis key过期通知
订单超时未支付,Redis中之前保存得到订单号会发过期通知,Python订阅该通知:

1. 把该订单置为 `已关闭`
2. 把该订单关联的优惠券 置为 `未使用` *
3. 将`cost_beli`返还到该订单的用户账户中



#### 在线购物商城类订单号有什么讲究?
1. 不能重复
2. 订单号最好是能有意义 比如包含:订单类型+订单时间+随机数


交易接口(以支付宝为例)

    沙箱环境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info
    支付宝收到钱先发 POST请求 给我
    再发 GET请求 给我
"""

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.ali.tools import verify_signature
from django.shortcuts import HttpResponse, redirect
from utils.ali.api import ali_api
from api import models
import datetime
import logging

logger = logging.getLogger(__name__)


class AliPayTradeView(APIView):

    # 倒计时五秒之后 支付宝会发get请求到 settings.py中配置的这个URL
    def get(self, request, *args, **kwargs):
        try:
            processed_dict = {}
            for key, value in self.request.query_params.items():
                processed_dict[key] = value
            # 校验签名
            verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key)
            if not verify_result:
                return HttpResponse("sign is invalid")
            out_trade_no = processed_dict.get("out_trade_no")  # 商户订单号
            redirect_to = "{0}?order_num={1}".format("http://39.98.199.144:8804/api/order/pay_success", out_trade_no)
            return redirect(redirect_to)  # 告诉用户的浏览器重定向到 我指定的付款成功的地址
        except Exception as e:
            logger.error(str(e))
            return HttpResponse("fail!")

    def post(self, request, *args, **kwargs):
        """
        处理支付宝的notify_url

        支付宝对应交易的四种状态:
            1. WAIT_BUYER_PAY    交易创建,等待买家付款
            2. TRADE_CLOSED      未付款交易超时关闭,或支付完成后全额退款
            3. TRADE_SUCCESS     交易支付成功
            4. TRADE_FINISHED    交易结束,不可退款

        如果支付成功, 将订单状态更改为交易完成
        """
        processed_dict = {}
        for key, value in self.request.data.items():
            processed_dict[key] = value
        # 校验签名
        verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key)

        if not verify_result:
            return Response("fail")  # 消息是会给支付宝服务器的

        order_sn = processed_dict.get("out_trade_no", "")  # 商户网站唯一订单号
        trade_no = processed_dict.get("trade_no", "")  # 该交易在支付宝系统中的交易流水号。最长64位
        trade_status = processed_dict.get("trade_status", "")  # 支付宝系统的交易状态
        #   支付成功要返回 success 否则会 2 4 6 8 --> 24小时重复给我发请求
        #   获取到订单号并查询该订单的状态是否为交易完成, 如果交易完成, 即直接返回成功信号
        #   为该用户创建报名课程, 创建报名时间及结束时间
        if trade_status == "TRADE_SUCCESS":
            gmt_payment = processed_dict.get("gmt_payment")  # 买家付款时间 格式 yyyy-MM-dd HH:mm:ss
            passback_params = processed_dict.get("passback_params", "{}")  # 公共回传参数

            # 修改订单状态
            save_status = self.change_order_status(order_sn, trade_no, gmt_payment, "alipay", passback_params)
            if save_status is True:
                return Response("success")
        return Response("fail")

    def change_order_status(order_num, payment_number, gmt_payment, trade_type, extra_params):
        """交易成功修改订单相关的状态

        Parameters
        ----------
        order_num : string
            订单号

        payment_number : string or None
            第三方订单号

        gmt_payment : string
            交易时间(要根据不同的交易方式格式化交易时间)

        trade_type: string
            交易方式

        extra_params: string json
            交易回传参数

        Returns
        -------
        bool
        """
        try:
            exist_order = models.Order.objects.get(order_number=order_num)
            pay_time = datetime.datetime.strptime(gmt_payment, "%Y-%m-%d %H:%M:%S")

            if exist_order.status == 0:
                return True
            # 变更订单状态
            exist_order.payment_number = payment_number
            exist_order.status = 0
            exist_order.pay_time = pay_time
            exist_order.save(update_fields=("payment_number", "status", "pay_time",), )
        except Exception as e:
            logger.error(str(e))
            pass
        return True


def heiheihei(request):
    return HttpResponse('S16扫码成功!')
views.trade.py
import time
from redis import StrictRedis

redis = StrictRedis(host='localhost', port=6379)

# redis 发布订阅
pubsub = redis.pubsub()
# 监听通知
pubsub.psubscribe('__keyspace@0__:*')

# 开始消息循环

while True:
   # 获取消息
    message = pubsub.get_message()
    if message:
        if message.get('data') != 1:
            data = message.get('data').decode("utf8")
            if data == "del":
                aa="正常付款"
                print(aa)
            elif data == "expired":
                aa="归还贝里"
                print(aa)
    else:
        time.sleep(0.01)



##########################订单
import redis
conn = redis.Redis(host="127.0.0.1",port=6379,password="",decode_responses=True) #decode_responses=True 设置取出的数据是字符,不是字节

conn.set("n999","v999",10)
conn.delete("n999")
贝里
"""
Django settings for luffy_api project.

Generated by 'django-admin startproject' using Django 1.11.16.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '3_777&oa36eo9r7(#5$yds*_ksxnacijb0h%h+v--qc58+iytf'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api.apps.ApiConfig',
    # 'rest_framework',
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'luffy_api.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'luffy_api.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'

# 告诉Django用我自己写的UserInfo表代替内置的 User 表
AUTH_USER_MODEL = 'api.UserInfo'  # app名.表名


# corsheaders配置项
CORS_ORIGIN_ALLOW_ALL = True

# Token有效期
# AUTH_TOKEN_TIMEOUT = 7
AUTH_TOKEN_TIMEOUT = 60*60*24*7

# 日志配置
BASE_LOG_DIR = os.path.join(BASE_DIR, "log")

# 封装的配置项
THIRD_PART_CONFIG = {
    # 阿里云服务配置
    "ALI_YUN": {

    },
    # 滑动验证码服务配置
    "GEE_TEST": {

    },
    # 支付宝支付相关配置
    "ALI_PAY": {
        # 默认使用配置
        "default": {
            "version": "1.0",  # 支付宝支付调用的接口版本(固定值1.0)
            "debug": True,     # 是否启用调试模式(False是正式环境)
            "app_id": "2016081400236741",  # 支付宝分配给开发者的应用ID(启用线上环境请更改),
            "app_private_key_path": os.path.join(BASE_DIR, 'keys', 'app_private_2048.txt'),  # APP应用的私钥
            "alipay_public_key_path": os.path.join(BASE_DIR, 'keys', 'alipay_public_2048.txt'),  # 支付宝的公钥
            "callback_url": "http://39.98.199.144:8804/api/trade/alipay/",  # 添加回调域名
        },
        # 目前针对支付业务进行切换
        "pay": {
        }
    }
}


# CACHE CONF 缓存配置
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100},
            # "PASSWORD": "密码",
            "DECODE_RESPONSES": True
        }
    }
}


LOGGING = {
    'version': 1,  # 保留字
    'disable_existing_loggers': False,  # 是否禁用已经存在的日志实例
    'formatters': {  # 定义日志的格式
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    'filters': {  # 定义日志的过滤器
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {  # 日志处理程序
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],  # 只有在Django debug为True时才在屏幕打印日志
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'SF': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,根据文件大小自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 500,  # 日志大小 500M(最好不要超过1G)
            'backupCount': 3,  # 备份数为3  xx.log --> xx.log.1 --> xx.log.2 --> xx.log.3
            'formatter': 'standard',
            'encoding': 'utf-8',  # 文件记录的编码格式
        },
        'TF': {
            'level': 'INFO',
            'class': 'logging.handlers.TimedRotatingFileHandler',  # 保存到文件,根据时间自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'backupCount': 3,  # 备份数为3  xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ...
            'when': 'D',  # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {  # 日志实例
        '': {  # 默认的logger应用如下配置
            'handlers': ['SF', 'console', 'error'],  # 上线之后可以把'console'移除
            'level': 'DEBUG',
            'propagate': True,  # 是否向上一级logger实例传递日志信息
        },
        'collect': {  # 名为 'collect' 的logger还单独处理
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}
settings.py
## 一、低配logging
日志总共分为以下五个级别,这个五个级别自下而上进行匹配 debug–>info–>warning–>error–>critical,默认最低级别为warning级别。
```python

############## v1 ##############
import logging

logging.debug('调试信息')
logging.info('正常信息')
logging.warning('警告信息')
logging.error('报错信息')
logging.critical('严重错误信息')
# v1版本无法指定日志的级别;无法指定日志的格式;只能往屏幕打印,无法写入文件。因此可以改成下述的代码。

############## v2 函数式简单配置 ##############
import logging

# 日志的基本配置

logging.basicConfig(filename='access.log',
                    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',
                    level=10)

logging.debug('调试信息')  # 10
logging.info('正常信息')  # 20
logging.warning('警告信息')  # 30
logging.error('报错信息')  # 40
logging.critical('严重错误信息')  # 50

"""
可在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有:
    
filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。
datefmt:指定日期时间格式。
level:设置rootlogger(后边会讲解具体概念)的日志级别
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。


format参数中可能用到的格式化串:

%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s用户输出的消息
"""
# v2版本不能指定字符编码;只能往文件中打印。


############## v3 logger对象配置 ##############
"""
logging模块包含四种角色:logger、Filter、Formatter对象、Handler

logger:产生日志的对象
Filter:过滤日志的对象
Formatter对象:可以定制不同的日志格式对象,然后绑定给不同的Handler对象使用,以此来控制不同的Handler的日志格式
Handler:接收日志然后控制打印到不同的地方,FileHandler用来打印到文件中,StreamHandler用来打印到终端
"""

'''
critical=50
error =40
warning =30
info = 20
debug =10
'''


import logging

# 1、logger对象:负责产生日志,然后交给Filter过滤,然后交给不同的Handler输出
logger = logging.getLogger(__file__)

# 2、Filter对象:不常用,略

# 3、Handler对象:接收logger传来的日志,然后控制输出
h1 = logging.FileHandler('t1.log')  # 打印到文件
h2 = logging.FileHandler('t2.log')  # 打印到文件
sm = logging.StreamHandler()  # 打印到终端

# 4、Formatter对象:日志格式
formmater1 = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                               datefmt='%Y-%m-%d %H:%M:%S %p',)

formmater2 = logging.Formatter('%(asctime)s :  %(message)s',
                               datefmt='%Y-%m-%d %H:%M:%S %p',)

formmater3 = logging.Formatter('%(name)s %(message)s',)


# 5、为Handler对象绑定格式
h1.setFormatter(formmater1)
h2.setFormatter(formmater2)
sm.setFormatter(formmater3)

# 6、将Handler添加给logger并设置日志级别
logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(sm)

# 设置日志级别,可以在两个关卡进行设置logger与handler
# logger是第一级过滤,然后才能到handler
logger.setLevel(30)
h1.setLevel(10)
h2.setLevel(10)
sm.setLevel(10)

# 7、测试
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')


try:
    int('abc')
except Exception as e:
    msg = str(e)#调用e.__str__方法
    # logger.error(msg,exc_info=True)#exc_info=True 打印堆栈信息
    logger.error('error')
    logger.critical('critical')

```

## 二、高配logging
```python

# settings.py
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
if not os.path.isdir(os.path.join(BASE_DIR, "logs")):
    os.mkdir(os.path.join(BASE_DIR, "logs"))

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {},
    'handlers': {
        # 打印到终端的日志  实际开发建议使用WARNING
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # 打印到文件的日志,收集info及以上的日志
        'file': {
            # 实际开发建议使用ERROR
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            # 日志位置,日志文件名,日志保存目录必须手动创建
            'filename': os.path.join(BASE_DIR, "logs", "action.log"),
            # 日志文件的最大值,这里我们设置300M
            'maxBytes': 300 * 1024 * 1024,
            # 日志文件的数量,设置最大日志数量为10
            'backupCount': 100,
            # 日志格式:详细格式
            'formatter': 'verbose',
            # 文件内容编码
            'encoding': 'utf-8'
        },
    },
    # 日志对象
    'loggers': {
        # logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['console', 'file'],
            'propagate': True,  # 是否让日志信息继续冒泡给其他的日志处理系统
        },
        'django': {
            'handlers': ['console', 'file'],
            'propagate': True,  # 是否让日志信息继续冒泡给其他的日志处理系统
        }
    },
}

# !!!强调!!!
# logging是一个包,需要使用其下的config、getLogger,可以如下导入
# from logging import config
# from logging import getLogger

# 也可以使用如下导入
import logging.config # 这样连同logging.getLogger都一起导入了,然后使用前缀logging.config.

# 加载配置
logging.config.dictConfig(LOGGING_DIC)


########################### 使用日志
import settings
import logging
# 生成一个log实例
logger=logging.getLogger(__name__)

if __name__ == '__main__':
    logger.info('11111111111111111111111111111111')
    logger.debug('22222222222222222222222')
    logger.warning('33333333333333333')
    logger.error('444444444444444444')
```



```python
# django日志的配置
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            # 实际开发建议使用WARNING
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            # 实际开发建议使用ERROR
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            # 日志位置,日志文件名,日志保存目录必须手动创建
            'filename': os.path.join(BASE_DIR, "logs", "action.log"),
            # 日志文件的最大值,这里我们设置300M
            'maxBytes': 300 * 1024 * 1024,
            # 日志文件的数量,设置最大日志数量为10
            'backupCount': 100,
            # 日志格式:详细格式
            'formatter': 'verbose',
            # 文件内容编码
            'encoding': 'utf-8'
        },
    },
    # 日志对象
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统
        },
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

if not os.path.isdir(os.path.join(BASE_DIR, "logs")):
    os.mkdir(os.path.join(BASE_DIR, "logs"))


"""
plugin中安装ideolog
使用:
    import logging
    logger = logging.getLogger('django')
    logger.info('11111111111111111111111111111111')
    logger.debug('22222222222222222222222')
    logger.warning('33333333333333333')
    logger.error('444444444444444444')
"""
```
View Code
原文地址:https://www.cnblogs.com/bubu99/p/11112009.html