说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!
接着上一篇博客继续往下写 :https://blog.csdn.net/qq_41782425/article/details/90081752
目录
一丶全局搜索功能
分析:当用户在首页搜索框进行搜索时,可以选择搜索课程和机构以及讲师,默认搜索类为公开课,点击搜索调转到各个列表页面;搜索按钮点击使用js代码进行链接地址调转,当用户没有输入任何搜索内容时,则跳转到用户选择的类别下的列表页,即链接地址为http://127.0.0.1:8000/course/list/?keywords= ;其中keywords=空;反之http://127.0.0.1:8000/course/list/?keywords=python
1.公开课搜索
说明:三个类别使用同一js代码
- 在static/js目录下的deco-common.js文件中,编写如下js代码,以下js代码逻辑很简单,就不细说了
//顶部搜索栏搜索方法
function search_click(){
var type = $('#jsSelectOption').attr('data-value'),
keywords = $('#search_keywords').val(),
request_url = '';
if(keywords == ""){
request_url = "/"+type+"/list?keywords="
}
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
}
$('#jsSearchBtn').on('click',function(){
search_click()
});
- 在courses/views下的课程列表视图中,获取请求地址keywords参数的值,根据搜索的关键字到对获取到的所有课程再进行此条件的筛选,需要注意的是不仅仅对Course模型表的name字段,还需要对课程简介desc以及课程详情detail字段进行关键字匹配搜索,字段__icontains为django提供的匹配方式,contains前面加i表示不区分大小写
# 全局搜索功能
search_keywords = request.GET.get("keywords", "")
if search_keywords:
all_courses = all_courses.filter(Q(name__icontains=search_keywords)|Q(desc__icontains=search_keywords)|Q(detail__icontains=search_keywords))
- Debug测试公开课类搜索python关键字
- 测试公开课搜索功能
2.课程机构搜索
- 同理在organization/views下的机构列表视图中,通过获取keywords关键字到CourseOrg机构模型表中去匹配name字段以及desc字段的数据
# 全局机构搜索功能
search_keywords = request.GET.get("keywords", "")
if search_keywords:
all_orgs = all_orgs.filter(
Q(name__icontains=search_keywords) | Q(desc__icontains=search_keywords))
- 测试机构搜索功能
3.课程讲师搜索
- 在organization/views下的讲师列表视图中,通过获取keywords关键字到Teacher讲师模型表中去匹配name字段和work_company字段以及work_position的数据
# 全局讲师搜索功能
search_keywords = request.GET.get("keywords", "")
if search_keywords:
all_teachers = all_teachers.filter(
Q(name__icontains=search_keywords) | Q(work_company__icontains=search_keywords)| Q(work_position__icontains=search_keywords))
- 测试课程讲师搜索功能
二丶用户个人中心(个人资料)
分析:在用户个人中心一共有4个大页面,跟机构页面类似,用户个人中心页面包括个人资料(usercenter-info)丶我的课程(usercenter-mycourse)丶我的收藏包括三个页面(usercenter-fav-course丶usercenter-fav-teacher丶usercenter-fav-org)丶我的消息(usercenter-message),整个用户中心页面为6个页面
1.提取父模板文件usercenter_base.html
- 因个人中心所有页面的样式除了各种内容不一样,其他都一样,所以利用模板继承来解决模板内容重复性
- 将usercenter-info模板内容复制到userc_base父模板中,在父模板中进行不同模块的提取操作,也就是挖坑
2.显示用户信息页面
- 模板继承
- 在根级urls中将users应用下的urls包含进来
url(r'users/', include('users.urls', namespace="users")), # 用户应用users下的urls
- 在users/views中定义用户信息类视图,需要注意的就是此页面必须为登录后才能进行访问的页面,所以该视图需要继承于LoginRequiredMixin类,来验证用户是否登录
class UserInfoView(LoginRequiredMixin, View):
"""用户信息页"""
def get(self, request):
current_page = "info"
return render(request, "usercenter-info.html", {"current_page":current_page})
- 在父模板中修改个人资料链接地址以及判断按钮激活状态
<li class="{% if current_page == 'info' %}active2{% endif %}"><a href="{% url 'users:user_info' %}">个人资料</a></li>
- 在浏览器中访问http://127.0.0.1:8000/users/info/ 成功进入用户信息页面
3.动态显示出用户信息页面数据
说明:因为当用户登录成功后,则request中就记录了登录成功后的用户信息user对象,所以通过该user对象即可以在模板中直接打印显示出动态数据
<form class="clearfix" id="jsAvatarForm" enctype="multipart/form-data" autocomplete="off"
method="post" action="" target='frameFile'>
<label class="changearea" for="avatarUp">
<span id="avatardiv" class="pic">
<img width="100" height="100" class="js-img-show" id="avatarShow"
src="{{ MEDIA_URL }}{{ request.user.image }}"/>
</span>
<span class="fl upload-inp-box" style="margin-left:70px;">
<span class="button btn-green btn-w100" id="jsAvatarBtn">修改头像</span>
<input type="file" name="image" id="avatarUp" class="js-img-up"/>
</span>
</label>
</form>
<form class="perinform" novalidate id="jsEditUserForm" autocomplete="off">
<ul class="right">
<li>昵 称:
<input type="text" name="nick_name" id="nick_name" value="{{ request.user.nick_name }}" maxlength="10">
<i class="error-tips"></i>
</li>
<li>生 日:
<input type="text" id="birth_day" name="birday" value="{{ request.user.birday }}" readonly="readonly"/>
<i class="error-tips"></i>
</li>
<li>性 别:
<label> <input type="radio" name="gender" value="male" {% if request.user.gender == 'male' %}checked="checked"{% endif %}>男</label>
<label> <input type="radio" name="gender" value="female"
{% if request.user.gender == 'female' %}checked="checked"{% endif %}>女</label>
</li>
<li class="p_infor_city">地 址:
<input type="text" name="address" id="address" placeholder="请输入你的地址" value="{{ request.user.address }}"
maxlength="10">
<i class="error-tips"></i>
</li>
<li>手 机 号:
<input type="text" name="mobile" id="mobile" placeholder="请输入你的手机号码" value="{{ request.user.mobile }}"
maxlength="10">
</li>
<li>邮 箱:
<input class="borderno" type="text" name="email" readonly="readonly"
value="{{ request.user.email }}"/>
<span class="green changeemai_btn">[修改]</span>
</li>
<li class="button heibtn">
<input type="button" id="jsEditUserBtn" value="保存">
</li>
</ul>
</form>
- 刷新页面成功显示出登录成功后的用户动态数据
4.修改用户头像
- 在模板文件中修改头像为form表单内容块,即所以需要在users/forms中定义上传图片的表单,使用forms.ModelForm进行完成
class UploadImageForm(forms.ModelForm):
"""上传头像表单"""
class Meta:
model = UserProfile
fields = ["image"] # 只保留想要的模型中的字段
- 需要注意的是,UploadImageForm表单类中指定的UserProfile模型类中的image字段名必须要与模板中form表单中修改头像所在的input标签的name属性的值必须相同
<span class="fl upload-inp-box" style="margin-left:70px;">
<span class="button btn-green btn-w100" id="jsAvatarBtn">修改头像</span>
<input type="file" name="image" id="avatarUp" class="js-img-up"/>
</span>
- 定义上传图片的类视图
class UploadImageView(LoginRequiredMixin, View):
"""修改用户头像"""
def post(self, request):
image_form = UploadImageForm(request.POST, request.FILES)
if image_form.is_valid():
pass
- 定义视图的路由
url(r'^image/upload/$', UploadImageView.as_view(), name="image_upload"), # 上传用户头像
- 在static/js/deco-user.js中 ,定义js提交上传头像form表单代码
//个人资料头像
$('.js-img-up').uploadPreview({ Img: ".js-img-show", Width: 94, Height: 94 ,Callback:function(){
$('#jsAvatarForm').submit();
}});
- Debug测试是否在image_form中生成了上传的图片数据,通过向UploadImageForm表单类中传递request.FILES对象也就是上传的图片对象,在变量image_form中就会生成cleaned_data字典对象中的image键值
- 即需要在视图中获取image_form对象cleaned_data字段中的image值,将该值保存到user对象的image属性中,则完成图片的修改以及保存
class UploadImageView(LoginRequiredMixin, View):
"""修改用户头像"""
def post(self, request):
# 向表单类中传递用户修改的头像文件
image_form = UploadImageForm(request.POST, request.FILES)
# 表单验证成功则将在生成的表单独享image_form中获取cleaned_data字典属性的image键的值保存到user对象的image属性中
# 即完成了用户上传保存头像的操作
if image_form.is_valid():
request.user.image = image_form.cleaned_data['image']
request.user.save()
- 测试修改用户头像功能
- 因为在上传头像图片表单类中继承的是forms.ModelForm类,所以在视图中可以定义instance属性指向当前的用户对象,即可完成上传图片操作,不需要从表单对象的cleaned_data属性中获取图片数据
class UploadImageView(LoginRequiredMixin, View):
"""修改用户头像"""
def post(self, request):
# 向表单类中传递用户修改的头像文件
image_form = UploadImageForm(request.POST, request.FILES, instance=request.user)
# 表单验证成功则将在生成的表单独享image_form中获取cleaned_data字典属性的image键的值保存到user对象的image属性中
# 即完成了用户上传保存头像的操作
if image_form.is_valid():
# request.user.image = image_form.cleaned_data['image']
request.user.save()
- Debug测试,成功进入save方法,并没有抛出异常,则以上方式也能完成修改头像操作
- 在视图中定义返回前端的响应数据
class UploadImageView(LoginRequiredMixin, View):
"""修改用户头像"""
def post(self, request):
# 向表单类中传递用户修改的头像文件
image_form = UploadImageForm(request.POST, request.FILES, instance=request.user)
# 表单验证成功则将在生成的表单独享image_form中获取cleaned_data字典属性的image键的值保存到user对象的image属性中
# 即完成了用户上传保存头像的操作
if image_form.is_valid():
# request.user.image = image_form.cleaned_data['image']
image_form.save(commit=True)
return HttpResponse('{"status":"success", "msg":"修改成功"}', content_type='application/json')
else:
return HttpResponse('{"status":"fail", "msg":"修改失败"}', content_type='application/json')
- 修改成功则返回json格式的响应数据
5.修改用户密码
说明:之前在用户网站登录时,有个功能叫忘记密码,在忘记密码验证邮箱时成功时会在邮件中发送修改密码的链接地址,在进入密码修改页面进行密码修改,需要注意的是之前修改密码是在用户未登录的状态下进行的修改,而在用户中心进行密码修改则是用户登录成功后进行修改,所有不需要在进行逻辑编写时,不需要获取邮箱地址,其他获取参数与之前写的修改密码一致;因为用户是必须登录成功才能进入到个人中心页面,即不需要用户输入旧密码
- 将之前定义的ModifyPwdView类视图代码复制一份,进行如下修改
class UpdatePwdView(LoginRequiredMixin, View):
"""个人中心修改密码"""
def post(self, request):
modify_form = ModifyPwdForm(request.POST)
if modify_form.is_valid():
pwd1 = request.POST.get("password1", "")
pwd2 = request.POST.get("password2", "")
# 当用户两次密码不一致则提示错误信息到页面,一致时则通过用户名也就是邮箱来获取用户信息user对象,设置对象中的密码为用户填写的密码
# 最后返回到登录页面
if pwd1 != pwd2:
return HttpResponse('{"status":"fail", "msg":"两次密码不一致"}', content_type='application/json')
user = request.user
user.password = make_password(pwd2)
user.save()
return HttpResponse('{"status":"success", "msg":"密码修改成功"}', content_type='application/json')
else:
return HttpResponse(json.dumps(modify_form.errors), content_type='application/json')
- 定义视图的路由
url(r'^update/pwd/$', UpdatePwdView.as_view(), name="update_pwd"), # 个人中心修改密码
- 顺便提示一下吧,form表单的提交需要传递csrf_token,所以在usercenter_base模板中找到修改密码的form表单,添加csrf_token的值
<form id="jsResetPwdForm" autocomplete="off">
<div class="box">
<span class="word2">新 密 码</span>
<input type="password" id="pwd" name="password1" placeholder="5-20位非中文字符"/>
</div>
<div class="box">
<span class="word2">确定密码</span>
<input type="password" id="repwd" name="password2" placeholder="5-20位非中文字符"/>
</div>
<div class="error btns" id="jsResetPwdTips"></div>
<div class="button">
<input id="jsResetPwdBtn" type="button" value="提交"/>
</div>
{% csrf_token %}
</form>
- 在deco-user.js中编写ajax post发送异步请求的js代码
//个人资料修改密码
$('#jsUserResetPwd').on('click', function(){
Dml.fun.showDialog('#jsResetDialog', '#jsResetPwdTips');
});
$('#jsResetPwdBtn').click(function(){
$.ajax({
cache: false,
type: "POST",
dataType:'json',
url:"/users/update/pwd/",
data:$('#jsResetPwdForm').serialize(),
async: true,
success: function(data) {
if(data.password1){
Dml.fun.showValidateError($("#pwd"), data.password1);
}else if(data.password2){
Dml.fun.showValidateError($("#repwd"), data.password2);
}else if(data.status == "success"){
Dml.fun.showTipsDialog({
title:'提交成功',
h2:'修改密码成功,请重新登录!',
});
Dml.fun.winReload();
}else if(data.msg){
Dml.fun.showValidateError($("#pwd"), data.msg);
Dml.fun.showValidateError($("#repwd"), data.msg);
}
}
});
});
- 测试个人中心修改用户密码(输入长度不一致的以及两次密码不一致的密码)
- 测试个人中心修改用户密码(输入一致的密码)
6.完成网站页面顶部栏的用户信息显示
- 说明:只需要在所有的父模板判断用户是否登录以及打印登录后的用户信息,登录后的用户信息直接从request中去获取即可
- 在base.html和org_base.html以及usercenter_base.html三个模板中进行如下修改即可
{% if request.user.is_authenticated %}
<div class="personal">
<dl class="user fr">
<dd>{{ request.user.username }}<img class="down fr"
src="{% static 'images/top_down.png' %}"/></dd>
<dt><img width="20" height="20" src="{{ MEDIA_URL }}{{ request.user.image }}"/></dt>
</dl>
<div class="userdetail">
<dl>
<dt><img width="80" height="80" src="{{ MEDIA_URL }}{{ request.user.image }}"/></dt>
<dd>
<h2>{{ request.user.nick_name }}</h2>
<p>{{ request.user.username }}</p>
</dd>
</dl>
<div class="btn">
<a class="personcenter fl" href="{% url 'users:user_info' %}">进入个人中心</a>
<a class="fr" href="/logout/">退出</a>
</div>
</div>
</div>
<a href="usercenter-message.html">
<div class="msg-num"><span id="MsgNum">0</span></div>
</a>
{% else %}
<a style="color:white" class="fr registerbtn" href="{% url 'register' %}">注册</a>
<a style="color:white" class="fr loginbtn" href="{% url 'login' %}">登录</a>
{% endif %}
- 刷新页面成功在顶部栏中显示登录成功的用户数据
7.个人中心修改邮箱
- 定义视图
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_register_email(email, "update_email")
return HttpResponse('{"status":"success"}', content_type='application/json')
- 定义视图路由
url(r'^sendemail_code/$', SendEmailCodeView.as_view(), name="sendemail_code"), # 邮箱验证码
- 在deco-user.js文件中编写发送邮件验证码js代码
//修改个人中心邮箱验证码
function sendCodeChangeEmail($btn){
var verify = verifyDialogSubmit(
[
{id: '#jsChangeEmail', tips: Dml.Msg.epMail, errorTips: Dml.Msg.erMail, regName: 'email', require: true}
]
);
if(!verify){
return;
}
$.ajax({
cache: false,
type: "get",
dataType:'json',
url:"/users/sendemail_code/",
data:$('#jsChangeEmailForm').serialize(),
async: true,
beforeSend:function(XMLHttpRequest){
$btn.val("发送中...");
$btn.attr('disabled',true);
},
success: function(data){
if(data.email){
Dml.fun.showValidateError($('#jsChangeEmail'), data.email);
}else if(data.status == 'success'){
Dml.fun.showErrorTips($('#jsChangeEmailTips'), "邮箱验证码已发送");
}else if(data.status == 'failure'){
Dml.fun.showValidateError($('#jsChangeEmail'), "邮箱验证码发送失败");
}else if(data.status == 'success'){
}
},
complete: function(XMLHttpRequest){
$btn.val("获取验证码");
$btn.removeAttr("disabled");
}
});
}
- Debug测试输入已存在的邮箱地址,进行发送
- Debug断点测试完成后,回到页面则提示邮箱已存在
- 为了测试发送邮件,将之前注册时的cdtaogang@sina.com邮箱修改成taogangshow@sina.com,因为之前settings配置的是cdtaogang@sina.com邮箱的配置
- 测试发送邮箱验证码功能
- 但这个邮箱验证码过长,一般都是4位,即所以在send_register_email函数中进行判断
if send_type == "update_email":
code = random_str(4)
else:
code = random_str(16)
- 重新测试发送邮箱验证码,查看验证码位数
- 逻辑说明:紧接着就是根据邮箱验证码完成验证,验证通过后,在数据库中修改邮箱地址,需要说明一点就是当用户发送邮箱验证码成功后,则需要在邮件中记住或复制验证码到输入框中,点击完成按钮,则进行邮箱验证码的验证,当验证通过后则表示修改邮箱地址成功
- 定义修改邮箱类视图
class UpdateEmailView(LoginRequiredMixin, View):
"""修改个人邮箱"""
def post(self, request):
email = request.POST.get("email", "")
code = request.POST.get("code", "")
exited_records = EmailVerifyRecord.objects.filter(email=email, code=code, send_type="update_email")
if exited_records:
user = request.user
user.email = email
user.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
return HttpResponse('{"email":"验证码错误"}', content_type='application/json')
- 定义视图路由
url(r'^update_email/$', UpdateEmailView.as_view(), name="update_email"), # 修改邮箱
- 在deco-user.js中定义邮箱修改js代码
//个人资料邮箱修改
function changeEmailSubmit($btn){
var verify = verifyDialogSubmit(
[
{id: '#jsChangeEmail', tips: Dml.Msg.epMail, errorTips: Dml.Msg.erMail, regName: 'email', require: true},
]
);
if(!verify){
return;
}
$.ajax({
cache: false,
type: 'post',
dataType:'json',
url:"/users/update_email/ ",
data:$('#jsChangeEmailForm').serialize(),
async: true,
beforeSend:function(XMLHttpRequest){
$btn.val("发送中...");
$btn.attr('disabled',true);
$("#jsChangeEmailTips").html("验证中...").show(500);
},
success: function(data) {
if(data.email){
Dml.fun.showValidateError($('#jsChangeEmail'), data.email);
}else if(data.status == "success"){
Dml.fun.showErrorTips($('#jsChangePhoneTips'), "邮箱信息更新成功");
setTimeout(function(){location.reload();},1000);
}else{
Dml.fun.showValidateError($('#jsChangeEmail'), "邮箱信息更新失败");
}
},
complete: function(XMLHttpRequest){
$btn.val("完成");
$btn.removeAttr("disabled");
}
});
}
- Debug测试修改邮箱地址(输入不存在的验证码)
- Debug断点测试完成后,查看修改页面,成功显示出错误信息
- 测试修改邮箱功能
8.个人资料修改
- 因为使用post表单提交的请求方式,所以直接在UserInfoView类视图中定义post方法来完成用户个人资料的修改保存操作
class UserInfoView(LoginRequiredMixin, View):
"""用户信息页"""
def get(self, request):
current_page = "info"
return render(request, "usercenter-info.html", {"current_page":current_page})
def post(self, request):
user_info_form = UserInfoForm(request.POST, instance=request.user)
if user_info_form.is_valid():
user_info_form.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
else:
return HttpResponse(json.dumps(user_info_form.errors), content_type='application/json')
- 在deco-user.js中编写js代码来操控表单的提交操作以及提示信息展示
//保存个人资料
$('#jsEditUserBtn').on('click', function(){
var _self = $(this),
$jsEditUserForm = $('#jsEditUserForm');
verify = verifySubmit(
[
{id: '#nick_name', tips: Dml.Msg.epNickName, require: true}
]
);
if(!verify){
return;
}
$.ajax({
cache: false,
type: 'post',
dataType:'json',
url:"/users/info/",
data:$jsEditUserForm.serialize(),
async: true,
beforeSend:function(XMLHttpRequest){
_self.val("保存中...");
_self.attr('disabled',true);
},
success: function(data) {
if(data.nick_name){
_showValidateError($('#nick_name'), data.nick_name);
}else if(data.birday){
_showValidateError($('#birth_day'), data.birday);
}else if(data.address){
_showValidateError($('#address'), data.address);
}else if(data.status == "failure"){
Dml.fun.showTipsDialog({
title: '保存失败',
h2: data.msg
});
}else if(data.status == "success"){
Dml.fun.showTipsDialog({
title: '保存成功',
h2: '个人信息修改成功!'
});
setTimeout(function(){window.location.href = window.location.href;},1500);
}
},
complete: function(XMLHttpRequest){
_self.val("保存");
_self.removeAttr("disabled");
}
});
});
- Debug测试输入空字段表单进行修改
- Debug断点测试完成后回到个人信息页面,则成功出现form表单验证的错误提示
- 测试个人资料修改功能
三丶用户个人中心(我的课程)
1.显示我的课程页面
- 模板继承
- 定义个人资料类视图
class MyCourseView(LoginRequiredMixin,View):
"""我的课程"""
def get(self, request):
current_page = "course"
return render(request, "usercenter-mycourse.html", {"current_page":current_page})
- 定义视图路由
url(r'^mycourse/$', MyCourseView.as_view(), name="mycourse"), # 我的课程
- 修改模板中我的课程链接地址以及判断链接激活状态
<li class="{% if current_page == 'course' %}active2{% endif %}"><a href="{% url 'users:mycourse' %}">我的课程</a></li>
- 测试显示我的课程页面
2.动态显示出我的课程数据
- 在视图中根据当前的登录成功的user对象到UserCourse模型表中查询对应usercourse对象
class MyCourseView(LoginRequiredMixin,View):
"""我的课程"""
def get(self, request):
current_page = "course"
user_courses = UserCourse.objects.filter(user=request.user)
return render(request, "usercenter-mycourse.html", {"current_page":current_page, "user_courses":user_courses})
- 在模板中打印页面数据
<div class="group_list brief">
{% for user_course in user_courses %}
<div class="module1_5 box">
<a href="{% url 'course:course_detail' user_course.course.id%}">
<img width="214" height="190" class="scrollLoading"
src="{{ MEDIA_URL }}{{ user_course.course.image }}"/>
</a>
<div class="des">
<a href="{% url 'course:course_detail' user_course.course.id%}"><h2>{{ user_course.course.name }}</h2></a>
<span class="fl">课时:<i class="key">{{ user_course.course.learn_times }}</i></span>
<span class="fr">学习人数:{{ user_course.course.students }}</span>
</div>
<div class="bottom">
<span class="fl">{{ user_course.course.course_org.name }}</span>
<span class="star fr notlogin" data-favid="15">{{ user_course.course.fav_nums }}</span>
</div>
</div>
{% endfor %}
</div>
- 测试显示我的课程页面动态数据
三丶
四丶用户个人中心(我的收藏)
再次说明:我的收藏模块包括三个页面(课程机构丶授课教师丶公开课程)
1.我的收藏——课程机构页面
- 模板继承
- 定义视图
class MyFavOrgView(LoginRequiredMixin,View):
"""我的收藏——课程机构"""
def get(self, request):
current_page = "myfav"
current_fav_page_1 = "myfav_org"
org_list = []
fav_orgs = UserFavorite.objects.filter(user=request.user, fav_type=2)
for fav_org in fav_orgs:
org_id = fav_org.fav_id
org = CourseOrg.objects.get(id=org_id)
org_list.append(org)
return render(request, "usercenter-fav-org.html", {"current_page":current_page, "current_fav_page_1":current_fav_page_1, "org_list":org_list})
- 定义视图路由
url(r'^myfav/org/$', MyFavOrgView.as_view(), name="myfav_org"), # 我的收藏——课程机构
- 修改父模板我的收藏链接地址以及判断点击激活状态
<li class="{% if current_page == 'myfav' %}active2{% endif %}"><a href="{% url 'users:myfav_org' %}">我的收藏</a></li>
- 修改模板中课程机构链接地址以及判断点击激活状态,这里根据current_fav_page_1来判断我的收藏页面中的课程机构激活状态
<li class="{% if current_fav_page_1 == 'myfav_org' %}active{% endif %}"><a href="{% url 'users:myfav_org' %}">课程机构</a> </li>
- 在模板中打印数据
<div class="messagelist">
{% for org in org_list %}
<div class="messages butler_list company company-fav-box">
<dl class="des fr">
<dt>
<a href="{% url 'org:org_home' org.id%}">
<img width="160" height="90" src="{{ MEDIA_URL }}{{ org.image }}"/>
</a>
</dt>
<dd>
<h1><a href="{% url 'org:org_home' org.id%}">{{ org.name }}</a></h1>
<div class="pic fl" style="width:auto;">
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</div>
<span class="c8 clear">{{ org.address }}</span>
<div class="delete jsDeleteFav_org" data-favid="1"></div>
</dd>
</dl>
</div>
{% endfor %}
</div>
- 测显示我的收藏——课程机构页面动态数据
- 测试对机构进行收藏后,查看我的收藏——课程机构页面是否成功显示出收藏的机构
2.我的收藏——授课教师页面
- 模板继承
- 逻辑跟课程机构页面一样,首先定义视图
class MyFavTeacherView(LoginRequiredMixin,View):
"""我的收藏——授课讲师"""
def get(self, request):
current_page = "myfav"
current_fav_page_2 = "myfav_teacher"
teacher_list = []
fav_teachers = 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", {"current_page":current_page, "current_fav_page_2":current_fav_page_2, "teacher_list":teacher_list})
- 定义视图路由
url(r'^myfav/teacher/$', MyFavTeacherView.as_view(), name="myfav_teacher"), # 我的收藏——授课教师
- 修改模板中的授课教师链接地址,根据current_fav_page_2变量的值来判断授课教师按钮的激活状态
<li class="{% if current_fav_page_2 == 'myfav_teacher' %}active{% endif %}"><a href="{% url 'users:myfav_teacher' %}">授课教师 </a></li>
- 页面需要显示出课程数,则需要在Teacher模型类中定义一个方法,来获取教师的课程数
def get_course_nums(self):
return self.course_set.all().count()
- 在模板中打印数据
<div class="messagelist">
{% for teacher in teacher_list %}
<div class=" butler_list butler-fav-box">
<dl class="des users">
<dt>
<a href="{% url 'org:teacher_detail' teacher.id %}">
<img width="100" height="100" src="{{ MEDIA_URL }}{{ teacher.image }}"/>
</a>
</dt>
<dd>
<h1>
<a href="{% url 'org:teacher_detail' teacher.id %}">
{{ teacher.name }}<span class="key">认证教师</span>
</a>
</h1>
<ul class="cont clearfix">
<li class="time">工作年限:<span>{{ teacher.work_years }}年</span></li>
<li class="c7">课程数:<span>{{ teacher.get_course_nums }}</span></li>
</ul>
<ul class="cont clearfix">
<li class="time">工作公司:<span>{{ teacher.work_company }}</span></li>
<li class="c7">公司职位:<span>{{ teacher.work_position }}</span></li>
</ul>
</dd>
<div class="delete jsDeleteFav_teacher" data-favid="1"></div>
</dl>
</div>
{% endfor %}
</div>
- 测试显示我的收藏——授课教师页面动态数据
- 测试对授课讲师进行收藏后,在个人中心我的收藏——授课教师页面,显示收藏的授课讲师
3.我的收藏——公开课程页面
- 模板继承
- 定义视图
class MyFavCourseView(LoginRequiredMixin,View):
"""我的收藏——公开课程"""
def get(self, request):
current_page = "myfav"
current_fav_page_3 = "myfav_course"
course_list = []
fav_courses = 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", {"current_page":current_page, "current_fav_page_3":current_fav_page_3, "course_list":course_list})
- 定义视图路由
url(r'^myfav/course/$', MyFavCourseView.as_view(), name="myfav_course"), # 我的收藏——公开课程
- 修改模板中公开课程链接地址,根据current_fav_page_3变量的值来判断我的收藏——公开课程点击激活状态
<li class="{% if current_fav_page_3 == 'myfav_course' %}active{% endif %}"><a href="{% url 'users:myfav_course' %}">公开课程</a></li>
- 在模板中打印页面数据
<div class="group_list brief">
{% for course in course_list %}
<div class="module1_5 box">
<a href="{% url 'course:course_detail' course.id%}">
<img width="214" height="190" src="{{ MEDIA_URL }}{{ course.image }}"/>
</a>
<div class="des">
<a href="course-detail.html"><h2>{{ course.name }}</h2></a>
<span class="fl">时长:<i class="key">{{ course.learn_times }}</i></span>
<span class="fr">学习人数:{{ course.students }}</span>
</div>
<div class="bottom">
<span class="fl">{{ course.course_org }}</span>
<span class="delete-group fr jsDeleteFav_course" data-favid="12"></span>
</div>
</div>
{% endfor %}
</div>
- 测试显示我的收藏——公开课程页面动态数据
- 测试在收藏公开课,回到个人中心我的收藏——公开课程页面,查看收藏的公开课
4.我的收藏所有页面中删除功能(取消收藏)
- 在父模板usercenter_base模板中编写三个页面的点击事件,使用ajax post异步请求方式,向后端AddFavView视图接口发送请求完成数据库对UserFavorite模型表的数据删除操作
<script type="text/javascript">
$('.jsDeleteFav_course').on('click', function(){
var _this = $(this),
favid = _this.attr('data-favid');
{#alert(favid);#}
$.ajax({
cache: false,
type: "POST",
url: "/org/add_fav/",
data: {
fav_type: 1,
fav_id: favid,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
async: true,
success: function(data) {
Dml.fun.winReload();
}
});
});
$('.jsDeleteFav_teacher').on('click', function(){
var _this = $(this),
favid = _this.attr('data-favid');
$.ajax({
cache: false,
type: "POST",
url: "/org/add_fav/",
data: {
fav_type: 3,
fav_id: favid,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
async: true,
success: function(data) {
Dml.fun.winReload();
}
});
});
$('.jsDeleteFav_org').on('click', function(){
var _this = $(this),
favid = _this.attr('data-favid');
$.ajax({
cache: false,
type: "POST",
url: "/org/add_fav/",
data: {
fav_type: 2,
fav_id: favid,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
async: true,
success: function(data) {
Dml.fun.winReload();
}
});
});
</script>
- 在三个页面对应的模板文件中,需要修改favid得值也就是删除按钮所在的标签中data-favid属性的值为页面模型表的id
<!-- 课程机构 -->
<div class="delete jsDeleteFav_org" data-favid="{{ org.id }}"></div>
<!-- 授课教师 -->
<div class="delete jsDeleteFav_teacher" data-favid="{{ teacher.id }}"></div>
<!-- 公开课程 -->
<span class="delete-group fr jsDeleteFav_course" data-favid="{{ course.id }}"></span>
- 测试我的收藏三个页面删除功能(取消收藏)
五丶用户个人中心(我的消息)
1.显示我的消息页面
- 模板继承
- 定义视图
class MyMessageView(LoginRequiredMixin, View):
"""我的消息"""
def get(self, request):
current_page = "message"
return render(request, "usercenter-message.html", {"current_page": current_page})
- 定义视图路由
url(r'^mymessage/$', MyMessageView.as_view(), name="mymessage"), # 我的消息
- 在父模板中修改我的消息链接地址,以及根据厚端传递的current_page的值判断点击激活状态
<li class="{% if current_page == 'message' %}active2{% endif %}">
<a href="{% url 'users:mymessage' %}" style="position: relative;">
我的消息
</a>
</li>
- 测试显示个人中心——我的消息页面
2.动态显示我的消息页面数据
说明:当用户注册成功后,应该在模型表UserMessage添加欢迎用户注册消息
分析:除了在我的消息中显示所有的用户消息之外,在每个页面顶部会有个小喇叭按钮,该按钮会显示出用户未读的消息数量,当用户点击进入我的消息后,则小喇叭的数量为0
- 首先在注册视图RegisterView中向UserMessage模型表中写入欢迎注册消息
# 写入欢迎注册消息
user_message = UserMessage()
user_message.user = user_profile
user_message.message = "欢迎注册慕学在线网"
user_message.save()
- 在MyMessageView类视图中首先获取当前登录用户所有的消息,该获取的所有消息用于在我的消息页面进行显示,不管用户已读还是未读都进行显示到此页面;再从数据库中获取该用户的未读消息,遍历每个对象设置每个对象的has_read字段的值为True也就是设置为已读;接下来就是分页数据处理了,这个已经在很多列表页中进行说明了,就不细说了
class MyMessageView(LoginRequiredMixin, View):
"""我的消息"""
def get(self, request):
current_page = "message"
# 查询当前用户的所有消息
all_messages = UserMessage.objects.filter(user=request.user.id)
# 用户进入个人消息后清空未读消息的记录
all_unread_messages = UserMessage.objects.filter(user=request.user.id, has_read=False)
for unread_message in all_unread_messages:
unread_message.has_read = True
unread_message.save()
# 分页处理
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(all_messages, 5, request=request)
messages = p.page(page)
return render(request, "usercenter-message.html", {"current_page": current_page, "messages": messages})
- 在模板中打印显示页面动态数据
<div class="head">
<ul class="tab_header messagehead">
<li class="active"><a href="{% url 'users:mymessage' %}">个人消息</a></li>
</ul>
</div>
<div class="messagelist">
{% for message in messages.object_list %}
<div class="messages">
<div class="fr">
<div class="top"><span class="fl time">{{ message.add_time }}</span><span
class="fr btn foldbtn"></span></div>
<p>
{{ message.message }}
</p>
</div>
</div>
{% endfor %}
</div>
- 页面分页代码
<div class="pageturn pagerright">
<ul class="pagelist">
{% if messages.has_previous %}
<li class="long"><a href="?{{ messages.previous_page_number.querystring }}">上一页</a></li>
{% endif %}
{% for page in messages.pages %}
{% if page %}
{% ifequal page messages.number %}
<li class="active"><a href="?{{ page.querystring }}">{{ page }}</a></li>
{% else %}
<li><a href="?{{ page.querystring }}" class="page">{{ page }}</a></li>
{% endifequal %}
{% else %}
<li class="none"><a href="">...</a></li>
{% endif %}
{% endfor %}
{% if messages.has_next %}
<li class="long"><a href="?{{ messages.next_page_number.querystring }}">下一页</a></li>
{% endif %}
</ul>
</div>
- 为了方便测试,所以在xadmin后台进行数据添加,需要注意的是用户字段(user)是填写用户的id,因为当初在模型类中user字段为int类型,默认是否已读字段为False
- 测试显示页面动态数据
3.在页面顶部栏小喇叭处显示未读消息
说明:因为所有页面顶部都设计小喇叭消息,所以需要在UserProfile模型类中,需定义一个方法来获取当前用户未读消息数量,可以让每个模板进行调用此方法
- 在UserProfile模型类中获取用户未读消息数量
def get_user_unread_nums(self):
"""获取用户未读消息数量"""
from operation.models import UserMessage
return UserMessage.objects.filter(user=self.id, has_read=0).count()
- 因为每个页面都涉及顶部小喇叭消息,所以需要在所有父模板文件中,首先修改链接地址,然后通过request.user对象调用其模型类中的定义的get_user_unread_nums方法获取未读数量
<a href="{% url 'users:mymessage' %}">
<div class="msg-num"><span id="MsgNum">{{ request.user.get_user_unread_nums }}</span></div>
</a>
- 测试之前需要在数据库中设置has_read字段的值为0也就表示未读消息,好方便进行测试
- 测试查看各个页面顶部小喇叭未读消息数量是否显示正确成功,然后进入我的消息页面,即各个页面顶部小喇叭未读消息数量则显示为0
六丶用户个人中心(退出)
1.定义退出视图
class LogoutView(View):
"""退出登录"""
def get(self, request):
logout(request)
# 重定向到主页
return HttpResponseRedirect(reverse('index'))
2.在根级urls中定义视图路由
url(r'^logout/$', LogoutView.as_view(), name="logout"), # 用户退出
3.在三个父模板中,修改退出功能按钮链接地址
<a class="fr" href="{% url 'logout' %}">退出</a>
4.测试退出功能
七丶 首页、全局功能细节和404以及500页面配置
说明:将所有页涉及到的跳转链接地址进行修改(此细节就不做演示了)
1.全局功能细节
- 课程点击数增加(courses/views.CourseInfoView)
# 当用点击开始学习进入章节信息页后则学习人数
course.students += 1
course.save()
- 教师点击数增加(organization/views.TeacherDetailView)
teacher.click_nums += 1
teacher.save()
- 机构点击数增加(organization/views.OrgHomeView)
course_org.click_nums += 1
course_org.save()
- 课程学习人数的增加(courses/views.VideoPlayView)
# 当用点击开始学习进入章节信息页后则学习人数
course.students += 1
course.save()
- 课程丶课程机构丶讲师收藏数的增加(organization/views.AddFavView)
# 设置课程丶机构丶讲师的收藏数+1
if int(fav_type) == 1:
course = Course.objects.get(id=int(fav_id))
course.fav_nums -= 1
if course.fav_nums < 0:
course.fav_nums = 0
course.save()
elif int(fav_type) == 2:
course_org = CourseOrg.objects.get(id=int(fav_id))
course_org.fav_nums -= 1
if course_org.fav_nums < 0:
course_org.fav_nums = 0
course_org.save()
elif int(fav_type) == 3:
teacher = Teacher.objects.get(id=int(fav_id))
teacher.fav_nums -= 1
if teacher.fav_nums < 0:
teacher.fav_nums = 0
teacher.save()
- 课程丶课程机构丶讲师收藏数的减少(organization/views.AddFavView)
# 设置课程丶机构丶讲师的收藏数-1
if int(fav_type) == 1:
course = Course.objects.get(id=int(fav_id))
course.fav_nums += 1
course.save()
elif int(fav_type) == 2:
course_org = CourseOrg.objects.get(id=int(fav_id))
course_org.fav_nums += 1
course_org.save()
elif int(fav_type) == 3:
teacher = Teacher.objects.get(id=int(fav_id))
teacher.fav_nums += 1
teacher.save()
2.首页动态数据显示
- 定义视图
class IndexView(View):
"""主页"""
def get(self, request):
# 驱逐主页轮播图
all_banners = Banner.objects.all().order_by('index')
# 取出主页课程栏的6门课程
courses = Course.objects.filter(is_banner=False)[:6]
# 取出主页课程栏的3门轮播课程
banner_courses = Course.objects.filter(is_banner=True)[:3]
# 取出主页授课机构栏的15门机构
course_orgs = CourseOrg.objects.all()[:15]
return render(request, "index.html", {"all_banners":all_banners, "courses":courses,
"banner_courses":banner_courses, "course_orgs":course_orgs})
- 在根据urls中配置路由对应的视图
url(r'^$', IndexView.as_view(), name="index"), # 主页
- 在xadmin后端中添加主页轮播图
- 在主页公开课程栏中还嵌套了一个小的轮播图,所以需要在Course模型类中增加一个字段,来判断该课程是否进行轮播标记
is_banner = models.BooleanField(default=False, verbose_name=u"是否轮播")
- 生成对应数据表字段
- 需要在xadmin后台中课程中设置要进行轮播的课程
- 在首页课程机构栏列表中的课程需要添加tag机构标签,所以需在organization/models中的CourseOrg模型表添加该字段
tag = models.CharField(default="全国知名", max_length=10, verbose_name=u"机构标签")
- 生成对应的数据表字段
- 在index模板中打印页面动态数据
<!-------------- 轮播图 -------------->
<div class="imgslide">
<ul class="imgs">
{% for banner in all_banners %}
<li>
<a href="{{ banner.url }}">
<img width="1200" height="478"
src="{{ MEDIA_URL }}{{ banner.image }}"/>
</a>
</li>
{% endfor %}
</ul>
</div>
<!-------------- 公开课程 -------------->
<div class="module1_1 left">
<img width="228" height="614" src="{% static 'images/module1_1.jpg' %}"/>
<p class="fisrt_word">名师授课<br/>专业权威</p>
<a class="more" href="{% url 'course:course_list' %}">查看更多课程 ></a>
</div>
<div class="right group_list">
<div class="module1_2 box">
<div class="imgslide2">
<ul class="imgs">
{% for banner_course in banner_courses %}
<li>
<a href="{% url 'course:course_detail' banner_course.id %}">
<img width="470" height="300"
src="{{ MEDIA_URL }}{{ banner_course.image }}"/>
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="unslider-arrow2 prev"></div>
<div class="unslider-arrow2 next"></div>
</div>
{% for course in courses %}
<div class="module1_{{ forloop.counter|add:2 }} box">
<a href="{% url 'course:course_detail' course.id%}">
<img width="233" height="190" src="{{ MEDIA_URL }}{{ course.image }}"/>
</a>
<div class="des">
<a href="course-detail.html">
<h2 title="django入门">{{ course.name }}</h2>
</a>
<span class="fl">难度:<i class="key">{{ course.get_degree_display }}</i></span>
<span class="fr">学习人数:{{ course.students }}</span>
</div>
<div class="bottom">
<span class="fl" title="慕课网">{{ course.course_org.name }}</span>
<span class="star fr">{{ course.fav_nums }}</span>
</div>
</div>
{% endfor %}
</div>
<!-------------- 课程机构 -------------->
<div class="module3 eachmod">
<div class="module3_1 left">
<img width="228" height="463" src="{% static 'images/module3_1.jpg' %}"/>
<p class="fisrt_word">名校来袭<br/>权威认证</p>
<a class="more" href="{% url 'org:org_list' %}">查看更多机构 ></a>
</div>
<div class="right">
<ul>
{% for course_org in course_orgs %}
<li class="{% if forloop.counter|divisibleby:'5' %}five{% endif %}">
<a href="{% url 'org:org_home' course_org.id %}">
<div class="company">
<img width="184" height="100" src="{{ MEDIA_URL }}{{ course_org.image }}"/>
<div class="score">
<div class="circle">
<h2>{{ course_org.tag }}</h2>
</div>
</div>
</div>
<p><span class="key" title="{{ course_org.name }}">{{ course_org.name }}</span></p>
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
- 测试显示主页动态数据
3.404以及500页面配置
- 在users/views中定义一个方法,获取404页面的响应数据并进行返回
def page_not_found(request):
"""全局404页面"""
from django.shortcuts import render_to_response
response = render_to_response("404.html")
response.status_code = 404
return response
- 在根级urls中配置全局404路由
# 全局404页面配置
handler404 = "users.views.page_not_found"
- 还需要将项目开发环境配置成生产环境(线上环境)
DEBUG = False
ALLOWED_HOSTS = ["*"]
- 访问不存在的页面http://127.0.0.1:8000/111 结果成功渲染了指定的404页面,但页面的静态资源文件并没有加载成功,这个问题在之前的项目Django项目中已经说明过了,因为将settings中的DEBUG设置为False后django就不会帮我们管理静态资源文件了
- 解决方法就是:想要在线上环境加载静态资源文件,并且不使用代理服务器如apache或nginx,那么就像配置media静态目录上传的图片那样进行配置,首先在settings文件中添加静态资源目录static
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
- 然后在根级urls中配置静态文件处理函数
url(r'^static/(?P<path>.*)$', serve, {"document_root":STATIC_ROOT}), # 配置静态文件处理函数
- 刷新页面成功加载出404页资源文件
- 500页面跟上面逻辑代码啥的都一样,首先在users/views中定义一个方法,获取500页面的响应数据并进行返回
def page_server_error(request):
"""全局404页面"""
from django.shortcuts import render_to_response
response = render_to_response("500.html")
response.status_code = 500
return response
- 在根级urls中配置全局500路由
handler500 = "users.views.page_server_error"
- 测试显示500服务器错误页面,完成该测试需要在主页视图IndexView中打印1/0则会抛出服务器错误500状态码