1. 程式人生 > >Django——線上教育專案總結

Django——線上教育專案總結

專案簡介

線上教育平臺

軟體依賴:

  • WEB框架:Django(1.11.7)、Django REST framework

  • 前端框架:Vue(2.5.16)

  • 資料庫: MySql、redis

  • 支付平臺:支付寶

  • 訊息推送平臺:微信服務號,電子郵件

主要功能如下:

  • 允許學生註冊、登陸

  • 瀏覽本線上教育平臺所提供的課程列表、課程介紹(課程大綱,)

  • 學生將課程加入購物車

  • 完成購物後,進入結算中心,通過支付寶支付購買課程

  • 完成課程購買後,分配導師

  • 開始學習,觀看本站提供的教學視訊

  • 通過答疑係統,提交學習中遇到的問題,與同學討論或者導師答疑

  • 完成章節學習後,提交作業

  • 導師批改作業並打分

  • 模組學習完成後進行考核

專案意義

目標:

通過互聯技術,變革傳統教育方式,依賴強大的導師服務系統,提高線上教育的效率與成功率。

專案策略:

  • 提供具有時間限制的教學服務

  • 安排合理的模組化學習

  • 聘用高素質的導師,提供優質服務

  • 導師對學員一對一線上考核

  • 學習週期中全程導師監督(跟進記錄)

  • 提供高效的答疑解惑服務(12小時)

  • 建立完善的獎學金制度

    • 是否按學習計劃完成學業

    • 每章節作業完成情況

 

專案開發團隊與進度

團隊構成:

  • 開發

    • 導師後臺,stark元件+rbac : 1人

    • 管理後臺,stark元件+rbac : 1人

    • 主站

      • vue.js 1人

      • api 村長+1/2文州+1/2Alex+其他 + 村長

  • 運維(1人)

  • 測試(1人)

  • 產品經理(1人)

  • UI設計(1人)

  • 運營(1人)

  • 銷售(4人)

  • 全職導師(2人)

  • 簽約講師(...)

週期:

  • 7月份專案開始

  • 11月份上線

  • 11月份~次年5月份: 修Bug,活動支援,廣告。。。

  • 6月份:開發題庫系統

資料庫設計

簡要描述如下:

  • 課程(13表)

    • 課程大類

    • 課程子類

    • 學位課

      • 講師

      • 獎學金

    • 專題課(學位課模組表)

    • 價格策略(contenttype)

    • 課程詳細(o2o -> 水平分表)

    • 常見問題

    • 課程大綱

    • 章節

    • 課時

    • 作業

  • 深科技(4+2)

    • 使用者表

    • 使用者token

    • 文章來源

    • 文章表

    • 通用評論表

    • 通用收藏表

  • 支付相關(4+2)

    • 優惠券表

    • 訂單表

    • 訂單詳情表

    • 文章表

    • 通用評論表

    • 通用收藏表

Django中實現:

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.db.models import Q




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 = "課程大類"
        verbose_name_plural = "課程大類"


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 = "課程子類"
        verbose_name_plural = "課程子類"


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)
    # 忽略,用於GenericForeignKey反向查詢, 不會生成表字段,切勿刪除
    coupon = GenericRelation("Coupon")

    # 為了計算學位獎學金
    period = models.PositiveIntegerField(verbose_name="建議學習週期(days)", default=150)
    prerequisite = models.TextField(verbose_name="課程先修要求", max_length=1024)
    teachers = models.ManyToManyField("Teacher", verbose_name="課程講師")

    # 忽略,用於GenericForeignKey反向查詢,不會生成表字段,切勿刪除
    degreecourse_price_policy = GenericRelation("PricePolicy")

    def __str__(self):
        return self.name


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 Course(models.Model):
    """課程"""
    name = models.CharField(max_length=128, unique=True)
    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")

    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("學位課程必須關聯對應的學位表")
        super(Course, self).save(*args, **kwargs)


class PricePolicy(models.Model):
    """價格與有課程效期表"""
    content_type = models.ForeignKey(ContentType)
    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")

    def __str__(self):
        return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)



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 OftenAskedQuestion(models.Model):
    """常見問題"""
    content_type = models.ForeignKey(ContentType,limit_choices_to={'model__contains': 'course'})  # 關聯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')


