🌈🌈🌈不定时的更新叒叒叕开始了,且更且珍惜🌈🌈🌈🌈

我要每次都写一遍:前面的还没补完,以此催促不定时更新的我
⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅⛅

 

BBS

  项目流程

      一个完整的项目通常包含下面几个过程👇:

项目需求(产品经理,架构师,开发组组长)
项目设计(框架的选择,数据库的选择,主要功能模块) 报价(工期,开发人员工资)
任务分发(开发组长>>>小弟开发)
测试(本地测试+测试人员测试)
交付上线

 

  项目分析

    功能分析:

    实现用户注册及头像上传,登录及验证码

    主页面设计(用户在登录和未登录状态下两种)

    用户个人站点设计

    个人站点侧边栏分类、标签、日期归档

    点击对应分类、标签、日期归档显示相应的文章

    文章详情

    点赞点踩功能

    表设计    

    一个项目在开始着手写之前,最重要的就是对表的分析,

    具体需要哪些表,表结构设计。

    所以,我们首先根据功能来对表进行分析。 

      表:字段关系

        用户表(UserInfo(AbstractUser))

          这里我们继承了Django的AbstractUser表,

          然后再增加需要的额外字段

          那我们还需要添加下面几个字段:

          phone

          avatar(记录用户头像的路径)

          create_time

          (这里又加了注册时间是为了结合前面学习

          的auto_now_add的用法,以及方便后面使用这个字段)  

        

          blog     >>>>>    site    一对一个人站点表

        个人站点表(Blog)

          站点名称site_time

          站点标题site_title

          站点样式site_theme

        文章标签表(Tag)

          标签名称name

 

          blog        >>>>>       Blog      一对多个人站点表

        文章分类表(Category)

          分类名称name

 

          blog           >>>>>         Blog      一对多个人站点表

        文章表(Article)

          文章标题title

          文章简介desc

          文章详情content

          文章发布时间create_time

          为什么另一张表里面有下面这三个字段,

          还要在这张表里面存呢?

          下面来解释这个问题,这就涉及到了数据库查询优化问题。

           
      我可以通过点赞点踩表拿到文章的点赞数(或点踩数),但要跨表然后去拿点赞/点踩数,而首页里面渲染了很多篇文章,每篇文章都要拿点赞点踩数,评论数,这样请求就太多了,一篇文章就要请求三个字段,跨表跨三次,太麻烦了,跨表跨的太频繁了,这时候该怎么做呢?
      虽然点赞点踩表里面已经记录了文章与点赞点踩直接的关系,但我们还可以建三个普通的字段:
      文章评论数content_num
      文章点赞数up_num
      文章点踩数down_num
      为什么已经有一张表存了,你还存它干嘛呢?这是因为我存这三个普通的字段我查文章的时候不需要跨表了,我直接就拿到我这个文章的评论数是多少,点赞数是多少,点踩数是多少
       这么做虽然优化了,但需要注意:你这里存了三个普通的字段,在后续操作点赞点踩表的时候,你这些字段必须同步更新,因为你这里存的是普通的字段,那也就意味着你在操作点赞点踩表的时候要回到这张表把对应的字段改掉,否则就不是一一对应了。那操作一张表的时候,另外一张表也跟着同步更新,并且不能出错,跟它一一对应着更新,---->想到了事务,在你更新字段的时候就可以用到事务,操作点赞点踩表的时候,对应这张表的这三个普通字段也同步更新,这样就实现了,这张表的这三个普通的字段就可以表示点赞点踩表里面的数据了,就不用再跨表查询了
数据库查询优化说明

          :数据库查询优化一定要同步更新两张表的对应字段

          文章评论数comment_num

          文章点赞数up_num

          文章点踩数down_num

          

          blog      >>>>>     Blog    一对多个人站点表
          tags      >>>>>          Tag     多对多标签表
          category    >>>>>     Category  一对多分类表

          

        点赞点踩表

      在另外一张表里面有,记录一张表里面到底有多少个点赞点踩,
      在这张表里面过滤一下,
      利用count就可以知道对应的文章到底有多少点赞点踩

          用户名字段user

          文章字段article

          点赞点踩is_up 

        文章评论表

      与点赞点踩表相同,将文章表拿出来到评论表筛选一下,
      把评论表对应的所有数据都筛选出来,
      然后统计一下就是你这篇文章的评论数

          用户名字段user   >>>>>    一对多  个人站点/用户

          文章字段article    >>>>>    一对多 文章表

          评论内容content  >>>>>      0/1

          父评论parent(自己跟自己关联)   >>>>>   一对多自身

           
