python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)
python 全棧開發(fā),Day104(DRF用戶認(rèn)證,結(jié)算中心,django-redis)
考試第二部分:MySQL數(shù)據(jù)庫(kù)
6.? MySQL中char和varchar的區(qū)別(1分)
char是定長(zhǎng),varchar是變長(zhǎng)。 char的查詢速度比varchar要快。 View Code?
7.? ?MySQL中varchar(50)的50表示什什么意思?(1分)
是字符長(zhǎng)度。一個(gè)中文,也是一個(gè)字符。 View Code?
8. left join、right join以及inner join的區(qū)別?(2分)
left join,表示左連接,以左表為基準(zhǔn),如果左表有不匹配的,顯示為空 right join,表示右連接,以右表為基準(zhǔn),如果右表有不匹配的,顯示為空 inner join,表示內(nèi)連接,只顯示2個(gè)表?xiàng)l件符合的記錄,不匹配的不顯示 View Code?
9. MySQL組合索引(2分)
where?子句句中有a、b、c 三個(gè)查詢條件,創(chuàng)建?一個(gè)組合索引 abc(a,b,c),那么如下那中情況會(huì)命 中索引:
a. where (a)
b. where (b)
c. where (c)
d. where (a,b)
e. where (b,c)
f. where (a,c)
g. where (a,b,c)
解釋:
索引有2個(gè)功能:加快查詢和約束。
這里的約束指的是唯一索引,聯(lián)合唯一索引。
索引遵循的原則: 最左前綴原則
你可以認(rèn)為聯(lián)合索引是闖關(guān)游戲的設(shè)計(jì)例如你這個(gè)聯(lián)合索引是state/city/zipCode那么state就是第一關(guān) city是第二關(guān), zipCode就是第三關(guān)你必須匹配了第一關(guān),才能匹配第二關(guān),匹配了第一關(guān)和第二關(guān),才能匹配第三關(guān)你不能直接到第二關(guān)的索引的格式就是第一層是state,第二層才是city索引是因?yàn)锽+樹結(jié)構(gòu) 所以查找快 如果單看第三列 是非排序的。 多列索引是先按照第一列進(jìn)行排序,然后在第一列排好序的基礎(chǔ)上再對(duì)第二列排序,如果沒有第一列的話,直接訪問第二列,那第二列肯定是無(wú)序的,直接訪問后面的列就用不到索引了。 所以如果不是在前面列的基礎(chǔ)上而是但看后面某一列,索引是失效的。 View Code簡(jiǎn)而言之,只要where條件包含最左邊的字段,那么它就會(huì)用到組合索引,反之亦然!
如果創(chuàng)建了組合索引,但是卻沒有命中,這是浪費(fèi)磁盤空間。因?yàn)樗饕舱加么疟P!
?
10. 假設(shè)學(xué)?生Student和教師Teacher關(guān)系模型如下:(4分) Student(學(xué)號(hào)、姓名、性別、類型、身份證號(hào)) Teacher(教師號(hào)、姓名、性別、類型、身份證號(hào)、工資)
其中,學(xué)?生表中類別為“本科生”和“研究生”兩類;性別為“男”和“女”兩類。
a. 性別為女的所有學(xué)生。
b. 學(xué)生表中類別分別對(duì)應(yīng)的個(gè)數(shù)。
select 類型,count(1) from Student group by 類型 View Codec.工資少于10000的女教師的身份證和姓名。
select 身份證,姓名 from Teacher where 性別= '女' and 類型='研究生' and 工資 < 10000 View Coded.? 研究生教師平均工資、最?高和最低工資。
select AVG(工資),MAX(工資),MIN(工資) from Teacher wherer 類型='研究生' View Code?
11. 根據(jù)如下表結(jié)構(gòu)建表:(2分)
CREATE TABLE `t1` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(32) NOT NULL,`balance` decimal(10,2) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; View Code?
12.? ? 根據(jù)如下表查詢每個(gè)?用戶第?一次下訂單的時(shí)間。(2分)
# 第一次下單時(shí)間,就是時(shí)間最早的 select name,MIN(order_time) from table group by name View Code?
13. 有?一個(gè)訂單系統(tǒng)包含訂單信息、商品信息、價(jià)格信息且還要?一些狀態(tài),如何設(shè)計(jì)表結(jié)構(gòu)(2分)
#最簡(jiǎn)單的設(shè)計(jì) 商品表 - id - 名稱 - 價(jià)格 - 描述信息訂單表 - id - 訂單號(hào)(唯一) - 商品id - 用戶id用戶表 - id - username - password View Code?
14. 有如下表:(3分)
products(商品表) columns為 id、name、price
orders(商城訂單表) columns為 id、reservations_id、product_id、quantity(數(shù)量量)
reservations(酒店訂單表) columns為 id、user_id、price、created_at
ps:這個(gè)一個(gè)真實(shí)面試題!
應(yīng)用場(chǎng)景:比如萬(wàn)達(dá)酒店,需要訂購(gòu)商品,比如紅酒,紅木家具...
?
a. 各個(gè)商品的售賣情況,需要字段:商品名、購(gòu)買總數(shù)、商品收?入(單價(jià)*數(shù)量量)
SELECTproducts. NAME,sum(orders.quantity),products.price * sum(orders.quantity) FROMorders LEFT JOIN products ON products.id = orders.product_id GROUP BYorders.product_id View Code?
b. 所有用戶在2018-01-01至2018-02-01下單次數(shù)、下單金額、商城下單次數(shù)、商城下單金額
# 注意:最后的期限要加1天。因?yàn)?3:59:59也是屬于當(dāng)天的 SELECTcount(1),sum(reservations.price),sum(orders.quantity),products.price * sum(orders.quantity) FROMreservations LEFT JOIN orders ON orders.reservations_id = reservations.id LEFT JOIN products ON products.id = orders.product_id WHEREreservations.created_at BETWEEN 2018-01-01 AND reservations.created_at '2018-02-02' View Code?
c. 歷月下單用戶數(shù):下單1次的用戶數(shù)、下單2次的用戶數(shù)、下單3次及以上的用戶數(shù)
# 下單1次的用戶數(shù) select DATE_FORMAT(created_at,'%Y-%m'),user_id,count(1) from reservations group by DATE_FORMAT(created_at,'%Y-%m'),user_id having count(user_id) = 1;# 下單2次的用戶數(shù) select DATE_FORMAT(created_at,'%Y-%m'),user_id,count(1) from reservations group by DATE_FORMAT(created_at,'%Y-%m'),user_id having count(user_id) = 1;# 下單3次及以上的用戶數(shù) select DATE_FORMAT(created_at,'%Y-%m'),user_id,count(1) from reservations group by DATE_FORMAT(created_at,'%Y-%m'),user_id having count(user_id) >= 3; View Code?
15.? 根據(jù)表寫SQL語(yǔ)句句:(5分)
? 查詢所有同學(xué)的學(xué)號(hào)、姓名、班級(jí)名稱。(1分)
select student.sid,student.sname,class.caption from student left jon class on class.cid = student.class_id View Code?
? 查詢沒有學(xué)?生的所有班級(jí)。(2分)
select class.caption from student left jon class on class.cid = student.class_id where class.cid is null View Code?
? 查詢有學(xué)?生的所有班級(jí)的名稱和學(xué)數(shù)量量。(2分)
select class.caption,count(1) from student left jon class on class.cid = student.class_id View Code?
一、DRF用戶認(rèn)證
流程圖
請(qǐng)求到達(dá)視圖的時(shí)候,需要進(jìn)行認(rèn)證。
認(rèn)證是在中間件之后的。如果一旦認(rèn)證失敗,則返回信息給用戶
?
啟動(dòng)項(xiàng)目luffcity,訪問購(gòu)物車
注意:要啟動(dòng)redis,否則提示獲取購(gòu)物車數(shù)據(jù)失敗
?
添加認(rèn)證?
購(gòu)物車需要登錄才能查看,登錄成功后,返回一個(gè)token
修改views目錄下的auth.py
from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from api import models from api.utils.response import BaseResponse import uuidclass AuthView(ViewSetMixin,APIView):def login(self,request,*args,**kwargs):"""用戶登陸認(rèn)證:param request::param args::param kwargs::return:"""response = BaseResponse() # 默認(rèn)狀態(tài)try:user = request.data.get('username')pwd = request.data.get('password')# 驗(yàn)證用戶和密碼obj = models.Account.objects.filter(username=user,password=pwd).first()if not obj:response.code = 10002response.error = '用戶名或密碼錯(cuò)誤'else:uid = str(uuid.uuid4()) # 生成唯一idresponse.code = 99999response.data = uidexcept Exception as e:response.code = 10005response.error = '操作異常'return Response(response.dict) View Code請(qǐng)確保已經(jīng)生成了表api_account,并添加了一條記錄
如果沒有,請(qǐng)參考昨天的文檔!
?
測(cè)試用戶和密碼
查看返回結(jié)果
?
輸入正確的用戶名和密碼
查看返回結(jié)果,返回一個(gè)隨機(jī)碼。這個(gè)就是token
9999表示登錄成功
?
既然token已經(jīng)生成了,并返回給了客戶端。那么服務(wù)器如何驗(yàn)證客戶端的token是否合法呢?
答案是,服務(wù)器需要保存token。推薦加一個(gè)有效期,比如微信公眾號(hào)的token有效期為8個(gè)小時(shí)!
增加token表
修改models.py,增加token表
它和用戶表是一對(duì)一的關(guān)系!
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# ######################## 課程相關(guān) ########################class CourseCategory(models.Model):"""課程大類, e.g 前端 后端..."""name = models.CharField(max_length=64, unique=True)def __str__(self):return "%s" % self.nameclass Meta:verbose_name_plural = "01.課程大類"class CourseSubCategory(models.Model):"""課程子類, e.g python linux """category = models.ForeignKey("CourseCategory")name = models.CharField(max_length=64, unique=True)def __str__(self):return "%s" % self.nameclass Meta:verbose_name_plural = "02.課程子類"class DegreeCourse(models.Model):"""學(xué)位課程"""name = models.CharField(max_length=128, unique=True)course_img = models.CharField(max_length=255, verbose_name="縮略圖")brief = models.TextField(verbose_name="學(xué)位課程簡(jiǎn)介", )total_scholarship = models.PositiveIntegerField(verbose_name="總獎(jiǎng)學(xué)金(貝里)", default=40000) # 2000 2000mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本課程的導(dǎo)師輔導(dǎo)費(fèi)用(貝里)", default=15000)period = models.PositiveIntegerField(verbose_name="建議學(xué)習(xí)周期(days)", default=150) # 為了計(jì)算學(xué)位獎(jiǎng)學(xué)金prerequisite = models.TextField(verbose_name="課程先修要求", max_length=1024)teachers = models.ManyToManyField("Teacher", verbose_name="課程講師")# 用于GenericForeignKey反向查詢, 不會(huì)生成表字段,切勿刪除# coupon = GenericRelation("Coupon")# 用于GenericForeignKey反向查詢,不會(huì)生成表字段,切勿刪除degreecourse_price_policy = GenericRelation("PricePolicy")def __str__(self):return self.nameclass Meta:verbose_name_plural = "03.學(xué)位課"class Teacher(models.Model):"""講師、導(dǎo)師表"""name = models.CharField(max_length=32)role_choices = ((0, '講師'), (1, '導(dǎo)師'))role = models.SmallIntegerField(choices=role_choices, default=0)title = models.CharField(max_length=64, verbose_name="職位、職稱")signature = models.CharField(max_length=255, help_text="導(dǎo)師簽名", blank=True, null=True)image = models.CharField(max_length=128)brief = models.TextField(max_length=1024)def __str__(self):return self.nameclass Meta:verbose_name_plural = "04.導(dǎo)師或講師"class Scholarship(models.Model):"""學(xué)位課程獎(jiǎng)學(xué)金"""degree_course = models.ForeignKey("DegreeCourse")time_percent = models.PositiveSmallIntegerField(verbose_name="獎(jiǎng)勵(lì)檔位(時(shí)間百分比)", help_text="只填百分值,如80,代表80%")value = models.PositiveIntegerField(verbose_name="獎(jiǎng)學(xué)金數(shù)額")def __str__(self):return "%s:%s" % (self.degree_course, self.value)class Meta:verbose_name_plural = "05.學(xué)位課獎(jiǎng)學(xué)金"class Course(models.Model):"""專題課/學(xué)位課模塊表"""name = models.CharField(max_length=128, unique=True)course_img = models.CharField(max_length=255)sub_category = models.ForeignKey("CourseSubCategory")course_type_choices = ((0, '付費(fèi)'), (1, 'VIP專享'), (2, '學(xué)位課程'))course_type = models.SmallIntegerField(choices=course_type_choices)# 不為空;學(xué)位課的某個(gè)模塊# 為空;專題課degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, help_text="若是學(xué)位課程,此處關(guān)聯(lián)學(xué)位表")brief = models.TextField(verbose_name="課程概述", max_length=2048)level_choices = ((0, '初級(jí)'), (1, '中級(jí)'), (2, '高級(jí)'))level = models.SmallIntegerField(choices=level_choices, default=1)pub_date = models.DateField(verbose_name="發(fā)布日期", blank=True, null=True)period = models.PositiveIntegerField(verbose_name="建議學(xué)習(xí)周期(days)", default=7) # order = models.IntegerField("課程順序", help_text="從上一個(gè)課程數(shù)字往后排")attachment_path = models.CharField(max_length=128, verbose_name="課件路徑", blank=True, null=True)status_choices = ((0, '上線'), (1, '下線'), (2, '預(yù)上線'))status = models.SmallIntegerField(choices=status_choices, default=0)template_id = models.SmallIntegerField("前端模板id", default=1)coupon = GenericRelation("Coupon")# 用于GenericForeignKey反向查詢,不會(huì)生成表字段,切勿刪除price_policy = GenericRelation("PricePolicy")asked_question = GenericRelation("OftenAskedQuestion")def __str__(self):return "%s(%s)" % (self.name, self.get_course_type_display())def save(self, *args, **kwargs):if self.course_type == 2:if not self.degree_course:raise ValueError("學(xué)位課程必須關(guān)聯(lián)對(duì)應(yīng)的學(xué)位表")super(Course, self).save(*args, **kwargs)class Meta:verbose_name_plural = "06.專題課或?qū)W位課模塊"class CourseDetail(models.Model):"""課程詳情頁(yè)內(nèi)容"""course = models.OneToOneField("Course")hours = models.IntegerField("課時(shí)")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="為什么學(xué)習(xí)這門課程")what_to_study_brief = models.TextField(verbose_name="我將學(xué)到哪些內(nèi)容")career_improvement = models.TextField(verbose_name="此項(xiàng)目如何有助于我的職業(yè)生涯")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.courseclass Meta:verbose_name_plural = "07.課程或?qū)W位模塊詳細(xì)"class OftenAskedQuestion(models.Model):"""常見問題"""content_type = models.ForeignKey(ContentType) # 關(guān)聯(lián)course or degree_courseobject_id = models.PositiveIntegerField()content_object = GenericForeignKey('content_type', 'object_id')question = models.CharField(max_length=255)answer = models.TextField(max_length=1024)def __str__(self):return "%s-%s" % (self.content_object, self.question)class Meta:unique_together = ('content_type', 'object_id', 'question')verbose_name_plural = "08. 常見問題"class CourseOutline(models.Model):"""課程大綱"""course_detail = models.ForeignKey("CourseDetail")title = models.CharField(max_length=128)# 前端顯示順序order = models.PositiveSmallIntegerField(default=1)content = models.TextField("內(nèi)容", max_length=2048)def __str__(self):return "%s" % self.titleclass Meta:unique_together = ('course_detail', 'title')verbose_name_plural = "09. 課程大綱"class CourseChapter(models.Model):"""課程章節(jié)"""course = models.ForeignKey("Course")chapter = models.SmallIntegerField(verbose_name="第幾章", default=1)name = models.CharField(max_length=128)summary = models.TextField(verbose_name="章節(jié)介紹", blank=True, null=True)pub_date = models.DateField(verbose_name="發(fā)布日期", auto_now_add=True)class Meta:unique_together = ("course", 'chapter')verbose_name_plural = "10. 課程章節(jié)"def __str__(self):return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)class CourseSection(models.Model):"""課時(shí)目錄"""chapter = models.ForeignKey("CourseChapter")name = models.CharField(max_length=128)order = models.PositiveSmallIntegerField(verbose_name="課時(shí)排序", help_text="建議每個(gè)課時(shí)之間空1至2個(gè)值,以備后續(xù)插入課時(shí)")section_type_choices = ((0, '文檔'), (1, '練習(xí)'), (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="視頻時(shí)長(zhǎng)", blank=True, null=True, max_length=32) # 僅在前端展示使用pub_date = models.DateTimeField(verbose_name="發(fā)布時(shí)間", auto_now_add=True)free_trail = models.BooleanField("是否可試看", default=False)class Meta:unique_together = ('chapter', 'section_link')verbose_name_plural = "11. 課時(shí)"def __str__(self):return "%s-%s" % (self.chapter, self.name)class Homework(models.Model):chapter = models.ForeignKey("CourseChapter")title = models.CharField(max_length=128, verbose_name="作業(yè)題目")order = models.PositiveSmallIntegerField("作業(yè)順序", help_text="同一課程的每個(gè)作業(yè)之前的order值間隔1-2個(gè)數(shù)")homework_type_choices = ((0, '作業(yè)'), (1, '模塊通關(guān)考核'))homework_type = models.SmallIntegerField(choices=homework_type_choices, default=0)requirement = models.TextField(max_length=1024, verbose_name="作業(yè)需求")threshold = models.TextField(max_length=1024, verbose_name="踩分點(diǎn)")recommend_period = models.PositiveSmallIntegerField("推薦完成周期(天)", default=7)scholarship_value = models.PositiveSmallIntegerField("為該作業(yè)分配的獎(jiǎng)學(xué)金(貝里)")note = models.TextField(blank=True, null=True)enabled = models.BooleanField(default=True, help_text="本作業(yè)如果后期不需要了,不想讓學(xué)員看到,可以設(shè)置為False")class Meta:unique_together = ("chapter", "title")verbose_name_plural = "12. 章節(jié)作業(yè)"def __str__(self):return "%s - %s" % (self.chapter, self.title)# class CourseReview(models.Model): # """課程評(píng)價(jià)""" # enrolled_course = models.OneToOneField("EnrolledCourse") # about_teacher = models.FloatField(default=0, verbose_name="講師講解是否清晰") # about_video = models.FloatField(default=0, verbose_name="內(nèi)容實(shí)用") # about_course = models.FloatField(default=0, verbose_name="課程內(nèi)容通俗易懂") # review = models.TextField(max_length=1024, verbose_name="評(píng)價(jià)") # disagree_number = models.IntegerField(default=0, verbose_name="踩") # agree_number = models.IntegerField(default=0, verbose_name="贊同數(shù)") # tags = models.ManyToManyField("Tags", blank=True, verbose_name="標(biāo)簽") # date = models.DateTimeField(auto_now_add=True, verbose_name="評(píng)價(jià)日期") # is_recommend = models.BooleanField("熱評(píng)推薦", default=False) # hide = models.BooleanField("不在前端頁(yè)面顯示此條評(píng)價(jià)", default=False) # # def __str__(self): # return "%s-%s" % (self.enrolled_course.course, self.review) # # class Meta: # verbose_name_plural = "13. 課程評(píng)價(jià)(購(gòu)買課程后才能評(píng)價(jià))" # # # class DegreeCourseReview(models.Model): # """學(xué)位課程評(píng)價(jià) # 為了以后可以定制單獨(dú)的評(píng)價(jià)內(nèi)容,所以不與普通課程的評(píng)價(jià)混在一起,單獨(dú)建表 # """ # enrolled_course = models.ForeignKey("EnrolledDegreeCourse") # course = models.ForeignKey("Course", verbose_name="評(píng)價(jià)學(xué)位模塊", blank=True, null=True, # help_text="不填寫即代表評(píng)價(jià)整個(gè)學(xué)位課程", limit_choices_to={'course_type': 2}) # about_teacher = models.FloatField(default=0, verbose_name="講師講解是否清晰") # about_video = models.FloatField(default=0, verbose_name="視頻質(zhì)量") # about_course = models.FloatField(default=0, verbose_name="課程") # review = models.TextField(max_length=1024, verbose_name="評(píng)價(jià)") # disagree_number = models.IntegerField(default=0, verbose_name="踩") # agree_number = models.IntegerField(default=0, verbose_name="贊同數(shù)") # tags = models.ManyToManyField("Tags", blank=True, verbose_name="標(biāo)簽") # date = models.DateTimeField(auto_now_add=True, verbose_name="評(píng)價(jià)日期") # is_recommend = models.BooleanField("熱評(píng)推薦", default=False) # hide = models.BooleanField("不在前端頁(yè)面顯示此條評(píng)價(jià)", default=False) # # def __str__(self): # return "%s-%s" % (self.enrolled_course, self.review) # # class Meta: # verbose_name_plural = "14. 學(xué)位課評(píng)價(jià)(購(gòu)買課程后才能評(píng)價(jià))"class PricePolicy(models.Model):"""價(jià)格與有課程效期表"""content_type = models.ForeignKey(ContentType) # 關(guān)聯(lián)course or degree_courseobject_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個(gè)月'),(60, '2個(gè)月'),(90, '3個(gè)月'),(180, '6個(gè)月'), (210, '12個(gè)月'),(540, '18個(gè)月'), (720, '24個(gè)月'),)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. 價(jià)格策略"def __str__(self):return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)# ################################### 優(yōu)惠券相關(guān) #################################class Coupon(models.Model):"""優(yōu)惠券生成規(guī)則"""name = models.CharField(max_length=64, verbose_name="活動(dòng)名稱")brief = models.TextField(blank=True, null=True, verbose_name="優(yōu)惠券介紹")coupon_type_choices = ((0, '立減'), (1, '滿減券'), (2, '折扣券'))coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券類型")money_equivalent_value = models.IntegerField(verbose_name="等值貨幣")off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只針對(duì)折扣券,例7.9折,寫79", blank=True, null=True)minimum_consume = models.PositiveIntegerField("最低消費(fèi)", default=0, help_text="僅在滿減券時(shí)填寫此字段")content_type = models.ForeignKey(ContentType, blank=True, null=True)object_id = models.PositiveIntegerField("綁定課程", blank=True, null=True, help_text="可以把優(yōu)惠券跟課程綁定")content_object = GenericForeignKey('content_type', 'object_id')quantity = models.PositiveIntegerField("數(shù)量(張)", default=1)open_date = models.DateField("優(yōu)惠券領(lǐng)取開始時(shí)間")close_date = models.DateField("優(yōu)惠券領(lǐng)取結(jié)束時(shí)間")valid_begin_date = models.DateField(verbose_name="有效期開始時(shí)間", blank=True, null=True)valid_end_date = models.DateField(verbose_name="有效結(jié)束時(shí)間", blank=True, null=True)# coupon_valid_days = models.PositiveIntegerField(verbose_name="優(yōu)惠券有效期(天)", blank=True, null=True,# help_text="自券被領(lǐng)時(shí)開始算起")date = models.DateTimeField(auto_now_add=True)class Meta:verbose_name_plural = "31. 優(yōu)惠券生成記錄"def __str__(self):return "%s(%s)" % (self.get_coupon_type_display(), self.name)class CouponRecord(models.Model):"""優(yōu)惠券發(fā)放、消費(fèi)紀(jì)錄"""coupon = models.ForeignKey("Coupon")account = models.ForeignKey("Account", verbose_name="擁有者")number = models.CharField(max_length=64, unique=True)status_choices = ((0, '未使用'), (1, '已使用'), (2, '已過期'))status = models.SmallIntegerField(choices=status_choices, default=0)get_time = models.DateTimeField(verbose_name="領(lǐng)取時(shí)間", help_text="用戶領(lǐng)取時(shí)間")used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用時(shí)間")# order = models.ForeignKey("Order", blank=True, null=True, verbose_name="關(guān)聯(lián)訂單") # 一個(gè)訂單可以有多個(gè)優(yōu)惠券order_id = models.IntegerField(verbose_name='關(guān)聯(lián)訂單ID')class Meta:verbose_name_plural = "32. 用戶優(yōu)惠券"def __str__(self):return '%s-%s-%s' % (self.account, self.number, self.status)class Account(models.Model):username = models.CharField("用戶名", max_length=64, unique=True)email = models.EmailField(verbose_name='郵箱',max_length=255,unique=True,blank=True,null=True)password = models.CharField('密碼', max_length=128)class Meta:verbose_name_plural = "33. 用戶表"class UserToken(models.Model):user = models.OneToOneField(to='Account')token = models.CharField(max_length=36)class Meta:verbose_name_plural = "34. token表" View Code使用2個(gè)命令,生成表
python manage.py makemigrations python manage.py migrate?
修改views目錄下的auth.py,保存token到數(shù)據(jù)庫(kù)中
from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from api import models from api.utils.response import BaseResponse import uuidclass AuthView(ViewSetMixin,APIView):def login(self,request,*args,**kwargs):"""用戶登陸認(rèn)證:param request::param args::param kwargs::return:"""response = BaseResponse() # 默認(rèn)狀態(tài)try:user = request.data.get('username')pwd = request.data.get('password')# 驗(yàn)證用戶和密碼obj = models.Account.objects.filter(username=user,password=pwd).first()if not obj:response.code = 10002response.error = '用戶名或密碼錯(cuò)誤'else:uid = str(uuid.uuid4()) # 生成唯一id# 保存到數(shù)據(jù)庫(kù)中,update_or_create表示更新或者創(chuàng)建# user=obj,這個(gè)是判斷條件。當(dāng)條件成立,更新token字段,值為uid# 當(dāng)條件不成立時(shí),增加一條記錄。注意:增加時(shí),有2個(gè)字段,分別是user和tokenmodels.UserToken.objects.update_or_create(user=obj, defaults={'token': uid})response.code = 99999response.data = uidexcept Exception as e:response.code = 10005response.error = '操作異常'return Response(response.dict) View Code再次發(fā)送POST請(qǐng)求,輸入正確的用戶名和密碼
查看表api_usertoken,發(fā)現(xiàn)和返回結(jié)果是一樣的!
再發(fā)送一次
表的數(shù)據(jù)隨之更新
?
增加認(rèn)證
不光購(gòu)物車會(huì)用到用戶認(rèn)證,結(jié)算中心也需要用到認(rèn)證,還有其他的視圖,也同樣需要登錄才能使用。
所以,這個(gè)認(rèn)證類需要放到utils里面
在utils目錄中新建文件auth.py
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailedfrom api import modelsclass LuffyAuthentication(BaseAuthentication):def authenticate(self, request):"""用戶認(rèn)證:param request::return:"""# 獲取get參數(shù)中的tokentoken = request.query_params.get('token')# 判斷token是否在數(shù)據(jù)庫(kù)中token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:# 認(rèn)證失敗raise AuthenticationFailed({'code':1008,'error':'認(rèn)證失敗'})# 認(rèn)證成功# return 必須返回2個(gè)參數(shù),請(qǐng)參考源碼解析# 這里的token_obj.user,表示UserToken表中的user字段# token_obj就是UserToken表的一條記錄,也就是一個(gè)objectreturn (token_obj.user,token_obj) View Code修改views目錄下的shoppingcart.py,導(dǎo)入utils下的auth模塊
from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from api.utils.response import BaseResponse import json import redis from django.conf import settings from api.utils.auth import LuffyAuthentication# redis連接 CONN = redis.Redis(host=settings.REDIS_SERVER.get('host'),port=settings.REDIS_SERVER.get('port'))# print(settings.REDIS_SERVER.get('host')) SHOPPING_CAR = {}USER_ID = 1 # 用戶id# SHOPPING_CAR = { # 1:{ # 2:{ # 'title':'xxxx', # 'price':1, # 'price_list':[ # {'id':11,}, # {'id':22}, # {'id':33}, # ] # }, # 3:{}, # 5:{} # }, # 2:{}, # 3:{}, # }class ShoppingCartView(ViewSetMixin,APIView):# 開啟認(rèn)證,指定認(rèn)證類authentication_classes = [LuffyAuthentication,]def list(self, request, *args, **kwargs):"""查看購(gòu)物車信息:param request::param args::param kwargs::return:"""ret = {'code':10000,'data':None,'error':None}try:# request.user和request.auth是源碼返回的# 如果自定義認(rèn)證類返回了一個(gè)元組,元組里面有2個(gè)值。# 它會(huì)覆蓋上面2個(gè)值,request.user和request.authprint(request.user) # 認(rèn)證類返回的第一個(gè)值print(request.auth) # 認(rèn)證類返回的第二個(gè)值# 獲取tokenprint('shopping',request.query_params.get('token'))shopping_car_course_list = []# pattern = "shopping_car_%s_*" % (USER_ID,)pattern = "shopping_car_%s_%s" % (USER_ID,'*',)user_key_list = CONN.keys(pattern)for key in user_key_list:temp = {'id': CONN.hget(key, 'id').decode('utf-8'),'name': CONN.hget(key, 'name').decode('utf-8'),'img':CONN.hget(key, 'img').decode('utf-8'),'default_price_id':CONN.hget(key, 'default_price_id').decode('utf-8'),'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))}shopping_car_course_list.append(temp)ret['data'] = shopping_car_course_listexcept Exception as e:# print(e)ret['code'] = 10005ret['error'] = '獲取購(gòu)物車數(shù)據(jù)失敗'# print(ret)# print(json.dumps(ret))return Response(ret)def create(self, request, *args, **kwargs):"""加入購(gòu)物車:param request::param args::param kwargs::return:""""""1. 接受用戶選中的課程ID和價(jià)格策略ID2. 判斷合法性- 課程是否存在?- 價(jià)格策略是否合法?3. 把商品和價(jià)格策略信息放入購(gòu)物車 SHOPPING_CAR注意:用戶ID=1"""# 1.接受用戶選中的課程ID和價(jià)格策略ID"""相關(guān)問題:a. 如果讓你編寫一個(gè)API程序,你需要先做什么?- 業(yè)務(wù)需求- 統(tǒng)一數(shù)據(jù)傳輸格式- 表結(jié)構(gòu)設(shè)計(jì)- 程序開發(fā)b. django restful framework的解析器的parser_classes的作用?根據(jù)請(qǐng)求中Content-Type請(qǐng)求頭的值,選擇指定解析對(duì)請(qǐng)求體中的數(shù)據(jù)進(jìn)行解析。如:請(qǐng)求頭中含有Content-type: application/json 則內(nèi)部使用的是JSONParser,JSONParser可以自動(dòng)去請(qǐng)求體request.body中獲取請(qǐng)求數(shù)據(jù),然后進(jìn)行 字節(jié)轉(zhuǎn)字符串、json.loads反序列化;c. 支持多個(gè)解析器(一般只是使用JSONParser即可)"""course_id = request.data.get('courseid')policy_id = request.data.get('policyid')# 2. 判斷合法性# - 課程是否存在?# - 價(jià)格策略是否合法?# 2.1 課程是否存在?course = models.Course.objects.filter(id=course_id).first()if not course:return Response({'code': 10001, 'error': '課程不存在'})# 2.2 價(jià)格策略是否合法?price_policy_queryset = course.price_policy.all()price_policy_dict = {}for item in price_policy_queryset:temp = {'id': item.id,'price': item.price,'valid_period': item.valid_period,'valid_period_display': item.get_valid_period_display()}price_policy_dict[item.id] = tempprint(price_policy_dict,type(price_policy_dict))print(policy_id,type(policy_id))if policy_id not in price_policy_dict:return Response({'code': 10002, 'error': '傻×,價(jià)格策略別瞎改'})# 3. 把商品和價(jià)格策略信息放入購(gòu)物車 SHOPPING_CAR"""購(gòu)物車中要放:課程ID課程名稱課程圖片默認(rèn)選中的價(jià)格策略所有價(jià)格策略{shopping_car_1_1:{id:課程IDname:課程名稱img:課程圖片defaut:默認(rèn)選中的價(jià)格策略price_list:所有價(jià)格策略},}"""pattern = "shopping_car_%s_%s" % (USER_ID, '*',)keys = CONN.keys(pattern)if keys and len(keys) >= 1000:return Response({'code': 10009, 'error': '購(gòu)物車東西太多,先去結(jié)算再進(jìn)行購(gòu)買..'})# key = "shopping_car_%s_%s" %(USER_ID,course_id,)key = "shopping_car_%s_%s" % (USER_ID, course_id,)print(key,'1111111111')CONN.hset(key, 'id', course_id)CONN.hset(key, 'name', course.name)CONN.hset(key, 'img', course.course_img)CONN.hset(key, 'default_price_id', policy_id)CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict))CONN.expire(key, 60 * 60 * 24) # 有效期,單位秒。表示一天return Response({'code': 10000, 'data': '購(gòu)買成功'})def destroy(self,request,*args,**kwargs):"""刪除購(gòu)物車中的某個(gè)課程:param request::param args::param kwargs::return:"""response = BaseResponse()try:# courseid = request.GET.get('courseid')courseid = request.data.get('courseid')print(courseid)# key = "shopping_car_%s_%s" % (USER_ID,courseid)key = "shopping_car_%s_%s" % (USER_ID, courseid,)CONN.delete(key)response.data = '刪除成功'except Exception as e:response.code = 10006response.error = '刪除失敗'return Response(response.dict)def update(self,request,*args,**kwargs):"""修改用戶選中的價(jià)格策略:param request::param args::param kwargs::return:""""""1. 獲取課程ID、要修改的價(jià)格策略ID2. 校驗(yàn)合法性(去redis中)"""response = BaseResponse()try:course_id = request.data.get('courseid')policy_id = str(request.data.get('policyid')) if request.data.get('policyid') else None# key = 'shopping_car_%s_%s' %(USER_ID,course_id,)key = "shopping_car_%s_%s" % (USER_ID, course_id,)if not CONN.exists(key):response.code = 10007response.error = '課程不存在'return Response(response.dict)price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))if policy_id not in price_policy_dict:response.code = 10008response.error = '價(jià)格策略不存在'return Response(response.dict)CONN.hset(key,'default_price_id',policy_id)CONN.expire(key, 20 * 60) # 有效期20分鐘response.data = '修改成功'except Exception as e:response.code = 10009response.error = '修改失敗'return Response(response.dict) View Code參數(shù)說明:
CONN.expire 表示設(shè)置有效期,單位是秒。60 * 60 * 24,表示一天
?
使用postman,發(fā)送get請(qǐng)求
提示認(rèn)證失敗
發(fā)送一個(gè)錯(cuò)誤的token
提示認(rèn)證失敗!注意:這里直接被認(rèn)證組件攔截了,并沒有到達(dá)視圖
發(fā)送一個(gè)正確的token,從數(shù)據(jù)庫(kù)里面copy一下
返回code為10000,表示認(rèn)證成功!
?查看Pycharm控制臺(tái)輸出:
Account object UserToken object shopping c8aa8609-fb14-43ea-a6cf-96b2c2469b01上面2個(gè)值,就被自定義類覆蓋了!
?
既然得到了用戶對(duì)象,那么常量USER_ID就可以刪除了
修改shoppingcart.py
from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from api.utils.response import BaseResponse import json import redis from django.conf import settings from api.utils.auth import LuffyAuthentication# redis連接 CONN = redis.Redis(host=settings.REDIS_SERVER.get('host'),port=settings.REDIS_SERVER.get('port'))class ShoppingCartView(ViewSetMixin,APIView):# 開啟認(rèn)證,指定認(rèn)證類authentication_classes = [LuffyAuthentication,]def list(self, request, *args, **kwargs):"""查看購(gòu)物車信息:param request::param args::param kwargs::return:"""ret = {'code':10000,'data':None,'error':None}try:# request.user和request.auth是源碼返回的# 如果自定義認(rèn)證類返回了一個(gè)元組,元組里面有2個(gè)值。# 它會(huì)覆蓋上面2個(gè)值,request.user和request.authprint(request.user) # 認(rèn)證類返回的第一個(gè)值print(request.auth) # 認(rèn)證類返回的第二個(gè)值# 獲取tokenprint('shopping',request.query_params.get('token'))shopping_car_course_list = []# pattern = "shopping_car_%s_*" % (request.user.id,)pattern = "shopping_car_%s_%s" % (request.user.id,'*',)user_key_list = CONN.keys(pattern)for key in user_key_list:temp = {'id': CONN.hget(key, 'id').decode('utf-8'),'name': CONN.hget(key, 'name').decode('utf-8'),'img':CONN.hget(key, 'img').decode('utf-8'),'default_price_id':CONN.hget(key, 'default_price_id').decode('utf-8'),'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))}shopping_car_course_list.append(temp)ret['data'] = shopping_car_course_listexcept Exception as e:# print(e)ret['code'] = 10005ret['error'] = '獲取購(gòu)物車數(shù)據(jù)失敗'# print(ret)# print(json.dumps(ret))return Response(ret)def create(self, request, *args, **kwargs):"""加入購(gòu)物車:param request::param args::param kwargs::return:""""""1. 接受用戶選中的課程ID和價(jià)格策略ID2. 判斷合法性- 課程是否存在?- 價(jià)格策略是否合法?3. 把商品和價(jià)格策略信息放入購(gòu)物車 SHOPPING_CAR注意:用戶ID=1"""# 1.接受用戶選中的課程ID和價(jià)格策略ID"""相關(guān)問題:a. 如果讓你編寫一個(gè)API程序,你需要先做什么?- 業(yè)務(wù)需求- 統(tǒng)一數(shù)據(jù)傳輸格式- 表結(jié)構(gòu)設(shè)計(jì)- 程序開發(fā)b. django restful framework的解析器的parser_classes的作用?根據(jù)請(qǐng)求中Content-Type請(qǐng)求頭的值,選擇指定解析對(duì)請(qǐng)求體中的數(shù)據(jù)進(jìn)行解析。如:請(qǐng)求頭中含有Content-type: application/json 則內(nèi)部使用的是JSONParser,JSONParser可以自動(dòng)去請(qǐng)求體request.body中獲取請(qǐng)求數(shù)據(jù),然后進(jìn)行 字節(jié)轉(zhuǎn)字符串、json.loads反序列化;c. 支持多個(gè)解析器(一般只是使用JSONParser即可)"""course_id = request.data.get('courseid')policy_id = request.data.get('policyid')# 2. 判斷合法性# - 課程是否存在?# - 價(jià)格策略是否合法?# 2.1 課程是否存在?course = models.Course.objects.filter(id=course_id).first()if not course:return Response({'code': 10001, 'error': '課程不存在'})# 2.2 價(jià)格策略是否合法?price_policy_queryset = course.price_policy.all()price_policy_dict = {}for item in price_policy_queryset:temp = {'id': item.id,'price': item.price,'valid_period': item.valid_period,'valid_period_display': item.get_valid_period_display()}price_policy_dict[item.id] = tempprint(price_policy_dict,type(price_policy_dict))print(policy_id,type(policy_id))if policy_id not in price_policy_dict:return Response({'code': 10002, 'error': '傻×,價(jià)格策略別瞎改'})# 3. 把商品和價(jià)格策略信息放入購(gòu)物車 SHOPPING_CAR"""購(gòu)物車中要放:課程ID課程名稱課程圖片默認(rèn)選中的價(jià)格策略所有價(jià)格策略{shopping_car_1_1:{id:課程IDname:課程名稱img:課程圖片defaut:默認(rèn)選中的價(jià)格策略price_list:所有價(jià)格策略},}"""pattern = "shopping_car_%s_%s" % (request.user.id, '*',)keys = CONN.keys(pattern)if keys and len(keys) >= 1000:return Response({'code': 10009, 'error': '購(gòu)物車東西太多,先去結(jié)算再進(jìn)行購(gòu)買..'})# key = "shopping_car_%s_%s" %(request.user.id,course_id,)key = "shopping_car_%s_%s" % (request.user.id, course_id,)print(key,'1111111111')CONN.hset(key, 'id', course_id)CONN.hset(key, 'name', course.name)CONN.hset(key, 'img', course.course_img)CONN.hset(key, 'default_price_id', policy_id)CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict))CONN.expire(key, 60 * 12 * 24) # 有效期return Response({'code': 10000, 'data': '購(gòu)買成功'})def destroy(self,request,*args,**kwargs):"""刪除購(gòu)物車中的某個(gè)課程:param request::param args::param kwargs::return:"""response = BaseResponse()try:# courseid = request.GET.get('courseid')courseid = request.data.get('courseid')print(courseid)# key = "shopping_car_%s_%s" % (request.user.id,courseid)key = "shopping_car_%s_%s" % (request.user.id, courseid,)CONN.delete(key)response.data = '刪除成功'except Exception as e:response.code = 10006response.error = '刪除失敗'return Response(response.dict)def update(self,request,*args,**kwargs):"""修改用戶選中的價(jià)格策略:param request::param args::param kwargs::return:""""""1. 獲取課程ID、要修改的價(jià)格策略ID2. 校驗(yàn)合法性(去redis中)"""response = BaseResponse()try:course_id = request.data.get('courseid')policy_id = str(request.data.get('policyid')) if request.data.get('policyid') else None# key = 'shopping_car_%s_%s' %(request.user.id,course_id,)key = "shopping_car_%s_%s" % (request.user.id, course_id,)if not CONN.exists(key):response.code = 10007response.error = '課程不存在'return Response(response.dict)price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))if policy_id not in price_policy_dict:response.code = 10008response.error = '價(jià)格策略不存在'return Response(response.dict)CONN.hset(key,'default_price_id',policy_id)CONN.expire(key, 20 * 60)response.data = '修改成功'except Exception as e:response.code = 10009response.error = '修改失敗'return Response(response.dict) View Code測(cè)試get請(qǐng)求
?
全局配置
假設(shè)有100個(gè)類,有98個(gè)視圖要認(rèn)證。可以加到全局里面,修改settings.py
REST_FRAMEWORK = {'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning','VERSION_PARAM':'version','DEFAULT_VERSION':'v1','ALLOWED_VERSIONS':['v1','v2'],'PAGE_SIZE':20,'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination','DEFAULT_AUTHENTICATION_CLASSES':['api.utils.auth.LuffyAuthentication',] } View Code?
那么登錄和查看課程,是不需要認(rèn)證的。怎么忽略呢?
修改views目錄下的auth.py,定義認(rèn)證類為空列表,表示不認(rèn)證!
from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from api import models from api.utils.response import BaseResponse import uuidclass AuthView(ViewSetMixin,APIView):authentication_classes = [] # 空列表表示不認(rèn)證def login(self,request,*args,**kwargs):"""用戶登陸認(rèn)證:param request::param args::param kwargs::return:"""response = BaseResponse() # 默認(rèn)狀態(tài)try:user = request.data.get('username')pwd = request.data.get('password')# 驗(yàn)證用戶和密碼obj = models.Account.objects.filter(username=user,password=pwd).first()if not obj:response.code = 10002response.error = '用戶名或密碼錯(cuò)誤'else:uid = str(uuid.uuid4()) # 生成唯一id# 保存到數(shù)據(jù)庫(kù)中,update_or_create表示更新或者創(chuàng)建# user=obj,這個(gè)是判斷條件。當(dāng)條件成立,更新token字段,值為uid# 當(dāng)條件不成立時(shí),增加一條記錄。注意:增加時(shí),有2個(gè)字段,分別是user和tokenmodels.UserToken.objects.update_or_create(user=obj, defaults={'token': uid})response.code = 99999response.data = uidexcept Exception as e:response.code = 10005response.error = '操作異常'return Response(response.dict) View Code使用postman測(cè)試登錄
查看返回結(jié)果
?
修改settings.py,注釋掉全局認(rèn)證。因?yàn)檫@里用到的登錄認(rèn)證的視圖不多
REST_FRAMEWORK = {'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning','VERSION_PARAM':'version','DEFAULT_VERSION':'v1','ALLOWED_VERSIONS':['v1','v2'],'PAGE_SIZE':20,'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',# 'DEFAULT_AUTHENTICATION_CLASSES':['api.utils.auth.LuffyAuthentication',] } View Code?
問題:認(rèn)證類為什么要繼承BaseAuthentication?
查看源碼BaseAuthentication
class BaseAuthentication(object):"""All authentication classes should extend BaseAuthentication."""def authenticate(self, request):"""Authenticate the request and return a two-tuple of (user, token)."""raise NotImplementedError(".authenticate() must be overridden.")def authenticate_header(self, request):"""Return a string to be used as the value of the `WWW-Authenticate`header in a `401 Unauthenticated` response, or `None` if theauthentication scheme should return `403 Permission Denied` responses."""pass View Code發(fā)現(xiàn),只要執(zhí)行了authenticate方法,它會(huì)執(zhí)行raise。它會(huì)主動(dòng)報(bào)錯(cuò)
為了不讓它報(bào)錯(cuò),子類繼承BaseAuthentication后,必須重寫authenticate方法,才不會(huì)報(bào)錯(cuò)。
這樣做的目的,是為了約束子類,哪些方法,必須要定義!
?
二、結(jié)算中心
?
點(diǎn)擊去結(jié)算,會(huì)發(fā)送一次post請(qǐng)求。那么它該發(fā)送什么數(shù)據(jù)呢?
只需要發(fā)送課程id就可以了?為什么呢?
因?yàn)閞edis中有購(gòu)物車相關(guān)數(shù)據(jù)!后臺(tái)根據(jù)課程id去購(gòu)物車中獲取,要結(jié)算的課程就可以了!
結(jié)算中心和購(gòu)物車一樣,也是一個(gè)臨時(shí)數(shù)據(jù)。它也需要放到redis中!
先來(lái)看購(gòu)物車的數(shù)據(jù)結(jié)構(gòu)
購(gòu)物車 = {'shopping_car_1_3':{name:'',src:'xx'price_id:1,price_dict = {1:....}},'shopping_car_1_1':{...},'shopping_car_1_5':{...},} View Code再來(lái)看結(jié)算中新的數(shù)據(jù)結(jié)構(gòu)
結(jié)算中心 = {'payment_1_3':{id:3,mame:Django框架學(xué)習(xí),price_id:1,price_priod:30,price:199,defaul_coupon_id:0,coupon_dict: { ----> 綁定了課程3的優(yōu)惠券0: '請(qǐng)選擇課程優(yōu)惠券',1:'xxx',2:'xxx',3:'xxx',4:'xxx',}},'payment_1_1':{id:1,mame:Django框架學(xué)習(xí),price_id:1,price_priod:30,price:199,defaul_coupon_id:0,coupon_dict: { ----> 綁定了課程1的優(yōu)惠券0: '請(qǐng)選擇課程優(yōu)惠券',1:'xxx',2:'xxx',3:'xxx',4:'xxx',}}, } View Code?
優(yōu)惠券
優(yōu)惠券分為2大類:綁定課程和非綁定課程
?
點(diǎn)擊去結(jié)算
在左下角,展示的是非綁定課程的優(yōu)惠券。
在右邊的下拉菜單中,展示的是綁定課程的優(yōu)惠券
?
在views目錄下,創(chuàng)建文件payment.py
import json import redis from django.conf import settings from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api.utils.auth import LuffyAuthentication from api import models from api.utils.response import BaseResponse# redis連接 CONN = redis.Redis(host=settings.REDIS_SERVER.get('host'),port=settings.REDIS_SERVER.get('port'))class PaymentView(ViewSetMixin, APIView):authentication_classes = [LuffyAuthentication, ]def create(self, request, *args, **kwargs):"""在結(jié)算中添加課程:param request::param args::param kwargs::return:"""# 1.接受用戶選擇的要結(jié)算的課程ID列表# 2.清空當(dāng)前用戶request.user.id結(jié)算中心的數(shù)據(jù)# key = payment_1*# 3.循環(huán)要加入結(jié)算中的所有課程ID列表"""for course_id in 用戶提交課程ID列表:3.1 根據(jù)course_id,request.user.id去購(gòu)物車中獲取商品信息:商品名稱、圖片、價(jià)格(id,周期,顯示周期,價(jià)格)3.2 根據(jù)course_id,request.user.id獲取 - 當(dāng)前用戶- 當(dāng)前課程- 可用的優(yōu)惠券加入結(jié)算中心提示:可以使用contenttypes"""# 4.獲取當(dāng)前用戶所有未綁定課程優(yōu)惠券# - 未使用# - 有效期內(nèi)# - 加入結(jié)算中心:glocal_coupon_用戶IDdef list(self, request, *args, **kwargs):"""查看結(jié)算中心:param request::param args::param kwargs::return:"""# 1. 根據(jù)用戶ID去結(jié)算中心獲取該用戶所有要結(jié)算課程# 2. 根據(jù)用戶ID去結(jié)算中心獲取該用戶所有可用未綁定課程的優(yōu)惠券# 3. 用戶表中獲取貝里余額# 4. 以上數(shù)據(jù)構(gòu)造成一個(gè)字典return Response('...')def update(self, request, *args, **kwargs):"""更新優(yōu)惠券:param request::param args::param kwargs::return:"""# 1. 獲取用戶提交:# course_id=1,coupon_id=3# course_id=0,coupon_id=6# 2. course_id=1 --> 去結(jié)算中心獲取當(dāng)前用戶所擁有的綁定當(dāng)前課程優(yōu)惠,并進(jìn)行校驗(yàn)# - 成功:defaul_coupon_id=3# - 否則:非法請(qǐng)求# 2. course_id=0 --> 去結(jié)算中心獲取當(dāng)前用戶所擁有的未綁定課程優(yōu)惠,并進(jìn)行校驗(yàn)# - 成功:defaul_coupon_id=3# - 否則:非法請(qǐng)求 View Codecourse_id為空,表示 未綁定課程,否則為綁定課程
這里面展示的是一些業(yè)務(wù)邏輯,需要自己用代碼來(lái)填充
提示你的代碼編寫能力!
?
三、django-redis
介紹
django-redis 基于 BSD 許可, 是一個(gè)使 Django 支持 Redis cache/session 后端的全功能組件
django-redis 中文文檔,請(qǐng)參考
http://django-redis-chs.readthedocs.io/zh_CN/latest/
為何要用 django-redis ?
因?yàn)?
- 持續(xù)更新
- 本地化的 redis-py URL 符號(hào)連接字符串
- 可擴(kuò)展客戶端
- 可擴(kuò)展解析器
- 可擴(kuò)展序列器
- 默認(rèn)客戶端主/從支持
- 完善的測(cè)試
- 已在一些項(xiàng)目的生產(chǎn)環(huán)境中作為 cache 和 session 使用
- 支持永不超時(shí)設(shè)置
- 原生進(jìn)入 redis 客戶端/連接池支持
- 高可配置 ( 例如仿真緩存的異常行為 )
- 默認(rèn)支持 unix 套接字
- 支持 Python 2.7, 3.4, 3.5 以及 3.6
?
安裝
安裝 django-redis 最簡(jiǎn)單的方法就是用 pip :
pip install django-redis作為 cache backend 使用配置
為了使用 django-redis , 你應(yīng)該將你的 django cache setting 改成這樣:
CACHES = {"default": {"BACKEND": "django_redis.cache.RedisCache","LOCATION": "redis://127.0.0.1:6379/1","OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient",}} }?
舉例:
在上面購(gòu)物車中,使用了緩存。結(jié)算中心也需要使用緩存,那么就可以定義一個(gè)全局配置。當(dāng)需要使用時(shí),導(dǎo)入一下配置即可!
修改settings.py,最后一行添加
# ######django-redis的配置 ################# CACHES = {"default": {"BACKEND": "django_redis.cache.RedisCache","LOCATION": "redis://192.168.218.140:6379","OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient","CONNECTION_POOL_KWARGS": {"max_connections": 100},# "PASSWORD": "密碼", }} }參數(shù)解釋:
BACKEND 表示后臺(tái)連接
OPTIONS 表示參數(shù)
CONNECTION_POOL_KWARGS 表示連接池。max_connections表示最大連接數(shù)
?
連接池,請(qǐng)參考鏈接:
https://baike.baidu.com/item/%E8%BF%9E%E6%8E%A5%E6%B1%A0%E6%8A%80%E6%9C%AF/523659?fr=aladdin
上面定義了100個(gè)連接池,假設(shè)100進(jìn)程,都在使用連接池。當(dāng)?shù)?01個(gè)訪問時(shí),會(huì)等待。直到有空閑的進(jìn)程時(shí),才處理!
不過redis的處理是很快的,很少會(huì)出現(xiàn)等待的情況!
使用連接池,有很多優(yōu)點(diǎn):
1.減少連接創(chuàng)建時(shí)間
2.簡(jiǎn)化的編程模式
3.受控的資源使用
?
使用連接池,性能會(huì)更高好!
視圖中使用
加上2行代碼,就可以了
from django_redis import get_redis_connection CONN = get_redis_connection("default")這里的default指的是settings.py中CACHES配置項(xiàng)的default
?
修改views目錄下的shoppingcar.py
from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from api.utils.response import BaseResponse import json from api.utils.auth import LuffyAuthentication from django_redis import get_redis_connectionCONN = get_redis_connection("default") # 使用redis連接池class ShoppingCartView(ViewSetMixin,APIView):# 開啟認(rèn)證,指定認(rèn)證類authentication_classes = [LuffyAuthentication,]def list(self, request, *args, **kwargs):"""查看購(gòu)物車信息:param request::param args::param kwargs::return:"""ret = {'code':10000,'data':None,'error':None}try:# request.user和request.auth是源碼返回的# 如果自定義認(rèn)證類返回了一個(gè)元組,元組里面有2個(gè)值。# 它會(huì)覆蓋上面2個(gè)值,request.user和request.authprint(request.user) # 認(rèn)證類返回的第一個(gè)值print(request.auth) # 認(rèn)證類返回的第二個(gè)值# 獲取tokenprint('shopping',request.query_params.get('token'))shopping_car_course_list = []# pattern = "shopping_car_%s_*" % (request.user.id,)pattern = "shopping_car_%s_%s" % (request.user.id,'*',)user_key_list = CONN.keys(pattern)for key in user_key_list:temp = {'id': CONN.hget(key, 'id').decode('utf-8'),'name': CONN.hget(key, 'name').decode('utf-8'),'img':CONN.hget(key, 'img').decode('utf-8'),'default_price_id':CONN.hget(key, 'default_price_id').decode('utf-8'),'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))}shopping_car_course_list.append(temp)ret['data'] = shopping_car_course_listexcept Exception as e:# print(e)ret['code'] = 10005ret['error'] = '獲取購(gòu)物車數(shù)據(jù)失敗'# print(ret)# print(json.dumps(ret))return Response(ret)def create(self, request, *args, **kwargs):"""加入購(gòu)物車:param request::param args::param kwargs::return:""""""1. 接受用戶選中的課程ID和價(jià)格策略ID2. 判斷合法性- 課程是否存在?- 價(jià)格策略是否合法?3. 把商品和價(jià)格策略信息放入購(gòu)物車 SHOPPING_CAR注意:用戶ID=1"""# 1.接受用戶選中的課程ID和價(jià)格策略ID"""相關(guān)問題:a. 如果讓你編寫一個(gè)API程序,你需要先做什么?- 業(yè)務(wù)需求- 統(tǒng)一數(shù)據(jù)傳輸格式- 表結(jié)構(gòu)設(shè)計(jì)- 程序開發(fā)b. django restful framework的解析器的parser_classes的作用?根據(jù)請(qǐng)求中Content-Type請(qǐng)求頭的值,選擇指定解析對(duì)請(qǐng)求體中的數(shù)據(jù)進(jìn)行解析。如:請(qǐng)求頭中含有Content-type: application/json 則內(nèi)部使用的是JSONParser,JSONParser可以自動(dòng)去請(qǐng)求體request.body中獲取請(qǐng)求數(shù)據(jù),然后進(jìn)行 字節(jié)轉(zhuǎn)字符串、json.loads反序列化;c. 支持多個(gè)解析器(一般只是使用JSONParser即可)"""course_id = request.data.get('courseid')policy_id = request.data.get('policyid')# 2. 判斷合法性# - 課程是否存在?# - 價(jià)格策略是否合法?# 2.1 課程是否存在?course = models.Course.objects.filter(id=course_id).first()if not course:return Response({'code': 10001, 'error': '課程不存在'})# 2.2 價(jià)格策略是否合法?price_policy_queryset = course.price_policy.all()price_policy_dict = {}for item in price_policy_queryset:temp = {'id': item.id,'price': item.price,'valid_period': item.valid_period,'valid_period_display': item.get_valid_period_display()}price_policy_dict[item.id] = tempprint(price_policy_dict,type(price_policy_dict))print(policy_id,type(policy_id))if policy_id not in price_policy_dict:return Response({'code': 10002, 'error': '傻×,價(jià)格策略別瞎改'})# 3. 把商品和價(jià)格策略信息放入購(gòu)物車 SHOPPING_CAR"""購(gòu)物車中要放:課程ID課程名稱課程圖片默認(rèn)選中的價(jià)格策略所有價(jià)格策略{shopping_car_1_1:{id:課程IDname:課程名稱img:課程圖片defaut:默認(rèn)選中的價(jià)格策略price_list:所有價(jià)格策略},}"""pattern = "shopping_car_%s_%s" % (request.user.id, '*',)keys = CONN.keys(pattern)if keys and len(keys) >= 1000:return Response({'code': 10009, 'error': '購(gòu)物車東西太多,先去結(jié)算再進(jìn)行購(gòu)買..'})# key = "shopping_car_%s_%s" %(request.user.id,course_id,)key = "shopping_car_%s_%s" % (request.user.id, course_id,)print(key,'1111111111')CONN.hset(key, 'id', course_id)CONN.hset(key, 'name', course.name)CONN.hset(key, 'img', course.course_img)CONN.hset(key, 'default_price_id', policy_id)CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict))CONN.expire(key, 60 * 12 * 24) # 有效期return Response({'code': 10000, 'data': '購(gòu)買成功'})def destroy(self,request,*args,**kwargs):"""刪除購(gòu)物車中的某個(gè)課程:param request::param args::param kwargs::return:"""response = BaseResponse()try:# courseid = request.GET.get('courseid')courseid = request.data.get('courseid')print(courseid)# key = "shopping_car_%s_%s" % (request.user.id,courseid)key = "shopping_car_%s_%s" % (request.user.id, courseid,)CONN.delete(key)response.data = '刪除成功'except Exception as e:response.code = 10006response.error = '刪除失敗'return Response(response.dict)def update(self,request,*args,**kwargs):"""修改用戶選中的價(jià)格策略:param request::param args::param kwargs::return:""""""1. 獲取課程ID、要修改的價(jià)格策略ID2. 校驗(yàn)合法性(去redis中)"""response = BaseResponse()try:course_id = request.data.get('courseid')policy_id = str(request.data.get('policyid')) if request.data.get('policyid') else None# key = 'shopping_car_%s_%s' %(request.user.id,course_id,)key = "shopping_car_%s_%s" % (request.user.id, course_id,)if not CONN.exists(key):response.code = 10007response.error = '課程不存在'return Response(response.dict)price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8'))if policy_id not in price_policy_dict:response.code = 10008response.error = '價(jià)格策略不存在'return Response(response.dict)CONN.hset(key,'default_price_id',policy_id)CONN.expire(key, 20 * 60)response.data = '修改成功'except Exception as e:response.code = 10009response.error = '修改失敗'return Response(response.dict) View Code使用postman測(cè)試訪問,要帶上正確的token
訪問正常
?
作為 session backend 使用配置
Django 默認(rèn)可以使用任何 cache backend 作為 session backend, 將 django-redis 作為 session 儲(chǔ)存后端不用安裝任何額外的 backend
SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "default"?
舉例:
修改settings.py
# ######django-redis的配置 ################# CACHES = {"default": {"BACKEND": "django_redis.cache.RedisCache","LOCATION": "redis://192.168.218.140:6379","OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient","CONNECTION_POOL_KWARGS": {"max_connections": 100},# "PASSWORD": "密碼", }} }###使用redis緩存session SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 SESSION_CACHE_ALIAS = 'default' # 使用的緩存別名(默認(rèn)內(nèi)存緩存,也可以是memcache),此處別名依賴緩存的設(shè)置 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時(shí)的key,即:sessionid=隨機(jī)字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關(guān)閉瀏覽器使得Session過期 SESSION_SAVE_EVERY_REQUEST = False # 是否每次請(qǐng)求都保存Session,默認(rèn)修改之后才保存 View Code簡(jiǎn)單來(lái)講,加上2行就可以了。下面的那些配置,是參考源碼設(shè)置的。
比如session失效時(shí)間是2周
如果需要修改,在這里指定一下,就可以了!
注意:里面的defalut就是redis配置的defalut,名字是一一對(duì)應(yīng)的!
?
總結(jié):
1. django-redis的作用- 連接redis并在redis中進(jìn)行操作(含redis連接池)。2. 幫助用戶將session放到redis- django-redis的配置- session的配置 View Code?
作業(yè):
完整結(jié)算中心的代碼,實(shí)現(xiàn)以下功能:
1. 添加
2. 查看
3. 修改
注意:使用認(rèn)證+django-redis?
?
修改utils目錄下的auth.py
當(dāng)為GET請(qǐng)求時(shí),從url中取token,否則從請(qǐng)求體中獲取token
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailedfrom api import modelsclass LuffyAuthentication(BaseAuthentication):def authenticate(self, request):"""用戶認(rèn)證:param request::return:"""# print(request.method)# 判斷請(qǐng)求方式if request.method == "GET":token = request.query_params.get('token')else:token = request.data.get('token')# print('auth',token)token_obj = models.UserToken.objects.filter(token=token).first()if not token_obj:# 認(rèn)證失敗raise AuthenticationFailed({'code':1008,'error':'認(rèn)證失敗'})# 認(rèn)證成功# return (token_obj.user,token_obj)return (token_obj.user,token_obj) View Code修改models.py,在用戶表增加字段
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.utils.safestring import mark_safe from django.db import models import hashlib# ######################## 課程相關(guān) ########################class CourseCategory(models.Model):"""課程大類, e.g 前端 后端..."""name = models.CharField(max_length=64, unique=True)def __str__(self):return "%s" % self.nameclass Meta:verbose_name_plural = "01.課程大類"class CourseSubCategory(models.Model):"""課程子類, e.g python linux """category = models.ForeignKey("CourseCategory")name = models.CharField(max_length=64, unique=True)def __str__(self):return "%s" % self.nameclass Meta:verbose_name_plural = "02.課程子類"class DegreeCourse(models.Model):"""學(xué)位課程"""name = models.CharField(max_length=128, unique=True)course_img = models.CharField(max_length=255, verbose_name="縮略圖")brief = models.TextField(verbose_name="學(xué)位課程簡(jiǎn)介", )total_scholarship = models.PositiveIntegerField(verbose_name="總獎(jiǎng)學(xué)金(貝里)", default=40000) # 2000 2000mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本課程的導(dǎo)師輔導(dǎo)費(fèi)用(貝里)", default=15000)period = models.PositiveIntegerField(verbose_name="建議學(xué)習(xí)周期(days)", default=150) # 為了計(jì)算學(xué)位獎(jiǎng)學(xué)金prerequisite = models.TextField(verbose_name="課程先修要求", max_length=1024)teachers = models.ManyToManyField("Teacher", verbose_name="課程講師")# 用于GenericForeignKey反向查詢, 不會(huì)生成表字段,切勿刪除# coupon = GenericRelation("Coupon")# 用于GenericForeignKey反向查詢,不會(huì)生成表字段,切勿刪除degreecourse_price_policy = GenericRelation("PricePolicy")def __str__(self):return self.nameclass Meta:verbose_name_plural = "03.學(xué)位課"class Teacher(models.Model):"""講師、導(dǎo)師表"""name = models.CharField(max_length=32)role_choices = ((0, '講師'), (1, '導(dǎo)師'))role = models.SmallIntegerField(choices=role_choices, default=0)title = models.CharField(max_length=64, verbose_name="職位、職稱")signature = models.CharField(max_length=255, help_text="導(dǎo)師簽名", blank=True, null=True)image = models.CharField(max_length=128)brief = models.TextField(max_length=1024)def __str__(self):return self.nameclass Meta:verbose_name_plural = "04.導(dǎo)師或講師"class Scholarship(models.Model):"""學(xué)位課程獎(jiǎng)學(xué)金"""degree_course = models.ForeignKey("DegreeCourse")time_percent = models.PositiveSmallIntegerField(verbose_name="獎(jiǎng)勵(lì)檔位(時(shí)間百分比)", help_text="只填百分值,如80,代表80%")value = models.PositiveIntegerField(verbose_name="獎(jiǎng)學(xué)金數(shù)額")def __str__(self):return "%s:%s" % (self.degree_course, self.value)class Meta:verbose_name_plural = "05.學(xué)位課獎(jiǎng)學(xué)金"class Course(models.Model):"""專題課/學(xué)位課模塊表"""name = models.CharField(max_length=128, unique=True)course_img = models.CharField(max_length=255)sub_category = models.ForeignKey("CourseSubCategory")course_type_choices = ((0, '付費(fèi)'), (1, 'VIP專享'), (2, '學(xué)位課程'))course_type = models.SmallIntegerField(choices=course_type_choices)# 不為空;學(xué)位課的某個(gè)模塊# 為空;專題課degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, help_text="若是學(xué)位課程,此處關(guān)聯(lián)學(xué)位表")brief = models.TextField(verbose_name="課程概述", max_length=2048)level_choices = ((0, '初級(jí)'), (1, '中級(jí)'), (2, '高級(jí)'))level = models.SmallIntegerField(choices=level_choices, default=1)pub_date = models.DateField(verbose_name="發(fā)布日期", blank=True, null=True)period = models.PositiveIntegerField(verbose_name="建議學(xué)習(xí)周期(days)", default=7) # order = models.IntegerField("課程順序", help_text="從上一個(gè)課程數(shù)字往后排")attachment_path = models.CharField(max_length=128, verbose_name="課件路徑", blank=True, null=True)status_choices = ((0, '上線'), (1, '下線'), (2, '預(yù)上線'))status = models.SmallIntegerField(choices=status_choices, default=0)template_id = models.SmallIntegerField("前端模板id", default=1)coupon = GenericRelation("Coupon")# 用于GenericForeignKey反向查詢,不會(huì)生成表字段,切勿刪除price_policy = GenericRelation("PricePolicy")asked_question = GenericRelation("OftenAskedQuestion")def __str__(self):return "%s(%s)" % (self.name, self.get_course_type_display())def save(self, *args, **kwargs):if self.course_type == 2:if not self.degree_course:raise ValueError("學(xué)位課程必須關(guān)聯(lián)對(duì)應(yīng)的學(xué)位表")super(Course, self).save(*args, **kwargs)class Meta:verbose_name_plural = "06.專題課或?qū)W位課模塊"class CourseDetail(models.Model):"""課程詳情頁(yè)內(nèi)容"""course = models.OneToOneField("Course")hours = models.IntegerField("課時(shí)")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="為什么學(xué)習(xí)這門課程")what_to_study_brief = models.TextField(verbose_name="我將學(xué)到哪些內(nèi)容")career_improvement = models.TextField(verbose_name="此項(xiàng)目如何有助于我的職業(yè)生涯")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.courseclass Meta:verbose_name_plural = "07.課程或?qū)W位模塊詳細(xì)"class OftenAskedQuestion(models.Model):"""常見問題"""content_type = models.ForeignKey(ContentType) # 關(guān)聯(lián)course or degree_courseobject_id = models.PositiveIntegerField()content_object = GenericForeignKey('content_type', 'object_id')question = models.CharField(max_length=255)answer = models.TextField(max_length=1024)def __str__(self):return "%s-%s" % (self.content_object, self.question)class Meta:unique_together = ('content_type', 'object_id', 'question')verbose_name_plural = "08. 常見問題"class CourseOutline(models.Model):"""課程大綱"""course_detail = models.ForeignKey("CourseDetail")title = models.CharField(max_length=128)# 前端顯示順序order = models.PositiveSmallIntegerField(default=1)content = models.TextField("內(nèi)容", max_length=2048)def __str__(self):return "%s" % self.titleclass Meta:unique_together = ('course_detail', 'title')verbose_name_plural = "09. 課程大綱"class CourseChapter(models.Model):"""課程章節(jié)"""course = models.ForeignKey("Course")chapter = models.SmallIntegerField(verbose_name="第幾章", default=1)name = models.CharField(max_length=128)summary = models.TextField(verbose_name="章節(jié)介紹", blank=True, null=True)pub_date = models.DateField(verbose_name="發(fā)布日期", auto_now_add=True)class Meta:unique_together = ("course", 'chapter')verbose_name_plural = "10. 課程章節(jié)"def __str__(self):return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)class CourseSection(models.Model):"""課時(shí)目錄"""chapter = models.ForeignKey("CourseChapter")name = models.CharField(max_length=128)order = models.PositiveSmallIntegerField(verbose_name="課時(shí)排序", help_text="建議每個(gè)課時(shí)之間空1至2個(gè)值,以備后續(xù)插入課時(shí)")section_type_choices = ((0, '文檔'), (1, '練習(xí)'), (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="視頻時(shí)長(zhǎng)", blank=True, null=True, max_length=32) # 僅在前端展示使用pub_date = models.DateTimeField(verbose_name="發(fā)布時(shí)間", auto_now_add=True)free_trail = models.BooleanField("是否可試看", default=False)class Meta:unique_together = ('chapter', 'section_link')verbose_name_plural = "11. 課時(shí)"def __str__(self):return "%s-%s" % (self.chapter, self.name)class Homework(models.Model):chapter = models.ForeignKey("CourseChapter")title = models.CharField(max_length=128, verbose_name="作業(yè)題目")order = models.PositiveSmallIntegerField("作業(yè)順序", help_text="同一課程的每個(gè)作業(yè)之前的order值間隔1-2個(gè)數(shù)")homework_type_choices = ((0, '作業(yè)'), (1, '模塊通關(guān)考核'))homework_type = models.SmallIntegerField(choices=homework_type_choices, default=0)requirement = models.TextField(max_length=1024, verbose_name="作業(yè)需求")threshold = models.TextField(max_length=1024, verbose_name="踩分點(diǎn)")recommend_period = models.PositiveSmallIntegerField("推薦完成周期(天)", default=7)scholarship_value = models.PositiveSmallIntegerField("為該作業(yè)分配的獎(jiǎng)學(xué)金(貝里)")note = models.TextField(blank=True, null=True)enabled = models.BooleanField(default=True, help_text="本作業(yè)如果后期不需要了,不想讓學(xué)員看到,可以設(shè)置為False")class Meta:unique_together = ("chapter", "title")verbose_name_plural = "12. 章節(jié)作業(yè)"def __str__(self):return "%s - %s" % (self.chapter, self.title)# class CourseReview(models.Model): # """課程評(píng)價(jià)""" # enrolled_course = models.OneToOneField("EnrolledCourse") # about_teacher = models.FloatField(default=0, verbose_name="講師講解是否清晰") # about_video = models.FloatField(default=0, verbose_name="內(nèi)容實(shí)用") # about_course = models.FloatField(default=0, verbose_name="課程內(nèi)容通俗易懂") # review = models.TextField(max_length=1024, verbose_name="評(píng)價(jià)") # disagree_number = models.IntegerField(default=0, verbose_name="踩") # agree_number = models.IntegerField(default=0, verbose_name="贊同數(shù)") # tags = models.ManyToManyField("Tags", blank=True, verbose_name="標(biāo)簽") # date = models.DateTimeField(auto_now_add=True, verbose_name="評(píng)價(jià)日期") # is_recommend = models.BooleanField("熱評(píng)推薦", default=False) # hide = models.BooleanField("不在前端頁(yè)面顯示此條評(píng)價(jià)", default=False) # # def __str__(self): # return "%s-%s" % (self.enrolled_course.course, self.review) # # class Meta: # verbose_name_plural = "13. 課程評(píng)價(jià)(購(gòu)買課程后才能評(píng)價(jià))" # # # class DegreeCourseReview(models.Model): # """學(xué)位課程評(píng)價(jià) # 為了以后可以定制單獨(dú)的評(píng)價(jià)內(nèi)容,所以不與普通課程的評(píng)價(jià)混在一起,單獨(dú)建表 # """ # enrolled_course = models.ForeignKey("EnrolledDegreeCourse") # course = models.ForeignKey("Course", verbose_name="評(píng)價(jià)學(xué)位模塊", blank=True, null=True, # help_text="不填寫即代表評(píng)價(jià)整個(gè)學(xué)位課程", limit_choices_to={'course_type': 2}) # about_teacher = models.FloatField(default=0, verbose_name="講師講解是否清晰") # about_video = models.FloatField(default=0, verbose_name="視頻質(zhì)量") # about_course = models.FloatField(default=0, verbose_name="課程") # review = models.TextField(max_length=1024, verbose_name="評(píng)價(jià)") # disagree_number = models.IntegerField(default=0, verbose_name="踩") # agree_number = models.IntegerField(default=0, verbose_name="贊同數(shù)") # tags = models.ManyToManyField("Tags", blank=True, verbose_name="標(biāo)簽") # date = models.DateTimeField(auto_now_add=True, verbose_name="評(píng)價(jià)日期") # is_recommend = models.BooleanField("熱評(píng)推薦", default=False) # hide = models.BooleanField("不在前端頁(yè)面顯示此條評(píng)價(jià)", default=False) # # def __str__(self): # return "%s-%s" % (self.enrolled_course, self.review) # # class Meta: # verbose_name_plural = "14. 學(xué)位課評(píng)價(jià)(購(gòu)買課程后才能評(píng)價(jià))"class PricePolicy(models.Model):"""價(jià)格與有課程效期表"""content_type = models.ForeignKey(ContentType) # 關(guān)聯(lián)course or degree_courseobject_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個(gè)月'),(60, '2個(gè)月'),(90, '3個(gè)月'),(180, '6個(gè)月'), (210, '12個(gè)月'),(540, '18個(gè)月'), (720, '24個(gè)月'),)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. 價(jià)格策略"def __str__(self):return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)# ################################### 優(yōu)惠券相關(guān) #################################class Coupon(models.Model):"""優(yōu)惠券生成規(guī)則"""name = models.CharField(max_length=64, verbose_name="活動(dòng)名稱")brief = models.TextField(blank=True, null=True, verbose_name="優(yōu)惠券介紹")coupon_type_choices = ((0, '立減'), (1, '滿減券'), (2, '折扣券'))coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券類型")money_equivalent_value = models.IntegerField(verbose_name="等值貨幣")off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只針對(duì)折扣券,例7.9折,寫79", blank=True, null=True)minimum_consume = models.PositiveIntegerField("最低消費(fèi)", default=0, help_text="僅在滿減券時(shí)填寫此字段")content_type = models.ForeignKey(ContentType, blank=True, null=True)object_id = models.PositiveIntegerField("綁定課程", blank=True, null=True, help_text="可以把優(yōu)惠券跟課程綁定")content_object = GenericForeignKey('content_type', 'object_id')quantity = models.PositiveIntegerField("數(shù)量(張)", default=1)open_date = models.DateField("優(yōu)惠券領(lǐng)取開始時(shí)間")close_date = models.DateField("優(yōu)惠券領(lǐng)取結(jié)束時(shí)間")valid_begin_date = models.DateField(verbose_name="有效期開始時(shí)間", blank=True, null=True)valid_end_date = models.DateField(verbose_name="有效結(jié)束時(shí)間", blank=True, null=True)# coupon_valid_days = models.PositiveIntegerField(verbose_name="優(yōu)惠券有效期(天)", blank=True, null=True,# help_text="自券被領(lǐng)時(shí)開始算起")date = models.DateTimeField(auto_now_add=True)class Meta:verbose_name_plural = "31. 優(yōu)惠券生成記錄"def __str__(self):return "%s(%s)" % (self.get_coupon_type_display(), self.name)class CouponRecord(models.Model):"""優(yōu)惠券發(fā)放、消費(fèi)紀(jì)錄"""coupon = models.ForeignKey("Coupon")account = models.ForeignKey("Account", verbose_name="擁有者")number = models.CharField(max_length=64, unique=True)status_choices = ((0, '未使用'), (1, '已使用'), (2, '已過期'))status = models.SmallIntegerField(choices=status_choices, default=0)get_time = models.DateTimeField(verbose_name="領(lǐng)取時(shí)間", help_text="用戶領(lǐng)取時(shí)間")used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用時(shí)間")# order = models.ForeignKey("Order", blank=True, null=True, verbose_name="關(guān)聯(lián)訂單") # 一個(gè)訂單可以有多個(gè)優(yōu)惠券order_id = models.IntegerField(verbose_name='關(guān)聯(lián)訂單ID')class Meta:verbose_name_plural = "32. 用戶優(yōu)惠券"def __str__(self):return '%s-%s-%s' % (self.account, self.number, self.status)class Account(models.Model):username = models.CharField("用戶名", max_length=64, unique=True)email = models.EmailField(verbose_name='郵箱',max_length=255,unique=True,blank=True,null=True)password = models.CharField('密碼', max_length=128)balance = models.FloatField('貝里',default=0)class Meta:verbose_name_plural = "33. 用戶表"class UserToken(models.Model):user = models.OneToOneField(to='Account')token = models.CharField(max_length=36)class Meta:verbose_name_plural = "34. token表" View Code執(zhí)行2個(gè)命令,生成字段
python manage.py makemigrations python manage.py migrate為用戶加點(diǎn)錢
?
修改admin.py,注冊(cè)所有表
from django.contrib import admin# Register your models here. from api import models admin.site.register(models.CourseCategory) admin.site.register(models.CourseSubCategory) admin.site.register(models.DegreeCourse) admin.site.register(models.Teacher) admin.site.register(models.Scholarship) admin.site.register(models.Course) admin.site.register(models.CourseDetail) admin.site.register(models.OftenAskedQuestion) admin.site.register(models.CourseOutline) admin.site.register(models.CourseChapter) admin.site.register(models.CourseSection) admin.site.register(models.Homework) admin.site.register(models.PricePolicy) admin.site.register(models.Coupon) admin.site.register(models.CouponRecord) admin.site.register(models.Account) View Code進(jìn)入admin后臺(tái),添加幾條優(yōu)惠券,并綁定用戶
list
修改payment.py,先做get請(qǐng)求的
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 BaseResponsefrom django_redis import get_redis_connectionCONN = get_redis_connection("default") # 使用redis連接池class PaymentView(ViewSetMixin, APIView):authentication_classes = [LuffyAuthentication, ]def create(self, request, *args, **kwargs):"""在結(jié)算中添加課程:param request::param args::param kwargs::return:"""# 1.接收用戶選擇的要結(jié)算的課程ID列表# 2.清空當(dāng)前用戶request.user.id結(jié)算中心的數(shù)據(jù)# key = payment_1*# 3.循環(huán)要加入結(jié)算中的所有課程ID列表"""for course_id in 用戶提交課程ID列表:3.1 根據(jù)course_id,request.user.id去購(gòu)物車中獲取商品信息:商品名稱、圖片、價(jià)格(id,周期,顯示周期,價(jià)格)3.2 根據(jù)course_id,request.user.id獲取 - 當(dāng)前用戶- 當(dāng)前課程- 可用的優(yōu)惠券加入結(jié)算中心提示:可以使用contenttypes"""# 4.獲取當(dāng)前用戶所有未綁定課程優(yōu)惠券# - 未使用# - 有效期內(nèi)# - 加入結(jié)算中心:glocal_coupon_用戶IDdef list(self, request, *args, **kwargs):"""查看結(jié)算中心:param request::param args::param kwargs::return:"""# 1. 根據(jù)用戶ID去結(jié)算中心獲取該用戶所有要結(jié)算課程course_id = request.query_params.get('course_id')print('課程id',course_id)obj = models.Course.objects.filter(id=course_id).first()print('結(jié)算課程',obj.name)# 2. 根據(jù)用戶ID去結(jié)算中心獲取該用戶所有可用未綁定課程的優(yōu)惠券user_id =request.user.idprint('用戶id', user_id)obj2 = models.CouponRecord.objects.filter(account=user_id, coupon__object_id__isnull=True).first()# print(obj2.coupon.get_coupon_type_display())if obj2.coupon.coupon_type == 0:print('{}{}'.format(obj2.coupon.get_coupon_type_display(),obj2.coupon.money_equivalent_value))elif obj2.coupon.coupon_type == 1:print('滿{}減{}'.format(obj2.coupon.minimum_consume,obj2.coupon.money_equivalent_value))else:print(obj2.coupon.id)print('{}折'.format(obj2.coupon.off_percent))# 3. 用戶表中獲取貝里余額beili = models.Account.objects.filter(id=user_id).first()print('用戶貝里',beili.balance)# 4. 以上數(shù)據(jù)構(gòu)造成一個(gè)字典return Response('...')def update(self, request, *args, **kwargs):"""更新優(yōu)惠券:param request::param args::param kwargs::return:"""# 1. 獲取用戶提交:# course_id=1,coupon_id=3# course_id=0,coupon_id=6# 2. course_id=1 --> 去結(jié)算中心獲取當(dāng)前用戶所擁有的綁定當(dāng)前課程優(yōu)惠,并進(jìn)行校驗(yàn)# - 成功:defaul_coupon_id=3# - 否則:非法請(qǐng)求# 3. course_id=0 --> 去結(jié)算中心獲取當(dāng)前用戶所擁有的未綁定課程優(yōu)惠,并進(jìn)行校驗(yàn)# - 成功:defaul_coupon_id=3# - 否則:非法請(qǐng)求 View Code使用postman發(fā)送GET請(qǐng)求
查看Pycharm控制臺(tái)輸出
課程id 1 結(jié)算課程 Python開發(fā)入門7天特訓(xùn)營(yíng) 用戶id 1 立減10 用戶貝里 100.0?
posted @ 2018-08-14 15:13 肖祥 閱讀( ...) 評(píng)論( ...) 編輯 收藏總結(jié)
以上是生活随笔為你收集整理的python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: luogu1091合唱队形
- 下一篇: 无线降噪耳机推荐,热销火爆的四款降噪耳机