- Django 开发 MxOnline 项目笔记 -- 第10章
- Django 开发 MxOnline 项目笔记 -- 第11章
- Django 开发 MxOnline 项目笔记 -- 第12章
- Django 开发 MxOnline 项目笔记 -- 第9章 课
- Django 开发 MxOnline 项目笔记 -- 第6章 用
- Django 开发 MxOnline 项目笔记 -- 第7章 课
- Django 开发 MxOnline 项目笔记 -- 第8章 课
- CoolBlog开发笔记第1课:项目分析
- django_rest_framework 入门笔记:分页,多条
- django_rest_framework 入门笔记:视图函数重
一、全局搜索功能实现
-
实现全局搜索功能前还需要解决的问题
(1)因为首页 "index.html" 页面没有继承 “base.html” 页面,开发全局搜索功能前先将页面改成继承 “base.html”,这样所有带有搜索功能页面就全部继承 “base.html”,就可以实现全局搜索功能了
01.png
(2)关于导航条激活状态的实现

①第一种方法,类视图中传递一个变量到前端,会被向上传递到 “base.html” 中,在 “base.html” 中导航条部分代码做判断,如果当前类视图被调用,变量传递到前端,前端页面定义的变量与后端传递过来的相等,则当前导航条添加 “active” class, 也就是处于激活状态,这种方法弊端是每个页面都要做判断,麻烦,容易漏掉,出错