注:通过博客园的评论我们可以看到
      评论内容包括根评论,子评论
      其中一个根评论可以对应多个子评论
      这时候我们需要另外一个字段
            父评论parent(自己跟自己关联):我在记录你这个评论的时候,还要记录你这个评论到底是针对某篇文章的还是针对某一条评论的,如果是针对某一条评论的,我在给你渲染的时候还要在前面加上一个@,到底是@谁,评论的是谁,如果是根评论的话我直接给你渲染出来就行了
             一条根评论可以有多个子评论,子评论也是这张表里面的
             也就相当于这张表自己和自己关联,默认可以不写,不写就是根评论,只要你一写就是子评论,我后续通过外键关联,我一点parent,如果我能拿到值说明就是子评论,然后我能拿到当前这个子评论你想评论的父评论是谁,id是多少,然后帮你渲染到对应的父评论上去,或者给你来个@
父评论解释

 

          不明白就看下图👇

          

     表关系分析图

          

          表与表之间的关系十分重要,是我们查询时的重要依据,

          所以一定要分析好。

 

  注册功能

   🐷利用form组件来帮我们完成校验,渲染标签

      在app01下新建一个py文件

      写forms组件:

        username
        password
        confirm_password
        email
        局部钩子 校验用户名是否存在
        全局钩子 校验密码是否一致

      代码如下:  

         myforms.py

         
from django import forms
from django.forms import widgets
from app01 import models


