数据

#结算字典:
#redis中获取课程信息
#print(payment_dict),打印如下
{
    '2':{
        ’title':'CRM客户关系管理系统实战开发-专题',
        'img':'CRM.jpg',
        'policy_id':'2',
        'coupon':{},
        'default_coupon':0  
        'period':210,
        'period_display':'12个月',
        'price':122.0,
        

    },
    '1':{
        ’title':'爬虫开发-专题',
        'img':'爬虫开发-专题.jpg',
        'policy_id':'1',
        'coupon':{},
        'default_coupon':0  
        'period':60,
        'period_display':'2个月',
        'price':599.0,

    }
}

#去数据库中获取当前用户的所有优惠券    ,
#for 优惠券 in 所有优惠券
    
#------------------------------------------------
#绑定课程:
#   满减券(大促)      2018-6-4  2018-6-22
#   立减券(促销)      2018-6-4  2018-6-29
#未绑定
#   满减券(1周年)     2018-6-5  2018-6-15

#用户ID=1    
    
#加入优惠券后的字典

payment_dict = {
    '2': {
        course_id:2,
        'title': 'CRM客户关系管理系统实战开发-专题', 
        'img': 'CRM.jpg', 
        'policy_id': '4', 
        'coupon': {}, 
        'default_coupon': 0, 
        'period': 210,'period_display': '12个月','price': 122.0}, 
    '1': {
        course_id:2,
        'title': '爬虫开发-专题', 
        'img': '爬虫开发-专题.jpg', 
        'policy_id': '2', 
        'coupon': {
            4: {'coupon_type': 0, 'coupon_display': '立减券', 'money_equivalent_value': 40}, 
            6: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 60, 'minimum_consume': 100}
        }, 
        'default_coupon': 0, 
        'period': 60, 
        'period_display': '2个月', 
        'price': 599.0}
}

global_coupon_dict = {
    'coupon': {
        2: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 200, 'minimum_consume': 500}
    }, 
    'default_coupon': 0
}
    
    
#------------redis中想存的数据如下------------------
redis = {
    payment_1_2:{
        course_id:2,
        'title': 'CRM客户关系管理系统实战开发-专题', 
        'img': 'CRM.jpg', 'policy_id': '4', 
        'coupon': {}, 
        'default_coupon': 0, 
        'period': 210, 'period_display': '12个月', 'price': 122.0}, 
    },
    payment_1_1:{
        course_id:1,
        'title': '爬虫开发-专题', 
        'img': '爬虫开发-专题.jpg', 
        'policy_id': '2', 
        'coupon': {
            4: {'coupon_type': 0, 'coupon_display': '立减券', 'money_equivalent_value': 40}, 
            6: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 60, 'minimum_consume': 100}
        }, 
        'default_coupon': 0, 
        'period': 60, 
        'period_display': '2个月', 
        'price': 599.0}
    },
    payment_global_coupon_1:{
        'coupon': {
            2: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 200, 'minimum_consume': 500}
        }, 
        'default_coupon': 0
    }
}
        
    
#最终得到的数据
{
    "data": {
        "course_list": [
            {
                "course_id": "2",
                "title": "CRM客户关系管理系统实战开发-专题",
                "img": "CRM.jpg",
                "policy_id": "4",
                "coupon": {},
                "default_coupon": "0",
                "period": "210",
                "period_display": "12个月",
                "price": "122.0"
            },
            {
                "course_id": "1",
                "title": "爬虫开发-专题",
                "img": "爬虫开发-专题.jpg",
                "policy_id": "2",
                "coupon": {
                    4: {'coupon_type': 0, 'coupon_display': '立减券', 'money_equivalent_value': 40}, 
                    6: {'coupon_type': 1, 'coupon_display': '满减券', 'money_equivalent_value': 60, 'minimum_consume': 100}
                },
                "default_coupon": "6",
                "period": "60",
                "period_display": "2个月",
                "price": "599.0"
            }
        ],
        "global_coupon_dict": {
            "coupon": {
             2: {
                'coupon_type': 1, 
                'coupon_display': '满减券', 
                'money_equivalent_value': 200, 
                'minimum_consume': 500}
            },
            "default_coupon": "0"
        }
    },
    "code": 1000,
    "error": null
}

