python 全栈开发,Day105(路飞其他数据库表结构,立即结算需求)
考试第三部分:Django
16. 列列举你熟悉的Http协议头以及作用。(1分)
Accept-Charset: 用于告诉浏览器,客户机采用的编码 Host: 客户机通过这个头告诉服务器,想访问的主机名 User-Agent: 客户机通过这个头告诉服务器,客户机的软件环境 Cookie: 客户机通过这个头可以向服务器带数据
17. 状态码含义:200、301、302、304、404、500。(2分)
200 请求已成功 301 永久重定向 302 临时重定向 304 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源 404 资源找不到 500 服务器内部错误,无法完成请求
18. 简述cookie和session?(2分)
cookie是保存在客户端浏览器的,每次请求网页,会携带cookie
session是保存在服务器上面的,用来判断请求客户端的cookie是否合法
19. django中get和filter的区别?(1分)
get 返回一个object。当返回结果,有且只有一条时,才会返回。否则报错
filter 返回一个queryset对象。即使没有匹配到条件,不会报错,返回一个空的queryset对象
20. django的中间件在1.7和1.11版本间有什什么区别?(1分)
1. 路由编写 2. ORM中,外键一定要有on_delete属性 3. 中间件 4. 模板配置
21. django中contenttypes组件的作⽤用?(1分)
用来解决一个对和多个表做外键关联,并且关联的字段,不会在表中产生新的字段
22. django中Q的作⽤用?(2分)
用来做一些复杂查询,比如or
23. 将如下SQL语句句使⽤用Django的ORM实现:(3分)
select * from order where id >= 12
order.object.filter(id__gte=12)
select * from order where id != 12
order.object.filter(id__exclude=12)
select * from order where id in [1,3,4]
order.object.filter(id__in=[1,3,4])
select * from order where id between 20 and 100
order.object.filter(id__range([20,100])
select * from order where id > 20 and (num < 60 or num > 70 )
# 注意:Q(id__gt=20)是一组查询,&表示and,| 表示or # Q(Q(num__lt=60)|Q(num__gt=70)) 是另外一组查询 order.object.filter(Q(id__gt=20) & Q(Q(num__lt=60)|Q(num__gt=70)))
select * from order order by id desc,age asc
# 默认使用asc(升序),-id表示以id字段,进行desc(降序)来排序 order.object.all().order_by('-id','age')
24. 编写查询语句句:(5分,前2个每个1分,最后⼀一题3分)
• 查看所有学⽣生,并打印 姓名、班级名称
# 注意:一定要以Student为基准表 # 当filter字段有其他表时,使用inner join。当使用values时,它是left join # 一个班级在创建的时候,是没有学生的。所以以classes为基础表时,会造成数据不准确 Student.objects.all().values('name','classes__name')
• 查看班级名称为"全栈12期"的所有学⽣生
Classes.object.filter(name="全栈12期").value("student__name")
• 查看没有学⽣生的所有班级ID、班级名称
# 当班级表的学生记录为空时,使用student__isnull=True Classes.objects.filter(student__isnull=True).values('id','name')
25. django中遇到复杂的SQL时ORM⽆无法完成,如何使⽤用原⽣生SQL执⾏行行?(2分)
1. 根据connections['default'].cursor(),最接近于pymysql操作 2. 使用extra
cursor = connection.cursor() 表示连接默认的数据库,看settings.py中的数据库配置
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
defalut表示默认的数据库。django可以同时连接多个数据库,可以这样
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, 'default2': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
extra是django自带的,默认就可以使用。而connection使用时,需要导入模块!
详情请参考链接:
http://www.cnblogs.com/wupeiqi/articles/5246483.html
搜索 "其他操作",展开里面的代码,就可以看到了!
一、路飞其他数据库表结构
深科技
先来看深科技的相关表
# ############深科技相关 ################### 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="来源") 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) 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="收藏数") 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") # 用于GenericForeignKey反向查询, 不会生成表字段,切勿删除,如有疑问请联系老村长 class Meta: verbose_name_plural = "17. 文章" def __str__(self): return "%s-%s" % (self.source, self.title) class ArticleDetail(models.Model): """文章详细""" article = models.OneToOneField(to='Article') content = models.TextField(verbose_name="文章正文") class Collection(models.Model): """通用收藏表""" content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') account = models.ForeignKey("Account") 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="类型") 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="父级评论") content = models.TextField(max_length=1024) account = models.ForeignKey("Account", verbose_name="会员名") 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. 通用评论表"
打开网页: https://www.luffycity.com/news
Article
中间的 斯嘉丽约翰逊 就是banner大图,对应Article表中position_choices类型
banner大图右边的罗胖子,是banner小图
banner大图下面的文章列表,表示信息流
ArticleDetail
文章详情表,存放了文章详情。里面包含了大量的html标签!占用空间比较大。它需要单独拆分。
它和文章表,做了一对一关联。也就是外键+唯一索引
Collection
通用评论表,看下面这几行代码
content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id')
如果要使用django ContentType组件,上面这3行,是必须要有的!不要问我为什么,这是约定俗成的!
例如:文章,课程,视频...,这些都需要评论。通常的做法是给这些表做外键,关联到评论表。那么这些表在数据库中会产生一个关联字段!
使用 ContentType组件后,只需要在这些表加一个属性等于GenericRelation("Collection"),那么就可以建立关联了。而且数据库不会产生新的字段!所以说,它是一个通用的评论表
订单相关
再来看订单相关的表
# ################# 订单相关 ################# class EnrolledCourse(models.Model): """已报名课程,不包括学位课程""" account = models.ForeignKey("Account") course = models.ForeignKey("Course", limit_choices_to=~Q(course_type=2)) enrolled_date = models.DateTimeField(auto_now_add=True) valid_begin_date = models.DateField(verbose_name="有效期开始自") valid_end_date = models.DateField(verbose_name="有效期结束至") status_choices = ((0, '已开通'), (1, '已过期')) status = models.SmallIntegerField(choices=status_choices, default=0) order_detail = models.OneToOneField("OrderDetail") # 使订单购买后支持 课程评价 def __str__(self): return "%s:%s" % (self.account, self.course) class Meta: verbose_name_plural = "34. 报名专题课" class ScoreRule(models.Model): """积分规则""" score_rule_choices = ( (0, '未按时交作业'), (1, '未及时批改作业'), (2, '作业成绩'), (3, '未在规定时间内对学员进行跟进'), (4, '未在规定时间内回复学员问题'), (5, '收到学员投诉'), (6, '导师相关'), (7, '学位奖学金'), ) rule = models.SmallIntegerField(choices=score_rule_choices, verbose_name="积分规则") score_type_choices = ((0, '奖励'), (1, '惩罚'), (2, '初始分配')) score_type = models.SmallIntegerField(choices=score_type_choices, verbose_name="奖惩", default=0) score = models.IntegerField(help_text="扣分数与贝里相等,若为0则代表规则的值可以从别处取得") # maturity_days = models.IntegerField("成熟周期", help_text="自纪录创建时开始计算") memo = models.TextField(blank=True, null=True) def __str__(self): return "%s-%s:%s" % (self.get_rule_display(), self.get_score_type_display(), self.score) class Meta: unique_together = ('rule', 'score_type') verbose_name_plural = "29. 奖惩规则" class ScoreRecord(models.Model): """积分奖惩记录""" content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True) content_object = GenericForeignKey('content_type', 'object_id') degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, verbose_name="关联学位课程") score_rule = models.ForeignKey("ScoreRule", verbose_name="关联规则") account = models.ForeignKey("Account", verbose_name="被执行人") score = models.IntegerField( verbose_name="金额(贝里)") # 这里单独有一个字段存积分而不是从score_rule里引用的原因是考虑到如果引用的话, # 一旦score_rule里的积分有变更,那么所有用户的历史积分也会被影响 received_score = models.IntegerField("实际到账金额贝里)", help_text="仅奖励用", default=0) # balance = models.PositiveIntegerField(verbose_name="奖金余额(贝里)") maturity_date = models.DateField("成熟日期(可提现日期)") applied = models.BooleanField(default=False, help_text="奖赏纪录是否已被执行", verbose_name="是否已被执行") applied_date = models.DateTimeField(blank=True, null=True, verbose_name="事件生效日期") date = models.DateTimeField(auto_now_add=True, verbose_name="事件触发日期") memo = models.TextField(blank=True, null=True) def __str__(self): return "%s-%s - %s - %s" % (self.id, self.score_rule, self.account, self.score,) class Meta: verbose_name_plural = "30. 奖惩记录" class CourseSchedule(models.Model): """课程进度计划表,针对学位课程,每开通一个模块,就为这个学员生成这个模块的推荐学习计划表,后面的奖惩均按此表进行""" study_record = models.ForeignKey("StudyRecord") homework = models.ForeignKey("Homework") recommend_date = models.DateField("推荐交作业日期") def __str__(self): return "%s - %s - %s " % (self.study_record, self.homework, self.recommend_date) class Meta: unique_together = ('study_record', 'homework') verbose_name_plural = "33. 课程模块计划表(学位课)" class StudyRecord(models.Model): """学位课程的模块学习进度,报名学位课程后,每个模块会立刻生成一条学习纪录""" enrolled_degree_course = models.ForeignKey("EnrolledDegreeCourse") course_module = models.ForeignKey("Course", verbose_name="学位模块", limit_choices_to={'course_type': 2}) open_date = models.DateField(blank=True, null=True, verbose_name="开通日期") end_date = models.DateField(blank=True, null=True, verbose_name="完成日期") status_choices = ((2, '在学'), (1, '未开通'), (0, '已完成')) status = models.SmallIntegerField(choices=status_choices, default=1) class Meta: verbose_name_plural = "39. 学习记录表(报名学位课程后,每个模块会立刻生成一条学习纪录)" unique_together = ('enrolled_degree_course', 'course_module') def __str__(self): return '%s-%s' % (self.enrolled_degree_course, self.course_module) def save(self, *args, **kwargs): if self.course_module.degree_course_id != self.enrolled_degree_course.degree_course_id: raise ValueError("学员要开通的模块必须与其报名的学位课程一致!") super(StudyRecord, self).save(*args, **kwargs) class DegreeRegistrationForm(models.Model): """学位课程报名表""" enrolled_degree = models.OneToOneField("EnrolledDegreeCourse") current_company = models.CharField(max_length=64, ) current_position = models.CharField(max_length=64, ) current_salary = models.IntegerField() work_experience_choices = ((0, "应届生"), (1, "1年"), (2, "2年"), (3, "3年"), (4, "4年"), (5, "5年"), (6, "6年"), (7, "7年"), (8, "8年"), (9, "9年"), (10, "10年"), (11, "超过10年"), ) work_experience = models.IntegerField() open_module = models.BooleanField("是否开通第1模块", default=True) stu_specified_mentor = models.CharField("学员自行指定的导师名", max_length=32, blank=True, null=True) study_plan_choices = ((0, "1-2小时/天"), (1, "2-3小时/天"), (2, "3-5小时/天"), (3, "5小时+/天"), ) study_plan = models.SmallIntegerField(choices=study_plan_choices, default=1) why_take_this_course = models.TextField("报此课程原因", max_length=1024) why_choose_us = models.TextField("为何选路飞", max_length=1024) your_expectation = models.TextField("你的期待", max_length=1024) memo = models.CharField(max_length=255, blank=True, null=True) class Meta: verbose_name_plural = "35. 报名表(学位课)" def __str__(self): return "%s" % self.enrolled_degree class EnrolledDegreeCourse(models.Model): """已报名的学位课程""" account = models.ForeignKey("Account") degree_course = models.ForeignKey("DegreeCourse") enrolled_date = models.DateTimeField(auto_now_add=True) valid_begin_date = models.DateField(verbose_name="有效期开始自", blank=True, null=True) # 开通第一个模块时,再添加课程有效期,2年 valid_end_date = models.DateField(verbose_name="有效期结束至", blank=True, null=True) status_choices = ( (0, '在学中'), (1, '休学中'), (2, '已毕业'), (3, '超时结业'), (4, '未开始'), # (3, '其它'), ) study_status = models.SmallIntegerField(choices=status_choices, default=0) mentor = models.ForeignKey("Account", verbose_name="导师", related_name='my_students', blank=True, null=True, limit_choices_to={'role': 1}) mentor_fee_balance = models.PositiveIntegerField("导师费用余额", help_text="这个学员的导师费用,每有惩罚,需在此字段同时扣除") order_detail = models.OneToOneField("OrderDetail") # 使订单购买后支持填写报名表 def __str__(self): return "%s:%s" % (self.account, self.degree_course) class Meta: unique_together = ('account', 'degree_course') verbose_name_plural = "36. 报名学位课" class Order(models.Model): """订单""" payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里')) 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("Account") actual_amount = models.FloatField(verbose_name="实付金额") status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')) status = models.SmallIntegerField(choices=status_choices, 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="订单取消时间") class Meta: verbose_name_plural = "37. 订单表" def __str__(self): return "%s" % self.order_number class OrderDetail(models.Model): """订单详情""" order = models.ForeignKey("Order") content_type = models.ForeignKey(ContentType) # 可关联普通课程或学位 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: verbose_name_plural = "38. 订单详细" unique_together = ("order", 'content_type', 'object_id') class TransactionRecord(models.Model): """贝里交易纪录""" account = models.ForeignKey("Account") amount = models.IntegerField("金额") balance = models.IntegerField("账户余额") transaction_type_choices = ((0, '收入'), (1, '支出'), (2, '退款'), (3, "提现")) # 2 为了处理 订单过期未支付时,锁定期贝里的回退 transaction_type = models.SmallIntegerField(choices=transaction_type_choices) content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True, verbose_name="关联对象") content_object = GenericForeignKey('content_type', 'object_id') transaction_number = models.CharField(unique=True, verbose_name="流水号", max_length=128) date = models.DateTimeField(auto_now_add=True) memo = models.CharField(max_length=128, blank=True, null=True) class Meta: verbose_name_plural = "40. 贝里交易记录" def __str__(self): return "%s" % self.transaction_number class HomeworkRecord(models.Model): """学员作业记录及成绩""" homework = models.ForeignKey("Homework") student = models.ForeignKey("EnrolledDegreeCourse", verbose_name="学生") score_choices = ((100, 'A+'), (90, 'A'), (85, 'B+'), (80, 'B'), (70, 'B-'), (60, 'C+'), (50, 'C'), (40, 'C-'), (-1, 'D'), (0, 'N/A'), (-100, 'COPY'), ) score = models.SmallIntegerField(verbose_name="分数", choices=score_choices, null=True, blank=True) mentor = models.ForeignKey("Account", related_name="my_stu_homework_record", limit_choices_to={'role': 1}, verbose_name="导师") mentor_comment = models.TextField(verbose_name="导师批注", blank=True, null=True) # 导师 status_choice = ( (0, '待批改'), (1, '已通过'), (2, '不合格'), ) status = models.SmallIntegerField(verbose_name='作业状态', choices=status_choice, default=0) submit_num = models.SmallIntegerField(verbose_name='提交次数', default=0) correct_date = models.DateTimeField('备注日期', blank=True, null=True) note = models.TextField(blank=True, null=True) date = models.DateTimeField("作业提交日期", auto_now_add=True) check_date = models.DateTimeField("批改日期", null=True, blank=True) update_time = models.DateTimeField(auto_now=True, verbose_name="提交日期") # homework_path = models.CharField(verbose_name='作业路径', max_length=256,blank=True,null=True) 作业路径可以动态拿到,没必要存 reward_choice = ((0, '新提交'), (1, '按时提交'), (2, '未按时提交'), (3, '成绩已奖励'), (4, '成绩已处罚'), (5, '未作按时检测'), ) reward_status = models.SmallIntegerField(verbose_name='作业记录奖惩状态', default=0) def __str__(self): return "%s %s" % (self.homework, self.student) class Meta: verbose_name_plural = "41. 作业" unique_together = ("homework", "student") class StuFollowUpRecord(models.Model): """学员跟进记录""" enrolled_degree_course = models.ForeignKey("EnrolledDegreeCourse", verbose_name="学生") mentor = models.ForeignKey("Account", related_name='mentor', limit_choices_to={'role': 1}, verbose_name="导师") followup_tool_choices = ((0, 'QQ'), (1, '微信'), (2, '电话'), (3, '系统通知')) followup_tool = models.SmallIntegerField(choices=followup_tool_choices, default=1) record = models.TextField(verbose_name="跟进记录") attachment_path = models.CharField(max_length=128, blank=True, null=True, verbose_name="附件路径", help_text="跟进记录的截图等") date = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "42. 学员跟进记录" def __str__(self): return "%s --%s --%s" % (self.enrolled_degree_course, self.record, self.date)
假设一个订单号20242359346940369,里面包含了10个课程。要如何体现一个订单号有10个课程呢?
需要这样设计
订单表
+----+-----------+----------+ | id | 订单号 | 用户id | +----+-----------+----------+ | 1 | 335446557 | 234 | +----+-----------+----------+
订单详细表
+----+-----------+----------+--------+--------+--------+
| id | 订单id | 课程id | 原价 | 价格 | 周期 |
+----+-----------+----------+--------+--------+--------+
| 1 | 335446557 | 12 | 20 | 15 | 7 |
...
+----+-----------+----------+--------+--------+--------+
Order
订单表,payment_number表示支付第3方订单号。比如:用户使用支付宝付款后,支付宝会发送一条POST请求,访问你的服务器,它会携带一个支付宝产生的订单号。这个就是payment_number的作用,用来进行后续的查询是否到账了!
actual_amount 表示抛开使用优惠券,贝里之后,实际付款的金额!
OrderDetail
订单详细表,content表示备注,用来给运营人员来加一些备注信息!
TransactionRecord
贝里交易纪录,当付款使用贝里时,这里会产生一条记录!
DegreeRegistrationForm
学位课程报名表,当用户购买学位课程后,是不能直接观看视频的。需要添加报名表才行,需要填写一些相关信息。有了这些信息后,才能给用户分配导师,促进学习!
ScoreRule
积分规则,主要约束导师的。初始分配:学员分配到某位导师后,这位导师的账户就会多一些积分,比如1500贝里。
如果学员未交作业,导师没有批改作业,受到学员投诉.... 都是要扣贝里的。这些贝里,到一定时间是可以提现的!
成熟周期这里注释掉了,意思就是学员毕业,就可以提现了!
在linux上面做了一个任务计划,每天凌晨2点跑一个脚本,用来更新成熟周期。
EnrolledCourse
已报名课程,不包括学位课程。也就是说,它只负责专题课。
当用户购买一个专题课后,需要在EnrolledCourse,Order,OrderDetail。这3个表产生记录即可
如果使用贝里支付,还需要操作TransactionRecord表。
设计到优惠券,还需要操作优惠券相关的表
如果是购买了学位课,除了EnrolledCourse表之外,其他表都要操作!
二、立即结算需求
看结算页面
当点击立即支付时,会向后端发送一些数据。发送哪些数据呢?
将页面的课程相关信息都发送过去?没有必要!为什么呢?
因为结算中心的数据,是存放在redis中的。所以发送时,只需要发送2个数据。
使用的贝里数,以及经过计算后的金额。后端接收到这2个数据时,再根据结算中心的数据,做计算。
当前端发送的金额和后端计算的金额一致时,跳转到支付宝页面,否则提示计算错误!
为什么呢?因为用户页面看到的是498,结果跳转到支付宝页面时,要付款510块,用户肯定不干了!
计算规则是这样的,先计算使用绑定课程的优惠券,然后将所有计算后的所有课程,做一个总价。
最后使用未绑定课程的优惠券,对总价进行计算。再使用贝里抵扣,最终得到实际付款金额!
注意:如果优惠券的金额大于实际课程的金额,则按照实际扣除。那么这个优惠券就不能使用了!没有返现的功能!
多个表操作时,要基于事务来做
总结:
后端接收: 贝里的数量和付款金额 a. 去结算中心获取要支付课程 b. 生成订单 c. 生成 订单详细,专题课购买表(如果优惠券>实际金额,则按照实际扣款) d. 如果使用了贝里,在贝里交易表中,记录一下; e. 优惠券表更新 f. 计算规则: 前端发送的金额和后端计算的金额 --> 相同,跳转到支付宝支付; 后端计算规则: 先计算绑定课程的优惠券 总价再使用未绑定课程优惠券 贝里抵扣 最终得到,实际付款金额 注意:多个表操作,使用事务
作业:
1.继续完成结算中心逻辑
2.熟悉今天的表结构
结算中心逻辑
修改views目录下的payment.py
create和list代码如下:
import json import redis from django.conf import settings from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api.utils.auth import LuffyAuthentication from api import models from api.utils.response import BaseResponse from django_redis import get_redis_connection CONN = get_redis_connection("default") # 使用redis连接池 class PaymentView(ViewSetMixin, APIView): authentication_classes = [LuffyAuthentication, ] def create(self, request, *args, **kwargs): """ 在结算中添加课程 :param request: :param args: :param kwargs: :return: """ # 1.接收用户选择的要结算的课程ID列表 choice_list = request.data.get('course_id') # print(choice_list) # 2.清空当前用户request.user.id结算中心的数据 # key = payment_1* CONN.delete('payment_1*') # 3.循环要加入结算中的所有课程ID列表 """ for course_id in 用户提交课程ID列表: 3.1 根据course_id,request.user.id去购物车中获取商品信息:商品名称、图片、价格(id,周期,显示周期,价格) 3.2 根据course_id,request.user.id获取 - 当前用户 - 当前课程 - 可用的优惠券 加入结算中心 提示:可以使用contenttypes """ ''' # 2.1 课程是否存在? temp = { 'id': CONN.hget(key, 'id').decode('utf-8'), 'name': CONN.hget(key, 'name').decode('utf-8'), 'img':CONN.hget(key, 'img').decode('utf-8'), 'default_price_id':CONN.hget(key, 'default_price_id').decode('utf-8'), 'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8')) } ''' user_id = request.user.id print('用户id', user_id) # 从购物车中获取数据 pattern = "shopping_car_%s_%s" % (request.user.id, '*',) # print(pattern) user_key_list = CONN.keys(pattern) for course_id in choice_list: # 用户选择的要结算的课程ID列表 for key in user_key_list: # 当前用户购物车列表 id = CONN.hget(key, 'id').decode('utf-8') # 获取购物车课程id if id == course_id: # 判断用户选择课程id和购物车课程id相等 name = CONN.hget(key, 'name').decode('utf-8') # 课程名 default_price_id = CONN.hget(key, 'default_price_id').decode('utf-8') # 默认价格策略id # 所有价格策略 price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8')) print('课程id',id) print('课程名',name) print('默认价格策略',default_price_id) valid_period = price_policy_dict[default_price_id].get('valid_period_display') print('有效期', valid_period) print('原价',price_policy_dict[default_price_id].get('price')) print('折后价', price_policy_dict[default_price_id].get('price')) print('所有价格策略',price_policy_dict) # 加入结算中心redis j_key = "payment_%s_%s" % (user_id, id,) CONN.hset(j_key, 'id', course_id) CONN.hset(j_key, 'name', name) CONN.hset(j_key, 'price_id', default_price_id) CONN.hset(j_key, 'price', price_policy_dict[default_price_id].get('price')) CONN.hset(j_key, 'valid_period', valid_period) CONN.hset(j_key, 'discount_price', price_policy_dict[default_price_id].get('price')) # 查询当前课程的 绑定课程优惠券 obj1 = models.Course.objects.filter(id=id).first() if obj1.coupon.all(): # 反向查询该课程的所有优惠券 print("绑定课程优惠券#########################") for i in obj1.coupon.all(): # 循环每一个优惠券 print('绑定课程优惠券个数',len(obj1.coupon.all())) coupon_dict = {} # 空字典 for j in range(len(obj1.coupon.all())): # for循环长度 if i.coupon_type == 0: # 类型为立减 coupon_dict[j] = '{}{}'.format(i.get_coupon_type_display(), i.money_equivalent_value) # 增加到redis中 CONN.hset(j_key, 'coupon_dict', coupon_dict) # print(111) print( '{}{}'.format(i.get_coupon_type_display(), i.money_equivalent_value)) elif i.coupon_type == 1: coupon_dict[j] = '满{}减{}'.format(i.minimum_consume, i.money_equivalent_value) CONN.hset(j_key, 'coupon_dict', coupon_dict) print('满{}减{}'.format(i.minimum_consume, i.money_equivalent_value)) else: # print(i.id) coupon_dict[j] = '{}折'.format(i.off_percent) CONN.hset(j_key, 'coupon_dict', coupon_dict) print('{}折'.format(i.off_percent)) # 绑定课程的优惠券 # obj = models.CouponRecord.objects.filter(account=user_id, coupon__object_id__isnull=False) # print('绑定课程优惠券#################') # if obj: # for i in obj: # if i.coupon.coupon_type == 0: # print('{}{}'.format(i.coupon.get_coupon_type_display(), i.coupon.money_equivalent_value)) # elif i.coupon.coupon_type == 1: # print('满{}减{}'.format(i.coupon.minimum_consume, i.coupon.money_equivalent_value)) # else: # # print(i.coupon.id) # print('{}折'.format(i.coupon.off_percent)) # 4.获取当前用户所有未绑定课程优惠券 # - 未使用 # - 有效期内 # - 加入结算中心:glocal_coupon_用户ID # 当前用户未绑定课程的优惠券 obj2 = models.CouponRecord.objects.filter(account=user_id, coupon__object_id__isnull=True) print('未绑定课程优惠券#################') if obj2: # 通用优惠券redis key coupon_key = "general_coupon_%s" % (user_id) for i in obj2: general_coupon_dict = {} # 空字典 print('未绑定课程优惠券个数 %s' % (len(obj2))) for j in range(len(obj2)): if i.coupon.coupon_type == 0: # 类型为立减 general_coupon_dict[j] = '{}{}'.format(i.coupon.get_coupon_type_display(), i.coupon.money_equivalent_value) CONN.hset(coupon_key, 'coupon_dict', general_coupon_dict) print('{}{}'.format(i.coupon.get_coupon_type_display(), i.coupon.money_equivalent_value)) elif i.coupon.coupon_type == 1: general_coupon_dict[j] = '满{}减{}'.format(i.coupon.minimum_consume, i.coupon.money_equivalent_value) CONN.hset(coupon_key, 'coupon_dict', general_coupon_dict) print('满{}减{}'.format(i.coupon.minimum_consume, i.coupon.money_equivalent_value)) else: general_coupon_dict[j] = '{}折'.format(i.coupon.off_percent) CONN.hset(coupon_key, 'coupon_dict', general_coupon_dict) print('{}折'.format(i.coupon.off_percent)) return Response('ok') def list(self, request, *args, **kwargs): """ 查看结算中心 :param request: :param args: :param kwargs: :return: """ # 1. 根据用户ID去结算中心获取该用户所有要结算课程 course_id = request.query_params.get('course_id') print('课程id',course_id) obj = models.Course.objects.filter(id=course_id).first() print('结算课程',obj.name) # 2. 根据用户ID去结算中心获取该用户所有可用未绑定课程的优惠券 user_id =request.user.id print('用户id', user_id) obj2 = models.CouponRecord.objects.filter(account=user_id, coupon__object_id__isnull=True) if obj2: for i in obj2: if i.coupon.coupon_type == 0: print('{}{}'.format(i.coupon.get_coupon_type_display(), i.coupon.money_equivalent_value)) elif i.coupon.coupon_type == 1: print('满{}减{}'.format(i.coupon.minimum_consume, i.coupon.money_equivalent_value)) else: print(i.coupon.id) print('{}折'.format(i.coupon.off_percent)) # 3. 用户表中获取贝里余额 beili = models.Account.objects.filter(id=user_id).first() print('用户贝里',beili.balance) # 4. 以上数据构造成一个字典 return Response('...') def update(self, request, *args, **kwargs): """ 更新优惠券 :param request: :param args: :param kwargs: :return: """ # 1. 获取用户提交: # course_id=1,coupon_id=3 # course_id=0,coupon_id=6 # 2. course_id=1 --> 去结算中心获取当前用户所拥有的绑定当前课程优惠,并进行校验 # - 成功:defaul_coupon_id=3 # - 否则:非法请求 # 3. course_id=0 --> 去结算中心获取当前用户所拥有的未绑定课程优惠,并进行校验 # - 成功:defaul_coupon_id=3 # - 否则:非法请求
使用postman测试 create
查看Pycharm控制台输出:
用户id 1 课程id 1 课程名 Python开发入门7天特训营 默认价格策略 1 有效期 1周 原价 10.0 折后价 10.0 所有价格策略 {'2': {'valid_period': 30, 'id': 2, 'price': 50.0, 'valid_period_display': '1个月'}, '1': {'valid_period': 7, 'id': 1, 'price': 10.0, 'valid_period_display': '1周'}} 绑定课程优惠券######################### 绑定课程优惠券个数 2 8折 8折 绑定课程优惠券个数 2 满50减10 满50减10 未绑定课程优惠券################# 未绑定课程优惠券个数 1 立减10
使用postman测试 list
查看Pycharm控制台输出:
课程id 1 结算课程 Python开发入门7天特训营 用户id 1 立减10 用户贝里 100.0
说明:
结算中心有2个key
一个是结算中心key
结算中心 = { 'payment_用户id_课程id':{ id:课程id, mame:课程名, price_id:价格策略id, price:原价, valid_period:有效期, discount_price:折后价, coupon_dict: { # 绑定课程优惠券 1:'优惠券1', 2:'优惠券2', 3:'优惠券3', } }, }
一个是用户通用优惠券,也就是未绑定课程优惠券
用户通用优惠券 = { 'general_coupon_用户id':{ coupon_dict: { # 未绑定课程优惠券 1:'优惠券1', 2:'优惠券2', 3:'优惠券3', } }, }
我没有把通用优惠放在结算中心key里面,为什么呢?
因为结算中心的key,都是用户id_课程id。如果用户购买了10个课程,那么就会产生10个key。
而每个key都用存储通用优惠券,那么这个通用优惠券重复了10次,完全没有必要!
所以我单独分离出来了!
完整代码请参考github
https://github.com/987334176/luffycity/archive/v1.6.zip