class MyForm(forms.Form):
    username = forms.CharField(max_length=8, min_length=3, label='用户名', error_messages={
        'required': '用户名不能为空',
        'max_length': '用户名最大8位',
        'min_length': '用户名最小3位'
    }, widget=widgets.TextInput(attrs={'class': 'form-control'}))
    password = forms.CharField(max_length=8, min_length=3, label='密码', error_messages={
        'required': '密码不能为空',
        'max_length': '密码最大8位',
        'min_length': '密码最小3位'
    }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    confirm_password = forms.CharField(max_length=8, min_length=3, label='确认密码', error_messages={
        'required': '确认密码不能为空',
        'max_length': '确认密码最大8位',
        'min_length': '确认密码最小3位'
    }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    email = forms.EmailField( label='邮箱', error_messages={
        'required': '邮箱不能为空',
        'invalid': '邮箱格式不正确',
    }, widget=widgets.EmailInput(attrs={'class': 'form-control'}))

    # 局部钩子校验用户名是否存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        user_obj = models.UserInfo.objects.filter(username=username).first()
        if user_obj:
            self.add_error('username', '用户名已存在')
        return username

    # 全局钩子,校验密码是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password', '两次密码不一致')
        return self.cleaned_data
myforms

      🐷搭建注册页面
        1.利用forms组件渲染前端页面,手动添加获取用户头像的input框
        2.将img标签放入label中,将input框隐藏
        3.利用文件阅读器动态展示用户上传的头像
      注意:需要等待文件阅读器读取完毕之后再赋值给src属性,利用onload
        4.ajax发送post请求
          利用内置对象FormData传递数据
          利用form标签序列化数组
          手动获取文件对象$('[input="file"]')[0].files[0]
          formdata发数据需要手动修改两个参数
            processData:false
            contentType:false

    代码参考:

      前端:(详细解释参考注释)

       
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="/static/bootstrap-3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/static/css/mycss.css">
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h2 class="text-center my_CSS3_class">注册</h2>
            <hr>
            <form id="myform">
                {% csrf_token %}
                {% for form in form_obj %}
                    <div class="form-group my_CSS3_class"> <!--has-error 把input框变成红色-->
                        <label for="{{ form.auto_id }}">{{ form.label }}</label>
                        {{ form }}
                        <span class="errors pull-right my_CSS3_class" style="color: red"></span>
                    </div>
                {% endfor %}
            </form>
            <div class="form-group my_CSS3_class">
                <label for="id_myfile">头像
                    <img src="/static/img/default.png" alt="" width="80" style="margin-left: 20px" id="id_img">
                </label>
                <input type="file" name="myfile" id="id_myfile" style="display: none">
            </div>
            <button class="btn btn-primary pull-right my_CSS3_class" id="id_submit"> 注册</button>
        </div>
    </div>
</div>

<script>
    $('#id_myfile').change(function () {
        // 先获取用户上传的文件对象,找到存在的标签,转成js对象,然后点files再加上[0],或者$(this),this就相当于js对象,直接就可以this点
        // 先获取用户上传的文件对象
        let fileObj = this.files[0];
        // 生成一个内置对象
        let fileReader = new FileReader();
        // 将文件对象传递给内置对象
        fileReader.readAsDataURL(fileObj);
        // 将读取出文件对象替换到img标签中
        fileReader.onload = function () {   //等待文件阅读器读取完毕再渲染图片
            $('#id_img').attr('src', fileReader.result)
        }    // 利用img标签src属性在放url的时候,一旦url发生变化,会自动朝当前的url地址请求数据
    });

    // ajax提交数据
    $('#id_submit').click(function () {
        // 提交的既有普通的键值,又有文件--->formdata
        // 生成一个Formdata对象
        let formData = new FormData();
        // 往FormData对象中添加键值   我们首先想到的可能是formData.append('','')键有username,password,conform_password,email,还有自己写的myfile,很明显一次性搞不定,然后我们可能会想到for循环,依次取没=每个字段username,password...当然可以做但是太麻烦了,选择有个简单的方式,这也就是前面为什么要用form包起来
        // 往Formdata对象中添加键值
        // console.log($('#myform').serializeArray());
        // 通过打印的结果可以看到这里面是只有csrf以及username这五个键的,并不包含文件,所以文件需要手动添加
        $.each($('#myform').serializeArray(), function (index, obj) {
            // console.log(index,obj)
            formData.append(obj.name, obj.value)
        });
        // 手动添加文件数据
        formData.append('myfile', $('#id_myfile')[0].files[0]);
        // 这个时候formData里面就有五组普通的键值,有一个是文件,然后ajax发送
        $.ajax({
            url: '',
            type: 'post',
            data: formData,
            // 需要指定的两个参数
            processData: false,
            contentType: false,
            success: function (data) {
                // console.log(data)
                    if (data.code == 100) {
                        // 跳转到登录页面
                        location.href = data.url
                    } else {
                        $.each(data.msg, function (index, obj) {
                            // console.log(index,obj)  // 一个个key对应一个个错误信息,那现在问题来了,如何能够确定哪个框是哪个框,如何把它这个的框的错误信息准确无误的渲染对应的框下面
                            // 要用ajax在某一个地方渲染某一个东西,要先找到这个地方某一个的标签的东西,然后对它进行一个DOM操作
                            // 先找到每个框,给这个框下面添一些信息
                            // 先分析每个框有什么特点id_username,id_....
                            // 然后我们发现通过打印$.each(data.msg,function (index,obj)对应的key刚好和它一样,只是少了id_,那只要加上id_就可以找到对应的标签了,对应的标签后面就是它要渲染的错误信息
                            let targetid = '#id_' + index;  // id_username,id_password
                            $(targetid).next().html(obj[0]).parent().addClass('has-error')
                            // 当有错误的时候将input框变红has-error,鼠标点进去的时候再变成正常的颜色
                        })
                    }
                }
            });
        // 当鼠标点进去(聚焦事件)的时候把红色去掉,并且下面的错误信息也去掉,
    })
    ;
    $('input').focus(function () {
        $(this).next().html('').parent().removeClass('has-error')
    });

</script>

</body>
</html>
register.html

      补充解释:

      

      ajax

      

      

 

      后端:

      利用cleaned_data是一个大字典特性,将confirm_password键值去掉
      手动获取用户头像,判断用户是否上传头像,

      再决定要不要放入cleaned_data字典中
      利用**{}将字典打散成关键字参数的形式

       views.py

       
def register(request):
    back_dic = {'code': 100, 'msg': ''}
    form_obj = myforms.MyForm()
    if request.method == 'POST':
        form_obj = myforms.MyForm(request.POST)  # form组件做第一层校验
        if form_obj.is_valid():
            data = form_obj.cleaned_data
            # cleaned_data是一个大字典,里面有username...那个几个字段,但是confirm_password我们不需要了,只要通过了这个,说明密码一致了,就不需要confirm_password了

            # 将confirm_password去掉
            data.pop('confirm_password')
            # 获取用户上传的文件对象
            file_obj = request.FILES.get('myfile')
            # 如果用户没有选择头像,也即没点头像,那myfile里面就没有值,让它走默认字段,这就不需要把avatar添加进来了,如果用户不传了,你在create_user里面令avatar=None,这样并不合理,所有需要判断一下用户有没有传,如果用户没有传字段就不需要再获取了,就直接传username,password,email这三个字段,avatar用默认的就可以了

            # 判断用户是否上传了自己的头像
            if file_obj:
                # 往data里面添加键值
                data['avatar'] = file_obj
                # 这时候data里面就有四组键值了,avatar如果传了就有,没有传就没有
            models.UserInfo.objects.create_user(**data)
            back_dic['msg'] = '注册成功'
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 101
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request,'register.html', locals())
注册功能后端代码

 

      注:1.在用ajax做前后端交互的时候通常后端,

            都会事先定义一个字典作为数据交互的媒介

        2.img标签src属性可以放文件路径

            也可以放文件二进制数据

         还可以放url

 

  登录功能

     首先分析登录页面需要哪些东西:用户名、密码、验证码

     那我们先来搭建一个登陆页面

     
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h2 class="text-center">登录</h2>
            {% csrf_token %}
            <div class="form-group">
                <label for="id_username">用户名</label>
                <input type="text" name="username" id="id_username" class="form-control">
            </div>
            <div class="form-group">
                <label for="id_password">密码</label>
                <input type="password" name="password" id="id_password" class="form-control">
            </div>
            <div class="form-group">
                <label for="id_code">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" id="id_code" class="form-control">
                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="" width="310" height="35" id="id_img">
                    </div>
                </div>
            </div>
            <button class="btn btn-success" id="id_button">登录</button>
            <span class="errors" style="color: red" id="id_error"></span>
        </div>
    </div>
</div>
简易登陆页面

      ❓思考:

  用户名和密码直接输入就可以了,关键是验证码如何解决呢?

  首先要考虑验证码是一刷新图片和里面的内容都会发生变化,

  而且一般的网页你一点图片,验证码就会自动刷新

  这时候你可能想到了局部刷新利用ajax,那接下来就是实现生成验证码了

    📢验证码的实现之路

    我们分四步来推导:

    利用img标签的src它既可以写文件路径,也可以放图片二进制数据,还可以放url

    第一步:生成验证码的图片,

        我们先用本地的图片来测试,打开本地文件发送二进制数据

  def get_code(request):
     with open(r'D:\SH_PY\day63\avatar\default.png', 'rb') as f:
          data = f.read()
      return HttpResponse(data)

 

    第二步:动态生成图片发送二进制数据

        需要用到 Image模块(这里我们主要用到它动态生成图片的功能)

        先下载pillow才能导

  from PIL import Image

  def get_code(request):    
      # img_obj = Image.new('RGB',(310,35),'green')  # 第三个参数既可以传颜色英文,也可以传rgb参数
      img_obj = Image.new('RGB',(310,35),(128,128,128))  # 第三个参数既可以传颜色英文,也可以传rgb参数
      # 先保存成文件   注: 不能直接发送,必须先保存到一个文件里面,再以二进制模式读取发送数据
      with open('demo.png','wb') as f:
          img_obj.save(f)
      # 再以二进制模式读取发送数据
      with open('demo.png', 'rb') as f:
          data = f.read()
      return HttpResponse(data)

 

    第三步:实现图片颜色动态变化,并且使图片存放不再依赖文件的形式

  from PIL import Image
  import random
  from io import BytesIO    

  # 随机生成rgb参数:因为rgb参数都是0-255之间,所有可以利用random模块随机产生数字
  # 为了能够让图片的颜色也能动态变化
  def get_random():   return random.randint(0,255),random.randint(0,255),random.randint(0,255)   def get_code(request):   # 推导步骤3:图片颜色动态变化 图片存放不再依赖文件的形式   img_obj = Image.new('RGB',(310,35), get_random())   # 生成一个BytesIO对象,利用内存管理器模块io   io_obj = BytesIO() # 将这个对象看成文件句柄   img_obj.save(io_obj, 'png') # 将图片数据存入内存管理器中 需要指定图片格式!!   return HttpResponse(io_obj.getvalue()) # 将保存的数据以二进制的数据返回出来

 

    第四步:最终版,颜色动态变化,在图片上显示数字且动态变化

      文字说明:

    图片上写字,并生成随机验证码
    1.生成一个画笔对象(将生成好的图片当做参数传入实例化产生对象)
    2.生成一个字体对象(字体文件.ttf,字体大小)

     字体文件:百度ttf选择自己喜欢的字体,下载下来,放在static文件夹下的

          font文件夹内
    3.随机验证码(数字+小写字母+大写字母)
      外层for循环规定验证码的位数
      内部利用random.choice每次从数字+小写字母+大写字母三个中取一个
      利用画笔对象往图片上写字(写的位置xy,写的内容,随机颜色数,字体对象)
    4.将生成好的随机验证码保存到session中,

      为了在之后的登录页面中校验用户输入的验证码是否正确

      代码实现:

        验证码之最终版

      补充:图片模糊处理

      from PIL import Image,ImageFilter

      def get_code(request):
          img_obj = Image.new('RGB',(310,35), get_random())
          # 图片模糊
          img_obj = img_obj.filter(ImageFilter.BLUR)

      为了页面更加人性化,给展示验证码的图片绑定了点击事件。

      用户每点一次局部刷新验证码
      利用img标签src属性在放url的时候,

      一旦url发生变化。会自动出现朝当前url地址请求数据

      前端代码:      

         login.html

      后端分析:

     获取用户输入的用户名 密码 验证码
      1、先校验验证码是否一致(可以不忽略大小写,统一转成小写或大写进行比较)
      2、利用auth模块校验用户名和密码是否正确

         user_obj = auth.authenticate(username=username,password=password)
      3、利用auth模块记录当前用户登录状态auth.login(request,user_obj)    

      后端views.py:

        
def login(request):
    back_dic = {'code': 100, 'msg': ''}
    # 判断是ajax请求还是正常form表单请求  request.is_ajax()  ajax请求不能用redirect
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        # 先校验验证码(可以区分大小写也可以不区分  不区分:统一转成大小或者转成小写进行对比即可)
        if request.session.get('code').upper() == code.upper():
            # 利用auth模块校验用户名和密码是否正确
            user_obj = auth.authenticate(username=username,password=password)  # 这里的request可以传可以不传,因为它有一个request=None
            if user_obj:
                # 利用auth模块记录当前登录状态
                auth.login(request,user_obj)  # 这里的request必须要传
                back_dic['msg'] = '登录成功'
                back_dic['url'] = '/home/'
            else:
                back_dic['code'] = 102
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 103
            back_dic['msg'] = '验证码错误'
        return JsonResponse(back_dic)
    return render(request, 'login.html')
后端登录代码

 

 

  主页搭建

    主页:

      后端:

      def home(request):
          # 将网站所有的文章都展示到主页
          article_list = models.Article.objects.all()
          return render(request,'home.html',locals())

       前端:

        home.html 

     最终主页搭建的页面👇:

      自己添加了一些样式,这是最终版,还有一些要增加的后面会慢慢补充

     

 

 

    顶部导航条

      右侧根据用户是否登录动态控制展示的内容
        1、当用户登录的情况下展示用户名和更多操作

          修改密码,修改头像,注销

        2、当用户没有登录的情况下展示注册和登录

       修改密码

      利用auth模块修改密码

        request.user.check_password()
        request.user.set_password()
        request.user.save() # 一定要save()

 

      后端代码:

      def set_password(request):
          old_password = request.POST.get('old_password')
          new_password = request.POST.get('new_password')
          confirm_password = request.POST.get('confirm_password')
          # 先判断旧密码是否正确
          res = request.user.check_password(old_password)
          if res:
              # 再来比对新旧密码是否一致
              if new_password == confirm_password:
                request.user.set_password(new_password)
                request.user.save()
                return redirect('/login')
          return render(request, 'set_password.html')

      前端代码:

        
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="/static/bootstrap-3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script>
{#    <style>#}
{#        @font-face {#}
{#            font-family: ttq; /*这里是说明调用来的字体名字*/#}
{#            src: url('/static/font/ttq.ttf'); /*这里是字体文件路径*/#}
{#            }#}
{#        .my_CSS3_class {#}
{#            font-family: ttq;#}
{#}#}
{#    </style>#}
    <link rel="stylesheet" href="/static/css/mycss.css">


</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h2 class="text-center my_CSS3_class">修改密码</h2>
            <form action="" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="id_username" class="my_CSS3_class">用户名</label>
                    <input type="text" name="username" id="id_username" class="form-control" disabled value="{{ request.user.username }}">
                </div>
                <div class="form-group">
                    <label for="id_old_password" class="my_CSS3_class">旧密码</label>
                    <input type="password" name="old_password" id="id_old_password" class="form-control">
                </div>
                <div class="form-group">
                    <label for="id_new_password" class="my_CSS3_class">新密码</label>
                    <input type="password" name="new_password" id="id_new_password" class="form-control">
                </div>
                <div class="form-group">
                    <label for="id_confirm_password" class="my_CSS3_class">确认密码</label>
                    <input type="password" name="confirm_password" id="id_confirm_password" class="form-control">
                </div>
                <button class="btn btn-warning my_CSS3_class" id="id_button">修改密码</button>
                <span class="errors" style="color: red" id="id_error"></span>
            </form>
        </div>
    </div>
</div>
</body>
</html>
set_password.html

     修改头像

      修改头像的后端逻辑并不难,获取用户上传的新头像,然后将数据库中avatar字段修改即可

      但是,我们在修改avatar字段的时候不能用queryset内置方法修改,这样会导致数据库中存储的路径不对,

      前面的avatar/并不会出现,所以要用对象去修改

      我用了注册时上传头像就会出现在当前头像的位置,所以前端代码稍微比直接上传有点区别

      后端代码:

        
def set_avatar(request):
    back_dic = {'code':100, 'msg': ''}
    avatar = request.user.avatar
    if request.method == 'POST':
        file_obj = request.FILES.get('myfile')
        # 修改用户avatar字段
        # 1.利用queryset内置方法修改  不行,结果会导致数据库保存的文件路径不对
        # models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj)
        # 2.利用对象修改
        user_obj = request.user
        user_obj.avatar = file_obj
        user_obj.save()
        back_dic['msg'] = '修改成功'
        back_dic['url'] = '/home/'
        return JsonResponse(back_dic)
    return render(request,'set_avatar.html',locals())
set_avatar

      前端代码:

        
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="/static/bootstrap-3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/static/css/mycss.css">
</head>
<body>
<div class="container-fluid my_CSS3_class">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h2 class="text-center">修改头像</h2>
            <hr>
            <form action="" method="post" enctype="multipart/form-data">
                <div class="form-group col-md-4 pull-left">
                    <p for="" style="font-size: 20px">原头像
                        <img src="/media/{{ avatar }}" alt="" width="160" style="margin-left: 20px">
                    </p>
                </div>
                <div class="col-md-4 pull-right">
                    <label for="id_myfile" style="font-size: 20px">点击上传新头像
                        <img src="/media/{{ avatar }}" alt="" width="160" style="margin-left: 20px" id="id_img">
                    </label>
                     <input type="file" name="myfile" id="id_myfile" style="display: none">
                </div>
            </form>
            <button class="btn btn-warning" style="margin:50px 250px " id="id_submit">确认</button>
        </div>

    </div>
</div>

<script>
    $('#id_myfile').change(function () {
        // 先获取用户上传的文件对象,找到存在的标签,转成js对象,然后点files再加上[0],或者$(this),this就相当于js对象,直接就可以this点
        // 先获取用户上传的文件对象
        let fileObj = this.files[0];
        // 生成一个内置对象
        let fileReader = new FileReader();
        // 将文件对象传递给内置对象
        fileReader.readAsDataURL(fileObj);
        // 将读取出文件对象替换到img标签中
        fileReader.onload = function () {   //等待文件阅读器读取完毕再渲染图片
            $('#id_img').attr('src', fileReader.result)
        }    // 利用img标签src属性在放url的时候,一旦url发生变化,会自动朝当前的url地址请求数据
    });

    // ajax提交数据
    $('#id_submit').click(function () {
        let formData = new FormData();
        formData.append('myfile', $('#id_myfile')[0].files[0]);
        formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
        // 这个时候formData里面就有五组普通的键值,有一个是文件,然后ajax发送
        $.ajax({
            url: '',
            type: 'post',
            data: formData,
            // 需要指定的两个参数
            processData: false,
            contentType: false,
            success: function (data) {
                console.log(data)
                    if (data.code == 100) {
                        // 跳转到登录页面
                        location.href = data.url
                    }
                    }
        // 当鼠标点进去(聚焦事件)的时候把红色去掉,并且下面的错误信息也去掉,
    });
});

</script>

</body>
</html>
set_avatar.html

 

    注销

      auth.logout(request)
      代码如下:

      def logout(request):
          auth.logout(request)
          return redirect('/home/')

 

  个人站点

 

未完待续......😂