一、settings.py

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": "1234",
        }
    }
}

SHOPPING_CAR_KEY = "luffy_shopping_car_%s_%s"
PAYMENT_KEY = "luffy_payment_%s_%s"
PAYMENT_COUPON_KEY = "luffy_payment_coupon_%s"

二、url

api\urls.py

urlpatterns = [
    url(r'^payment/$', payment.PaymentViewSet.as_view()),
]

三、models

# ########################### 优惠券 ################################
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=100
        off_percent=null
        minimum_consume=0
    满减:
        money_equivalent_value=100
        off_percent=null
        minimum_consume=1000
    折扣:
        money_equivalent_value=0
        off_percent=79
        minimum_consume=0
    """
    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,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="自券被领时开始算起")
    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)

    def save(self, *args, **kwargs):
        if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date):
            if self.valid_begin_date and self.valid_end_date:
                if self.valid_end_date <= self.valid_begin_date:
                    raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ")
            if self.coupon_valid_days == 0:
                raise ValueError("coupon_valid_days 有效期不能为0")
        if self.close_date < self.open_date:
            raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间 ")

        super(Coupon, self).save(*args, **kwargs)


class CouponRecord(models.Model):
    """优惠券发放、消费纪录"""
    coupon = models.ForeignKey("Coupon",on_delete=models.CASCADE)
    number = models.CharField(max_length=64, unique=True)
    account = models.ForeignKey("Account", verbose_name="拥有者",on_delete=models.CASCADE)
    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="关联订单")  # 一个订单可以有多个优惠券

    class Meta:
        verbose_name_plural = "32. 用户优惠券"

    def __str__(self):
        return '%s-%s-%s' % (self.account, self.number, self.status)

系统结算中心的架构 结算中心结算流程_django

系统结算中心的架构 结算中心结算流程_django_02

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",on_delete=models.CASCADE)
    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)
    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")

    # 查询常见问题
    asked_question = GenericRelation("OftenAskedQuestion")

    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",on_delete=models.CASCADE)
    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):
    """专题课程 OR 学位课模块"""
    name = models.CharField(max_length=128, unique=True) # Python基础
    course_img = models.CharField(max_length=255)

    sub_category = models.ForeignKey("CourseSubCategory",on_delete=models.CASCADE) #

    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="若是学位课程,此处关联学位表",on_delete=models.CASCADE)


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

    class Meta:
        verbose_name_plural = "06.专题课或学位课模块"

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(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,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 = "08. 常见问题"


class CourseOutline(models.Model):
    """课程大纲"""
    course_detail = models.ForeignKey("CourseDetail",on_delete=models.CASCADE)

    # 前端显示顺序
    order = models.PositiveSmallIntegerField(default=1)

    title = models.CharField(max_length=128)
    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", 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)
    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", 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)
    # 59EE3275E977AADB9C33DC5901307461
    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",on_delete=models.CASCADE)
    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",on_delete=models.CASCADE)
#     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",on_delete=models.CASCADE)
#     course = models.ForeignKey("Course", verbose_name="评价学位模块", blank=True, null=True,
#                                help_text="不填写即代表评价整个学位课程", limit_choices_to={'course_type': 2},on_delete=models.CASCADE)
#     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,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个月'),
                            (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 ArticleSource(models.Model):
    """文章来源"""
    name = models.CharField(max_length=64, unique=True)

    class Meta:
        verbose_name_plural = "16. 文章来源"

    def __str__(self):
        return self.name

class Article(models.Model):
    """文章资讯"""
    title = models.CharField(max_length=255, unique=True, db_index=True, verbose_name="标题")
    source = models.ForeignKey("ArticleSource", verbose_name="来源",on_delete=models.CASCADE)
    article_type_choices = ((0, '资讯'), (1, '视频'))
    article_type = models.SmallIntegerField(choices=article_type_choices, default=0)
    brief = models.TextField(max_length=512, verbose_name="摘要")
    head_img = models.CharField(max_length=255)
    content = models.TextField(verbose_name="文章正文")
    pub_date = models.DateTimeField(verbose_name="上架日期")
    offline_date = models.DateTimeField(verbose_name="下架日期")
    status_choices = ((0, '在线'), (1, '下线'))
    status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="状态")
    order = models.SmallIntegerField(default=0, verbose_name="权重", help_text="文章想置顶,可以把数字调大,不要超过1000")
    vid = models.CharField(max_length=128, verbose_name="视频VID", help_text="文章类型是视频, 则需要添加视频VID", blank=True, null=True)
    comment_num = models.SmallIntegerField(default=0, verbose_name="评论数")
    agree_num = models.SmallIntegerField(default=0, verbose_name="点赞数")
    view_num = models.SmallIntegerField(default=0, verbose_name="观看数")
    collect_num = models.SmallIntegerField(default=0, verbose_name="收藏数")

    # tags = models.ManyToManyField("Tags", blank=True, verbose_name="标签")
    date = models.DateTimeField(auto_now_add=True, verbose_name="创建日期")

    position_choices = ((0, '信息流'), (1, 'banner大图'), (2, 'banner小图'))
    position = models.SmallIntegerField(choices=position_choices, default=0, verbose_name="位置")


    #comment = GenericRelation("Comment")

    class Meta:
        verbose_name_plural = "17. 文章"

    def __str__(self):
        return "%s-%s" % (self.source, self.title)

class Collection(models.Model):
    """收藏"""
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    account = models.ForeignKey("Account",on_delete=models.CASCADE)
    date = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('content_type', 'object_id', 'account')
        verbose_name_plural = "18. 通用收藏表"

class Comment(models.Model):
    """通用的评论表"""
    content_type = models.ForeignKey(ContentType, blank=True, null=True, verbose_name="类型",on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(blank=True, null=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    p_node = models.ForeignKey("self", blank=True, null=True, verbose_name="父级评论",on_delete=models.CASCADE)
    content = models.TextField(max_length=1024)
    account = models.ForeignKey("Account", verbose_name="会员名",on_delete=models.CASCADE)
    disagree_number = models.IntegerField(default=0, verbose_name="踩")
    agree_number = models.IntegerField(default=0, verbose_name="赞同数")
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

    class Meta:
        verbose_name_plural = "19. 通用评论表"

class Account(models.Model):
    username = models.CharField("用户名", max_length=64, unique=True)
    password = models.CharField("密码", max_length=64)

class UserAuthToken(models.Model):
    """
    用户Token表
    """
    user = models.OneToOneField(to="Account",on_delete=models.CASCADE)
    token = models.CharField(max_length=64, unique=True)




# ########################### 优惠券 ################################
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=100
        off_percent=null
        minimum_consume=0
    满减:
        money_equivalent_value=100
        off_percent=null
        minimum_consume=1000
    折扣:
        money_equivalent_value=0
        off_percent=79
        minimum_consume=0
    """
    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,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="自券被领时开始算起")
    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)

    def save(self, *args, **kwargs):
        if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date):
            if self.valid_begin_date and self.valid_end_date:
                if self.valid_end_date <= self.valid_begin_date:
                    raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ")
            if self.coupon_valid_days == 0:
                raise ValueError("coupon_valid_days 有效期不能为0")
        if self.close_date < self.open_date:
            raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间 ")

        super(Coupon, self).save(*args, **kwargs)


