課程模塊,包括了免費課程以及專題課程兩個方向。前端
主要是課程的展現,點擊課程進入課程詳細頁面。課程詳細頁面展現,課程的概述,課程的價格策略,課程章節,評價以及常見問題。數據庫
在項目中建立課程模塊APP——Course。設計表結構以下所示:django
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文件初始內容設計如上所示。後端
在完成數據庫遷移和管理員建立後。經過admin組件在數據庫添加數據示例以下所示:api
表結構基本定下來後,能夠根據業務場景看須要哪些接口。frontend
對於課程這個模塊,全部的功能都是展現,基於數據展現,一般稱爲數據接口。ide
課程頁面:課程全部分類的接口、展現課程的接口。post
課程詳情頁面:點擊課程要進入課程詳情頁面,即詳情頁面的數據接口。學習
詳情頁面下子路由對應子組件數據接口:課程章節課時、課程評論、課程常見問題。ui
能夠看到,上述接口都是讀取數據庫、序列化數據、返回。所以主要是使用DRF的序列化組件實現。
編寫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中實現增刪改查的功能。
編寫LuffyCity/Course/urls.py文件以下所示:
from django.urls import path from .views import CategoryView urlpatterns = [ path('category', CategoryView.as_view()), ]
建立新文件:LuffyCity/Course/serializers.py,在該文件中執行序列化操做。
from rest_framework import serializers from . import models class CategorySerializer(serializers.ModelSerializer): class Meta: # 配置元信息 model = models.Category # 課程分類表 fields = "__all__"
編寫課程分類視圖以下所示:
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訪問接口以下所示:
在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()) # 查看課程 ]
在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"] # 提取部分字段
在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)
靜態資源(多媒體資源)配置。
# 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"。
修改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目錄以下所示:
訪問接口以下所示:
所以咱們上傳的圖片,數據庫保存的是路徑地址,前端向後端的media路徑發送請求來拿到圖片、視頻等資源。