美文网首页
Django 开发 MxOnline 项目笔记 -- 第10章

Django 开发 MxOnline 项目笔记 -- 第10章

作者: 江湖十年 | 来源:发表于2018-03-07 18:51 被阅读396次

一、全局搜索功能实现

  • 实现全局搜索功能前还需要解决的问题
      (1)因为首页 "index.html" 页面没有继承 “base.html” 页面,开发全局搜索功能前先将页面改成继承 “base.html”,这样所有带有搜索功能页面就全部继承 “base.html”,就可以实现全局搜索功能了


    01.png

  (2)关于导航条激活状态的实现


02.png

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


03.png
04.png

    ②第二种方法,直接在前端页面通过 “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>

  • 全局搜索功能实现
  1. 搜索功能可以实现“公开课、课程机构、授课老师”的搜索,选择要搜索的内容,输入搜索文字后会自动跳转相应页面


    05.png

    比如用户选择“公开课”,输入内容进行搜索,页面自动跳转到公开课列表页,并将搜索结果展示出来,这个过程是通过 get 请求在 url 中传参来实现的,所以要重写课程列表页类视图 CourseListView


    06.png
07.png 08.png
  1. 修改类视图 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)

  1. 这是前端页面搜索按钮的 js 代码 “deco-common.js”


    09.png
  2. 测试搜索功能


    010.png
    011.png
  3. 课程机构和授课老师的搜索功能同理

二、个人中心功能开发

  • 用户点击进入个人中心按钮跳转到个人中心页面


    012.png
    013.png
  • 个人中心总共有6个页面,拷贝到 templates/ 目录下


    014.png
  • 定义 “userconter_base.html” 页面,其他6个页面都继承此页面

  1. 个人信息展示页
  • 定义 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>昵&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;称:
                           <input type="text" name="nick_name" id="nick_name" value="{{ request.user.nick_name }}" maxlength="10">
                            <i class="error-tips"></i>
                        </li>
                        <li>生&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;日:
                            {# 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>性&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;别:
                            <label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="radio"  name="gender" value="male" {% if request.user.gender == "male" %}checked="checked"{% endif %}>男</label>
                            <label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="radio" name="gender" value="female" {% if request.user.gender == "female" %}checked="checked"{% endif %}>女</label>
                        </li>
                        <li class="p_infor_city">地&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;址:
                            <input type="text" name="address" id="address" placeholder="请输入你的地址" value="{{ request.user.address }}" maxlength="10">
                            <i class="error-tips"></i>
                        </li>
                        <li>手&nbsp;&nbsp;机&nbsp;&nbsp;号:
                            <input type="text" name="mobile" id="mobile" placeholder="请输入你的手机号码" value="{{ request.user.mobile }}" maxlength="10">
                        </li>
                        <li>邮&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;箱:
                            <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
  1. 修改头像功能
  • 定义 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
  1. 修改密码功能
  • 定义个人中心修改用户密码类视图 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" >新&nbsp;&nbsp;密&nbsp;&nbsp;码</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
  1. 修改邮箱
    注册的时候是让用户验证过邮箱的,所以修改的时候是不能让用户随意修改的,修改邮箱也是要通过验证码来完成的


    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

  1. 页面所有表单的提交


    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>昵&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;称:
           <input type="text" name="nick_name" id="nick_name" value="{{ request.user.nick_name }}" maxlength="10">
            <i class="error-tips"></i>
        </li>
        <li>生&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;日:
            {# 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>性&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;别:
            <label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="radio"  name="gender" value="male" {% if request.user.gender == "male" %}checked="checked"{% endif %}>男</label>
            <label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="radio" name="gender" value="female" {% if request.user.gender == "female" %}checked="checked"{% endif %}>女</label>
        </li>
        <li class="p_infor_city">地&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;址:
            <input type="text" name="address" id="address" placeholder="请输入你的地址" value="{{ request.user.address }}" maxlength="10">
            <i class="error-tips"></i>
        </li>
        <li>手&nbsp;&nbsp;机&nbsp;&nbsp;号:
            <input type="text" name="mobile" id="mobile" placeholder="请输入你的手机号码" value="{{ request.user.mobile }}" maxlength="11">
        </li>
        <li>邮&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;箱:
            <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>

  1. 我的课程页面实现


    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>

  1. 我的收藏功能实现
  • 我的收藏-课程机构


    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>

  • 我的收藏-授课教师/公开课程逻辑一样
  1. 我的消息功能实现


    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>

相关文章

网友评论

      本文标题:Django 开发 MxOnline 项目笔记 -- 第10章

      本文链接:https://www.haomeiwen.com/subject/xumxfftx.html