class CouponRecord(models.Model):
    """优惠券发放、消费纪录"""
    coupon = models.ForeignKey("Coupon",on_delete=models.CASCADE)
    number = models.CharField(max_length=64, unique=True)
    account = models.ForeignKey("Account", verbose_name="拥有者",on_delete=models.CASCADE)
    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="关联订单")  # 一个订单可以有多个优惠券

    class Meta:
        verbose_name_plural = "32. 用户优惠券"

    def __str__(self):
        return '%s-%s-%s' % (self.account, self.number, self.status)

api\models.py

四、视图

api\views\payment.py

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.auth import LuffyAuth
from django.conf import settings
from django_redis import get_redis_connection
import json
from utils.response import BaseResponse
from api import models
import datetime

class PaymentViewSet(APIView):

    authentication_classes =  [LuffyAuth,]
    conn = get_redis_connection("default")

    def post(self,request,*args,**kwargs):
        '''
        去结算+获取优惠券

        '''
        ret = BaseResponse()
        try:
            # 清空当前用户结算中心的数据
            # luffy_payment_1_*
            # luffy_payment_coupon_1
            key_list = self.conn.keys( settings.PAYMENT_KEY %(request.auth.user_id,"*",) ) #key多的话效率可能会慢
            key_list.append(settings.PAYMENT_COUPON_KEY %(request.auth.user_id,))
            self.conn.delete(*key_list)

            payment_dict = {}

            global_coupon_dict = {
                "coupon":{},
                "default_coupon":0
            }

            # 1. 获取用户要结算课程ID
            course_id_list = request.data.get('courseids')
            for course_id in course_id_list:
                car_key = settings.SHOPPING_CAR_KEY %(request.auth.user_id,course_id,)

                # 1.1 检测用户要结算的课程是否已经加入购物车
                if not self.conn.exists(car_key):
                    ret.code = 1001
                    ret.error = "课程需要先加入购物车才能结算"
                # 1.2 从购物车中获取信息,放入到结算中心。
                # 获取标题和图片
                policy = json.loads(self.conn.hget(car_key, 'policy').decode('utf-8'))
                default_policy = self.conn.hget(car_key, 'default_policy').decode('utf-8')
                policy_info = policy[default_policy]

                payment_course_dict = {
                    "course_id":str(course_id),
                    "title":self.conn.hget(car_key, 'title').decode('utf-8'),
                    "img":self.conn.hget(car_key, 'img').decode('utf-8'),
                    "policy_id":default_policy,
                    "coupon":{},
                    "default_coupon":0
                }
                payment_course_dict.update(policy_info)
                payment_dict[str(course_id)] = payment_course_dict

            # 2. 获取优惠券
            ctime = datetime.date.today()

            coupon_list = models.CouponRecord.objects.filter(
                account=request.auth.user,
                status=0,
                coupon__valid_begin_date__lte=ctime,
                coupon__valid_end_date__gte=ctime,
            )
