# django_onlineclass **Repository Path**: lihaowen2017/django_onlineclass ## Basic Information - **Project Name**: django_onlineclass - **Description**: Django实现在线视频课堂播放网站 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-02-07 - **Last Updated**: 2021-11-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## Django快速复习及学习及大型在线课程平台项目 ### 配置app两种方式 setting文件中 INSTALLED_APPS 中添加 'message_form.apps.MessageformConfig' # 推荐的配置方式 'message_form' # 直接配包也可以 允许用户上传文件,则在文件结构中建立media 内部自己创建的app,建立apps包,在里面创建自己的应用 外部调用的app,建立extra_apps包,导入的源码三方包,放在这里面。 static文件夹,静态资源文件夹(css, js, images)放入static 默认会创建templates模板文件夹(放入html) #### 如何配置一个html页面显示的步骤 1. 配置url 2. 配置对应是views逻辑 3. 拆分静态文件(css, js, images)放入到static,html放入到templates之下 3-1. 可以放到对应的app下面 3-2. 也可以放入到全局的tempate和static目录之下 4. 配置全局的statiac文件访问路径的配置STATICFILES_DIRS setting文件静态资源配置 ```python STATIC_URL = '/static/' # 静态文件访问前缀 STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] # 静态相对资源路径 ``` #### Django 中使用mysql的两种该方法 在setting文件中修改数据库中会报错 1. 安装pymysql 在__init__文件中 ```python import pymysql # 将mysqldb当pymysql pymysql.install_as_MySQLdb() ``` 2. 安装mysqlclient包 www.lfd.uci.edu/~gohlke/pythonlibs/ 3. 安装mysqlclient包报错解决 安装mysql5.7的源解决: [root@localhost ~]# cd /etc/yum.repos.d/ [root@localhost ~]# rpm -ivh http://repo.mysql.com/mysql57-community-release-el7-8.noarch.rpm yum install mysql-devel 再pip install mysqlclient #### django 前端模板的继承 1. 在需要自定义前端代码的地方使用如下代码包裹 ```html {% block custom_bread %} {% endblock %} {% block title %}首页 - 慕学在线网{% endblock %} ``` 2. 在继承的前端页面中 {% extends 'base.html' %} 自定义处使用相同的变量名包裹 {% block title %}机构列表页-慕课在线网 {% endblock %} 3. 模板中使用静态文件 {% load staticfiles %} href="{% static 'css/reset.css' %}" #### 层级url设计 ```python url(r'^org/', include(('apps.organizations.urls', "organizations"), namespace="org")) ``` 使用include来引入app的url,url以org/为路径能找到apps.organizations.urls中的url,设计命名空间防止引入三方库时url重复,命名空间使用时必须在include中包含命名空间对应的app名 前端使用时{% url 'org:add_ask' %} 命名空间:path重命名 ## 课程在线平台项目实战 ### 综合问题解析 #### 1. ajax post请求的crsf_token问题 发送前在报文头中设置crsf_token ```html beforeSend: function (xhr, settings) { xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}"); }, ``` #### 2. url中传递参数前后端 前端传递参数 ```html href='{% url "course:video" course.id video.id %}' ``` 后端url文件接收参数 ```python url(r"^(?P\d+)/video/(?P\d+)$", VideoView.as_view(), name="video"), ``` #### 表单验证问题 1. 表单继承forms.Form, 表单类字段与前端字段一致;校验方法写clean_表单字段的方法 ```python 传统Form校验 class CommentsForm(forms.Form): course = forms.CharField(required=True, min_length=1) comments = forms.CharField(required=True, max_length=200) def clean_course_id(self): course = int(self.data.get("course")) course = CourseComments.objects.get(id=course).id return course ``` 2. 表单继承forms.ModelForm 前端name属性与后端模型字段属性一致 外键字段的表单数据校验 表单传递外键的id值,名称仍然必须与模型一致; 清洗的外键数据为外键对象。 3. view视图使用校验后的数据 ```python # 利用model.Forms 校验,传入外键的id 返回外键的对象 course = comment_form.cleaned_data["course"] # 通过forms.ModelForm表单外键 筛选的数据对象 comments = comment_form.cleaned_data["comments"] ``` #### 利用主键获取外键值 后端视图 ```python # 通过标签的外键课程反向获取对应的标签 tags = course.coursetag_set.all() ``` 前端不调用而已与后端一致 ```html {% for video in lesson.video_set.all %} ``` #### 根据视图不同动态调整页面css样式 ## 项目分析及功能模块 #### 需求分析 app设计 users-用户相关 courses - 课程相关 organization - 机构相关 operation - 用户操作相关 ####覆盖原生User表 ```python from django.contrib.auth.models import AbstractUser # 自定义userprofile表覆盖默认的user表 class UserProfile(AbstractUser): # 继承user类 新增的字段 ``` setting文件中新增AUTH_USER_MODEL变量 ```python AUTH_USER_MODEL = "users.UserProfile" # 指定自己设计的user表代替django自带的auth_user # 自带的auth_user表字段不满足业务需求 # 在上边已经引入了apps.users,所以直接使用users.UserProfile即可指定代替 # 删除原表,重新makemigrations,以及迁移 ``` #### 表结构设计 1. 设计表结构(以课程表为例)有几个重要的点 ''' 一个实体对应一张表 实体1 < 关系 > 实体2 课程 章节 视频 课程资源 ''' 2. 课程实体的具体字段 3. 每一个字段的类型,是否必填 ```python image = models.ImageField(upload_to="teacher/%Y/%m", verbose_name="头像", max_length=100) file = models.FileField(upload_to="courses/resource/%Y/%m", verbose_name="下载地址", max_length=200) ``` 上述两个字段要有upload_to参数,指定图像或文字上传文件夹%Y/%m,是年/月,为路径创建文件夹 #### app设计 避免循环引用问题 分层设计 避免模型设计中循环引用 第一层:operation 第二层:courses organization 底层 : users 上层可以引用下层,下层不能引用上层 #### 多数模型都需要的字段可以定义一个基类模型 ex:必须放在底层app中。(users app中) ```python class BaseModel(models.Model): # users在最底层所以放置基类 add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间") class Meta: # 不生成表只用来继承 abstract = True ``` ### 原生后台管理系统 1. 权限管理 2. 少前端样式 3. 快速开发 4. 在admin.py中注册 ```python from django.contrib.auth.admin import UserAdmin from apps.users.models import UserProfile class UserProfileAdmin(admin.ModelAdmin): """用户管理器""" pass admin.site.register(UserProfile, UserAdmin) # 原生admin注册管理器 ``` 此时会在admin管理后台中显示模型中verbose_name的名字 5. app会显示英文,在apps.py中添加 ```python verbose_name = "用户" # 修改admin中变成中文(用户信息上面) ``` 此时admin中app会变中文 ### x-admin 的初步运用 x-admin初步运用 1. 下载xadmin源码 2. 在settings的INSTALLED_APPS中添加 crispy_forms 和 xadmin 3. 安装xadmin的依赖包 在cmd中移动到 xadmin的路径下,pip intall -r requirements.txt -i https://pypi.douban.com/simple 4. 通过migrate生成xadmin需要的表 在migrations文件中会有提前建立好的迁移文件,直接迁移就好 5. 如果迁移时报错,根据报错在插件中删除相应文件,并在 ``` __init__.py ``` 中删除注册 6. 在 xadmin中注册模型并添加功能 新建xadmin.py文件 ```python import xadmin from apps.courses.models import Course class CityAdmin(object): list_display = ["id", "name", "desc"] # 配置显示字段 search_fields = ["name", "desc"] # 配置搜索字段 list_filter = ["name", "desc", "add_time"] # 过滤器 list_editable = ["name", "desc"] # 列表页直接修改 xadmin.site.register(Course, CourseAdmin) ``` 还是存在app 为英文 同理在apps.py中添加 ```python verbose_name = "课程管理" # 修改admin中变成中文(用户信息上面) ``` 此时admin中app会变中文 7. 外键数据关联必须依次注册到xadmin.py 8. 在添加数据(以城市表为例)后,数据描述符为City object (1),此时必须要重写; 必须要return一个不为null的值,否则会报错 ```python def __str__(self): return self.name ``` 9. xadmin 全局配置及注册 ```python class GlobalSettings(object): """xadmin 全局样式 左上角标题和底部公司名""" site_title = "在线课程平台后台管理系统" site_footer = "在线课程平台" # menu_style = "accordion" # 左侧列表折叠布局 class BaseSettings(object): # 增加皮肤选择 enable_themes = True use_bootswatch = True xadmin.site.register(xadmin.views.CommAdminView, GlobalSettings) xadmin.site.register(xadmin.views.BaseAdminView, BaseSettings) # 注册xadmin全局样式; 必须相对应的进行绑定 ``` ### 前端模板html及url 1. 后端渲染页面的两种方法 ```python 1. view.py 中 return render(request, template_name='a.html') 2. url.py中 from django.views.generic import TemplateView # 使用内置类来返回html页面 path('', TemplateView.as_view(template_name="index.html")), # 返回某个html页面 path('login/',TemplateView.as_view(template_name="login.html"), name="login") # 配置别名 前端页面使用href="{% url "login" %}跳转 ``` 2. 前端页面中静态资源配置 2-1 在setting.py中 配置全局的statiac文件访问路径的配置STATICFILES_DIRS ```python STATIC_URL = '/static/' # 静态文件访问前缀 STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] # 静态相对资源路径 ``` 静态资源文件夹放置在static文件夹下 将前端静态资源的href改为/static/+文件夹路径 ### 账号密码登录功能视图类及前端问题 #### django 视图的两种类型 1. CBV(class base view) 有利于代码复用 2. FBV(function base view) #### 视图中的问题 1. django 内部的View类,自定义的类要继承 ```python from django.views.generic.base import View ``` 2. 自定义登录类继承View,重写get,post方法 ```python class LoginView(View): """同时处理get和post""" def get(self, request, *args, **kwargs): pass def post(self, request, *args, **kwargs): pass ``` 3. 登录时由于为了减少数据库查询压力,要对前端传递的字段数据进行检验;前端由于存在脚本可以绕过js检验,所以必须要后端进行字段检验。创建forms.py ```python from django import forms class LoginForm(forms.Form): username = forms.CharField(required=True, min_length=2) password = forms.CharField(required=True, min_length=6) # required=True 是否必填;username等变量必须要和前端input的name值一致。 ``` 4. 在视图中,将前端传的POST表单传入表单类中进行实例化 ```python from apps.users.forms import LoginForm login_form = LoginForm(request.POST) ``` 5. 如果表单验证通过则可以获取到通过表单验证的字段。 ```python if login_form.is_valid(): username = login_form.cleaned_data["username"] password = login_form.cleaned_data["password"] # username等字段必须要和前端input的name值一致。 ``` 6. 利用django内置的auth的方法进行数据库查询,登录登出 ```python from django.contrib.auth import authenticate, login, logout # authenticate的内置验证方法 # login 内置登录方法,实现session,cookie设置 from django.http import HttpResponseRedirect # 重定向url类 from django.urls import reverse # 通过别名反解析地址路径 # 表单验证 login_form = LoginForm(request.POST) if login_form.is_valid(): username = login_form.cleaned_data["username"] password = login_form.cleaned_data["password"] user = authenticate(username=username, password=password) # 参数为:数据库字段=表单验证后字段 if user is not None: # 查询到用户 login(request, user) # 内置登录方法 # 原理利用浏览器是否记录到sessionid判断是否登录 return HttpResponseRedirect(reverse("index")) # 重定向,由别名反解路径 # 登出 logout(request) # 删除浏览器记录的sessionid ``` #### 前端响应 1. 模板判断是否已经登录 在django的setting文件中,templates设置中,request和user为全局变量;可以判断user是否有该属性user.is_authenticated判断浏览器是否记录该sessionid从而判断是否登录 ```html {% if user.is_authenticated %} ```` 2. 在登录失败后前端反馈 后端传递login_form对象 该对象有errors属性属于一个字典,显示错误信息 ```html {% if login_form.errors %}{% for key,error in login_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %} ``` 3. 错误字段后的字段回填,在需要回填的字段设置value属性 ```html value="{{ login_form.username.value }}" ``` login_form对象在前端显示时是form表单,获取表单中name属性的value值即为之前填的值,进行了回填 #### 手机验证码 以云片网为例 新建工具文件 导入requests模块,利用requests 模块给云片网的url发送post请求 ```python import requests def send_single_sms(apikey, code, moblie): """ 发送单条短信 :param apikey: :param code: 验证码 :param moblie: 发送的手机号 :return: """ url = "https://sms.yunpian.com/v2/sms/single_send.json" text = f"【李浩文】您的验证码是{code}。如非本人操作,请忽略本短信" # 模板 res = requests.post(url, data={ "apikey": apikey, "mobile": moblie, "text": text }) res_json = json.loads(res.text) # 返回json格式的返回值,详情见api文档,将json解析为python对象,可以使用返回值的反馈信息进行下一步操作 return res_json ``` 利用返回值进行反馈操作 ```python res = send_single_sms("d5c5106ff6612db9ec97f688f4eed638", "123456", "手机号") import json res_json = json.loads(res.text) code = res_json["code"] msg = res_json["msg"] if code == 0: print("发送成功") else: print(f"发送失败: {msg}") print(res.text) # res.text获取request返回的response的值 ``` 返回值实例 ```json { "code": 0, "msg": "发送成功", "count": 1, "fee": 0.05, "unit": "RMB", "mobile": "+93701234567", "sid": 3310228982 } ``` apikey应该属于全局配置,应该放入到setting.py文件中,在视图中调用即可 ```python # 云片网相关设置 yp_apikey = "" ``` ##### ajax的异步短信发送的crsf问题 1. 去除crsf检查 ```python from django.views.decorators.csrf import csrf_exempt # 去除token验证,对某一个view url(r'^send_sms/', csrf_exempt(SendSmsView.as_view()), name="send_sms") ``` ##### 表单验证 1. 定义表单类,表单类的字段必须与前端页面的输入框的name值一致。 2. 表单类实例化并传入request.POST ```python login_form = DynamicLoginPostForm(request.POST) ``` 表单对象在传递给前端时,可以看做是一段html代码,在后端调试中也具有一些属性,比如error属性,反馈表单验证错误。该属性是字典类型,提取值时需要进行解构.items()获取key和value 3. 前端表单错误信息显示 后端传递表单对象,前端接收进行显示 ```python return render(request, "register.html", { "register_get_form": register_get_form, "register_post_form": register_post_form }) ``` ```html {% if register_post_form.errors %}{% for key,error in register_post_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %} ``` ##### 动态验证码保存及验证问题 如果存在setting中的全局变量存在的问题 1. 如果重启django,变量不存在 2. 随着验证码越来越多,内存占用越来越大 3. 验证码过期,代码逻辑复杂 利用redis保存随机的验证码; ```python r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password="123456", db=0, charset="utf8",decode_responses=True) r.set(str(mobile), code) r.expire(str(mobile), 60*5) # 设置验证码5分钟过期 ``` 在表单中验证用户输入的验证码和redis保存的验证码是否一致; ```python class DynamicLoginPostForm(forms.Form): """登录""" mobile = forms.CharField(required=True, min_length=11, max_length=11) code = forms.CharField(required=True, min_length=4, max_length=4) def clean_code(self): """验证""" mobile = self.data.get("mobile") code = self.data.get("code") r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password="123456", db=0, charset="utf8",decode_responses=True) redis_code = r.get(str(mobile)) if code != redis_code: raise forms.ValidationError("验证码不正确") return self.cleaned_data ``` 动态登录视图类中,表单验证通过后,利用mobile作为用户名创建用户。 问题:密码设置问题,密码使用随机字符串并使用django内置加密方法设置密码 ```python # 新建一个用户 user = UserProfile(username=mobile) password = random_string(10) user.set_password(password) # 内置方法加密 user.mobile = mobile user.save() ``` #### 图像验证码 图像验证码的两种方案 一、使用django自带的django-simple-captcha模块 1. pip install django-simple-captcha 2. 将'captcha'添加至setting.py中的INSTALLED_APPS中 3. 执行migrate 数据库中多出captcha_captchastore表 导包 from captcha.fields import CaptchaField 该方案只能使用form表单实例化来使用。 无论是get还是post请求; 只要是需要显示图像验证码的地方需要实例化带captchaField的表单类。 ```python forms.py class RegisterGetForm(forms.Form): captcha = CaptchaField() views.py class RegisterView(View): def get(self, request, *args, **kwargs): register_get_form = RegisterGetForm() return render(request, "register.html", { "register_get_form": register_get_form }) ``` 4. 前端 ```html {{ login_form.captcha }} {# 会自动生成html #} {{ d_form.captcha }} ``` 二、使用captcha源码文件进行创建图像验证码 1. 在应用下添加字体文件夹fonts 2. 在url 中添加图像验证码的视图对应的url地址 ```python path('captcha/', views.get_captcha) ``` 3. 视图函数中服务端的session存储图像验证码的源字符串(个人觉得redis也可以实现),然后在生成图片并相应,图像大小和字体源码中修改 ```python def get_captcha(request): """生成图片显示码""" code = random_string() request.session['captcha_code'] = code image_data = Captcha.instance().generate(code, fmt='PNG') return HttpResponse(image_data, content_type='image/png') ``` 4. 前端显示,利用将查询参数置为随机数修改图片的src属性进行点击刷新 ```javascript function showCaptcha() { $('#captcha').attr('src', '/captcha/?' + Math.random()) } $(() => { showCaptcha(); // 全局回调函数,先显示验证码,再点击修改 $('#captcha').on('click', showCaptcha) }) ``` 5. 比对验证码进行登录,获取session中存储的图像验证码字符串再获取经过表单验证的用户输入的字符串,均比对小写 ```python code_from_session = request.session.get('captcha_code') code_from_user = form.cleaned_data['code'] if code_from_user.lower() == code_from_session.lower(): ``` ##### 前端页面具有动态效果需要根据后端响应来控制前端显示 例:前端页面具有账号密码登录及动态手机验证码登录 动态手机验证码登录失败返回动态手机验证码登录界面 传递一个bool类型的值来控制前端页面的css类型显示 ```html ``` 利用布尔值dynamic_login对active进行控制 #### cookie和session的原理 服务器想要知道同一批请求是不是同一用户的解决方案 1. 每个请求在header或者url的参数中加上username和password 安全性问题(请求被拦截) 2. 给用户一串随机字符串(令牌),字符串满足几个条件(session机制) 2-1 够随机 2-2 这个字符串是由服务器生成的。 2-3 这个字符串需要和用户对应起来。 3. 登录的过程 3-1 查询用户 3-2 login的逻辑 先将用户的基本信息组成json,然后加密生成加密的session字符串 随机生成一串长的字符,叫做sessionid 将sessionid和session值绑定在一起保存到数据库中 将sessionid写入cookie中 将http的报文返回请求给浏览器 4. 浏览器 4-1 拿到文本发现里面在cookie中写入了sessionid 4-2 将cookie中的所有值以(key:value)形式,写入到本地存储(文件) 4-3 后续针对该网站的所有请求浏览器都会加上cookie的sessionid 5. django是如何确定某个请求是否登录 5-1 拦截器(中间件)拦截所有的请求 5-2 在拦截器中发现了在cookie中的sessionid后,通过该sessionid查询到该session,从session中解析出用户的id,通过id查询到用户 5-3 给每个request都设置一个属性 -user 6. cookie 和 session的区别 session在服务端 cookie在客户端(浏览器) ### 上传静态资源的路径问题 media是上传文件的文件夹 1. 在setting.py文件中添加中间件以及根路径地址 ```python MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, "media") # 将media的url注入到所有的html中 'django.template.context_processors.media', ``` 2. 配置media的url ```python from django.views.static import serve # 配置上传文件的访问URL url(r'media/(?P.*)$', serve, {"document_root": MEDIA_ROOT}), ``` 3. 前端文件显示 ```html ``` 4. 模型 ```python class CourseResource(BaseModel): course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="课程") name = models.CharField(max_length=100, verbose_name=u"名称") file = models.FileField(upload_to="courses/resource/%Y/%m", verbose_name="下载地址", max_length=200) # courses/resource/%Y/%m 在media目录下会创建courses/resource/年/月 递归的文件夹 ``` ### 显示课程信息 通过外键模型反向取主键;这样在前端取实例化类的时候相当于直接能取到外键对应的主键元素,当属性使用即可。 ```python def courses_count(self): """通过外键获取机构的课程数""" courses_count = self.course_set.count() return courses_count def courses(self): # from apps.courses.models import Course # 写在外面会循环调用,报错 courses = self.course_set.filter(is_classics=True)[:3] # 当前模型是couse的外键,通过course_set反向取到关联的courses,再截断 # courses = Course.objects.filter(course_org=self) return courses ``` #### 分页 1. 安装 django-pure-pagination库 用法见官方文档 2. 导入 from pure_pagination import Paginator, EmptyPage, PageNotAnInteger 3. 分页后端代码 ```python # 对课程机构数据进行分页 try: page = request.GET.get('page', 1) except PageNotAnInteger: page = 1 p = Paginator(all_orgs, per_page=1, request=request) # 传入筛选后的查询集每页展示多少数据 orgs = p.page(page) # 页码包含的对象 "all_orgs": orgs, # 传递给前端 ``` 4. 前端代码 ```html
    {% if all_orgs.has_previous %}
  • 上一页
  • {% endif %} {% for page in all_orgs.pages %} {% if page %} {% ifequal page all_orgs.number %}
  • {{ page }}
  • {% else %}
  • {{ page }}
  • {% endifequal %} {% else %}
  • ...
  • {% endif %} {% endfor %} {% if all_orgs.has_next %}
  • 下一页
  • {% endif %}