class CourseOutline(models.Model):
    """課程大綱"""
    course_detail = models.ForeignKey("CourseDetail")
    title = models.CharField(max_length=128)
    # 前端顯示順序
    order = models.PositiveSmallIntegerField(default=1)

    content = models.TextField("內容", max_length=2048)

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

    class Meta:
        unique_together = ('course_detail', 'title')


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

    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)
    # vid: cc視訊,唯一標識:Dfadljfosdfjlsdfjs
    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')


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

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

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 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 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 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:
        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 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')

# 學生交作業
class HomeworkRecord(models.Model):
    """學員作業記錄及成績"""
    homework = models.ForeignKey("Homework")
    student = models.ForeignKey("EnrolledDegreeCourse", verbose_name="學生")
    score_choices = (
        (100, 'A+'),
        (90, 'A'),
        (85, 'B+'),
        (80, 'B'),
        (70, 'B-'),
        (60, 'C+'),
        (50, 'C'),
        (40, 'C-'),
        (-1, 'D'),
        (0, 'N/A'),
        (-100, 'COPY'),
    )
    score = models.SmallIntegerField(verbose_name="分數", choices=score_choices, null=True, blank=True)
    mentor = models.ForeignKey("Account", related_name="my_stu_homework_record", limit_choices_to={'role': 1},
                               verbose_name="導師")
    mentor_comment = models.TextField(verbose_name="導師批註", blank=True, null=True)  # 導師
    status_choice = (
        (0, '待批改'),
        (1, '已通過'),
        (2, '不合格'),
    )
    status = models.SmallIntegerField(verbose_name='作業狀態', choices=status_choice, default=0)

    submit_num = models.SmallIntegerField(verbose_name='提交次數', default=0)
    correct_date = models.DateTimeField('備註日期', blank=True, null=True)
    note = models.TextField(blank=True, null=True)
    date = models.DateTimeField("作業提交日期", auto_now_add=True)

    check_date = models.DateTimeField("批改日期", null=True, blank=True)

    update_time = models.DateTimeField(auto_now=True, verbose_name="提交日期")

    # homework_path = models.CharField(verbose_name='作業路徑', max_length=256,blank=True,null=True) 作業路徑可以動態拿到,沒必要存

    reward_choice = (
        (0, '新提交'),
        (1, '按時提交'),
        (2, '未按時提交'),
        (3, '成績已獎勵'),
        (4, '成績已處罰'),
        (5, '未作按時檢測'),
    )
    reward_status = models.SmallIntegerField(verbose_name='作業記錄獎懲狀態', default=0)

    def __str__(self):
        return "%s %s" % (self.homework, self.student)

    class Meta:
        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)

    def __str__(self):
        return "%s --%s --%s" % (self.enrolled_degree_course, self.record, self.date)

# ######################## 深科技 ########################
class ArticleSource(models.Model):
    """文章來源"""
    name = models.CharField(max_length=64, unique=True)

    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="收藏數")

    # tags = models.ManyToManyField("Tags", blank=True, verbose_name="標籤")
    date = models.DateTimeField(auto_now_add=True, verbose_name="建立日期")

    position_choices = ((0, '資訊流'), (1, 'banner大圖'), (2, 'banner小圖'))
    position = models.SmallIntegerField(choices=position_choices, default=0, verbose_name="位置")

    # comment = GenericRelation("Comment")  # 用於GenericForeignKey反向查詢, 不會生成表字段,切勿刪除,如有疑問請聯絡老村長

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


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')
    # FK(Article)
    article = models.ForeignKey('Article')
    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 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")  # 使訂單購買後支援 課程評價

    # order = models.ForeignKey("Order",blank=True,null=True)

    def __str__(self):
        return "%s:%s" % (self.account, self.course)

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)

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

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)

    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="使用時間")
    order = models.ForeignKey("Order", blank=True, null=True, verbose_name="關聯訂單")  # 一個訂單可以有多個優惠券

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="訂單取消時間")

    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:
        # unique_together = ("order", 'course')
        unique_together = ("order", 'content_type', 'object_id')

class TransactionRecord(models.Model):
    """貝里交易紀錄"""
    account = models.ForeignKey("Account")
    amount = models.Int