for item in coupon_list:
                # 只处理绑定课程的优惠券
                if not item.coupon.object_id:
                    # 优惠券ID
                    coupon_id = item.id

                    # 优惠券类型:满减、折扣、立减
                    coupon_type = item.coupon.coupon_type

                    info = {}
                    info['coupon_type'] = coupon_type
                    info['coupon_display'] = item.coupon.get_coupon_type_display()
                    if coupon_type == 0:  # 立减
                        info['money_equivalent_value'] = item.coupon.money_equivalent_value
                    elif coupon_type == 1:  # 满减券
                        info['money_equivalent_value'] = item.coupon.money_equivalent_value
                        info['minimum_consume'] = item.coupon.minimum_consume
                    else:  # 折扣
                        info['off_percent'] = item.coupon.off_percent

                    global_coupon_dict['coupon'][coupon_id] = info

                    continue
                # 优惠券绑定课程的ID
                coupon_course_id = str(item.coupon.object_id)

                # 优惠券ID
                coupon_id = item.id

                # 优惠券类型:满减、折扣、立减
                coupon_type = item.coupon.coupon_type

                info = {}
                info['coupon_type'] = coupon_type
                info['coupon_display'] = item.coupon.get_coupon_type_display()
                if coupon_type == 0: # 立减
                    info['money_equivalent_value'] = item.coupon.money_equivalent_value
                elif coupon_type == 1: # 满减券
                    info['money_equivalent_value'] = item.coupon.money_equivalent_value
                    info['minimum_consume'] = item.coupon.minimum_consume
                else: # 折扣
                    info['off_percent'] = item.coupon.off_percent

                if coupon_course_id not in payment_dict:
                    # 获取到优惠券,但是没有购买此课程
                    continue
                # 将优惠券设置到指定的课程字典中
                payment_dict[coupon_course_id]['coupon'][coupon_id] = info

            # 可以获取绑定的优惠券

            # 3. 将绑定优惠券课程+全站优惠券 写入到redis中(结算中心)。
            # 3.1 绑定优惠券课程放入redis
            for cid,cinfo in payment_dict.items():
                pay_key = settings.PAYMENT_KEY %(request.auth.user_id,cid,)
                cinfo['coupon'] = json.dumps(cinfo['coupon'])
                self.conn.hmset(pay_key,cinfo)
            # 3.2 将全站优惠券写入redis
            gcoupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,)
            global_coupon_dict['coupon'] = json.dumps(global_coupon_dict['coupon'])
            self.conn.hmset(gcoupon_key,global_coupon_dict)

        except Exception as e:
            pass

        return Response(ret.dict)

    def patch(self,request,*args,**kwargs):
        '''
        修改优惠券
        :param request:
        :param args:
        :param kwargs:
        :return:
        '''
        ret = BaseResponse()
        try:
            # 1. 用户提交要修改的优惠券
            course = request.data.get('courseid')
            course_id = str(course) if course else course

            coupon_id = str(request.data.get('couponid'))

            # payment_global_coupon_1
            redis_global_coupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,)

            # 修改全站优惠券
            if not course_id:
                if coupon_id == "0":
                    # 不使用优惠券,请求数据:{"couponid":0}
                    self.conn.hset(redis_global_coupon_key,'default_coupon',coupon_id)
                    ret.data = "修改成功"
                    return Response(ret.dict)

                # 使用优惠券,请求数据:{"couponid":2}
                coupon_dict = json.loads(self.conn.hget(redis_global_coupon_key,'coupon').decode('utf-8'))

                # 判断用户选择得优惠券是否合法
                if coupon_id not in coupon_dict:
                    ret.code = 1001
                    ret.error = "全站优惠券不存在"
                    return Response(ret.dict)

                # 选择的优惠券合法
                self.conn.hset(redis_global_coupon_key, 'default_coupon', coupon_id)
                ret.data = "修改成功"
                return Response(ret.dict)

            # 修改课程优惠券
            # luffy_payment_1_1
            redis_payment_key = settings.PAYMENT_KEY % (request.auth.user_id, course_id,)
            # print(request.data) #{'courseid': 1, 'couponid': 3}
            # 不使用优惠券
            if coupon_id == "0":
                self.conn.hset(redis_payment_key,'default_coupon',coupon_id)
                ret.data = "修改成功"
                return Response(ret.dict)

            # 使用优惠券
            coupon_dict = json.loads(self.conn.hget(redis_payment_key,'coupon').decode('utf-8'))
            # 验证优惠券是否合法
            if coupon_id not in coupon_dict:
                ret.code = 1010
                ret.error = "课程优惠券不存在"
                return Response(ret.dict)

            self.conn.hset(redis_payment_key,'default_coupon',coupon_id)

        except Exception as e:
            ret.code = 1111
            ret.error = "修改失败"

        return Response(ret.dict)

    def get(self,request,*args,**kwargs):
        '''
        获取结算数据

        '''

        ret = BaseResponse()

        try:
            # luffy_payment_1_*
            redis_payment_key = settings.PAYMENT_KEY %(request.auth.user_id,"*",)

            # luffy_payment_coupon_1
            redis_global_coupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,)

            # 1. 获取绑定课程信息
            course_list = []
            for key in self.conn.scan_iter(redis_payment_key):
                info = {}
                data = self.conn.hgetall(key)
                for k,v in data.items():
                    kk = k.decode('utf-8')
                    if kk == "coupon":
                        info[kk] = json.loads(v.decode('utf-8'))
                    else:
                        info[kk] = v.decode('utf-8')
                course_list.append(info)

            # 2.全站优惠券
            global_coupon_dict = {
                'coupon':json.loads(self.conn.hget(redis_global_coupon_key,'coupon').decode('utf-8')),
                'default_coupon':self.conn.hget(redis_global_coupon_key,'default_coupon').decode('utf-8')
            }

            ret.data = {
                "course_list":course_list,
                "global_coupon_dict":global_coupon_dict
            }
        except Exception as e:
            ret.code = 1001
            ret.error = "获取失败"

        return Response(ret.dict)