考试第三部分:Django
16. 列列举你熟悉的Http协议头以及作用。(1分)
Accept-Charset: 用于告诉浏览器,客户机采用的编码
Host: 客户机通过这个头告诉服务器,想访问的主机名
User-Agent: 客户机通过这个头告诉服务器,客户机的软件环境
Cookie: 客户机通过这个头可以向服务器带数据
View Code
17. 状态码含义:200、301、302、304、404、500。(2分)
200请求已成功301永久重定向302临时重定向304未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源404资源找不到500 服务器内部错误,无法完成请求
View Code
18. 简述cookie和session?(2分)
cookie是保存在客户端浏览器的,每次请求网页,会携带cookie
session是保存在服务器上面的,用来判断请求客户端的cookie是否合法
View Code
19. django中get和filter的区别?(1分)
get 返回一个object。当返回结果,有且只有一条时,才会返回。否则报错
filter 返回一个queryset对象。即使没有匹配到条件,不会报错,返回一个空的queryset对象
View Code
20. django的中间件在1.7和1.11版本间有什什么区别?(1分)
1. 路由编写2. ORM中,外键一定要有on_delete属性3. 中间件4. 模板配置
View Code
21. django中contenttypes组件的作⽤用?(1分)
用来解决一个对和多个表做外键关联,并且关联的字段,不会在表中产生新的字段
View Code
22. django中Q的作⽤用?(2分)
用来做一些复杂查询,比如or
View Code
23. 将如下SQL语句句使⽤用Django的ORM实现:(3分)
select * from order where id >= 12
order.object.filter(id__gte=12)
View Code
select * from order where id != 12
order.object.filter(id__exclude=12)
View Code
select * from order where id in [1,3,4]
order.object.filter(id__in=[1,3,4])
View Code
select * from order where id between 20 and 100
order.object.filter(id__range([20,100])
View Code
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)))
View Code
select * from order order by id desc,age asc
#默认使用asc(升序),-id表示以id字段,进行desc(降序)来排序
order.object.all().order_by('-id','age')
View Code
24. 编写查询语句句:(5分,前2个每个1分,最后⼀一题3分)
• 查看所有学⽣生,并打印 姓名、班级名称
#注意:一定要以Student为基准表#当filter字段有其他表时,使用inner join。当使用values时,它是left join#一个班级在创建的时候,是没有学生的。所以以classes为基础表时,会造成数据不准确
Student.objects.all().values('name','classes__name')
View Code
• 查看班级名称为"全栈12期"的所有学⽣生
Classes.object.filter(name="全栈12期").value("student__name")
View Code
• 查看没有学⽣生的所有班级ID、班级名称
#当班级表的学生记录为空时,使用student__isnull=True
Classes.objects.filter(student__isnull=True).values('id','name')
View Code
25. django中遇到复杂的SQL时ORM⽆无法完成,如何使⽤用原⽣生SQL执⾏行行?(2分)
1. 根据connections['default'].cursor(),最接近于pymysql操作2. 使用extra
View Code
cursor = connection.cursor() 表示连接默认的数据库,看settings.py中的数据库配置
DATABASES ={'default': {'ENGINE': 'django.db.backends.sqlite3','NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
View Code
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'),
}
}
View Code
extra是django自带的,默认就可以使用。而connection使用时,需要导入模块!
详情请参考链接:
搜索 "其他操作",展开里面的代码,就可以看到了!
一、路飞其他数据库表结构
深科技
先来看深科技的相关表
#############深科技相关 ###################
classArticleSource(models.Model):"""文章来源"""name= models.CharField(max_length=64, unique=True)classMeta:
verbose_name_plural= "16. 文章来源"
def __str__(self):returnself.nameclassArticle(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反向查询, 不会生成表字段,切勿删除,如有疑问请联系老村长
classMeta:
verbose_name_plural= "17. 文章"
def __str__(self):return "%s-%s" %(self.source, self.title)classArticleDetail(models.Model):"""文章详细"""article= models.OneToOneField(to='Article')
content= models.TextField(verbose_name="文章正文")classCollection(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)classMeta:
unique_together= ('content_type', 'object_id', 'account')
verbose_name_plural= "18. 通用收藏表"
classComment(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):returnself.contentclassMeta:
verbose_name_plural= "19. 通用评论表"
View Code
打开网页: 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"),那么就可以建立关联了。而且数据库不会产生新的字段!所以说,它是一个通用的评论表
订单相关
再来看订单相关的表
################## 订单相关 #################
classEnrolledCourse(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)classMeta:
verbose_name_plural= "34. 报名专题课"
classScoreRule(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)classMeta:
unique_together= ('rule', 'score_type')
verbose_name_plural= "29. 奖惩规则"
classScoreRecord(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,)classMeta:
verbose_name_plural= "30. 奖惩记录"
classCourseSchedule(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)classMeta:
unique_together= ('study_record', 'homework')
verbose_name_plural= "33. 课程模块计划表(学位课)"
classStudyRecord(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)classMeta:
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)classDegreeRegistrationForm(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)classMeta:
verbose_name_plural= "35. 报名表(学位课)"
def __str__(self):return "%s" %self.enrolled_degreeclassEnrolledDegreeCourse(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)classMeta:
unique_together= ('account', 'degree_course')
verbose_name_plural= "36. 报名学位课"
classOrder(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="订单取消时间")classMeta:
verbose_name_plural= "37. 订单表"
def __str__(self):return "%s" %self.order_numberclassOrderDetail(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)classMeta:
verbose_name_plural= "38. 订单详细"unique_together= ("order", 'content_type', 'object_id')classTransactionRecord(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)classMeta:
verbose_name_plural= "40. 贝里交易记录"
def __str__(self):return "%s" %self.transaction_numberclassHomeworkRecord(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)classMeta:
verbose_name_plural= "41. 作业"unique_together= ("homework", "student")classStuFollowUpRecord(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)classMeta:
verbose_name_plural= "42. 学员跟进记录"
def __str__(self):return "%s --%s --%s" % (self.enrolled_degree_course, self.record, self.date)
View Code
假设一个订单号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. 计算规则:
前端发送的金额和后端计算的金额-->相同,跳转到支付宝支付;
后端计算规则:
先计算绑定课程的优惠券
总价再使用未绑定课程优惠券
贝里抵扣
最终得到,实际付款金额
注意:多个表操作,使用事务
View Code
作业:
1.继续完成结算中心逻辑
2.熟悉今天的表结构
结算中心逻辑
修改views目录下的payment.py
create和list代码如下:
importjsonimportredisfrom django.conf importsettingsfrom rest_framework.views importAPIViewfrom rest_framework.viewsets importViewSetMixinfrom rest_framework.response importResponsefrom api.utils.auth importLuffyAuthenticationfrom api importmodelsfrom api.utils.response importBaseResponsefrom django_redis importget_redis_connection
CONN= get_redis_connection("default") #使用redis连接池
classPaymentView(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.idprint('用户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('未绑定课程优惠券#################')ifobj2:#通用优惠券redis key
coupon_key = "general_coupon_%s" %(user_id)for i inobj2:
general_coupon_dict= {} #空字典
print('未绑定课程优惠券个数 %s' %(len(obj2)))for j inrange(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.idprint('用户id', user_id)
obj2= models.CouponRecord.objects.filter(account=user_id, coupon__object_id__isnull=True)ifobj2:for i inobj2: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
#- 否则:非法请求
View Code
使用postman测试 create
查看Pycharm控制台输出:
用户id 1课程id1课程名 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周'}}
绑定课程优惠券#########################
绑定课程优惠券个数 28折
8折
绑定课程优惠券个数2满50减10
满50减10
未绑定课程优惠券#################
未绑定课程优惠券个数 1立减10
View Code
使用postman测试 list
查看Pycharm控制台输出:
课程id 1结算课程 Python开发入门7天特训营
用户id1立减10
用户贝里100.0
View Code
说明:
结算中心有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