②第二种方法,直接在前端页面通过 “request.path” 变量来判断,“request.path” 是当前访问的网址,django 自带的变量,可以通过 slice 过滤器截取 “request.path” 字符串的前几位与自定义的当前页面变量所比较
# templates/index.html
<div class="nav">
<div class="wp">
<ul>
<li {% if request.path == '/' %}class="active"{% endif %}><a href="{% url "index" %}">首页</a></li>
{# 千万注意 request.path|slice:"7" 写的时候, slice:紧接着写"7",冒号后面一定不能有空格 #}
{# 写成 request.path|slice: "7" 这样会报错 #}
<li {% if request.path|slice:"7" == "/course" %}class="active"{% endif %}>
<a href="{% url "course:course_list" %}">
公开课<img class="hot" src="{% static "images/nav_hot.png" %}">
</a>
</li>
<li {% if request.path|slice:"12" == "/org/teacher" %}class="active"{% endif %}>
<a href="{% url "org:teacher_list" %}">授课教师</a>
</li>
<li {% if request.path|slice:"9" == "/org/list" %}class="active"{% endif %}><a href="{% url "org:org_list" %}">授课机构</a></li>
</ul>
</div>
</div>
- 全局搜索功能实现
-
搜索功能可以实现“公开课、课程机构、授课老师”的搜索,选择要搜索的内容,输入搜索文字后会自动跳转相应页面
05.png
比如用户选择“公开课”,输入内容进行搜索,页面自动跳转到公开课列表页,并将搜索结果展示出来,这个过程是通过 get 请求在 url 中传参来实现的,所以要重写课程列表页类视图 CourseListView
06.png


- 修改类视图 CourseListView
# apps/courses/views.py
class CourseListView(View):
"""
课程列表页类视图
"""
def get(self, request):
# 所有课程
all_courses = Course.objects.all().order_by("-add_time")
# 课程总数量
course_nums = Course.objects.count()
# 热门课程
hot_courses = Course.objects.all().order_by("-click_nums")[:3]
# 课程搜索
search_keywords = request.GET.get("keywords", "")
# 注意这里 name__icontains 是 django 的用法, django 的 orm 会自动
# 执行一条 like 的 SQL 语句在数据库中进行模糊查询, 其中 i 代表忽略大
# 小写, 通常情况下, django 中出现 i 都是忽略大小写的意思
# all_courses = Course.objects.filter(name__icontains=search_keywords)
# 我们不仅要搜索 name 中包含 search_keywords 的数据, 也要搜索 describe 和
# detail 字段中也包含 search_keywords 的数据, 所以用 Q 来做 “或” 操作
all_courses = Course.objects.filter(
Q(name__icontains=search_keywords) |
Q(describe__icontains=search_keywords) |
Q(detail__icontains=search_keywords))
# 课程排序
sort = request.GET.get("sort", "")
if sort:
if sort == "hot":
all_courses = all_courses.order_by("-click_nums")
elif sort == "students":
all_courses = all_courses.order_by("-students")
# 对课程进行分页
try:
page = request.GET.get("page", 1)
except PageNotAnInteger:
page = 1
p = Paginator(all_courses, 3, request=request)
courses = p.page(page)
context = {
"all_courses": courses,
"course_nums": course_nums,
"hot_courses": hot_courses,
"sort": sort,
}
return render(request, "course-list.html", context)
-
这是前端页面搜索按钮的 js 代码 “deco-common.js”
09.png
-
测试搜索功能
010.png
011.png
-
课程机构和授课老师的搜索功能同理
二、个人中心功能开发
-
用户点击进入个人中心按钮跳转到个人中心页面
012.png
013.png
-
个人中心总共有6个页面,拷贝到 templates/ 目录下
014.png
-
定义 “userconter_base.html” 页面,其他6个页面都继承此页面
- 个人信息展示页
- 定义 UserInfoView
# apps/users/views.py
class UserInfoView(LoginRequiredMixin, View):
"""
用户个人信息类视图
只有用户登录后才能访问, 所以需要继承 LoginRequiredMixin
"""
def get(self, request):
context = {}
return render(request, "usercenter-info.html", context)
- 配置 url
# apps/users/urls.py
from django.urls import path, re_path
from .views import UserInfoView
app_name = "users"
urlpatterns = [
# 用户个人信息页
path("info/", UserInfoView.as_view(), name="user_info"),
]
- 前端页面 “usercenter-info.html” 代码
# templates/usercenter-info.html
{% extends "userconter_base.html" %}
{% block custom_bread %}
<section>
<div class="wp">
<ul class="crumbs">
<li><a href="{% url "index" %}">首页</a>></li>
<li><a href="/user/home/">个人中心</a>></li>
<li>个人信息</li>
</ul>
</div>
</section>
{% endblock custom_bread %}
{% block right_content %}
<div class="right">
<div class="personal_des ">
<div class="head" style="border:1px solid #eaeaea;">
<h1>个人信息</h1>
</div>
<div class="inforcon">
<div class="left" style="width:242px;">
<iframe id='frameFile' name='frameFile' style='display: none;'></iframe>
<form class="clearfix" id="jsAvatarForm" enctype="multipart/form-data" autocomplete="off" method="post" action="/users/image/upload/" 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>
{% csrf_token %}
</form>
<div style="border-top:1px solid #eaeaea;margin-top:30px;">
<a class="button btn-green btn-w100" id="jsUserResetPwd" style="margin:80px auto;width:100px;">修改密码</a>
</div>
</div>
<form class="perinform" 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>生 日:
{# django 为我们提供了过滤器 default_if_none:'' 来判断如果值为 None 的时候,设定显示的默认值 #}
{# 如果这里不设定用户生日为空时显示空字符串的话, 前端页面表单部分会显示成 None, 不够友好 #}
<input type="text" id="birth_day" name="birday" value="{{ request.user.birthday|default_if_none:'' }}" 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>
{% csrf_token %}
</form>
</div>
</div>
</div>
{% endblock right_content %}
-
访问效果
015.png
- 修改头像功能
- 定义 UploadImageView 类视图
# apps/users/views.py
class UploadImageView(LoginRequiredMixin, View):
"""
用户修改头像类视图
"""
def post(self, request):
# 如何获取用户的头像呢?
# 实际上在 django 的 admin 中, 如果 form 被定义为文件
# 的时候, django 会自动对上传的文件做保存的
image_form = UploadImageForm(request.POST, request.FILES)
if image_form.is_valid():
image = image_form.cleaned_data["image"]
request.user.image = image
request.user.save()
return JsonResponse(data='{"status": "success"}', safe=False)
else:
return JsonResponse(data='{"status": "fail"}', safe=False)
- 定义 用户上传头像表单
# apps/users/forms.py
class UploadImageForm(forms.ModelForm):
"""
用户上传头像表单
"""
class Meta:
model = UserProfile
fields = ["image"]
- 配置 url
# apps/users/urls.py
from django.urls import path, re_path
from .views import UserInfoView, UploadImageView
app_name = "users"
urlpatterns = [
# 用户个人信息页
path("info/", UserInfoView.as_view(), name="user_info"),
# 用户头像上传
path("image/upload/", UploadImageView.as_view(), name="image_upload"),
]
-
测试可以成功修改用户头像
016.png
- 修改密码功能
- 定义个人中心修改用户密码类视图 UpdatePwdView
# apps/users/views.py
class UpdatePwdView(View):
"""
个人中心修改用户密码类视图
逻辑同 ModifyPwdView 类似, 只不过这里因为用户是已经登录状态, 不需要验证用户邮箱
"""
def post(self, request):
modify_pwd_form = ModifyPwdForm(request.POST)
if modify_pwd_form.is_valid():
# 如果 form 验证成功, 取出用户输入的两次密码和邮箱
password1 = request.POST.get("password1", "")
password2 = request.POST.get("password2", "")
# 如果两次输入密码不一致, 返回此页面和错误信息
if password1 != password2:
return JsonResponse(data='{"status": "fail", "msg": "两次输入密码不一致"}', safe=False)
# 如果两次输入密码相同, 保存用户新密码到数据库, 并返回登录页
user = request.user
user.password = make_password(password1)
user.save()
return JsonResponse(data='{"status": "success"}', safe=False)
else:
# 如果 form 验证失败, 返回此页面和错误信息
return JsonResponse(data=json.dumps(modify_pwd_form.errors), safe=False)
- 配置 url
# apps/users/urls.py
from django.urls import path, re_path
from .views import UserInfoView, UploadImageView, UpdatePwdView
app_name = "users"
urlpatterns = [
# 用户个人信息页
path("info/", UserInfoView.as_view(), name="user_info"),
# 用户头像上传
path("image/upload/", UploadImageView.as_view(), name="image_upload"),
# 用户个人中心修改密码
path("update/pwd/", UpdatePwdView.as_view(), name="update_pwd"),
]
- 前端修改密码提交表单部分代码
# templates/usercenter_base.html
<form id="jsResetPwdForm" autocomplete="off">
<div class="box">
<span class="word2" >新 密 码</span>
<input type="password" id="pwd" name="password1" placeholder="6-20位非中文字符"/>
</div>
<div class="box">
<span class="word2" >确定密码</span>
<input type="password" id="repwd" name="password2" placeholder="6-20位非中文字符"/>
</div>
<div class="error btns" id="jsResetPwdTips"></div>
<div class="button">
<input id="jsResetPwdBtn" type="button" value="提交" />
</div>
{% csrf_token %}
</form>
- 相关部分 js 代码
# static/js/deco-user.js
//个人资料修改密码
$('#jsUserResetPwd').on('click', function(){
Dml.fun.showDialog('#jsResetDialog', '#jsResetPwdTips');
});
$('#jsResetPwdBtn').click(function(){
$.ajax({
cache: false,
type: "POST",
dataType:'json',
//注意, 这地方的 url 只能这样写, 不能写成 {% url "users:update_pwd" %},
// 这种写法只有在 .html 文件中支持, .js 中不支持
url:"/users/update/pwd/",
data:$('#jsResetPwdForm').serialize(),
async: true,
success: function(data) {
var data=JSON.parse(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);
}
}
});
});
-
测试
017.png
-
修改邮箱
注册的时候是让用户验证过邮箱的,所以修改的时候是不能让用户随意修改的,修改邮箱也是要通过验证码来完成的
018.png
- 定义 SendEmailCodeView 和 UpdateEmailView 两个类视图,分别用于发送邮箱验证码和修改邮箱
# apps/users/views.py
class SendEmailCodeView(LoginRequiredMixin, View):
"""
发送邮箱验证码类视图
"""
def get(self, request):
email = request.GET.get("email", "")
# 用户在修改邮箱的时候, 要判断这个邮箱是否已经注册过,
# 如果注册过, 是不能进行修改的
if UserProfile.objects.filter(email=email):
# 如果邮箱已经存在, 直接返回邮箱信息
return JsonResponse(data='{"email": "邮箱已经存在"}', safe=False)
# 邮箱不存在, 向邮箱发送验证码
send_register_email(email, "update_email")
return JsonResponse(data='{"status": "success"}', safe=False)
class UpdateEmailView(View):
"""
修改邮箱类视图
"""
def post(self, request):
email = request.POST.get("email", "")
code = request.POST.get("code", "")
# 已存在的记录
existed_records = EmailVerifyRecord.objects.filter(email=email, code=code, send_type="update_email")
if existed_records:
# 如果记录已经存在
user = request.user
user.email = email
user.save()
return JsonResponse(data='{"status": "success"}', safe=False)
else:
return JsonResponse(data='{"email": "验证码出错"}', safe=False)
- 配置 url
# apps/users/urls.py
from django.urls import path, re_path
from .views import UserInfoView, UploadImageView, UpdatePwdView, SendEmailCodeView, UpdateEmailView
app_name = "users"
urlpatterns = [
...
# 发送邮箱验证码
path("sendemail_code/", SendEmailCodeView.as_view(), name="sendemail_code"),
# 修改邮箱
path("update_email/", UpdateEmailView.as_view(), name="update_email"),
]
- 在邮箱验证码的 model(EmailVerifyRecord) 中, send_type 字段 == "update_email" 的时候是修改邮箱
# apps/users/models.py
class EmailVerifyRecord(models.Model):
"""
邮箱验证码表
"""
register = "register"
forget = "forget"
update_email = "update"
send_type_choices = (
(register, "注册"),
(forget, "找回密码"),
(update_email, "修改邮箱"),
)
code = models.CharField(max_length=20, verbose_name="验证码")
email = models.EmailField(max_length=50, verbose_name="邮箱")
# 定义 send_type 字段,可以区分验证码的类型,如 注册验证码、找回密码验证码
send_type = models.CharField(max_length=12, choices=send_type_choices, verbose_name="验证码类型")
# 注意, datetime.now 不能写成 datetime.now(),
# 有括号的话,会根据当前 model 编译的时间来生成默认时间
# 去掉括号,是根据当前 class 实例化的时间来生成默认时间
send_time = models.DateTimeField(default=datetime.now, verbose_name="发送时间")
class Meta:
verbose_name = "邮箱验证码"
verbose_name_plural = verbose_name
def __str__(self):
return "%s (%s)" % (self.code, self.email)
- 在 之前定义的 send_register_email 发送邮件函数中新增类型判断 send_type == "update_email" 逻辑处理
# apps/utils/emali_send.py
def send_register_email(email, send_type="register"):
"""
定义发送邮件的基础函数, 此函数接收两个参数,
:param email: 需要发送邮件的邮箱,
:param send_type: 发送验证码类型, 在 EmailVerifyRecord 模型类中, "register" 代表注册, "forget" 代表找回密码
:return:
"""
# 在发送邮件之前, 先将要发送的内容保存到数据库中,
# 因为要在用户点击了这个邮箱链接跳转回来的时候,
# 我们需要查询下这个链接是否存在数据库中
email_record = EmailVerifyRecord()
# 通常, 我们在实现邮箱验证码的这个功能时候,
# 会在发给用户的链接里面加一个随机字符串, 这个字符串是后台生成的,
# 别人是没法伪造的, 在 EmailVerifyRecord 中的 code 字段就是这个随机字符串,
# 用户注册的时候, 收到带有这个 code 随机字符串的链接,
# 用户点击这个链接, 我们将里面的 code 取出来, 查询是否在数据库中存在,
# 如果存在, 就将用户的账号激活, 如果不存在, 抛出相应错误给用户
if send_type == "update_email":
code = random_str(4)
else:
code = random_str(16)
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save()
# 向用户发送邮件, 可以通过 django 为我们提供的内部函数函数 send_email 来完成,
# 这是 django 的方便之处, 它为我们封装好了函数, 在发送邮件之前, 我们需要定义好我们的邮件内容
# 定义邮件标题和内容
email_title = ""
email_body = ""
# 要对邮件的类型做判断, 注册邮件、找回密码邮件是不一样的
if send_type == "register":
# 如果是发送注册邮件, 按照以下逻辑处理
email_title = "慕学在线网激活链接"
email_body = "请点击下面的链接来激活你的账号:http://127.0.0.1:8000/active/%s" % code
# 使用 send_email 来发送邮件, 我们只需要调用它, django 会根据我们定义好的配置自动发送邮件,
# 发送之前需要去 settings.py 中配置发送者的参数信息,
# send_mail 需要几个参数,
# param subject: 邮件标题
# param message: 邮件内容
# param from_email: 可以直接调用 settings.py 中配置的 EMAIL_FROM
# param recipient_list: 需要接收邮件的列表(也就是用户注册的邮箱, 必须是一个列表类型)
# send_mail 函数将会返回一个布尔类型的值, True/False, 指示邮件是否发送成功
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
if send_status:
# 如果邮件发送成功
pass
elif send_type == "forget":
# 如果是发送找回密码邮件, 按照以下逻辑处理
email_title = "慕学在线网密码重置链接"
email_body = "请点击下面的链接来重置你的密码:http://127.0.0.1:8000/reset/%s" % code
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
if send_status:
pass
elif send_type == "update_email":
# 如果是发送找回密码邮件, 按照以下逻辑处理
email_title = "慕学在线网邮箱修改验证码"
email_body = "你的邮箱验证码为:%s" % code
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
if send_status:
pass
-
页面所有表单的提交
019.png
- 表单提交也在 UserInfoView 类视图下完成,新增一个 post 方法用来处理表单的提交
# apps/users/views.py
class UserInfoView(LoginRequiredMixin, View):
"""
用户个人信息类视图
只有用户登录后才能访问, 所以需要继承 LoginRequiredMixin
"""
def get(self, request):
return render(request, "usercenter-info.html", context={})
def post(self, request):
# instance=request.user 参数必须要有, 有了这个参数才能修改, 不然是新增加了一个用户
user_info_form = UserInfoForm(request.POST, instance=request.user)
if user_info_form.is_valid():
user_info_form.save()
return JsonResponse(data='{"status": "success"}', safe=False)
else:
return JsonResponse(data=json.dumps(user_info_form.errors), safe=False)
- 定义一个form UserInfoForm 用来验证用户填写字段
# apps/users/forms.py
class UserInfoForm(forms.ModelForm):
"""
用户信息表单
"""
class Meta:
model = UserProfile
fields = ["nick_name", "birthday", "gender", "address", "mobile"]
- 前端表单部分代码
# templates/usercenter-info.html
<form class="perinform" id="jsEditUserForm" autocomplete="off" action="{% url "users:user_info" %}">
<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>生 日:
{# django 为我们提供了过滤器 default_if_none:'' 来判断如果值为 None 的时候,设定显示的默认值 #}
{# 如果这里不设定用户生日为空时显示空字符串的话, 前端页面表单部分会显示成 None, 不够友好 #}
<input type="text" id="birth_day" name="birthday" value="{{ request.user.birthday|default_if_none:'' }}" 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="11">
</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>
{% csrf_token %}
</form>
-
我的课程页面实现
020.png
- 定义 MyCourseView 类视图
# apps/users/views.py
class MyCourseView(LoginRequiredMixin, View):
"""
我的课程类视图
"""
def get(self, request):
user_courses = UserCourse.objects.filter(user=request.user)
context = {
"user_courses": user_courses,
}
return render(request, "usercenter-mycourse.html", context)
- 配置 url
# apps/users/urls.py
from django.urls import path, re_path
from .views import (UserInfoView, UploadImageView, UpdatePwdView, SendEmailCodeView,
UpdateEmailView, MyCourseView)
app_name = "users"
urlpatterns = [
...
# 我的课程
path("mycourse/", MyCourseView.as_view(), name="mycourse"),
]
- 前端部分代码
# templates/usercenter-mycourse.html
<div class="personal_des permessage">
<div class="companycenter">
<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.favorite_nums }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
- 我的收藏功能实现
-
我的收藏-课程机构
021.png
-
定义 MyFavoriteOrg 类视图
# apps/users/views.py
class MyFavoriteOrg(LoginRequiredMixin, View):
"""
我收藏的课程机构类视图
"""
def get(self, request):
org_list = []
favorite_orgs = UserFavorite.objects.filter(user=request.user, favorite_type=2)
for favorite_org in favorite_orgs:
org_id = favorite_org.favorite_id
org = CourseOrg.objects.get(pk=org_id)
org_list.append(org)
context = {
"org_list": org_list,
}
return render(request, "usercenter-fav-org.html", context)
- 配置 url
# apps/users/urls.py
from django.urls import path, re_path
from .views import (UserInfoView, UploadImageView, UpdatePwdView, SendEmailCodeView,
UpdateEmailView, MyCourseView, MyFavoriteOrg)
app_name = "users"
urlpatterns = [
...
# 我收藏的课程机构
path("myfavorite/org/", MyFavoriteOrg.as_view(), name="myfavorite_org"),
]
- 前端代码
# templates/usercenter-fav-org.html
<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.city }}</span>
<div class="delete jsDeleteFav_org" data-favid="1"></div>
</dd>
</dl>
</div>
{% endfor %}
</div>
- 我的收藏-授课教师/公开课程逻辑一样
-
我的消息功能实现
022.png
- 定义 我的消息类视图 MyMessageView
# apps/users/views.py
class MyMessageView(LoginRequiredMixin, View):
"""
我的消息类视图
"""
def get(self, request):
all_messages = UserMessage.objects.filter(user=request.user.id)
# 对我的消息进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# Paginator 接收 3 个参数, 需要分页的对象列表, 每页显示的对象数量, 以及 request
p = Paginator(all_messages, 1, request=request)
messages = p.page(page)
context = {
"messages": messages,
}
return render(request, "usercenter-message.html", context)
- 配置 url
# apps/users/urls.py
from django.urls import path, re_path
from .views import MyMessageView
app_name = "users"
urlpatterns = [
...
# 我的消息
path("mymessage/", MyMessageView.as_view(), name="mymessage"),
]
- 前端代码
# templates/usercenter-message.html
{% extends "userconter_base.html" %}
{% load staticfiles %}
{% block title %}<title>我的消息- 慕学在线网</title>{% endblock title %}
{% block custom_bread %}
<section>
<div class="wp">
<ul class="crumbs">
<li><a href="{% url "index" %}">首页</a>></li>
<li><a href="{% url "users:user_info" %}">个人中心</a>></li>
<li>我的消息</li>
</ul>
</div>
</section>
{% endblock custom_bread %}
{% block right_content %}
<div class="right" >
<div class="personal_des Releasecont">
<div class="head">
<h1>我的消息</h1>
</div>
</div>
<div class="personal_des permessage">
<div class="head">
<ul class="tab_header messagehead">
<li class="active"><a href="/user/message/">个人消息</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.send_time }}</span><span class="fr btn foldbtn"></span></div>
<p>
{{ message.message }}
</p>
</div>
</div>
{% endfor %}
</div>
<div class="pageturn pagerright">
<ul>
{% 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="page 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>
</div>
</div>
{% endblock right_content %}
- 注意,我的消息还有一个功能,只要账户有未读消息,不管在那个页面,顶部都有一个小喇叭,并且显示未读消息数量
-
因为 request.user 是全局都能获取到的变量,所以将该方法定义在 UserProfile 中
023.png
- 实现方法是在 UserProfile 的 model 中配置一个方法 get_unread_message_nums,用来在全局判断用户是否有未读消息
# apps/users/models.py
class UserProfile(AbstractUser):
"""
用户管理表,继承自 django 自带的用户表 AbstractUser。
"""
male = "male"
female = "female"
gender_choices = (
(male, "男"),
(female, "女"),
)
nick_name = models.CharField(max_length=50, verbose_name="昵称", default="")
# 用户注册的时候生日可以不是必填字段
birthday = models.DateField(verbose_name="生日", null=True, blank=True)
gender = models.CharField(max_length=6, choices=gender_choices, default=female, verbose_name="性别")
address = models.CharField(max_length=100, verbose_name="地址", default="")
mobile = models.CharField(max_length=11, verbose_name="手机")
# ImageField 字段依赖 pillow 库
# ImageField 字段存储到数据库中实际是 varchar 类型,所以也需要指定最大长度
image = models.ImageField(max_length=100, upload_to="images/%Y/%m/", verbose_name="头像", default="images/default.png")
class Meta:
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __str__(self):
return self.username
def get_unread_message_nums(self):
"""
获取未读消息数量
"""
# 一定要在这里导入 UserMessage, 不能在顶部, 在顶部的话会造成循环引用
from operation.models import UserMessage
# 在 UserMessage 中 user 存的是一个 int 类型, 并不是外键, 所以要取 self.id 而不是 self
return UserMessage.objects.filter(user=self.id).count()
- 在所有的 base 页面找到显示小喇叭的代码,添加未读消息数量
# tempaltes/base.html | userconter_base.html | org_base.html
<a href="{% url "users:mymessage" %}">
<div class="msg-num"><span id="MsgNum">{{ request.user.get_unread_message_nums }}</span></div>
</a>
网友评论