``` #### 收藏与取消收藏 1. 判断用户是否登录;未登录跳转至登录界面,登录跳转由前端ajax完成 ```python user_fav_form = UserFavForm(request.POST) # 如果用户未登录不能收藏 if not request.user.is_authenticated: # 先判断用户是否登录 return JsonResponse({ "status": "fail", "msg": "用户未登录" }) ``` 2. 判断是否已经收藏;即通过用户收藏表进行查询 ```python existed_records = UserFavorite.objects.filter(user=request.user, fav_id=fav_id, fav_type=fav_type) ``` 3. 未收藏,添加收藏,已收藏的,取消收藏;相关表字段进行修改 ```python if fav_type == 1: # 判断收藏的类型(课程) course = Course.objects.get(id=fav_id) course.fav_nums -= 1 course.save() ``` 4. 页面显示是否已收藏;在对应的页面视图中新加一个判断是否收藏的字段,查询收藏表,查询到的话,修改是否收藏的字段,返回至前端 course\views.py ```python # 获取不同类型对象的收藏状态 has_fav_course = False has_fav_org = False if request.user.is_authenticated: if UserFavorite.objects.filter(user=request.user, fav_id=course.id, fav_type=1): has_fav_course = True if UserFavorite.objects.filter(user=request.user, fav_id=course.course_org.id, fav_type=2): has_fav_org = True ``` #### 热门课程推荐 用点击数进行排序 #### 根据当前课程进行相关课程推荐 给课程新增tag属性,tag表中存储课程的标签属性,利用与当前课程相同的标签属性进行筛选。 ```python # 通过课程的tag外键表做课程的推荐 tags = course.coursetag_set.all() # 当前课程拥有的所有标签对象 tag_list = [tag.tag for tag in tags] # 列表生成式将queryset中的对象的tag的value值取出来放到列表中 course_tags = CourseTag.objects.filter(tag__in=tag_list).exclude(course__id=course.id) # 去除原本课程本身 # course__id, 取外键的属性__(双下划线) # 如果原课程具有多个标签与其它单一课程重复,由于是用标签表生成对象则会生成多个相同对象(相同标签个数),导致重复。 # 解决方案利用集合自动去重 related_courses = set() for course_tag in course_tags: related_courses.add(course_tag.course) ``` #### 登录后定位原页面进行访问 在需要登录认证后才能访问页面,其视图类继承LoginRequiredMixin类进行当前用户是否登录的验证。 1. 导入LoginRequiredMixin类 ```python from django.contrib.auth.mixins import LoginRequiredMixin ``` 2. 未登录重定向到登录视图 ```python class CourseCommentView(LoginRequiredMixin, View): login_url = "/login/" ``` 3. 在登录视图中写入登录后跳转至登录之前需要登录验证的页面 当未登录时跳转到登录界面后,url存在next的查询参数,next参数是跳转过来的页面 ```url http://127.0.0.1:8000/login/?next=/course/1/lesson/ ``` 4. next参数的传递问题,在前端form表单action添加next查询参数 ```html action="{% url "login" %}?next={{ next }}" ``` 然而登录的post表单提交时因为采用重定向无法传递next参数,前端未接受到next的值 4-1 get方法中获取next参数并传递给前端 ```python def get(self, request, *args, **kwargs): # login_label = True if request.user.is_authenticated: # 登录后浏览器根据是否有sessionid来判断是否登录的 return HttpResponseRedirect(reverse("index")) next = request.GET.get("next", "") login_form = DynamicLoginForm() return render(request, "login.html", { "login_form": login_form, # "login_label": login_label "next": next, }) ``` 4-2 post方法中登录执行后获取查询参数并重定向,如果有next参数重定向至next,没有该参数重定向至首页 ```python login(request, user) next = request.GET.get("next", "") if next: return HttpResponseRedirect(next) return HttpResponseRedirect(reverse("index")) ``` #### 登录后学习时进行学生与课程的关联,记录学生学习过什么课 ```python # 查询用户是否已经关联了该课程 user_courses = UserCourse.objects.filter(user=request.user, course=course) if not user_courses: # 未查询到关联信息,在表中创建关联信息 user_course = UserCourse(user=request.user, course=course) user_course.save() course.students += 1 course.save() ``` #### 学习过该课程的同学还学过什么课程的类似简易推荐 利用课程学生关联表 1. 查询学习过该课程的所有学生 2. 拿到所有学生的id 3. 查询这些学生学过的所有课程 4. 去除该课程本身遍历的课程外键id不等于当前课程id,以及去重,去重利用集合循环遍历自动去重 ```python # 该课的同学还学习过 # 1. 学习过该课程的所有同学 user_courses = UserCourse.objects.filter(course=course) # 2. 拿到所有该课程学习同学的id user_ids = [user_course.user.id for user_course in user_courses] # 3. 查询这些id的关联课程记录,并按课程的热度排序 all_courses = UserCourse.objects.filter(user_id__in=user_ids).order_by("-course__click_nums")[:5] # 4. 通过课程关联表拿到相关课程对象,-course__click_nums外键属性 # related_courses = [user_course.course for user_course in all_courses if user_course.course.id != course.id] related_courses = set() for item in all_courses: # 课程学生关联表的课程外键的id值进行筛选 if item.course.id != course.id: related_courses.add(item.course) ``` #### 课程资源前端获取 ```html {% for resource in course_resouces %}
  •   {{ resource.name }} 下载 {# resource.file.url 获取上传文件的路径 #}
  • {% endfor %} ``` #### 课程评论 前端利用ajax以POST类型传递评论的所属的课程id以及评论内容,传递给后端。 后端响应成功后刷新当前页面显示评论内容。 1. post请求进入表单校验: 继承forms.ModelForm,由于course是外键,在前端传入course是外键course的id值时,校验后清洗出的数据直接为外键对象 ```html data: {'course': {{ course.id }}, 'comments': comments}, ``` ```python class CommentsForm(forms.ModelForm): class Meta: model = CourseComments fields = ["course", "comments"] ``` 2. 实例化用户评论模型,存储字段数据并保存 ```python comment = CourseComments() comment.user = request.user comment.comments = comments comment.course = course comment.save() ``` 3. 反馈json类型的状态响应 ```python return JsonResponse({ "status": "success" }) ``` #### 视频播放 ##### 后端模型的url字段要长要不然可能存储不下服务器静态资源的url ##### 利用video的外键lesson反向取video ```html {% for video in lesson.video_set.all %}
  • {{ video.name }}({{ video.learn_times }})
  • {% endfor %} ``` ##### 视频在线播放 后端返回video对象即可,渲染播放页 使用js-video插件https://videojs.com/ ```javascript ``` ##### 表单文件上传 前端form表单上传,form中必须有enctype="multipart/form-data"属性以及 ```html ``` ```html
    {# 文件上传的表单 enctype="multipart/form-data" #} {% csrf_token %}
    ``` 后端代码 1. 传统的文件读写 ```python # 传统的文件保存存在如下问题 def save_file(self, file): """保存文件""" with open("E:/code/online_class/media/head_image/uploaded.jpg", "wb") as f: for chunk in file.chunks(): f.write(chunk) def post(self, request, *args, **kwargs): # 处理用户上传的头像,文件用request的FILES属性中 files = request.FILES["image"] self.save_file(files) # # 1. 如果同一个文件上传多次,相同名称的文件该如何处理 # # 2. 文件的保存路径应该写入到user # # 3. 还没有做表单验证 ``` 2. 利用ModelForm进行保存 ```python def post(self, request, *args, **kwargs): """处理用户上传的头像""" image_form = UploadImageForm(request.POST, request.FILES, instance=request.user) # instance = request.user 指明修改的是哪个实例,指明是修改行为,request.FILES传入文件类型的request if image_form.is_valid(): image_form.save() return JsonResponse({ "status": "success" }) else: return JsonResponse({ "status": "fail" }) ``` #### 不用写视图函数如何直接渲染页面 1. 导入模板视图的包 ```python # 当页面可以直接渲染时不需要写视图时候引入TemplateView from django.views.generic import TemplateView ``` 2. 如果该页面需要登录访问 导入认证的装饰器 ```python from django.contrib.auth.decorators import login_required ``` ```python url(r'^mycourse/$', login_required(TemplateView.as_view(template_name="usercenter-mycourse.html"), login_url='/login/'), {"current_page": "mycourse"}, name="mycourse"), # 传递变量值放在url第三个参数中, login_required作为装饰器进行登录校验 ``` #### 搜索功能实现 利用模糊查询进行匹配 前端js利用筛选框中不同的类型,指定响应的path路径,添加查询参数 ```javascript //顶部搜索栏搜索方法 function search_click(){ var type = $('#jsSelectOption').attr('data-value'), keywords = $('#search_keywords').val(), request_url = ''; if(keywords == ""){ return } if(type == "course"){ request_url = "/course/list?keywords="+keywords }else if(type == "teacher"){ request_url = "/org/teachers?keywords="+keywords }else if(type == "org"){ request_url = "/org/list?keywords="+keywords } window.location.href = request_url } ``` 后端响应的视图类中进行sql的模糊搜索 ```python keywords = request.GET.get("keywords", "") s_type = "org" if keywords: all_orgs = all_orgs.filter(Q(name__icontains=keywords)| Q(desc__icontains=keywords)) # 返回如下两值完成输入框回填 "s_type": s_type, "keywords": keywords, ``` #### 自定义用户验证 需求,登录时用户名与数据库中用户名和手机号一致即可通过验证 自定义用户验证类 继承ModelBackend类重写authenticate方法 ```python class CustomAuth(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): try: user = UserProfile.objects.get(Q(username=username) | Q(mobile=username)) if user.check_password(password): return user except Exception as e: return None ``` setting.py文件中 ```python # 自定义登录验证类配置 AUTHENTICATION_BACKENDS = [ "apps.users.views.CustomAuth" ] ``` view的login中 ```python user = authenticate(username=username, password=password) ``` 进行校验时走自定义的验证类