(项目)在线教育平台(九)
十一、课程章节功能
1、前端页面配置
将课程章节页面course-video.html拷贝到templates目录下。
继承base.html页面,重写需要block的地方:
2、课程章节接口
1 class CourseLessonView(View): 2 """课程章节""" 3 def get(self, request, course_id): 4 # 根据前端传递的课程id找到对应的课程 5 course = Course.objects.get(id=int(course_id)) 6 7 return render(request, 'course-video.html', { 8 'course': course 9 })
配置url:
1 from .views import CourseLessonView 2 3 urlpatterns = [ 4 re_path('lesson/(?P<course_id>\d+)/', CourseLessonView.as_view(), name='course_lesson'), # 课程章节 5 ]
然后在课程详情页面修改点击开始学习后进入课程章节页面的url:
修改课程章节页面面包屑中的url跳转:
在章节中有视频信息,需要给视频信息添加一个访问地址字段,在视频的model中添加视频访问地址的字段:
1 class Video(models.Model): 2 """视频""" 3 lesson = models.ForeignKey(Lesson, verbose_name='章节', on_delete=models.CASCADE) 4 name = models.CharField('视频名', max_length=100) 5 url = models.CharField('访问地址', default='', max_length=200) 6 add_time = models.DateTimeField('添加时间', default=datetime.now) 7 8 class Meta: 9 verbose_name = '视频' 10 verbose_name_plural = verbose_name
迁移数据库。
然后在后台中添加章节和视频的数据。
在课程章节页面需要有课程的所有章节、以及视频,所以需要在课程的model中获取所有的章节和章节的model中获取所有的视频以及视频的model中添加一个学习时长字段:
1 class Course(models.Model): 2 """课程""" 3 DEGREE_CHOICES = ( 4 ('cj', '初级'), 5 ('zj', '中级'), 6 ('gj', '高级') 7 ) 8 9 name = models.CharField('课程名', max_length=50) 10 desc = models.CharField('课程描述', max_length=300) 11 detail = models.TextField('课程详情') 12 degree = models.CharField('课程难度', choices=DEGREE_CHOICES, max_length=2) 13 learn_times = models.IntegerField('学习时长(分钟数)', default=0) 14 students = models.IntegerField('学习人数', default=0) 15 fav_nums = models.IntegerField('收藏人数', default=0) 16 click_nums = models.IntegerField('点击数', default=0) 17 image = models.ImageField('封面图', upload_to='courses/%Y/%m', max_length=100) 18 course_org = models.ForeignKey(CourseOrg, verbose_name='所属机构', on_delete=models.CASCADE, null=True, blank=True) 19 category = models.CharField('课程类别', max_length=20, default='') 20 tag = models.CharField('标签', max_length=10, default='') 21 add_time = models.DateTimeField('添加时间', default=datetime.now) 22 23 class Meta: 24 verbose_name = '课程' 25 verbose_name_plural = verbose_name 26 27 # 获取章节数 28 def get_zj_nums(self): 29 return self.lesson_set.all().count() 30 31 # 获取学习用户 32 def get_learn_users(self): 33 return self.usercourse_set.all()[:5] 34 35 # 获取章节 36 def get_course_lesson(self): 37 return self.lesson_set.all() 38 39 def __str__(self): 40 return self.name 41 42 43 class Lesson(models.Model): 44 """章节""" 45 course = models.ForeignKey(Course, verbose_name='课程', on_delete=models.CASCADE) 46 name = models.CharField('章节名', max_length=100) 47 add_time = models.DateTimeField('添加时间', default=datetime.now) 48 49 class Meta: 50 verbose_name = '章节' 51 verbose_name_plural = verbose_name 52 53 # 获取所有的视频 54 def get_lesson_video(self): 55 return self.video_set.all() 56 57 def __str__(self): 58 return '《{}》课程的章节:{}'.format(self.course.name, self.name) 59 60 61 class Video(models.Model): 62 """视频""" 63 lesson = models.ForeignKey(Lesson, verbose_name='章节', on_delete=models.CASCADE) 64 name = models.CharField('视频名', max_length=100) 65 url = models.CharField('访问地址', default='', max_length=200) 66 learn_times = models.IntegerField('学习时长(分钟数)', default=0) 67 add_time = models.DateTimeField('添加时间', default=datetime.now) 68 69 class Meta: 70 verbose_name = '视频' 71 verbose_name_plural = verbose_name
然后迁移数据库。
修改课程章节页面中显示课程章节及视频的代码:
刷新之后即可看到后台添加的视频章节及视频信息。
在课程章节页面右侧有资源下载,首先在后台添加资源文件,然后完善课程章节接口获取资源文件的逻辑:
1 class CourseLessonView(View): 2 """课程章节""" 3 def get(self, request, course_id): 4 # 根据前端传递的课程id找到对应的课程 5 course = Course.objects.get(id=int(course_id)) 6 7 # 获取所有的资源文件 8 all_resources = CourseResourse.objects.filter(course=course) 9 10 return render(request, 'course-video.html', { 11 'course': course, 12 'all_resources': all_resources 13 })
修改课程章节页面中显示资源下载的代码:
然后修改章节上面显示课程信息的相关代码:
在资源下载下面有讲师提示,需要在课程的model中添加教师的外键,还有课程须知以及老师告诉你的两个字段:
1 class Course(models.Model): 2 """课程""" 3 DEGREE_CHOICES = ( 4 ('cj', '初级'), 5 ('zj', '中级'), 6 ('gj', '高级') 7 ) 8 9 name = models.CharField('课程名', max_length=50) 10 desc = models.CharField('课程描述', max_length=300) 11 detail = models.TextField('课程详情') 12 degree = models.CharField('课程难度', choices=DEGREE_CHOICES, max_length=2) 13 learn_times = models.IntegerField('学习时长(分钟数)', default=0) 14 students = models.IntegerField('学习人数', default=0) 15 fav_nums = models.IntegerField('收藏人数', default=0) 16 click_nums = models.IntegerField('点击数', default=0) 17 image = models.ImageField('封面图', upload_to='courses/%Y/%m', max_length=100) 18 course_org = models.ForeignKey(CourseOrg, verbose_name='所属机构', on_delete=models.CASCADE, null=True, blank=True) 19 category = models.CharField('课程类别', max_length=20, default='') 20 tag = models.CharField('标签', max_length=10, default='') 21 teacher = models.ForeignKey(Teacher, verbose_name='机构讲师', on_delete=models.CASCADE, null=True, blank=True) 22 courseneed_know = models.CharField('课程须知', max_length=300, default='') 23 teacher_tellyou = models.CharField('老师告诉你', max_length=300, default='') 24 add_time = models.DateTimeField('添加时间', default=datetime.now) 25 26 class Meta: 27 verbose_name = '课程' 28 verbose_name_plural = verbose_name 29 30 # 获取章节数 31 def get_zj_nums(self): 32 return self.lesson_set.all().count() 33 34 # 获取学习用户 35 def get_learn_users(self): 36 return self.usercourse_set.all()[:5] 37 38 # 获取章节 39 def get_course_lesson(self): 40 return self.lesson_set.all() 41 42 def __str__(self): 43 return self.name
迁移数据库,然后在后台的课程中添加讲师,修改课程章节页面中显示讲师提示的代码:
3、相关课程推荐
在课程章节页面的右侧最下方有相关课程的推荐,首先在课程章节接口中添加相关课程推荐的逻辑:
1 class CourseLessonView(View): 2 """课程章节""" 3 def get(self, request, course_id): 4 # 根据前端传递的课程id找到对应的课程 5 course = Course.objects.get(id=int(course_id)) 6 7 # 获取所有的资源文件 8 all_resources = CourseResourse.objects.filter(course=course) 9 10 # 相关课程推荐 11 # 找到学习这门课程的所有用户 12 user_courses = UserCourse.objects.filter(course=course) 13 # 找到学习这门课的所有用户的id 14 user_ids = [user_course.user_id for user_course in user_courses] 15 # 通过所有用户的id,找到所有用户学习过的所有课程 16 all_user_courses = UserCourse.objects.filter(user_id__in=user_ids) 17 # 取出所有课程id 18 course_ids = [all_user_course.course_id for all_user_course in all_user_courses] 19 # 通过所有课程id找到所有的课程,按点击量区5个 20 relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_nums')[:5] 21 22 23 return render(request, 'course-video.html', { 24 'course': course, 25 'all_resources': all_resources, 26 'relate_courses': relate_courses 27 })
然后修改课程章节页面中显示相关课程推荐的代码:
十二、课程评论功能
1、前端页面配置
将课程评论页面course-comment.html拷贝到templates目录下。
继承base.html页面,重写需要block的地方:
2、课程评论接口
1 class CourseCommentsView(View): 2 """课程评论""" 3 def get(self, request, course_id): 4 course = Course.objects.get(id=int(course_id)) 5 all_resources = CourseResourse.objects.filter(course=course) 6 7 # 获取所有的评论信息 8 all_comments = CourseComments.objects.all() 9 10 return render(request, 'course-comment.html', { 11 'course': course, 12 'all_resources': all_resources, 13 'all_comments': all_comments 14 })
配置url:
1 from .views import CourseCommentsView 2 3 urlpatterns = [ 4 re_path('comment/(?P<course_id>\d+)/', CourseCommentsView.as_view(), name='course_comment'), # 课程评论 5 ]
然后修改课程章节页面跳转到课程评论页面的url:
课程评论页面右侧的课程资源下载和讲师提示的代码和课程章节页面的代码一样,只需要拷贝过来即可。
添加评论功能需要重新写一个添加评论的接口:
1 class AddCommentsView(View): 2 """添加评论""" 3 def post(self, request): 4 # 未登录不能评论,返回json数据由前端返回登录页面 5 if not request.user.is_authenticated: 6 return HttpResponse('{"status": "fail", "msg": "用户未登录"}', content_type='application/json') 7 8 # 从request中获取课程的id和评论信息 9 course_id = request.POST.get('course_id', 0) 10 comments = request.POST.get('comments', '') 11 12 # 将评论信息保存到数据库 13 if int(course_id)>0 and comments: 14 course_comments = CourseComments() 15 course = Course.objects.get(id=int(course_id)) 16 course_comments.course = course 17 course_comments.comments = comments 18 course_comments.user = request.user 19 course_comments.save() 20 21 return HttpResponse('{"status": "success", "msg": "评论成功"}', content_type='application/json') 22 else: 23 return HttpResponse('{"status": "fail", "msg": "评论失败"}', content_type='application/json')
配置url:
1 from .views import AddCommentsView 2 3 urlpatterns = [ 4 path('add_comment/', AddCommentsView.as_view(), name='add_comment'), # 添加评论 5 ]
评论信息是通过script代码通过ajax异步提交的,需要在课程评论页面的最后加上script代码:
1 {% block custom_js %} 2 <script type="text/javascript"> 3 //添加评论 4 $('#js-pl-submit').on('click', function(){ 5 var comments = $("#js-pl-textarea").val() 6 if(comments == ""){ 7 alert("评论不能为空") 8 return 9 } 10 $.ajax({ 11 cache: false, 12 type: "POST", 13 url:"{% url 'course:add_comment' %}", 14 data:{'course_id':{{ course.id }}, 'comments':comments}, 15 async: true, 16 beforeSend:function(xhr, settings){ 17 xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}"); 18 }, 19 success: function(data) { 20 if(data.status == 'fail'){ 21 if(data.msg == '用户未登录'){ 22 window.location.href="/login/"; 23 }else{ 24 alert(data.msg) 25 } 26 27 }else if(data.status == 'success'){ 28 window.location.reload();//刷新当前页面. 29 } 30 }, 31 }); 32 }); 33 34 </script> 35 {% endblock %}
然后修改课程评论页面中显示评论信息的代码:
现在就可以提交评论信息了,并且是异步提交,不会刷新页面。
3、课程与用户相关联
在课程详情页面点击开始学习之后,应该将课程与用户关联起来。
如果点击开始学习,需要验证用户是否是登录状态,如果没有登录需要让用户先登录,将验证登录这个功能写成一个类,哪个接口需要验证登录继承这个类即可。在utils目录下创建文件mixin_utils.py(将最基本的类都放在这个文件中):
1 from django.utils.decorators import method_decorator 2 from django.contrib.auth.decorators import login_required 3 4 5 class LoginRequiredMixin(object): 6 """验证登录基类""" 7 8 @method_decorator(login_required(login_url='/login/')) 9 def dispatch(self, request, *args, **kwargs): 10 return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
然后让课程章节接口和课程评论接口都继承这个基类,继承之后,没有登录的用户点击开始学习,会跳转到登录页面:
1 class CourseLessonView(LoginRequiredMixin, View): 2 """课程章节""" 3 def get(self, request, course_id): 4 # 根据前端传递的课程id找到对应的课程 5 course = Course.objects.get(id=int(course_id)) 6 7 # 获取所有的资源文件 8 all_resources = CourseResourse.objects.filter(course=course) 9 10 # 相关课程推荐 11 # 找到学习这门课程的所有用户 12 user_courses = UserCourse.objects.filter(course=course) 13 # 找到学习这门课的所有用户的id 14 user_ids = [user_course.user_id for user_course in user_courses] 15 # 通过所有用户的id,找到所有用户学习过的所有课程 16 all_user_courses = UserCourse.objects.filter(user_id__in=user_ids) 17 # 取出所有课程id 18 course_ids = [all_user_course.course_id for all_user_course in all_user_courses] 19 # 通过所有课程id找到所有的课程,按点击量区5个 20 relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_nums')[:5] 21 22 23 return render(request, 'course-video.html', { 24 'course': course, 25 'all_resources': all_resources, 26 'relate_courses': relate_courses 27 }) 28 29 30 class CourseCommentsView(LoginRequiredMixin, View): 31 """课程评论""" 32 def get(self, request, course_id): 33 course = Course.objects.get(id=int(course_id)) 34 all_resources = CourseResourse.objects.filter(course=course) 35 36 # 获取所有的评论信息 37 all_comments = CourseComments.objects.all() 38 39 return render(request, 'course-comment.html', { 40 'course': course, 41 'all_resources': all_resources, 42 'all_comments': all_comments 43 })
然后将用户和课程关联起来,完善课程章节接口:
1 class CourseLessonView(LoginRequiredMixin, View): 2 """课程章节""" 3 def get(self, request, course_id): 4 # 根据前端传递的课程id找到对应的课程 5 course = Course.objects.get(id=int(course_id)) 6 7 # 课程和用户关联,先判断用户是否已经学习过该课程 8 user_courses = UserCourse.objects.filter(user=request.user, course=course) 9 if not user_courses: 10 # 没有学习过该课程,关联 11 user_course = UserCourse(user=request.user, course=course) 12 user_course.save() 13 14 # 获取所有的资源文件 15 all_resources = CourseResourse.objects.filter(course=course) 16 17 # 相关课程推荐 18 # 找到学习这门课程的所有用户 19 user_courses = UserCourse.objects.filter(course=course) 20 # 找到学习这门课的所有用户的id 21 user_ids = [user_course.user_id for user_course in user_courses] 22 # 通过所有用户的id,找到所有用户学习过的所有课程 23 all_user_courses = UserCourse.objects.filter(user_id__in=user_ids) 24 # 取出所有课程id 25 course_ids = [all_user_course.course_id for all_user_course in all_user_courses] 26 # 通过所有课程id找到所有的课程,按点击量区5个 27 relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_nums')[:5] 28 29 return render(request, 'course-video.html', { 30 'course': course, 31 'all_resources': all_resources, 32 'relate_courses': relate_courses 33 })
现在点击开始学习之后,在页面的右下角就有学过的其他课程了。
十三、视频播放功能
视频播放器我们使用video.js,它是一款基于HTML5的网络视频播放器。它支持HTML5和Flash视频,以及YouTube和Vimeo(通过插件)。支持在桌面和移动设备上播放视频。
1、前端页面配置
将视频播放页面course-play.html拷贝到templates目录下。
继承base.html页面,重写需要block的地方:
custom_css中的代码如下:
然后下载video-js.min.css和video.min.js分别放到css和js目录下。
2、视频播放接口
1 class VideoPlayView(LoginRequiredMixin, View): 2 """视频播放""" 3 def get(self, request, video_id): 4 # 根据前端的视频id找到对应的视频 5 video = Video.objects.get(id=int(video_id)) 6 7 # 通过外键先找到章节在找到对应的课程,然后学习人数加1 8 course = video.lesson.course 9 course.students += 1 10 course.save() 11 12 # 查询用户是否已经学习了该课程,如果没有将课程和用户关联 13 user_courses = UserCourse.objects.filter(user=request.user, course=course) 14 if not user_courses: 15 user_course = UserCourse(user=request.user, course=course) 16 user_course.save() 17 18 # 相关课程推荐 19 user_courses = UserCourse.objects.filter(course=course) 20 user_ids = [user_course.user_id for user_course in user_courses] 21 all_user_courses = UserCourse.objects.filter(user_id__in=user_ids) 22 course_ids = [all_user_course.course_id for all_user_course in all_user_courses] 23 relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_nums')[:5] 24 25 # 获取所有资源文件 26 all_resources = CourseResourse.objects.filter(course=course) 27 28 return render(request, 'course-play.html', { 29 'course': course, 30 'all_resources': all_resources, 31 'relate_courses': relate_courses, 32 'video': video 33 })
配置url:
1 from .views import VideoPlayView 2 3 urlpatterns = [ 4 re_path('video/(?P<video_id>\d+)/', VideoPlayView.as_view(), name='video_play'), # 视频播放 5 ]
然后修改课程章节页面章节中跳转到视频页面的url:
最后将视频播放页面中显示代码进行修改:
1 {% extends 'base.html' %} 2 3 {% load staticfiles %} 4 5 {% block titile %} 6 {{ video.name }} -- 知能网 7 {% endblock %} 8 9 {% block custom_bread %} 10 <section> 11 <div class="wp"> 12 <div class="crumbs"> 13 <ul> 14 <li><a href="{% url 'index' %}">首页</a>></li> 15 <li><a href="{% url 'course:course_list' %}">公开课程</a>></li> 16 <li><a href="{% url 'course:course_detail' course.id %}">{{ course.name }}</a>></li> 17 <li>{{ video.name }}</li> 18 </ul> 19 </div> 20 </div> 21 </section> 22 {% endblock %} 23 24 {% block custom_css %} 25 <link rel="stylesheet" type="text/css" href="{% static 'css/video-js.min.css' %}"> 26 <link rel="stylesheet" type="text/css" href="{% static 'css/muke/base.css' %}"/> 27 <link rel="stylesheet" type="text/css" href="{% static 'css/muke/common-less.css' %}"/> 28 <link rel="stylesheet" type="text/css" href="{% static 'css/muke/course/learn-less.css' %}"/> 29 <link rel="stylesheet" type="text/css" href="{% static 'css/mooc.css' %}"/> 30 <link rel="stylesheet" type="text/css" href="{% static 'css/muke/course/common-less.css' %}"> 31 <style> 32 .video-js .vjs-big-play-button { 33 top: 50%; 34 left: 50%; 35 } 36 </style> 37 {% endblock %} 38 39 {% block custom_js %} 40 <script src="{% static 'js/video.min.js' %}" type="text/javascript"></script> 41 {% endblock %} 42 43 {% block content %} 44 <div id="main"> 45 {# video.js视频播放器#} 46 <div style="width:1200px;height: 650px; margin-left:100px"> 47 <video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="1200" 48 poster="http://video-js.zencoder.com/oceans-clip.png" 49 data-setup="{}" > 50 <source src="{{ video.url }}" type="video/mp4"> 51 </video> 52 </div> 53 54 <div class="course-info-main clearfix w has-progress"> 55 <div class="info-bar clearfix"> 56 <div class="content-wrap clearfix"> 57 <div class="content"> 58 <div class="mod-tab-menu"> 59 <ul class="course-menu clearfix"> 60 <li><a class="ui-tabs-active active" id="learnOn" 61 href="{% url 'course:course_lesson' course.id %}"><span>章节</span></a></li> 62 <li><a id="commentOn" class="" 63 href="{% url 'course:course_comment' course.id %}"><span>评论</span></a></li> 64 </ul> 65 </div> 66 <div id="notice" class="clearfix"> 67 <div class="l"><strong>课程公告:</strong> <a 68 href="javascript:void(0)">Spring的文档以及相关的jar文件已上传</a></div> 69 </div> 70 71 <div class="mod-chapters"> 72 {% for lesson in course.lesson_set.get_queryset %} 73 <div class="chapter chapter-active"> 74 <h3> 75 <strong><i class="state-expand"></i>{{ lesson.name }}</strong> 76 </h3> 77 <ul class="video"> 78 79 {% for video in lesson.video_set.get_queryset %} 80 <li> 81 <a target="_blank" href='{% url 'course:video_play' video.id %}' 82 class="J-media-item studyvideo">{{ video.name }} 83 ({{ video.learn_times }}) 84 <i class="study-state"></i> 85 </a> 86 </li> 87 {% endfor %} 88 89 </ul> 90 </div> 91 {% endfor %} 92 </div> 93 94 </div> 95 <div class="aside r"> 96 <div class="bd"> 97 98 <div class="box mb40"> 99 <h4>资料下载</h4> 100 <ul class="downlist"> 101 {% for course_resource in course.courseresource_set.get_queryset %} 102 <li> 103 <span><i 104 class="aui-iconfont aui-icon-file"></i> {{ course_resource.name }}</span> 105 <a href="{{ MEDIA_URL }}{{ course_resource.download }}" class="downcode" 106 target="_blank" download="" data-id="274" title="">下载</a> 107 </li> 108 {% endfor %} 109 </ul> 110 </div> 111 <div class="box mb40"> 112 <h4>讲师提示</h4> 113 <div class="teacher-info"> 114 <a href="{% url 'org:org_teacher' course.teacher.id %}" target="_blank"> 115 <img src='{{ MEDIA_URL }}{{ course.teacher.image }}' width='80' height='80'/> 116 </a> 117 <span class="tit"> 118 <a href="{% url 'org:org_teacher' course.teacher.id %}" target="_blank">{{ course.teacher.name }}</a> 119 </span> 120 <span class="job">{{ course.teacher.work_position }}</span> 121 </div> 122 <div class="course-info-tip"> 123 <dl class="first"> 124 <dt>课程须知</dt> 125 <dd class="autowrap">{{ course.you_need_know }}</dd> 126 </dl> 127 <dl> 128 <dt>老师告诉你能学到什么?</dt> 129 <dd class="autowrap">{{ course.teacher_tell }}</dd> 130 </dl> 131 </div> 132 </div> 133 134 135 <div class="cp-other-learned js-comp-tabs"> 136 <div class="cp-header clearfix"> 137 <h2 class="cp-tit l">该课的同学还学过</h2> 138 </div> 139 <div class="cp-body"> 140 <div class="cp-tab-pannel js-comp-tab-pannel" data-pannel="course" 141 style="display: block"> 142 <!-- img 200 x 112 --> 143 <ul class="other-list"> 144 145 {% for relate_course in relate_courses %} 146 <li class="curr"> 147 <a href="{% url 'course:course_detail' relate_course.id %}" 148 target="_blank"> 149 <img src="{{ MEDIA_URL }}{{ relate_course.image }}" 150 alt="{{ relate_course.name }}"> 151 <span class="name autowrap">{{ relate_course.name }}</span> 152 </a> 153 </li> 154 {% endfor %} 155 156 </ul> 157 </div> 158 <div class="cp-tab-pannel js-comp-tab-pannel" data-pannel="plan"> 159 <ul class="other-list"> 160 161 </ul> 162 </div> 163 </div> 164 </div> 165 166 </div> 167 </div> 168 </div> 169 <div class="clear"></div> 170 171 </div> 172 173 </div> 174 </div> 175 {% endblock %}