zoukankan      html  css  js  c++  java
  • 项目课程模块

      课程模块,包括了免费课程以及专题课程两个方向。

      主要是课程的展示,点击课程进入课程详细页面。课程详细页面展示,课程的概述,课程的价格策略,课程章节,评价以及常见问题。

    一、根据功能设计表结构

    1、设计表结构

      在项目中创建课程模块APP——Course。设计表结构如下所示:

    from django.db import models
    # Create your models here.
    
    from django.db import models
    from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
    from django.contrib.contenttypes.models import ContentType
    
    # Create your models here.
    __all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter",
               "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"]
    
    
    class Category(models.Model):
        """课程分类表"""
        title = models.CharField(max_length=32, unique=True, verbose_name="课程的分类")
    
        def __str__(self):
            return self.title
    
        class Meta:    # 元信息配置
            verbose_name = "01-课程分类表"
            db_table = verbose_name    # 数据库表名(正式上线需要去除,不使用中文表名)
            verbose_name_plural = verbose_name
    
    
    class Course(models.Model):
        """课程表"""
        title = models.CharField(max_length=128, unique=True, verbose_name="课程的名称")
        course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='课程的图片')    # 上传图片路径,以年月划分文件夹
        category = models.ForeignKey(to="Category", verbose_name="课程的分类", on_delete=None)   # 课程分类和课程是一对多的关系
    
        COURSE_TYPE_CHOICES = ((0, "付费"), (1, "vip专享"), (2, "学位课程"))
        course_type = models.SmallIntegerField(choices=COURSE_TYPE_CHOICES)     # 课程类型
        degree_course = models.ForeignKey(to="DegreeCourse", blank=True, null=True, help_text="如果是学位课程,必须关联学位表", on_delete=None)
    
        brief = models.CharField(verbose_name="课程简介", max_length=1024)
        level_choices = ((0, '初级'), (1, '中级'), (2, '高级'))
        level = models.SmallIntegerField(choices=level_choices, default=1)      # 难度等级
    
        status_choices = ((0, '上线'), (1, '下线'), (2, '预上线'))
        status = models.SmallIntegerField(choices=status_choices, default=0)    # 课程状态,只有上线和预上线的课程才能购买
        pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True)
    
        order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排")  # 课程排序
        # 学习人数保存在这里,提升展示效率,降低主站访问压力,减少访问数据库
        study_num = models.IntegerField(verbose_name="学习人数", help_text="只要有人买课程,订单表加入数据的同时给这个字段+1")
    
        # order_details = GenericRelation("OrderDetail", related_query_name="course")
        # coupon = GenericRelation("Coupon")
        # GenericRelation只用于反向查询不生成字段
        price_policy = GenericRelation("PricePolicy")     # 价格策略
        often_ask_questions = GenericRelation("OftenAskedQuestion")    # 常见问题
        course_comments = GenericRelation("Comment")      # 评论
    
        def save(self, *args, **kwargs):
            if self.course_type == 2:    # 判断是否是学位课程
                if not self.degree_course:
                    raise ValueError("学位课必须关联学位课程表")
            super(Course, self).save(*args, **kwargs)   # 执行父类的save方法
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = "02-课程表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
    
    
    class CourseDetail(models.Model):
        """课程详细表"""
        course = models.OneToOneField(to="Course", on_delete=None)    # 课程表与课程详情表一对一关系
        hours = models.IntegerField(verbose_name="课时", default=7)    # 课时
        course_slogan = models.CharField(max_length=125, blank=True, null=True, verbose_name="课程口号")
        video_brief_link = models.CharField(max_length=255, blank=True, null=True)    # 简介视频链接
        summary = models.TextField(max_length=2048, verbose_name="课程概述")
        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 self.course.title
    
        class Meta:
            verbose_name = "03-课程详细表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
    
    
    class Teacher(models.Model):
        """讲师表"""
        name = models.CharField(max_length=32, verbose_name="讲师名字")
        brief = models.TextField(max_length=1024, verbose_name="讲师介绍")
    
        def __str__(self):
            return self.name
    
        class Meta:
            verbose_name = "04-教师表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
    
    
    class DegreeCourse(models.Model):
        """学位课程表:字段大体跟课程表相同,哪些不同根据业务逻辑去区分"""
        title = models.CharField(max_length=32, verbose_name="学位课程名字")
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = "05-学位课程表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
    
    
    class CourseChapter(models.Model):
        """课程章节表"""
        course = models.ForeignKey(to="Course", related_name="course_chapters", on_delete=None)   # 课程与章节:一对多关系
        chapter = models.SmallIntegerField(default=1, verbose_name="第几章")    # 数字类型,章节排序
        title = models.CharField(max_length=32, verbose_name="课程章节名称")
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = "06-课程章节表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
            unique_together = ("course", "chapter")   # 联合唯一,课程下的章节应该是唯一的(比如:第2章第2节)
    
    
    class CourseSection(models.Model):
        """课时表"""
        chapter = models.ForeignKey(to="CourseChapter", related_name="course_sections", on_delete=None) # 章节与课时:一对多关系
        title = models.CharField(max_length=32, verbose_name="课时")   # 课时名称:比如:认证组件介绍
        section_order = models.SmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时")
        free_trail = models.BooleanField("是否可试看", default=False)   # 是否可试看
        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")   # 课时链接
    
        def course_chapter(self):   # 章节
            return self.chapter.chapter
    
        def course_name(self):      # 课程名
            return self.chapter.course.title
    
        def __str__(self):
            return "%s-%s" % (self.chapter, self.title)
    
        class Meta:
            verbose_name = "07-课程课时表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
            unique_together = ('chapter', 'section_link')      # 每一个课时的章节和链接,联合唯一
    
    
    class PricePolicy(models.Model):
        """价格策略表,ContentType来实现价格策略,避免出现多个ForeignKey"""
        content_type = models.ForeignKey(ContentType, on_delete=None)  # 价格策略与contentType表建立外键关系,同时关联course表和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个月'), (120, '4个月'), (180, '6个月'), (210, '12个月'), (540, '18个月'),
            (720, '24个月'), (722, '24个月'), (723, '24个月')
        )
        valid_period = models.SmallIntegerField(choices=valid_period_choices)   # 周期
        price = models.FloatField()      # 价格,float格式
    
        def __str__(self):
            return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)
    
        class Meta:
            verbose_name = "08-价格策略表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
            unique_together = ("content_type", 'object_id', "valid_period")     # 三者联合唯一
    
    
    class OftenAskedQuestion(models.Model):
        """常见问题"""
        content_type = models.ForeignKey(ContentType, on_delete=None)   # 关联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:
            verbose_name = "09-常见问题表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
            unique_together = ('content_type', 'object_id', 'question')
    
    
    class Comment(models.Model):
        """通用的评论表"""
        content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None)
        object_id = models.PositiveIntegerField(blank=True, null=True)
        content_object = GenericForeignKey('content_type', 'object_id')
    
        content = models.TextField(max_length=1024, verbose_name="评论内容")
        account = models.ForeignKey("Account", verbose_name="会员名", on_delete=None)
        date = models.DateTimeField(auto_now_add=True)     # 评论的日期,记录添加即添加时间
    
        def __str__(self):
            return self.content
    
        class Meta:
            verbose_name = "10-评价表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
    
    
    class Account(models.Model):
        """用户表"""
        username = models.CharField(max_length=32, verbose_name="用户姓名")
        pwd = models.CharField(max_length=32, verbose_name="密文密码")
        # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png',
        #                             verbose_name="个人头像")
    
        def __str__(self):
            return self.username
    
        class Meta:
            verbose_name = "11-用户表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
    
    
    class CourseOutline(models.Model):
        """课程大纲"""
        course_detail = models.ForeignKey(to="CourseDetail", related_name="course_outline", on_delete=None)
        title = models.CharField(max_length=128)      # 课程大纲标题(行业认知、基础知识等)
        order = models.PositiveSmallIntegerField(default=1)     # 课程大纲排序
        # 前端显示顺序
    
        content = models.TextField("内容", max_length=2048)   # TextField可以保存并展示空格、回车
    
        def __str__(self):
            return "%s" % self.title
    
        class Meta:
            verbose_name = "12-课程大纲表"
            db_table = verbose_name
            verbose_name_plural = verbose_name
            unique_together = ('course_detail', 'title')   # 联合唯一:一个课程详情下的大纲标题只能有一个

      LuffyCity/Course/models.py文件初始内容设计如上所示。

    2、添加表数据

      在完成数据库迁移和管理员创建后。通过admin组件在数据库添加数据示例如下所示:

      

    二、接口编写——课程分类接口

      表结构基本定下来后,可以根据业务场景看需要哪些接口。

      对于课程这个模块,所有的功能都是展示,基于数据展示,通常称为数据接口

    1、梳理需要哪些接口

      课程页面:课程所有分类的接口、展示课程的接口。

      课程详情页面:点击课程要进入课程详情页面,即详情页面的数据接口。

      详情页面下子路由对应子组件数据接口:课程章节课时、课程评论、课程常见问题。

      可以看到,上述接口都是读取数据库、序列化数据、返回。因此主要是使用DRF的序列化组件实现。

    2、注册数据模型到 admin

      编写LuffyCity/Course/admin.py文件如下所示:

    from django.contrib import admin
    
    # Register your models here.
    from . import models
    
    for table in models.__all__:      # __all__变量保存所有的表名
        admin.site.register(getattr(models, table))      # 用反射在models中找到每一个表并注册进来

      由于models定义了__all__属性,以列表格式保存所有表名。

      因此可以使用上述方法将Model中所有类注册,即可在Admin中实现增删改查的功能。

    3、课程路由配置

      编写LuffyCity/Course/urls.py文件如下所示:

    from django.urls import path
    from .views import CategoryView
    
    
    urlpatterns = [
        path('category', CategoryView.as_view()),
    ]

    4、序列化数据

      创建新文件:LuffyCity/Course/serializers.py,在该文件中执行序列化操作。

    from rest_framework import serializers
    from . import models
    
    
    class CategorySerializer(serializers.ModelSerializer):
        class Meta:   # 配置元信息
            model = models.Category    # 课程分类表
            fields = "__all__"

    5、课程视图编写

      编写课程分类视图如下所示:

    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from . import models    # 引入所有的models
    from .serializers import CategorySerializer    # 引入序列化器
    # Create your views here.
    
    
    class CategoryView(APIView):
        def get(self, request):
            # 通过ORM操作获取所有分类数据
            queryset = models.Category.objects.all()
            # 利用DRF序列化器去序列化数据
            ser_obj = CategorySerializer(queryset, many=True)
            # 返回
            return Response(ser_obj.data)

      执行程序后,通过postman访问接口如下所示:

      

    三、获取课程接口

    1、添加课程url

      在LuffyCity/Course/urls.py中添加查看课程内容:

    from django.urls import path
    from .views import CategoryView, CourseView
    
    
    urlpatterns = [
        path('category', CategoryView.as_view()),   # 课程分类
        path('list', CourseView.as_view())    # 查看课程
    ]

    2、添加课程序列号

      在LuffyCity/Course/serializers.py中添加课程序列化内容:

    from rest_framework import serializers
    from . import models
    
    
    class CategorySerializer(serializers.ModelSerializer):
        class Meta:   # 配置元信息
            model = models.Category    # 课程分类表
            fields = "__all__"
    
    
    class CourseSerializer(serializers.ModelSerializer):
        # level字段获取的是数字,需要处理
        level = serializers.CharField(source="get_level_display")   # 指定资源,执行ORM操作get_level_display,拿到中文展示
        # 根据价格策略获取价格数据
        price = serializers.SerializerMethodField()
    
        def get_price(self, obj):
            # 拿到所有的价格策略中,最便宜的价格
            return obj.price_policy.all().order_by("price").first().price
    
        class Meta:
            model = models.Course
            fields = ["id", "title", "course_img", "brief", "level", "study_num", "price"]   # 提取部分字段

    3、添加课程视图

      在LuffyCity/Course/views.py中添加课程视图内容:

    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from . import models    # 引入所有的models
    from .serializers import CategorySerializer, CourseSerializer   # 引入序列化器
    # Create your views here.
    
    
    class CategoryView(APIView):...
    
    
    class CourseView(APIView):
        def get(self, request):
            # 获取过滤条件中的分类id
            category_id = request.query_params.get("category", 0)   # 默认id为0,即所有课程
            # 分类id作为过滤条件来获取课程
            if category_id == 0:
                queryset = models.Course.objects.all().order_by("order")    # 以order(课程顺序)字段排序
            else:
                # 按分类过滤
                queryset = models.Course.objects.filter(category_id=category_id).all().order_by("order")
            # 序列化课程数据
            ser_obj = CourseSerializer(queryset, many=True)       # 实例化序列化器
            # 返回
            return Response(ser_obj.data)

    四、Django的MEDIA配置

      静态资源(多媒体资源)配置。

    1、settings.py配置

    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/2.0/howto/static-files/
    
    STATIC_URL = '/static/'
    
    # Media配置
    MEDIA_URL = "media/"        # 前端访问图片资源,存的时候要放一个路径
    MEDIA_ROOT = os.path.join(BASE_DIR, "media")

    (1)MEDIA_ROOT

      默认值: '' (空的字符串)

      一个绝对路径, 用于保存媒体文件. 例子: "/home/media/media.lawrence.com/" 。

    (2)MEDIA_URL

      默认值: '' (空的字符串)

      处理媒体服务的URL(媒体文件来自 MEDIA_ROOT). 如: "http://media.lawrence.com"。

    2、urls.py配置media路径

      修改LuffyCity/LuffyCity/urls.py如下所示:

    from django.contrib import admin
    from django.urls import path, include, re_path
    from django.views.static import serve
    from LuffyCity import settings
    
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/course/', include("Course.urls")),
    
        # media路径配置
        re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
    ]

      通过admin组件添加数据,为课程添加图片数据。可以看到项目自动创建media目录如下所示:

      

    3、访问接口查看保存的图片

      访问接口如下所示:

      

      因此我们上传的图片,数据库保存的是路径地址,前端向后端的media路径发送请求来拿到图片、视频等资源。

  • 相关阅读:
    抽象类与接口
    二叉树的镜像
    树的子结构
    合并两个排序的链表
    反转链表
    链表中倒数第k个结点
    调整数组顺序使奇数位于偶数前面
    230. Kth Smallest Element in a BST
    98. Validate Binary Search Tree
    94. Binary Tree Inorder Traversal(二叉树中序遍历)
  • 原文地址:https://www.cnblogs.com/xiugeng/p/11580233.html
Copyright © 2011-2022 走看看