mxonline 总结
课程相关
- 课程列表
- 课程的剪接
- 课程详情
- 课程章节
- 课程关联的授课机构,课程关联的授课教师
- 热门课程
- 相关课程推荐
课程留言
- 需要登录
- 若未登录,返回到登录页面
- 留言失败反馈信息
- 留言成功,异步刷新页面
讲师相关
- 教师简介
- 讲师所有课程
- 热门讲师
授课机构相关
- 机构简介
- 机构所有课程
- 机构所有教师
用户相关
1.收藏
收藏前检查用户登录状态 实现异步功能 课程,教师,机构的收藏
2.注册
验证是否注册
发送注册邮件
用户激活
密码通过django自带的make_password加密保存到数据库
3.登陆
验证用户邮箱与密码
4.密码找回
通过post的邮件发送验证修改的用户名密码,通过用户邮箱点击链接,给予修改权限
后台相关
Xamin:定制后后台样式
修改页头页尾
通过superuser登陆
数据增删改查,查看数据访问次数
搜索相关
Q Icontains
用户名与游戏都可登录
功能实现
登陆功能
自定义auth认证
官方文档 自定义认证
先在settings.py中设置auth允许的函数
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
然后在views中重载authenticate方法
from django.contrib.auth.backends import ModelBackend
from .models import UserProfile
from django.db.models import Q
class CustomBackend(ModelBackend):
# 继承ModelBackend 重载authenticate方法,使得邮件账户也能登陆
def authenticate(self, request, username=None, password=None, **kwargs):
try:
# 返回0个和2个 get都会失败
user = UserProfile.objects.get(Q(username=username)|Q(email=username))
if user.check_password(password): # 这里如果密码错误 user为None
return user
except Exception as e:
return None
FBV改成CBV
CBV比FBV会有很多好处,所以改写为CBV方式书写。(菜鸡我一直写FBV……)
在views中
from django.views.generic.base import View
在views中定义的class要继承自这个基类View,然后书写get和post方法,记得传入request
在urls中
path(‘login/’, LoginView.as_view(), name=‘login’)
要调用as_view()方法,这样会返回使用LoginView处理的函数句柄
注册功能
规范养成:在模板中使用{% static ‘’ %}便于后期维护,记得先{% load staticfiles %}
captcha库
使用django-simple-captcha这个库来快速实现验证码功能。python3直接pip install django-simple-captcha
官方使用手册 ,这里简要记录
要把captcha注册进INSTALLED_APPS
然后添加path(‘captcha’, include(‘captcha.urls’))
在生成数据库 migrate一下。
这里注意下,点击图片刷新验证码是用前端js写的,手册上都有实例代码!
发送邮件
之前没有使用过Django发送过邮件,这里可以实验学习下,get!
发送电子邮件的最简单方法是使用 django.core.mail.send_mail()。
的subject,message,from_email和recipient_list参数是必需的。
subject:一个字符串。
message:一个字符串。
from_email:一个字符串。
recipient_list:字符串列表,每个字符串都是电子邮件地址。每个成员都recipient_list将在电子邮件的“收件人:”字段中看到其他收件人。
fail_silently:一个布尔值。如果是的话False,send_mail会提出一个smtplib.SMTPException。有关smtplib可能的例外列表,请参阅文档,所有这些例外都是。的子类 SMTPException。
auth_user:用于向SMTP服务器进行身份验证的可选用户名。如果没有提供,Django将使用该EMAIL_HOST_USER设置的值 。
auth_password:用于验证SMTP服务器的可选密码。如果没有提供,Django将使用该EMAIL_HOST_PASSWORD设置的值 。
connection:用于发送邮件的可选电子邮件后端。如果未指定,将使用默认后端的实例。有关 更多详细信息,请参阅电子邮件后端的文档。
html_message:如果html_message被提供,所得到的电子邮件将是一个 多部分/替代电子邮件message作为 文本/无格式内容类型和html_message作为 text / html的内容类型。
这里选择django自带的send_mail,故下面的步骤是按此而言
settings中配置
发送邮件的setting设置
EMAIL_HOST = “smtp.qq.com”
EMAIL_PORT = 25
EMAIL_HOST_USER = “xxx”
EMAIL_HOST_PASSWORD = " "
EMAIL_USE_TLS= True
EMAIL_FROM = “xxx”
这边我吧邮箱配置放到了另一个脚本中,并计入了gitignore
from random import Random
from users.models import EmailVerifyRecord
from django.core.mail import send_mail
from mxonline.settings import EMAIL_FROM
def random_str(random_len=8):
“”"
生成随机字符串
:param random_len: 字符串长度,默认为8
:return: 返回字符串
“”"
rd_str = ‘’
chars = ‘AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789’
length = len(chars) - 1
random = Random()
for i in range(random_len):
rd_str += chars[random.randint(0, length)]
return rd_str
注册发信
def send_mx_email(email, send_type=‘register’):
“”"
发送邮件函数 : 发送前将生成的str拼接成url放入数据库,到时候查询数据库是否存在url
:param email: 邮箱
:param send_type: 发送类型,默认是注册,还有是找回密码
:return:
“”"
email_record = EmailVerifyRecord()
code = random_str(20) # 生成随机字符串,数据库中code最长为20
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save()
# 定义邮件的内容
email_title = ''
email_body = ''
if send_type == 'register':
email_title = '我的慕学网站 注册激活链接'
email_body = '请点击下面的链接激活你的账号:http://127.0.0.1:8000/active/{0}'.format(code)
# 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,从哪里发,接受者list
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
if send_status:
return True
else:
return False
暂时没有加入body模板之类的,先用简单的进行测试,后续再进行添加修改完善整个发信函数
对应的在view中添加了一个激活的视图函数ActiveUserView
class ActiveUserView(View):
“”"
激活用户视图函数
“”"
def get(self, request, active_code):
# 取出所有符合验证码的记录 如果查询不到 激活链接肯定是假的 直接返回404
record_list = get_list_or_404(models.EmailVerifyRecord,code= active_code)
msg = ‘’
for record in record_list:
email = record.email
# 根据邮箱 去查用户表中 未激活的用户
try:
user = models.UserProfile.objects.get(email=email, is_active=False)
user.is_active = True
user.save()
msg = '用户激活成功,请登录!'
except models.UserProfile.DoesNotExist:
msg = '你的账户已是激活状态!'
return render(request, 'login.html', {'msg': msg})
并在LoginView中需要添加逻辑判断是否用户已经激活
找回密码
首次看视频和代码感觉到的两个问题:(好像后面有review)
重复输入密码应该放在 forms is_valid中,自写clean函数进行检测
根据前端传入的email,明显存在任意密码重置漏洞
还要考虑重置密码链接是否被点击过,过期时间等!!!
设计url
path(‘forget/’, ForgetPwdView.as_view(), name=‘forget_pwd’),
找回密码url
re_path(‘reset/(?P<reset_code>.*)/’, ResetView.as_view(), name=‘reset_pwd’),
后台修改密码操作的url
path(‘modify_pwd/’, ModifyPwdView.as_view(), name=‘modify_pwd’)
其实我觉得应该把这些都放入users这个app的urls内,然后include下,
设计对应form
form我认为只用来做校验比较好,不要直接拿到前端用,因为前端太多样式css了,在form里面写widget会累死的
class ForgetForm(forms.Form):
# 此处email与前端name需保持一致。
email = forms.EmailField(required=True)
# 应用验证码 自定义错误输出key必须与异常一样
captcha = CaptchaField(error_messages={“invalid”: u"验证码错误"})
class ModifyPwdForm(forms.Form):
# 提交新密码的form
password = forms.CharField(required=True, min_length=6, error_messages={
‘min_length’: ‘最少为6个字符’,
‘required’: ‘必须输入密码啊!’
})
# 再次输入密码的字段
re_pwd = forms.CharField(required=True)
def clean_re_pwd(self):
pwd = self.cleaned_data.get('password')
re_pwd = self.cleaned_data.get('re_pwd')
if pwd is None:
raise ValidationError("请先正确输入密码!!")
if pwd != re_pwd:
raise ValidationError("两次密码不一样!!")
return re_pwd
我在这里利用了它本身的hook机制 重载了re_pwd的clean方法,验证两次密码是否一致。不放在view中去做验证了
完善发信
def send_mx_email(email, send_type=‘register’):
“”"
发送邮件函数 : 发送前将生成的str拼接成url放入数据库,到时候查询数据库是否存在url
:param email: 邮箱
:param send_type: 发送类型,默认是注册,还有是找回密码
:return:
“”"
email_record = EmailVerifyRecord()
code = random_str(20) # 生成随机字符串,数据库中code最长为20
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save()
# 定义邮件的内容
email_title = ''
email_body = ''
if send_type == 'register':
email_title = '我的慕学网站 注册激活链接'
email_body = '请点击下面的链接激活你的账号:http://127.0.0.1:8000/active/{0}'.format(code)
# 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,从哪里发,接受者list
elif send_type == 'forget':
email_title = '我的慕学网站 密码找回链接'
email_body = '请点击下面的链接重置密码:http://127.0.0.1:8000/reset/{0}'.format(code)
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
if send_status:
return True
else:
return False
视图View
按照视频中的思路写了3个视图类,但是我感觉有点臃肿,后面考虑是否重写
class ForgetPwdView(View):
"""
忘记密码显示页面
"""
def get(self, request):
forget_from = forms.ForgetForm()
return render(request, "forgetpwd.html", {"forget_from": forget_from})
def post(self, request):
forget_form = forms.ForgetForm(request.POST)
# form验证合法情况下取出email,从clean data中取
if forget_form.is_valid():
email = forget_form.cleaned_data.get("email", "")
# 发送找回密码邮件
send_mx_email(email, "forget")
# 发送完毕返回登录页面并显示发送邮件成功。
return render(request, "login.html", {"msg": "重置密码邮件已发送,请注意查收"})
# 如果表单验证失败也就是他验证码输错等。
else:
return render(request, "forgetpwd.html", {"forget_from": forget_form})
class ResetView(View):
"""
重置密码显示页面函数
"""
def get(self, request, reset_code):
# 查询生成的随机字符串 和对应的email
record_list = get_object_or_404(models.EmailVerifyRecord, code=reset_code)
return render(request, 'password_reset.html', {'reset_code': reset_code})
class ModifyPwdView(View):
"""
修改密码操作视图
"""
def post(self, request):
modifypwd_form = forms.ModifyPwdForm(request.POST)
if modifypwd_form.is_valid():
# 两次密码是否一致验证已经在form检测中 验证
password = modifypwd_form.cleaned_data.get("password")
# 从前端取到resetcode这样定位到是谁在重置密码
reset_code = request.POST.get("reset_code")
# 如果重置reset code是伪造的 取不到object 直接给他返回404 干嘛还给他友好显示!!!
email_obj = get_object_or_404(models.EmailVerifyRecord, code=reset_code)
# 从记录验证码表中取出对应的email 去寻找user
user = models.UserProfile.objects.get(email=email_obj.email)
user.password = make_password(password)
user.save()
return render(request, 'login.html',{'msg':'密码重置成功!请登录'})
else:
reset_code = request.POST.get("reset_code")
return render(request, 'password_reset.html', {'reset_code': reset_code, 'modifypwd_form':modifypwd_form})
补充
视频中没有提及的个人认为补充下:
前端回传forms内容时可以不用if,error显示和value显示也好,感觉直接用filter过滤器的default更方便简单些
{{ modifypwd_form.password.errors.0 |default:’’ }} # 错误
{{ register_form.password.value |default:’’}} # 值
password_reset模板中的标签
最好修改成这样,onclick可能会触发失败的
错误可以回显在中,这里教程中好像没有提及
应该还要添加链接是否点击,是否过期等考虑项
机构列表
终于开始采用模板继承的方式。
这里记录下MEDIA的配置方式:
MEDIA_ROOT:在settings中配置,这样上传的文件会相对于这个路径。否则会相对于项目路径的
MEDIA_URL:可以使得在模板中动态使用media,便于后期维护。记得先再setting的template中加入’django.template.context_processors.media’才能用
要在URL中配置让media访问的url,下面的方法仅仅推荐在本地开发环境中,生产环境千万不要这样
from django.views.static import serve
from mxonline.settings import MEDIA_ROOT
from django.urls import path, include, re_path
urlpatterns = [
设置media访问url
re_path(r'^media/(?P<path>.*)', serve, {"document_root": MEDIA_ROOT})
]
分页功能
这里分页功能选择了GitHub上一个Django的第三方包 django-pure-pagination
配合我们的例子使用是这样:
class OrgListView(View):
def get(self, request):
org_list = models.CourseOrg.objects.all()
citys_list = models.CityDict.objects.all()
org_sum = models.CourseOrg.objects.count()
# 尝试获取前台get请求传递过来的page参数
# 如果是不合法的配置参数默认返回第一页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger as e:
page = 1
p = Paginator(org_list, 5, request=request)
orgs = p.page(page)
return render(request, 'org_list.html', {
'org_list': orgs,
'citys_list': citys_list,
'org_sum': org_sum
}
)
模板中使用原样式,然后添加一些判断和遍历,不建议直接使用内置的样式,或者可以自己魔改他的模板
- {% if org_list.has_previous %}
- 上一页
- {{ page }}
- {{ page }}
- 下一页
{% endif %} {% for page in org_list.pages %} {% if page %} {% ifequal page org_list.number %}
{% else %}
{% endifequal %} {% else %} ... {% endif %} {% endfor %} {% if org_list.has_next %}
{% endif %}
分类筛选
对模板中的 city_id category sort 参数在view中进行filter筛选,返回结果org_list 然后显示内容
”我要学习“部分
在这里使用modelForm了,就是把form和model结合在一起,form验证完可以直接插入model内,更加方便快捷。
import re
from django import forms
from operation.models import UserAsk
class UserAskForm(forms.ModelForm):
# 继承model外也能新增其他字段
class Meta:
model = UserAsk
# 指定验证的字段
fields = ['name', 'mobile', 'course_name']
def clean_mobile(self):
mobile = self.cleaned_data['mobile']
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
p = re.compile(REGEX_MOBILE)
if p.match(mobile):
return mobile
else:
raise forms.ValidationError(u"手机号码非法", code="mobile_invalid")
在这里我直接修改了前端模板中的js代码,
视图里简单这样写了
class AddUserAskView(View):
"""
我要学习模块
"""
def post(self, request):
userask_form = forms.UserAskForm(request.POST)
if userask_form.is_valid():
# 如果验证通过 就存入数据库中
userask_form.save(commit=True)
return JsonResponse({"status": "success", "msg": "咨询成功,请等待回复"})
else:
return JsonResponse({"status": "fail", "msg": '咨询失败,稍后再试'})
机构详情
机构详情分为四个部分:主页、课程、介绍、教师
都是赋值模板 遍历的东西,重复做就行了,不过这边的URL我与视频中设计不同。我觉得他那样有点冗余,所以我分发了一下
from django.urls import path, include, re_path
from organization import views
app_name = 'org'
extra_patterns = [
path('home/', views.OrgHomeView.as_view(), name='org_home'),
path('course/', views.OrgCourseView.as_view(), name='org_course'),
path('desc/', views.OrgDescView.as_view(), name='org_desc'),
path('teacher/', views.OrgTeacherView.as_view(), name='org_teacher'),
]
urlpatterns = [
path('list/', views.OrgListView.as_view(), name='org_list'),
path('add_ask/', views.AddUserAskView.as_view(), name='add_ask'),
path('show/<int:org_id>/', include(extra_patterns)), # org_id一致 url分发一下 减少冗余
]
公开课
课程列表
模板还是一样的继承,变量赋值循环,不过这里get一个小知识点,CharField中的choice可以用特定方法显示为中文
<div class="head">热门课程推荐</div>
<div class="group_recommend">
{% for course in hot_courses %}
<dl>
<dt>
<a target="_blank" href="">
<img width="240" height="220" class="scrollLoading"
src="{{ MEDIA_URL }}{{ course.image }}"/>
</a>
</dt>
<dd>
<a target="_blank" href=""><h2> {{ course.name }}</h2></a>
<span class="fl">难度:<i class="key">{{ course.get_degree_display }}</i></span>
</dd> # 这里get_degree_display
</dl>
{% endfor %}
</div>
视图简单这样写
class CourseListView(View):
"""
课程列表页面
def get(self, request):
all_course = models.Course.objects.all()
hot_courses = models.Course.objects.all().order_by("-students")[:3]
# 对课程进行分页
# 尝试获取前台get请求传递过来的page参数
# 如果是不合法的配置参数默认返回第一页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# sort排序
sort = request.GET.get('sort', "")
if sort:
if sort == "students":
all_course = all_course.order_by("-students")
elif sort == "hot":
all_course = all_course.order_by("-click_nums")
p = Paginator(all_course, 6, request=request)
courses = p.page(page)
return render(request, 'course-list.html', {
'all_course': courses,
'sort': sort,
"hot_courses": hot_courses
})
分页功能前端模板直接照搬org_list页面就行了,注意变量名修改下
详情页面
详情页中有些前端模板中需要显示的内容,需要后端对象用自己的外键跨表查询,通常一般我View视图内,但是这次看到教程中直接写在了model内。然后还是一样的render页面和变量。直接在模板中使用该model的方法就行了。感觉不错!
下面以Course为例,model修改为
class Course(models.Model):
“”"
课程model
“”"
DEGREE_CHOICES = (
(‘cj’, ‘初级’),
(‘zj’, ‘中级’),
(‘gj’, ‘高级’)
)
name = models.CharField(max_length=50, verbose_name="课程名")
desc = models.CharField(max_length=300, verbose_name="课程描述")
detail = models.TextField(verbose_name="课程详情")
learn_times = models.IntegerField(default=0, verbose_name='学习时长(分钟)') # 分钟为单位
course_org = models.ForeignKey(to=CourseOrg, verbose_name='所属机构', on_delete=models.CASCADE, null=True, blank=True)
students = models.IntegerField(default=0, verbose_name='学习人数')
category = models.CharField(verbose_name='课程类别', max_length=20, default='编程开发')
tag = models.CharField(verbose_name='课程标签',default='', max_length=10)
fav_nums = models.IntegerField(default=0, verbose_name='收藏人数')
image = models.ImageField(upload_to='courses/%Y/%m', verbose_name='封面图', max_length=100)
click_nums = models.IntegerField(default=0, verbose_name='点击数')
add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间')
degree = models.CharField(verbose_name='课程难度', choices=DEGREE_CHOICES, max_length=2, default='cj')
class Meta:
verbose_name = '课程'
verbose_name_plural = verbose_name
def get_lesson_nums(self):
"""
获取章节数
:return: int
"""
return self.lesson_set.all().count()
def get_learn_user(self):
"""
获取学习该课程的5个用户
:return: QurySet
"""
return self.usercourse_set.all()[:5]
def __str__(self):
return self.name
在模板中直接这样使用了
{{ course.name }}
{{ course.desc }}
- 时 长:{{ course.learn_times }}
- 章 节 数:{{ course.get_lesson_nums }}
- 课程类别:{{ course.category }}
- 学习用户: {% for usercourse in course.get_learn_user %}
{% endfor %} </li>
</ul>
<div class="btns">
<div class="btn colectgroupbtn" id="jsLeftBtn">
收藏
</div>
<div class="buy btn"><a style="color: white" href="course-video.html">开始学习</a></div>
</div>
View中只这样写
class CourseDetailView(View):
“”"
详情页
“”"
def get(self, request, course_id):
course_obj = get_object_or_404(models.Course, pk=course_id)
course_obj.click_nums += 1
course_obj.save()
return render(request, ‘course-detail.html’, {
‘course’: course_obj
})
章节信息
课程和章节是一对多关系,章节和视频也是一对多关系。
在视频中新添加url字段。给vedio添加一个学习时长的字段。然后添加模拟数据。
在course中添加获取章节的方法
在lesson中添加获取所有视频的方法
创建课程与讲师直接的外键关联
{{ lesson.name }}
- {% for vedio in lesson.get_lesson_vedio %}
- {{ vedio.name }}
{% endfor %}
{% endfor %}
前端模板中for嵌套就形成了层级界面
课程评论
评论页面模板类似继承,然后添加评论也是ajax。
相关课程推荐
这里要通过orm去找 当前学习当前课程的用户还学过的其他课程
选出学了这门课的学生关系
user_courses = UserCourse.objects.filter(course= course)
从关系中取出user_id
user_ids = [user_course.user_id for user_course in user_courses]
这些用户学了的课程,外键会自动有id,取到字段
all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)
取出所有课程id
course_ids = [all_user_course.course_id for all_user_course in all_user_courses]
获取学过该课程用户学过的其他课程
relate_courses = Course.objects.filter(id__in=course_ids).order_by("-click_nums")[:5]
是否收藏课程
return render(request, “course-video.html”, {
“course”: course,
“all_resources”: all_resources,
“relate_courses”:relate_courses,
})
然后还要把这个添加到三个视图中,我觉得这样代码重复太多了,考虑其他方式减少冗余。
使用include_tag
考虑到课程详情章节页面和评论页面,右边部分内容重复,我自己把他单拿出来放到了include_tag里面了。这样减少代码重复度。记录下简要步骤
在APP下建立templatetags包文件然后新建tag文件,这里以get_content.py为例
在tag文件中创建定义函数,
这里定义takes_context=True,可以自动获取上下文变量。就是在模板中的变量可以从context这个字典中取出来
from django import template
from courses import models
from operation.models import UserCourse
from mxonline.settings import MEDIA_URL
register = template.Library()
@register.inclusion_tag('course_aside.html', takes_context=True)
def get_right_side(context):
# 从上下文变量中取出course
course = context['course']
all_resources = models.CourseResource.objects.filter(course=course)
# 选出学了这门课的学生
user_courses = UserCourse.objects.filter(course=course)
# 从关系中取出user_id遍历成列表
user_ids = [user_course.user_id for user_course in user_courses]
# 查询出用户课程表中 用户id在上面列表中的 其他课程
all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)
# 取出所有课程id
course_ids = [all_user_course.course_id for all_user_course in all_user_courses]
# 获取学过该课程用户学过的其他课程
relate_courses = models.Course.objects.filter(id__in=course_ids).order_by("-click_nums")[:5]
# 是否收藏课程
return {
'all_resources': all_resources,
'course': course,
# 我也不知道为啥 这个MEDIA 不传前端就获取不到了 那就显式传递一下吧
'MEDIA_URL': MEDIA_URL,
"relate_courses": relate_courses,
}
这个是get_right_side中使用的模板
<div class="box mb40">
<h4>资料下载</h4>
<ul class="downlist">
{% for resource in all_resources %}
<li>
<span><i class="aui-iconfont aui-icon-file"></i> {{ resource.name }}</span>
<a href="{{ MEDIA_URL }}{{ resource.download }}" class="downcode" target="_blank" download=""
data-id="274" title="">下载</a>
</li>
{% endfor %}
</ul>
</div>
<div class="box mb40">
<h4>讲师提示</h4>
<div class="teacher-info">
<a href="/u/315464/courses?sort=publish" target="_blank">
<img src='{{ MEDIA_URL }}{{ course.teacher.image }}' width='80' height='80'/>
</a>
<span class="tit">
<a href="/u/315464/courses?sort=publish" target="_blank">{{ course.teacher.name }}</a>
</span>
<span class="job">{{ course.teacher.work_position }}</span>
</div>
<div class="course-info-tip">
<dl class="first">
<dt>课程须知</dt>
<dd class="autowrap">{{ course.need_know }}</dd>
</dl>
<dl>
<dt>老师告诉你能学到什么?</dt>
<dd class="autowrap">{{ course.teacher_tell }}</dd>
</dl>
</div>
</div>
<div class="cp-other-learned js-comp-tabs">
<div class="cp-header clearfix">
<h2 class="cp-tit l">该课的同学还学过</h2>
</div>
<div class="cp-body">
<div class="cp-tab-pannel js-comp-tab-pannel" data-pannel="course" style="display: block">
<!-- img 200 x 112 -->
<ul class="other-list">
{% for course in relate_courses %}
<li class="curr">
<a href="{% url 'course:detail' course.id %}" target="_blank">
<img src="{{ MEDIA_URL }}{{ course.image }}"
alt="{{ course.name }}">
<span class="name autowrap">{{ course.name }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
再到前面模板中使用这个include_tag
{% load get_content %}
{% get_right_side %}
这样就不用在course_comment,course_vedio页面两次使用相同内容的HTML代码,也不用在CourseInfoView,CommentsView视图里用同样的代码获取变量了。
CBV装饰器
一些视图的操作必须登录才能进行,这样就要给某些视图类加上登录的判断。当然也可以用
if request.user.is_authenticated:
但是这样判断后在进行操作总归觉得不是最好的解决方式。
所以这里改用装饰器。FBV模式直接用@login_required就行了。但是CBV模式的需要另外的方法
apps/utils/mixin_utils.py
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
class LoginRequiredMixin(object):
@method_decorator(login_required(login_url='/login/'))
def dispatch(self,request,*args,**kwargs):
return super(LoginRequiredMixin, self).dispatch(request,*args,**kwargs)
在django中已Mixin结尾的,就代表最基本的View
然后让CourseInfoView和CommentsView都继承LoginRequiredMixin
主页
重新继承自base,之前开始写的时候很随便,直接用的index.html 现在要考虑整体性,所以教程中又继承自base了。其他操作没有什么特殊,就是替换render而已。这里有个对主页导航栏active判断的方法之前没有想到过,记录下
配置全局导航和全局"active"状态
说明:
request.path 可以获取当前访问页面的相对url
比如“http://127.0.0.1:8000/org/teacher_list/”,则request.path 就是“/org/teacher_list/”
比如"http://127.0.0.1:8000/org/teacher/detail/1/",则request.path 就是“/org/teacher/detail/1/”
slice:12 是过滤器,取前七位数
利用这种发发可以达到全局的“active”效果,而不用每个子页面都要去设置“active”了
全局搜索功能
先来看看deco-common.js中的代码
//顶部搜索栏搜索方法
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/teacher/list?keywords=”+keywords
}else if(type == “org”){
request_url = “/org/list?keywords=”+keywords
}
window.location.href = request_url
}
在对应的视图中取keywords这个键值,然后使用orm操作sql like方法找到结果,再正常返回页面,这里仅以Course为例
搜索功能
search_keywords = request.GET.get('keywords', '')
if search_keywords:
# Q可以实现多个字段,之间是or的关系
all_course = all_course.filter(
Q(name__icontains=search_keywords) |
Q(desc__icontains=search_keywords) |
Q(detail__icontains=search_keywords)
)
Q方法是or的用法
个人信息中心
个人资料的修改这部分是后面逻辑性较强的一块了。
修改头像
使用modelform,然后用meta指定field只为image,上传后直接保存
修改密码
使用modelform,再自写其中的clean方法,对两次输入的密码做相同比对,就是使用的上次的modifyform
class ModifyPwdForm(forms.Form):
# 提交新密码的form
password = forms.CharField(required=True, min_length=6, error_messages={
‘min_length’: ‘最少为6个字符’,
‘required’: ‘必须输入密码啊!’
})
# 再次输入密码的字段
re_pwd = forms.CharField(required=True)
def clean_re_pwd(self):
pwd = self.cleaned_data.get('password')
re_pwd = self.cleaned_data.get('re_pwd')
if pwd is None:
raise ValidationError("请先正确输入密码!!")
if pwd != re_pwd:
raise ValidationError("两次密码不一样!!")
return re_pwd
view里面这样写
class UpdatePwdView(LoginRequiredMixin, View):
“”"
个人中心修改用户密码
“”"
def post(self, request):
modify_form = ModifyPwdForm(request.POST)
# 我把两次密码是否相同验证放在clean方法内了
if modify_form.is_valid():
password = modify_form.cleaned_data.get(“password”)
user = request.user
user.password = make_password(password)
user.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
else:
# 两次密码不一致会在这里返回的
return HttpResponse(json.dumps(modify_form.errors), content_type='application/json')
修改邮箱
修改邮箱属于这里面逻辑更多的一部分
判断要修改的邮箱是否已经存在,不存在就发送邮件验证码
判断验证码是否正确(去验证码表中寻找和当前用户比对)
class SendEmailCodeView(LoginRequiredMixin, View):
“”"
发送邮箱验证码视图
“”"
def get(self, request):
email = request.GET.get(‘email’, ‘’)
if UserProfile.objects.filter(email=email):
return HttpResponse('{"email":"邮箱已存在"}', content_type='application/json')
send_mx_email(email, 'update_email')
return HttpResponse('{"status":"success"}', content_type='application/json')
class UpdateEmailView(LoginRequiredMixin, View):
“”"
修改邮箱视图
“”"
def get(self, request):
email = request.POST.get(“email”, “”)
code = request.POST.get(“code”, “”)
existed_records = models.EmailVerifyRecord.objects.filter(email=email, code=code, send_type='update_email')
if existed_records:
user = request.user
user.email = email
user.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
else:
return HttpResponse('{"email":"验证码无效"}', content_type='application/json')
查看收藏和信息
个人中心还可以查看收藏(机构、教师、课程)、消息、和自己学习的公开课程。还是一样的查然后赋值给模板渲染。
class MyCourseView(LoginRequiredMixin, View):
def get(self, request):
user_courses = opmodel.UserCourse.objects.filter(user=request.user)
return render(request, “usercenter-mycourse.html”, {
“user_courses”: user_courses,
})
class MyFavOrgView(LoginRequiredMixin, View):
“”"
我收藏的课程机构
“”"
def get(self, request):
org_list = []
fav_orgs = opmodel.UserFavorite.objects.filter(user=request.user, fav_type=2)
# 上面的fav_orgs只是存放了id。我们还需要通过id找到机构对象
for fav_org in fav_orgs:
# 取出fav_id也就是机构的id。
org_id = fav_org.fav_id
# 获取这个机构对象
org = CourseOrg.objects.get(id=org_id)
org_list.append(org)
return render(request, "usercenter-fav-org.html", {
"org_list": org_list,
})
class MyFavTeacherView(LoginRequiredMixin, View):
"""
我收藏的授课讲师
"""
def get(self, request):
teacher_list = []
fav_teachers = opmodel.UserFavorite.objects.filter(user=request.user, fav_type=3)
for fav_teacher in fav_teachers:
teacher_id = fav_teacher.fav_id
teacher = Teacher.objects.get(id=teacher_id)
teacher_list.append(teacher)
return render(request, "usercenter-fav-teacher.html", {
"teacher_list": teacher_list,
})
class MyFavCourseView(LoginRequiredMixin, View):
"""
我收藏的公开课程
"""
def get(self, request):
course_list = []
fav_courses = opmodel.UserFavorite.objects.filter(user=request.user, fav_type=1)
for fav_course in fav_courses:
course_id = fav_course.fav_id
course = Course.objects.get(id=course_id)
course_list.append(course)
return render(request, 'usercenter-fav-course.html', {
"course_list": course_list,
})
class MyMessageView(LoginRequiredMixin, View):
def get(self, request):
all_message = opmodel.UserMessage.objects.filter(user=request.user.id)
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(all_message, 4, request=request)
messages = p.page(page)
return render(request, "usercenter-message.html", {
"messages": messages,
})
在页面导航栏都会有消息个数提醒,那如何把消息个数变量放置在每个页面中?
考虑到每个页面中的request.user本来就不用显式赋值给模板调用,所以可以直接在userProfile的model中加入一个获取消息个数的方法!!Mark 之前都未想到过这样使用。
细节收尾
对各种点击数保存在视图中添加相应代码,对收藏操作进行一定判断。对导航栏class是否有active状态进行判断,
这里以usercenter-base为例,这样处理判断active的。
全局配置404和500
视频中讲到了配置需要注意的点:
handler
在urls中定义handler404和handler500
全局404页面配置
handler404 = ‘users.views.page_not_found’
全局500页面配置
handler500 = ‘users.views.page_error’
page_not_found和page_error两个函数定义在视图中,404和500时执行对应的操作
定义处理函数
from django.shortcuts import render_to_response
def pag_not_found(request):
# 全局404处理函数
response = render_to_response('404.html', {})
response.status_code = 404
return response
def page_error(request):
# 全局500处理函数
from django.shortcuts import render_to_response
response = render_to_response('500.html', {})
response.status_code = 500
return response
关闭DEBUG
如果不关闭debug模式,不会执行我们指定的404和500函数内的代码,而且仍旧报错,这是由于Django的调试模式决定的
设置static访问
视频中说到,关闭debug模式后,Django不会接管我们的静态文件,需要和media一样设置一个url路由给它访问。
re_path(r’^static/(?P.*)’, serve, {“document_root”: STATIC_ROOT }),
一般静态文件通过第三方http服务器代理转发。nignx 和 Apache都会自动代理这些静态文件
allowed_hosts
settings中的allowed_hosts设置项为允许运行该站点的ip,暂时先设置为*。
富文本编辑器
教程中使用了DjangoUeditor。这里记录一下添加这个组件的过程。
下载
https://github.com/twz915/DjangoUeditor3/
下载后放置随意一个目录下,我放于extra_apps下
配置
settings中添加app
INSTALLED_APPS = [
‘DjangoUeditor’,
]
mxonline/urls.py
富文本编辑器url
path('ueditor/',include('DjangoUeditor.urls' )),
course/models.py中Course修改detail字段
from DjangoUeditor.models import UEditorField
class Course(models.Model):
# detail = models.TextField(“课程详情”)
detail = UEditorField(verbose_name=u’课程详情’, width=600, height=300, imagePath=“courses/ueditor/”,
filePath=“courses/ueditor/”, default=’’)
xadmin/plugs目录下新建ueditor.py文件
import xadmin
from xadmin.views import BaseAdminPlugin, CreateAdminView, ModelFormAdminView, UpdateAdminView
from DjangoUeditor.models import UEditorField
from DjangoUeditor.widgets import UEditorWidget
from django.conf import settings
class XadminUEditorWidget(UEditorWidget):
def init(self, **kwargs):
self.ueditor_options = kwargs
self.Media.js = None
super(XadminUEditorWidget,self).init(kwargs)
class UeditorPlugin(BaseAdminPlugin):
def get_field_style(self, attrs, db_field, style, **kwargs):
if style == 'ueditor':
if isinstance(db_field, UEditorField):
widget = db_field.formfield().widget
param = {}
param.update(widget.ueditor_settings)
param.update(widget.attrs)
return {'widget':XadminUEditorWidget(**param)}
return attrs
def block_extrahead(self, context, nodes):
js = '<script type="text/javascript" src="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.config.js")
js += '<script type="text/javascript" src="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.all.min.js")
nodes.append(js)
xadmin.site.register_plugin(UeditorPlugin, UpdateAdminView)
xadmin.site.register_plugin(UeditorPlugin, CreateAdminView)
xadmin/plugs/init.py里面添加ueditor插件
PLUGINS = (
‘ueditor’,
)
course/adminx.py中使用
class CourseAdmin(object):
#detail就是要显示为富文本的字段名
style_fields = {“detail”: “ueditor”}
course-detail.html
必须要在需要显示富文本的地方关闭自动转义
{{ course.detail }}
{% endautoescape %}
课程中还有xadmin进阶开发和部署上线等步骤在此不展开了,代码开发全部完成。结束啦撒花!!!
下面说说收获吧!
收获
CBV模式的进一步熟悉使用,CBV方式的装饰器
model建立思想,一对多关系等。为防止循环调用,需要构建的有层次。
在model中书写某些orm查询,而后在模板中直接调用该变量的属性来使用。而不直接在view中书写
注册、找回密码、更换邮箱、全局消息等具体实现逻辑思路。
xadmin组件的进阶使用
全局配置处理404和500的方法。
项目部署上线的大体步骤。
mxonline 总结的更多相关文章
- IntegrityError at /admin/users/userprofile/add/ (1452, 'Cannot add or update a child row: a foreign key constraint fails (`mxonline`.`django_admin_log`, CONSTRAINT `django_admin_log_user_id_c564eba6_
报错现象 在执行 django 后台管理的时候添加数据导致 1452 错误 报错代码 IntegrityError at /admin/users/userprofile/add/ (1452, 'C ...
- mxonline实战17,上线部署
在线演示: http://47.244.22.82 python3+django2.0的环境 需要安装的库pip install django-simple-captcha django-pure-p ...
- mxonline实战16:首页配置和xadmin进阶,Ueditor
对应github地址:第16天 一. 首页配置 1. courses/models.py -->class Course增加字段,迁移数据库
- mxonline实战10,课程列表页,课程详情页1
对应github地址:第10天 一. 课程列表页 1. 拷贝course-list.html到templates目录中 2. 编写url和view 在courses/views.py中新加
- mxonline实战9,我要学习功能块,机构详情展示,收藏功能
对应github地址:第9天 一. 实现我要学习功能
- mxonline实战7,模板继承和模板标签
对应github地址:https://github.com/pshyms/django/tree/master/mxonline/7_day 一. 定制不同页面中样式相同,内容不同的模 ...
- mxonline实战5,用户注册的验证码
github对应地址:验证码好麻烦 一. 安装 配置 1. pip install django-simple-captcha 2. add captcha to the INSTAL ...
- mxonline实战4,用户登陆页面2和用户注册1
一. 基于类来定义view.py diango中使用基于类来定义views的功能,其实更加方便,因为这样可继承一些定义好的基类,来减少我们的代码量 1. 使用基于类的方法,来重新定 ...
- mxonline实战3,编写首页及用户登录页面1
对应github地址:首页和用户登陆1 一. 显示首页 1. 修改mxonline/setttings.py 在TEMPLATES代码块修改DIRS为 'DIRS': [os. ...
随机推荐
- Mat补充
Mat的创建 1.使用Mat的构造函数 Mat test(2,2,CV_8UC3,Scalar(0,0,255)); 2.使用Mat的构造函数 int sizes[3] = {2,2,2}; Mat ...
- Java设计模式——结构型模式
Java设计模式中共有7种结构型模式:适配器模式.装饰模式.代理模式.外观模式.桥接模式.组合模式.享元模式.其中对象的适配器模式是各种模式的起源,其关系如下面的图:1.适配器模式 适配器模式将某个类 ...
- java的Scanner获取输入内容
//导入 scanner的包 import java.util.Scanner; Scanner scanner = new Scanner(System.in); System.out.printl ...
- Java查看项目目录以及导入项目
1.查看项目目录 右击项目,选择properties,选择resource,查看location 2.导入已有项目 (1)右击项目 ,选择import (2)在弹出的窗口中选择Existing pro ...
- js短信验证码
短信验证码,无注释,url顺便写的错的,所以会报错 <!DOCTYPE html> <html> <head> <meta charset="UTF ...
- 【慕课网实战】Spark Streaming实时流处理项目实战笔记三之铭文升级版
铭文一级: Flume概述Flume is a distributed, reliable, and available service for efficiently collecting(收集), ...
- MIT Molecular Biology 笔记1 DNA的复制,染色体组装
视频 https://www.bilibili.com/video/av7973580?from=search&seid=16993146754254492690 教材 Molecular ...
- C(m,n)算法
排列组合:C(m,n),m为给定数列,n为要从数列m中取元素的数量,GetResult()获取所有不重复的组合. public class MathCombination<T> { Lis ...
- 关于Lambda
1. 查询时,包含关联子对象.如: 数据库中包含表Father和Son,映射实体如下: public class Father { public string Name{get;set;} publi ...
- bzoj2002(lct模板)
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #inclu ...