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.db import models # ########################### 1.課程相關 ################################ class CourseCategory(models.Model): """課程大類, e.g 前端 后端...""" name = models.CharField(max_length=64, unique=True) def __str__(self): return "%s" % self.name class Meta: verbose_name_plural = "01.課程大類" class CourseSubCategory(models.Model): """課程子類, e.g python linux """ category = models.ForeignKey("CourseCategory") name = models.CharField(max_length=64, unique=True) def __str__(self): return "%s" % self.name class Meta: verbose_name_plural = "02.課程子類" class DegreeCourse(models.Model): """學位課程""" name = models.CharField(max_length=128, unique=True) course_img = models.CharField(max_length=255, verbose_name="縮略圖") brief = models.TextField(verbose_name="學位課程簡介", ) total_scholarship = models.PositiveIntegerField(verbose_name="總獎學金(貝里)", default=40000) mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本課程的導師輔導費用(貝里)", default=15000) period = models.PositiveIntegerField(verbose_name="建議學習周期(days)", default=150) # 為了計算學位獎學金 prerequisite = models.TextField(verbose_name="課程先修要求", max_length=1024) teachers = models.ManyToManyField("Teacher", verbose_name="課程講師") # 用於GenericForeignKey反向查詢, 不會生成表字段,切勿刪除 coupon = GenericRelation("Coupon") # 用於GenericForeignKey反向查詢,不會生成表字段,切勿刪除 degreecourse_price_policy = GenericRelation("PricePolicy") # 查詢常見問題 asked_question = GenericRelation("OftenAskedQuestion") def __str__(self): return self.name class Meta: verbose_name_plural = "03.學位課" class Teacher(models.Model): """講師、導師表""" name = models.CharField(max_length=32) role_choices = ((0, '講師'), (1, '導師')) role = models.SmallIntegerField(choices=role_choices, default=0) title = models.CharField(max_length=64, verbose_name="職位、職稱") signature = models.CharField(max_length=255, help_text="導師簽名", blank=True, null=True) image = models.CharField(max_length=128) brief = models.TextField(max_length=1024) def __str__(self): return self.name class Meta: verbose_name_plural = "04.導師或講師" class Scholarship(models.Model): """學位課程獎學金""" degree_course = models.ForeignKey("DegreeCourse") time_percent = models.PositiveSmallIntegerField(verbose_name="獎勵檔位(時間百分比)", help_text="只填百分值,如80,代表80%") value = models.PositiveIntegerField(verbose_name="獎學金數額") def __str__(self): return "%s:%s" % (self.degree_course, self.value) class Meta: verbose_name_plural = "05.學位課獎學金" class Course(models.Model): """專題課程 OR 學位課模塊""" name = models.CharField(max_length=128, unique=True) # Python基礎 course_img = models.CharField(max_length=255) sub_category = models.ForeignKey("CourseSubCategory") # course_type_choices = ((0, '付費'), (1, 'VIP專享'), (2, '學位課程')) course_type = models.SmallIntegerField(choices=course_type_choices) degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, help_text="若是學位課程,此處關聯學位表") brief = models.TextField(verbose_name="課程(模塊)概述", max_length=2048) level_choices = ((0, '初級'), (1, '中級'), (2, '高級')) level = models.SmallIntegerField(choices=level_choices, default=1) pub_date = models.DateField(verbose_name="發布日期", blank=True, null=True) period = models.PositiveIntegerField(verbose_name="建議學習周期(days)", default=7) order = models.IntegerField("課程順序", help_text="從上一個課程數字往后排") attachment_path = models.CharField(max_length=128, verbose_name="課件路徑", blank=True, null=True) status_choices = ((0, '上線'), (1, '下線'), (2, '預上線')) status = models.SmallIntegerField(choices=status_choices, default=0) template_id = models.SmallIntegerField("前端模板id", default=1) # 如果是專題課時,獲取相關優惠券 coupon = GenericRelation("Coupon") # 用於GenericForeignKey反向查詢,不會生成表字段,切勿刪除 price_policy = GenericRelation("PricePolicy") # 查詢常見問題 asked_question = GenericRelation("OftenAskedQuestion") class Meta: verbose_name_plural = "06.專題課或學位課模塊" class CourseDetail(models.Model): """課程詳情頁內容""" course = models.OneToOneField("Course") hours = models.IntegerField("課時") course_slogan = models.CharField(max_length=125, blank=True, null=True) video_brief_link = models.CharField(verbose_name='課程介紹', max_length=255, blank=True, null=True) why_study = models.TextField(verbose_name="為什么學習這門課程") what_to_study_brief = models.TextField(verbose_name="我將學到哪些內容") career_improvement = models.TextField(verbose_name="此項目如何有助於我的職業生涯") prerequisite = models.TextField(verbose_name="課程先修要求", max_length=1024) recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True) teachers = models.ManyToManyField("Teacher", verbose_name="課程講師") def __str__(self): return "%s" % self.course class Meta: verbose_name_plural = "07.課程或學位模塊詳細" class OftenAskedQuestion(models.Model): """常見問題""" content_type = models.ForeignKey(ContentType) # 關聯course or degree_course object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') question = models.CharField(max_length=255) answer = models.TextField(max_length=1024) def __str__(self): return "%s-%s" % (self.content_object, self.question) class Meta: unique_together = ('content_type', 'object_id', 'question') verbose_name_plural = "08. 常見問題" class CourseOutline(models.Model): """課程大綱""" course_detail = models.ForeignKey("CourseDetail") # 前端顯示順序 order = models.PositiveSmallIntegerField(default=1) title = models.CharField(max_length=128) content = models.TextField("內容", max_length=2048) def __str__(self): return "%s" % self.title class Meta: unique_together = ('course_detail', 'title') verbose_name_plural = "09. 課程大綱" class CourseChapter(models.Model): """課程章節""" course = models.ForeignKey("Course", related_name='coursechapters') chapter = models.SmallIntegerField(verbose_name="第幾章", default=1) name = models.CharField(max_length=128) summary = models.TextField(verbose_name="章節介紹", blank=True, null=True) pub_date = models.DateField(verbose_name="發布日期", auto_now_add=True) class Meta: unique_together = ("course", 'chapter') verbose_name_plural = "10. 課程章節" def __str__(self): return "%s:(第%s章)%s" % (self.course, self.chapter, self.name) class CourseSection(models.Model): """課時目錄""" chapter = models.ForeignKey("CourseChapter", related_name='coursesections') name = models.CharField(max_length=128) order = models.PositiveSmallIntegerField(verbose_name="課時排序", help_text="建議每個課時之間空1至2個值,以備后續插入課時") section_type_choices = ((0, '文檔'), (1, '練習'), (2, '視頻')) section_type = models.SmallIntegerField(default=2, choices=section_type_choices) section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文檔,填link") video_time = models.CharField(verbose_name="視頻時長", blank=True, null=True, max_length=32) # 僅在前端展示使用 pub_date = models.DateTimeField(verbose_name="發布時間", auto_now_add=True) free_trail = models.BooleanField("是否可試看", default=False) class Meta: unique_together = ('chapter', 'section_link') verbose_name_plural = "11. 課時" def __str__(self): return "%s-%s" % (self.chapter, self.name) class Homework(models.Model): chapter = models.ForeignKey("CourseChapter") title = models.CharField(max_length=128, verbose_name="作業題目") order = models.PositiveSmallIntegerField("作業順序", help_text="同一課程的每個作業之前的order值間隔1-2個數") homework_type_choices = ((0, '作業'), (1, '模塊通關考核')) homework_type = models.SmallIntegerField(choices=homework_type_choices, default=0) requirement = models.TextField(max_length=1024, verbose_name="作業需求") threshold = models.TextField(max_length=1024, verbose_name="踩分點") recommend_period = models.PositiveSmallIntegerField("推薦完成周期(天)", default=7) scholarship_value = models.PositiveSmallIntegerField("為該作業分配的獎學金(貝里)") note = models.TextField(blank=True, null=True) enabled = models.BooleanField(default=True, help_text="本作業如果后期不需要了,不想讓學員看到,可以設置為False") class Meta: unique_together = ("chapter", "title") verbose_name_plural = "12. 章節作業" def __str__(self): return "%s - %s" % (self.chapter, self.title) class CourseReview(models.Model): """課程評價""" enrolled_course = models.OneToOneField("EnrolledCourse") about_teacher = models.FloatField(default=0, verbose_name="講師講解是否清晰") about_video = models.FloatField(default=0, verbose_name="內容實用") about_course = models.FloatField(default=0, verbose_name="課程內容通俗易懂") review = models.TextField(max_length=1024, verbose_name="評價") disagree_number = models.IntegerField(default=0, verbose_name="踩") agree_number = models.IntegerField(default=0, verbose_name="贊同數") date = models.DateTimeField(auto_now_add=True, verbose_name="評價日期") is_recommend = models.BooleanField("熱評推薦", default=False) hide = models.BooleanField("不在前端頁面顯示此條評價", default=False) def __str__(self): return "%s-%s" % (self.enrolled_course.course, self.review) class Meta: verbose_name_plural = "13. 課程評價(購買課程后才能評價)" class DegreeCourseReview(models.Model): """學位課程評價 為了以后可以定制單獨的評價內容,所以不與普通課程的評價混在一起,單獨建表 """ enrolled_course = models.ForeignKey("EnrolledDegreeCourse") course = models.ForeignKey("Course", verbose_name="評價學位模塊", blank=True, null=True, help_text="不填寫即代表評價整個學位課程", limit_choices_to={'course_type': 2}) about_teacher = models.FloatField(default=0, verbose_name="講師講解是否清晰") about_video = models.FloatField(default=0, verbose_name="視頻質量") about_course = models.FloatField(default=0, verbose_name="課程") review = models.TextField(max_length=1024, verbose_name="評價") disagree_number = models.IntegerField(default=0, verbose_name="踩") agree_number = models.IntegerField(default=0, verbose_name="贊同數") date = models.DateTimeField(auto_now_add=True, verbose_name="評價日期") is_recommend = models.BooleanField("熱評推薦", default=False) hide = models.BooleanField("不在前端頁面顯示此條評價", default=False) def __str__(self): return "%s-%s" % (self.enrolled_course, self.review) class Meta: verbose_name_plural = "14. 學位課評價(購買課程后才能評價)" class PricePolicy(models.Model): """價格與有課程效期表""" content_type = models.ForeignKey(ContentType) # 關聯course or degree_course object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') valid_period_choices = ((1, '1天'), (3, '3天'), (7, '1周'), (14, '2周'), (30, '1個月'), (60, '2個月'), (90, '3個月'), (180, '6個月'), (210, '12個月'), (540, '18個月'), (720, '24個月'), ) valid_period = models.SmallIntegerField(choices=valid_period_choices) price = models.FloatField() class Meta: unique_together = ("content_type", 'object_id', "valid_period") verbose_name_plural = "15. 價格策略" def __str__(self): return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price) # ########################### 2. 深科技相關 ################################ class ArticleSource(models.Model): """文章來源""" name = models.CharField(max_length=64, unique=True) class Meta: verbose_name_plural = "16. 文章來源" def __str__(self): return self.name class Article(models.Model): """文章資訊""" title = models.CharField(max_length=255, unique=True, db_index=True, verbose_name="標題") source = models.ForeignKey("ArticleSource", verbose_name="來源") article_type_choices = ((0, '資訊'), (1, '視頻')) article_type = models.SmallIntegerField(choices=article_type_choices, default=0) brief = models.TextField(max_length=512, verbose_name="摘要") head_img = models.CharField(max_length=255) content = models.TextField(verbose_name="文章正文") pub_date = models.DateTimeField(verbose_name="上架日期") offline_date = models.DateTimeField(verbose_name="下架日期") status_choices = ((0, '在線'), (1, '下線')) status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="狀態") order = models.SmallIntegerField(default=0, verbose_name="權重", help_text="文章想置頂,可以把數字調大,不要超過1000") vid = models.CharField(max_length=128, verbose_name="視頻VID", help_text="文章類型是視頻, 則需要添加視頻VID", blank=True, null=True) comment_num = models.SmallIntegerField(default=0, verbose_name="評論數") agree_num = models.SmallIntegerField(default=0, verbose_name="點贊數") view_num = models.SmallIntegerField(default=0, verbose_name="觀看數") collect_num = models.SmallIntegerField(default=0, verbose_name="收藏數") 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="位置") class Meta: verbose_name_plural = "17. 文章" def __str__(self): return "%s-%s" % (self.source, self.title) class Collection(models.Model): """收藏""" content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') account = models.ForeignKey("Account") date = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('content_type', 'object_id', 'account') verbose_name_plural = "18. 通用收藏表" class Comment(models.Model): """通用的評論表""" content_type = models.ForeignKey(ContentType, blank=True, null=True, verbose_name="類型") object_id = models.PositiveIntegerField(blank=True, null=True) content_object = GenericForeignKey('content_type', 'object_id') p_node = models.ForeignKey("self", blank=True, null=True, verbose_name="父級評論") content = models.TextField(max_length=1024) account = models.ForeignKey("Account", verbose_name="會員名") disagree_number = models.IntegerField(default=0, verbose_name="踩") agree_number = models.IntegerField(default=0, verbose_name="贊同數") date = models.DateTimeField(auto_now_add=True) def __str__(self): return self.content class Meta: verbose_name_plural = "19. 通用評論表" # ########################### 3. 用戶相關 ################################ class Account(models.Model): username = models.CharField("用戶名", max_length=64, unique=True) password = models.CharField("密碼", max_length=64) uid = models.CharField(max_length=64, unique=True, help_text='微信用戶綁定和CC視頻統計') # 與第3方交互用戶信息時,用這個uid,以避免泄露敏感用戶信息 openid = models.CharField(max_length=128, blank=True, null=True) # 貝里余額 balance = models.PositiveIntegerField(default=0, verbose_name="可提現和使用余額") class UserAuthToken(models.Model): """ 用戶Token表 """ user = models.OneToOneField(to="Account") token = models.CharField(max_length=64, unique=True) # ########################### 4. 獎懲相關 ################################ class ScoreRule(models.Model): """積分規則""" score_rule_choices = ( (0, '未按時交作業'), (1, '未及時批改作業'), (2, '作業成績'), (3, '未在規定時間內對學員進行跟進'), (4, '未在規定時間內回復學員問題'), (5, '收到學員投訴'), (6, '導師相關'), (7, '學位獎學金'), ) rule = models.SmallIntegerField(choices=score_rule_choices, verbose_name="積分規則") score_type_choices = ((0, '獎勵'), (1, '懲罰'), (2, '初始分配')) score_type = models.SmallIntegerField(choices=score_type_choices, verbose_name="獎懲", default=0) score = models.IntegerField(help_text="扣分數與貝里相等,若為0則代表規則的值可以從別處取得") # maturity_days = models.IntegerField("成熟周期", help_text="自紀錄創建時開始計算") memo = models.TextField(blank=True, null=True) def __str__(self): return "%s-%s:%s" % (self.get_rule_display(), self.get_score_type_display(), self.score) class Meta: unique_together = ('rule', 'score_type') verbose_name_plural = "29. 獎懲規則" class ScoreRecord(models.Model): """積分獎懲記錄""" content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True) content_object = GenericForeignKey('content_type', 'object_id') degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, verbose_name="關聯學位課程") score_rule = models.ForeignKey("ScoreRule", verbose_name="關聯規則") account = models.ForeignKey("Account", verbose_name="被執行人") score = models.IntegerField(verbose_name="金額(貝里)") # 這里單獨有一個字段存積分而不是從score_rule里引用的原因是考慮到如果引用的話, # 一旦score_rule里的積分有變更,那么所有用戶的歷史積分也會被影響 received_score = models.IntegerField("實際到賬金額貝里)", help_text="僅獎勵用", default=0) balance = models.PositiveIntegerField(verbose_name="獎金余額(貝里)") maturity_date = models.DateField("成熟日期(可提現日期)") applied = models.BooleanField(default=False, help_text="獎賞紀錄是否已被執行", verbose_name="是否已被執行") applied_date = models.DateTimeField(blank=True, null=True, verbose_name="事件生效日期") date = models.DateTimeField(auto_now_add=True, verbose_name="事件觸發日期") memo = models.TextField(blank=True, null=True) def __str__(self): return "%s-%s - %s - %s 獎金余額:%s" % (self.id, self.score_rule, self.account, self.score, self.balance) class Meta: verbose_name_plural = "30. 獎懲記錄" # ########################### 5. 支付相關 ################################ class Coupon(models.Model): """優惠券生成規則""" name = models.CharField(max_length=64, verbose_name="活動名稱") brief = models.TextField(blank=True, null=True, verbose_name="優惠券介紹") coupon_type_choices = ((0, '立減券'), (1, '滿減券'), (2, '折扣券')) coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券類型") money_equivalent_value = models.IntegerField(verbose_name="等值貨幣") off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只針對折扣券,例7.9折,寫79", blank=True, null=True) minimum_consume = models.PositiveIntegerField("最低消費", default=0, help_text="僅在滿減券時填寫此字段") content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField("綁定課程", blank=True, null=True, help_text="可以把優惠券跟課程綁定") content_object = GenericForeignKey('content_type', 'object_id') quantity = models.PositiveIntegerField("數量(張)", default=1) open_date = models.DateField("優惠券領取開始時間") close_date = models.DateField("優惠券領取結束時間") valid_begin_date = models.DateField(verbose_name="有效期開始時間", blank=True, null=True) valid_end_date = models.DateField(verbose_name="有效結束時間", blank=True, null=True) coupon_valid_days = models.PositiveIntegerField(verbose_name="優惠券有效期(天)", blank=True, null=True, help_text="自券被領時開始算起") date = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "31. 優惠券生成記錄" def __str__(self): return "%s(%s)" % (self.get_coupon_type_display(), self.name) def save(self, *args, **kwargs): if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date): if self.valid_begin_date and self.valid_end_date: if self.valid_end_date <= self.valid_begin_date: raise ValueError("valid_end_date 有效期結束日期必須晚於 valid_begin_date ") if self.coupon_valid_days == 0: raise ValueError("coupon_valid_days 有效期不能為0") if self.close_date < self.open_date: raise ValueError("close_date 優惠券領取結束時間必須晚於 open_date優惠券領取開始時間 ") super(Coupon, self).save(*args, **kwargs) class CouponRecord(models.Model): """優惠券發放、消費紀錄""" coupon = models.ForeignKey("Coupon") number = models.CharField(max_length=64, unique=True) account = models.ForeignKey("Account", verbose_name="擁有者") status_choices = ((0, '未使用'), (1, '已使用'), (2, '已過期')) status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(verbose_name="領取時間", help_text="用戶領取時間") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用時間") class Meta: verbose_name_plural = "32. 用戶優惠券" def __str__(self): return '%s-%s-%s' % (self.account, self.number, self.status) class CourseSchedule(models.Model): """課程進度計划表,針對學位課程,每開通一個模塊,就為這個學員生成這個模塊的推薦學習計划表,后面的獎懲均按此表進行""" study_record = models.ForeignKey("StudyRecord") homework = models.ForeignKey("Homework") recommend_date = models.DateField("推薦交作業日期") def __str__(self): return "%s - %s - %s " % (self.study_record, self.homework, self.recommend_date) class Meta: unique_together = ('study_record', 'homework') verbose_name_plural = "33. 課程模塊計划表(學位課)" class EnrolledCourse(models.Model): """已報名課程,不包括學位課程""" account = models.ForeignKey("Account") course = models.ForeignKey("Course", limit_choices_to=~Q(course_type=2)) enrolled_date = models.DateTimeField(auto_now_add=True) valid_begin_date = models.DateField(verbose_name="有效期開始自") valid_end_date = models.DateField(verbose_name="有效期結束至") status_choices = ((0, '已開通'), (1, '已過期')) status = models.SmallIntegerField(choices=status_choices, default=0) order_detail = models.OneToOneField("OrderDetail") # 使訂單購買后支持 課程評價 def __str__(self): return "%s:%s" % (self.account, self.course) class Meta: verbose_name_plural = "34. 報名專題課" class DegreeRegistrationForm(models.Model): """學位課程報名表""" enrolled_degree = models.OneToOneField("EnrolledDegreeCourse") current_company = models.CharField(max_length=64, ) current_position = models.CharField(max_length=64, ) current_salary = models.IntegerField() work_experience_choices = ((0, "應屆生"), (1, "1年"), (2, "2年"), (3, "3年"), (4, "4年"), (5, "5年"), (6, "6年"), (7, "7年"), (8, "8年"), (9, "9年"), (10, "10年"), (11, "超過10年"), ) work_experience = models.IntegerField() open_module = models.BooleanField("是否開通第1模塊", default=True) stu_specified_mentor = models.CharField("學員自行指定的導師名", max_length=32, blank=True, null=True) study_plan_choices = ((0, "1-2小時/天"), (1, "2-3小時/天"), (2, "3-5小時/天"), (3, "5小時+/天"), ) study_plan = models.SmallIntegerField(choices=study_plan_choices, default=1) why_take_this_course = models.TextField("報此課程原因", max_length=1024) why_choose_us = models.TextField("為何選路飛", max_length=1024) your_expectation = models.TextField("你的期待", max_length=1024) memo = models.CharField(max_length=255, blank=True, null=True) class Meta: verbose_name_plural = "35. 報名表(學位課)" def __str__(self): return "%s" % self.enrolled_degree class EnrolledDegreeCourse(models.Model): """已報名的學位課程""" account = models.ForeignKey("Account") degree_course = models.ForeignKey("DegreeCourse") enrolled_date = models.DateTimeField(auto_now_add=True) valid_begin_date = models.DateField(verbose_name="有效期開始自", blank=True, null=True) # 開通第一個模塊時,再添加課程有效期,2年 valid_end_date = models.DateField(verbose_name="有效期結束至", blank=True, null=True) status_choices = ( (0, '在學中'), (1, '休學中'), (2, '已畢業'), (3, '超時結業'), (4, '未開始'), # (3, '其它'), ) study_status = models.SmallIntegerField(choices=status_choices, default=0) mentor = models.ForeignKey("Account", verbose_name="導師", related_name='my_students', blank=True, null=True, limit_choices_to={'role': 1}) mentor_fee_balance = models.PositiveIntegerField("導師費用余額", help_text="這個學員的導師費用,每有懲罰,需在此字段同時扣除") order_detail = models.OneToOneField("OrderDetail") # 使訂單購買后支持填寫報名表 def __str__(self): return "%s:%s" % (self.account, self.degree_course) class Meta: unique_together = ('account', 'degree_course') verbose_name_plural = "36. 報名學位課" class Order(models.Model): """訂單""" payment_type_choices = ((0, '微信'), (1, '支付寶'), (2, '優惠碼'), (3, '貝里')) payment_type = models.SmallIntegerField(choices=payment_type_choices) payment_number = models.CharField(max_length=128, verbose_name="支付第3方訂單號", null=True, blank=True) order_number = models.CharField(max_length=128, verbose_name="訂單號", unique=True) # 考慮到訂單合並支付的問題 account = models.ForeignKey("Account") actual_amount = models.FloatField(verbose_name="實付金額") status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退費申請中'), (3, '已退費'), (4, '主動取消'), (5, '超時取消')) status = models.SmallIntegerField(choices=status_choices, verbose_name="狀態") date = models.DateTimeField(auto_now_add=True, verbose_name="訂單生成時間") pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款時間") cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="訂單取消時間") class Meta: verbose_name_plural = "37. 訂單表" def __str__(self): return "%s" % self.order_number class OrderDetail(models.Model): """訂單詳情""" order = models.ForeignKey("Order") content_type = models.ForeignKey(ContentType) # 可關聯普通課程或學位 object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') original_price = models.FloatField("課程原價") price = models.FloatField("折后價格") content = models.CharField(max_length=255, blank=True, null=True) # ? valid_period_display = models.CharField("有效期顯示", max_length=32) # 在訂單頁顯示 valid_period = models.PositiveIntegerField("有效期(days)") # 課程有效期 memo = models.CharField(max_length=255, blank=True, null=True) def __str__(self): return "%s - %s - %s" % (self.order, self.content_type, self.price) class Meta: verbose_name_plural = "38. 訂單詳細" unique_together = ("order", 'content_type', 'object_id') class StudyRecord(models.Model): """學位課程的模塊學習進度,報名學位課程后,每個模塊會立刻生成一條學習紀錄""" enrolled_degree_course = models.ForeignKey("EnrolledDegreeCourse") course_module = models.ForeignKey("Course", verbose_name="學位模塊", limit_choices_to={'course_type': 2}) open_date = models.DateField(blank=True, null=True, verbose_name="開通日期") end_date = models.DateField(blank=True, null=True, verbose_name="完成日期") status_choices = ((2, '在學'), (1, '未開通'), (0, '已完成')) status = models.SmallIntegerField(choices=status_choices, default=1) class Meta: verbose_name_plural = "39. 學習記錄表(報名學位課程后,每個模塊會立刻生成一條學習紀錄)" unique_together = ('enrolled_degree_course', 'course_module') def __str__(self): return '%s-%s' % (self.enrolled_degree_course, self.course_module) def save(self, *args, **kwargs): if self.course_module.degree_course_id != self.enrolled_degree_course.degree_course_id: raise ValueError("學員要開通的模塊必須與其報名的學位課程一致!") super(StudyRecord, self).save(*args, **kwargs) class TransactionRecord(models.Model): """貝里交易紀錄""" account = models.ForeignKey("Account") amount = models.IntegerField("金額") balance = models.IntegerField("賬戶余額") transaction_type_choices = ((0, '收入'), (1, '支出'), (2, '退款'), (3, "提現")) # 2 為了處理 訂單過期未支付時,鎖定期貝里的回退 transaction_type = models.SmallIntegerField(choices=transaction_type_choices) content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True, verbose_name="關聯對象") content_object = GenericForeignKey('content_type', 'object_id') transaction_number = models.CharField(unique=True, verbose_name="流水號", max_length=128) date = models.DateTimeField(auto_now_add=True) memo = models.CharField(max_length=128, blank=True, null=True) class Meta: verbose_name_plural = "40. 貝里交易記錄" def __str__(self): return "%s" % self.transaction_number class HomeworkRecord(models.Model): """學員作業記錄及成績""" homework = models.ForeignKey("Homework") student = models.ForeignKey("EnrolledDegreeCourse", verbose_name="學生") score_choices = ((100, 'A+'), (90, 'A'), (85, 'B+'), (80, 'B'), (70, 'B-'), (60, 'C+'), (50, 'C'), (40, 'C-'), (-1, 'D'), (0, 'N/A'), (-100, 'COPY'), ) score = models.SmallIntegerField(verbose_name="分數", choices=score_choices, null=True, blank=True) mentor = models.ForeignKey("Account", related_name="my_stu_homework_record", limit_choices_to={'role': 1}, verbose_name="導師") mentor_comment = models.TextField(verbose_name="導師批注", blank=True, null=True) # 導師 status_choice = ( (0, '待批改'), (1, '已通過'), (2, '不合格'), ) status = models.SmallIntegerField(verbose_name='作業狀態', choices=status_choice, default=0) submit_num = models.SmallIntegerField(verbose_name='提交次數', default=0) correct_date = models.DateTimeField('備注日期', blank=True, null=True) note = models.TextField(blank=True, null=True) date = models.DateTimeField("作業提交日期", auto_now_add=True) check_date = models.DateTimeField("批改日期", null=True, blank=True) update_time = models.DateTimeField(auto_now=True, verbose_name="提交日期") reward_choice = ((0, '新提交'), (1, '按時提交'), (2, '未按時提交'), (3, '成績已獎勵'), (4, '成績已處罰'), (5, '未作按時檢測'), ) reward_status = models.SmallIntegerField(verbose_name='作業記錄獎懲狀態', default=0) def __str__(self): return "%s %s" % (self.homework, self.student) class Meta: verbose_name_plural = "41. 作業" unique_together = ("homework", "student") class StuFollowUpRecord(models.Model): """學員跟進記錄""" enrolled_degree_course = models.ForeignKey("EnrolledDegreeCourse", verbose_name="學生") mentor = models.ForeignKey("Account", related_name='mentor', limit_choices_to={'role': 1}, verbose_name="導師") followup_tool_choices = ((0, 'QQ'), (1, '微信'), (2, '電話'), (3, '系統通知')) followup_tool = models.SmallIntegerField(choices=followup_tool_choices, default=1) record = models.TextField(verbose_name="跟進記錄") attachment_path = models.CharField(max_length=128, blank=True, null=True, verbose_name="附件路徑", help_text="跟進記錄的截圖等") date = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "42. 學員跟進記錄" def __str__(self): return "%s --%s --%s" % (self.enrolled_degree_course, self.record, self.date)
shoppingcar.py
"""這是一個關於redis購物車的模塊""" import json from rest_framework.views import APIView from rest_framework.response import Response from api import models from mysite import settings from django_redis import get_redis_connection from utils.response import BaseResponse USER_ID = 1 # 模擬用戶 class ShoppingCarViewSet(APIView): # 類下的全局變量 conn = get_redis_connection("default") def post(self, request, *args, **kwargs): """ 將課程添加到購物車 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: # 1.獲取用戶提交的課程id和價格策略id course_id = int(request.data.get("courseid")) policy_id = int(request.data.get("policyid")) # 2.獲取專題課程信息 course = models.Course.objects.get(id=course_id) # 3.獲取該課程相關的所有價格策略 price_policy_list = course.price_policy.all() price_policy_dict = {} for item in price_policy_list: price_policy_dict[item.id] = { "period": item.valid_period, "period_display": item.get_valid_period_display(), "price": item.price, } # 4.判斷用戶提交的價格策略是否合法 if policy_id not in price_policy_dict: # 價格策略不合法 ret.code = 1001 ret.error = "價格異常" return Response(ret.dict) # 5.將購物信息添加到redis中 car_key = settings.SHOPPING_CAR_KEY % (USER_ID, course_id) car_dict = { "title": course.name, "img": course.course_img, "default_policy": policy_id, "policy": json.dumps(price_policy_dict) } self.conn.hmset(car_key, car_dict) ret.data = "添加成功" except Exception as e: ret.code = 1002 ret.error = "獲取購物車失敗" return Response(ret.dict) def delete(self, request, *args, **kwargs): """ 刪除購物車中課程 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: course_id_list = request.data.get("courseids") key_list = [settings.SHOPPING_CAR_KEY % (USER_ID, course_id) for course_id in course_id_list] self.conn.delete(*key_list) except Exception as e: ret.code = 1001 ret.error = "刪除失敗" return Response(ret.dict) def patch(self, request, *args, **kwargs): """ 修改課程的價格策略 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: # 1.獲取價格策略和課程id course_id = request.data.get("courseid") policy_id = request.data.get("policyid") # 2.拼接課程的key key = settings.SHOPPING_CAR_KEY % (USER_ID, course_id) if not self.conn.exists(key): ret.code = 1001 ret.error = "購物車中不存在此課程" return Response(ret.dict) # 3.redis中獲取所有的價格策略 policy_dict = json.loads(str(self.conn.hget(key, "policy"), encoding="utf-8")) if policy_id not in policy_dict: ret.code = 1002 ret.data = "價格策略不合法" return Response(ret.dict) # 4.在購物車中修改該課程默認價格策略 self.conn.hset(key, "default_policy", policy_id) ret.data = "修改成功" except Exception as e: ret.code = 1003 ret.error = "修改失敗" return Response(ret.dict) def get(self, request, *args, **kwargs): """ 查看購物車中所有的商品 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: key_match = settings.SHOPPING_CAR_KEY % (USER_ID, "*") course_list = [] for key in self.conn.scan_iter(key_match, count=10): info = { "title": self.conn.hget(key, "title").decode("utf-8"), "img": self.conn.hget(key, "img").decode("utf-8"), "policy": json.loads(self.conn.hget(key, "policy").decode("utf-8")), "default_policy": self.conn.hget(key, "default_policy").decode("utf-8"), } course_list.append(info) ret.data = course_list except Exception as e: ret.code = 1002 ret.error = "獲取失敗" return Response(ret.dict)
payment.py
"""這是一個結算中心模塊""" import json import datetime from rest_framework.views import APIView from rest_framework.response import Response from django_redis import get_redis_connection from utils.response import BaseResponse from mysite import settings from api import models USER_ID = 1 class PayMentViewSet(APIView): conn = get_redis_connection("default") def post(self, request, *args, **kwargs): """ 購物車數據提交到結算中心 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: # 清空當前用戶結算中心的數據 key_list = self.conn.keys(settings.PAYMENT_KEY % (USER_ID, "*")) key_list.append(settings.PAYMENT_COUPON_KEY % (USER_ID)) self.conn.delete(*key_list) payment_dict = {} # 通用優惠券 global_coupon_dict = { "coupon": {}, "default_coupon": 0 } # 1.獲取用戶要結算課程id course_id_list = request.data.get("courseids") for course_id in course_id_list: car_key = settings.SHOPPING_CAR_KEY % (USER_ID, course_id) # 1.1結算時檢測用戶的課程是否已經加入購物車 if not self.conn.exists(car_key): ret.code = 1001 ret.error = "課程需要先加入購物車才能結算" return Response(ret.dict) # 1.2從購物車中獲取價格策略和默認價格,放入到結算中心 policy = json.loads(self.conn.hget(car_key, "policy").decode("utf-8")) default_policy = self.conn.hget(car_key, "default_policy").decode("utf-8") policy_info = policy[default_policy] payment_course_dict = { "course_id": str(course_id), "title": self.conn.hget(car_key, "title").decode("utf-8"), "img": self.conn.hget(car_key, "img").decode("utf-8"), "policy_id": default_policy, "coupon": {}, # 優惠券 "default_policy": 0 # 默認沒有選擇優惠券 } payment_course_dict.update(policy_info) payment_dict[str(course_id)] = payment_course_dict # 1.3獲取優惠券 ctime = datetime.date.today() coupon_list = models.CouponRecord.objects.filter( account=USER_ID, status=0, coupon__valid_begin_date__lte=ctime, # 優惠券開始時間小於等於今天 coupon__valid_end_date__gte=ctime, ) for item in coupon_list: # 通用優惠券 if not item.coupon.object_id: coupon_id = item.id # 優惠券的類型: 滿減、折扣、立減 coupon_type = item.coupon.coupon_type # 獲取編號和中文解釋 info = {} info["coupon_type"] = coupon_type info["coupon_display"] = item.coupon.get_coupon_type_display() if coupon_type == 0: # 立減 info["money_equivalent_value"] = item.coupon.money_equivalent_value elif coupon_type == 1: # 滿減券 info["money_equivalent_value"] = item.coupon.money_equivalent_value info["minimum_consume"] = item.coupon.minimum_consume else: # 折扣 info["off_percent"] = item.coupon.off_percent # 通用優惠券 global_coupon_dict["coupon"][coupon_id] = info continue # 綁定課程優惠券id coupon_course_id = str(item.coupon.object_id) # 優惠券id coupon_id = item.id # (優惠券規則表)優惠券類型: 滿減、折扣、立減 coupon_type = item.coupon.coupon_type info = {} info["coupon_type"] = coupon_type info["coupon_display"] = item.coupon.get_coupon_type_display() if coupon_type == 0: # 立減 info["money_equivalent_value"] = item.coupon.money_equivalent_value elif coupon_type == 1: # 滿減券 info["money_equivalent_value"] = item.coupon.money_equivalent_value info["minimum_consume"] = item.coupon.minimum_consume else: # 折扣 info["off_percent"] = item.coupon.off_percent if coupon_course_id not in payment_dict: # 獲取到優惠券,但是沒有購買此課程 continue # 將優惠券設置到指定的課程字典中 payment_dict[coupon_course_id]["coupon"][coupon_id] = info # 1.4將綁定優惠券課程寫入到redis中(結算中心) for cid, cinfo in payment_dict.items(): pay_key = settings.PAYMENT_KEY % (USER_ID, cid) cinfo["coupon"] = json.dumps(cinfo["coupon"]) self.conn.hmset(pay_key, cinfo) # 1.5將通用優惠券寫入到redis中 gcoupon_key = settings.PAYMENT_COUPON_KEY % (USER_ID,) global_coupon_dict["coupon"] = json.dumps(global_coupon_dict["coupon"]) self.conn.hmset(gcoupon_key, global_coupon_dict) ret.data = "提交成功" except Exception as e: ret.code = 1003 ret.error = "提交失敗" return Response(ret.dict) def patch(self, request, *args, **kwargs): """ 選擇優惠券 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: # 1.用戶提交要選擇的優惠券 course = request.data.get("courseid") course_id = str(course) if course else course coupon_id = str(request.data.get("courseid")) redis_global_coupon_key = settings.PAYMENT_COUPON_KEY % (USER_ID,) # 1.1修改通用優惠券 if not course_id: if coupon_id == "0": # 不使用優惠券請求數據 self.conn.hset(redis_global_coupon_key, "default_coupon", coupon_id) ret.data = "修改成功" return Response(ret.dict) # 使用優惠券請求數據 coupon_dict = json.loads(self.conn.hget(redis_global_coupon_key, "coupon").decode("utf-8")) # 判斷用戶選擇的優惠券是否合法 if coupon_id not in coupon_dict: ret.code = 1003 ret.error = "通用優惠券不存在" return Response(ret.dict) # 選擇的優惠券合法 self.conn.hset(redis_global_coupon_key, "default_coupon", coupon_id) ret.data = "修改成功" return Response(ret.dict) # 修改課程優惠券 redis_payment_key = settings.PAYMENT_KEY % (USER_ID, course_id) # 不使用優惠券 if coupon_id == "0": self.conn.hset(redis_payment_key, "default_coupon", coupon_id) ret.data = "修改成功" return Response(ret.dict) # 使用優惠券 coupon_dict = json.loads(self.conn.hget(redis_payment_key, "coupon").decode("utf-8")) if coupon_id not in coupon_dict: ret.code = 1003 ret.error = "課程優惠券不存在" return Response(ret.dict) self.conn.hset(redis_payment_key, "default_coupon", coupon_id) ret.data = "修改課程優惠券成功" except Exception as e: ret.code = 1002 ret.error = "修改失敗" return Response(ret.dict) def get(self, request, *args, **kwargs): """ 查看優惠券信息 :param request: :param args: :param kwargs: :return: """ ret = BaseResponse() try: # payment_1_* redis_payment_key = settings.PAYMENT_KEY % (USER_ID, "*") # payment_coupon_1 redis_global_coupon_key = settings.PAYMENT_COUPON_KEY % (USER_ID,) # 1.獲取綁定課程信息 course_list = [] for key in self.conn.scan_iter(redis_payment_key): info = {} data = self.conn.hgetall(key) for k, v in data.items(): kk = k.decode("utf-8") if kk == "coupon": info[kk] = json.loads(v.decode("utf-8")) else: info[kk] = v.decode("utf-8") course_list.append(info) # 通用優惠券 global_coupon_didct = { "coupon": json.loads(self.conn.hget(redis_global_coupon_key, "coupon").decode("utf-8")), "default_coupon": self.conn.hget(redis_global_coupon_key, "default_coupon").decode("utf-8") } ret.data = { "course_list": course_list, "global_coupon_dict": global_coupon_didct } except Exception as e: ret.code = 1003 ret.error = "獲取失敗" return Response(ret.dict)
app/urls.py
from django.conf.urls import url from api.views import shoppingcar, payment, order urlpatterns = [ url(r'^shoppingcar/$', shoppingcar.ShoppingCarViewSet.as_view()), url(r'^payment/$', payment.PayMentViewSet.as_view()), url(r'^order/$', order.OrderViewset.as_view()), ]
mysite/urls.py
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/(?P<version>\w+)/', include("api.urls")), ]
settings.py
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning', 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 } # redis設置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100}, "PASSWORD": "123456", } } } # 購物車相關設置 SHOPPING_CAR_KEY = "shopping_car_%s_%s" PAYMENT_KEY = "payment_%s_%s" PAYMENT_COUPON_KEY = "payment_coupon_%s"
utils/response.py
class BaseResponse(object): def __init__(self): self.code = 1000 self.data = None self.error = None @property def dict(self): return self.__dict__
