• django框架之BBS项目之登陆


    前情回顾
        1. 认证系统
            1. auth
                - 默认使用的是auth_user
                - 添加用户
                    - python manage.py createsuperuser
                
                - auth.authenticate(username=, password=)    --> 校验用户名或密码是否正确
                    - 如果校验通过会返回一个user对象
                    - 否则返回None
                    
                - user.is_authenticated()                    --> 判断当前用户是否经过了认证
                - auth.login(request, user)                  --> 登录成功  
                - auth.logout(request)                       --> 注销当前用户
                
                - login_required装饰器
                    - from django.contrib.auth.decorators import login_required
                    
                - 创建用户
                    from django.contrib.auth.models import User
                        1. User.objects.create_superuser()   --> 创建超级用户
                        2. User.objects.create_user()        --> 创建普通用户
                - 检查密码和修改密码
                    - 对已经登陆的用户做操作
                    - user.check_password(原密码)
                    - user.set_password(新密码)
                    - user.save()                            --> 保存到数据库
            2. 扩展默认的auth_user表
                1. 为什么要扩展auth_user表?
                    1. 默认auth_user表的字段比较固定,不能满足项目需求
                    2. 我还想用Django的auth模块给我提供的上面那些方法
                    
                2. 如何扩展?
                    1. 在models.py中定义自己的类
                        from django.contrib.auth.models import AbstractUser
                        class UserInfo(AbstractUser):
                            phone = models.CharField(max_length=11)
                    
                    2. 在settings.py中告诉Django,使用我的UserInfo表代替默认的auth_user表做认证
                        AUTH_USER_MODEL='app名字.类名'

    从今天开始就开始将项目啦!

    今日内容:

    1.BBS项目表结构设计

        我们在建表结构时会遇到多对多的关联字段,我们先来补充一个知识点,就是在Django创建多对多字段的三种模式:

        1. 使用默认的ManyToManyField创建第三张表、

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
        def __str__(self):
            return self.title
    
    
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to='Book')
    
        def __str__(self):
            return self.name

    在查表得时候可以这样查:

    # 查作者写过的所有书
    author_obj = models.Author.objects.get(id=1)
    ret = author_obj.books.all()
    print(ret)

        1. 优势
                        1. 可以使用ORM提供的快捷方法
                            1. add()
                            2. clear()
                            3. set()
                            4. remove()
                            6. all()
                    2. 劣势:
                        1. 不能扩展第三张表

    2. 自己创建第三张关系表

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
        def __str__(self):
            return self.title
    
    
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
    
        def __str__(self):
            return self.name
    
    
    # 自己创建多对多的第三张表
    class Author2Book(models.Model):
        book = models.ForeignKey(to='Book')
        author = models.ForeignKey(to='Author')
    
        class Meta:
            unique_together = (('book', 'author'), )

    在查表得时候可以这样查:

    # 查作者写过的所有书
    author_obj = models.Author.objects.get(id=1)
    ret = author_obj.author2book_set.all().values_list("book__title")
    print(ret)

        1. 优势:
                        1. 可以自己扩展第三章关系表的字段(婚恋网站的男女用户的约会记录)
                    2. 劣势:
                        1. 不能使用ORM提供的快捷方法

    3. 自己创建第三张表,在ManyToManyField中通过through和through_fields指定表名和字段

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
        def __str__(self):
            return self.title
    
    
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(
            to='Book',
            through='Author2Book',
            through_fields=('author', 'book')
        )
    
        def __str__(self):
            return self.name
    
    
    # 自己创建多对多的第三张表
    class Author2Book(models.Model):
        book = models.ForeignKey(to='Book')
        author = models.ForeignKey(to='Author')
    
        class Meta:
            unique_together = (('book', 'author'), )

    在查表得时候可以这样查:

    # 查作者写过的所有书
    author_obj = models.Author.objects.get(id=1)
    ret = author_obj.books.all()
        print(ret)

        1. 优势:
                        1. 可以使用ORM提供的部分快捷方法
                              1. all()
                        2. 可以扩展第三张关系表的字段

    2. BBS项目登录

    我们先来创建项目中需要的表:

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    # Create your models here.
    class UserInfo(AbstractUser):
        '''用户信息表'''
        phone = models.CharField(max_length=11,null=True,unique=True)#手机号
        avtar = models.FileField(upload_to='avatar/',default='avatar/default.png')#头像
    
        blog = models.OneToOneField(to="Blog", null=True)
    
        def __str__(self):
            return self.username
    
        class Meta:
            verbose_name = "用户信息"
            verbose_name_plural = verbose_name
    
    class Blog(models.Model):
        '''
        博客信息
        '''
        title = models.CharField(max_length=64)  # 个人博客标题
        theme = models.CharField(max_length=32)  # 博客主题
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = "博客"
            verbose_name_plural = verbose_name
    
    class Category(models.Model):
        """
        个人博客文章分类
        """
        title = models.CharField(max_length=32)  # 分类标题
        blog = models.ForeignKey(to="Blog")  # 外键关联博客,一个博客站点可以有多个分类
    
        def __str__(self):
            return "{}-{}".format(self.blog.title, self.title)
    
        class Meta:
            verbose_name = "文章分类"
            verbose_name_plural = verbose_name
    
    class Tag(models.Model):
        """
        标签
        """
        title = models.CharField(max_length=32)  # 标签名
        blog = models.ForeignKey(to="Blog")  # 所属博客
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = "标签"
            verbose_name_plural = verbose_name
    
    class Article(models.Model):
        """
        文章
        """
        title = models.CharField(max_length=50)  # 文章标题
        desc = models.CharField(max_length=255)  # 文章描述
        create_time = models.DateTimeField(auto_now_add=True)  # 创建时间
        category = models.ForeignKey(to="Category", null=True)  # 文章分类
        user = models.ForeignKey(to="UserInfo")  # 作者
        tags = models.ManyToManyField(  # 文章的标签
            to="Tag",
            through="Article2Tag",
            through_fields=("article", "tag"),
        )
    
        def __str__(self):
            return self.title
    
        class Meta:
            verbose_name = "文章"
            verbose_name_plural = verbose_name
    
    class Article2Tag(models.Model):
        """
        文章和标签的多对多关系表
        """
        article = models.ForeignKey(to="Article")
        tag = models.ForeignKey(to="Tag")
    
        def __str__(self):
            return "{}-{}".format(self.article, self.tag)
    
        class Meta:
            unique_together = (("article", "tag"),)
            verbose_name = "文章-标签"
            verbose_name_plural = verbose_name
    
    class ArticleDetail(models.Model):
        """
        文章详情表
        """
        content = models.TextField()  # 文章内容
        article = models.OneToOneField(to="Article")
    
        class Meta:
            verbose_name = "文章详情"
            verbose_name_plural = verbose_name
    
    class ArticleUpDown(models.Model):
        """
        点赞表
        """
        user = models.ForeignKey(to="UserInfo", null=True)
        article = models.ForeignKey(to="Article", null=True)
        is_up = models.BooleanField(default=True)  # 点赞还是踩灭
    
        def __str__(self):
            return "{}-{}".format(self.user_id, self.article_id)
    
        class Meta:
            unique_together = (("article", "user"),)  # 同一个人只能给一篇文章点一次赞
            verbose_name = "点赞"
            verbose_name_plural = verbose_name
    
    
    class Comment(models.Model):
        """
        评论表
        """
        article = models.ForeignKey(to="Article")
        user = models.ForeignKey(to="UserInfo")
        content = models.CharField(max_length=255)  # 评论内容
        create_time = models.DateTimeField(auto_now_add=True)
    
        parent_comment = models.ForeignKey("self", null=True)  # 自己关联自己
    
        def __str__(self):
            return self.content
    
        class Meta:
            verbose_name = "评论"
            verbose_name_plural = verbose_name

    注意:一定要在setting中配置:

    AUTH_USER_MODEL = "blog.UserInfo"  #   app名.类名    告诉django用咱们自己间的表

    因为在项目中我们即用到了django自带的表,也用到了自己所建的表。然后执行数据库迁移的两条命令。

    然后我们写一个登陆:

    利用django中的form表单,app下创建一个form文件

    from django import forms
    
    
    class LoginForm(forms.Form):
        username = forms.CharField(
            label='用户名',
            min_length=4,
            error_messages={
                'required':'用户名不能为空',
                'min_length':'用户名不能少于4位',
            },
            widget=forms.widgets.TextInput(
                attrs={'class':'form-control'}
            )
        )
        password= forms.CharField(
            label='密码',
            min_length=6,
            error_messages={
                'required': '密码不能为空',
                'min_length': '密码不能少于6位',
            },
            widget=forms.widgets.PasswordInput(
                attrs={'class': 'form-control'}
            )
        )

    然后html代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登陆</title>
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
        <link rel="stylesheet" href="/static/css/blog.css">
    </head>
    <body>
    {% csrf_token %}
    <div class="container">
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                <form>
                <div class="form-group">
                    <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label>
                    {{ form_obj.username }}
                </div>
                <div class="form-group">
                    <label for="{{ form_obj.password.id_for_label }}">{{ form_obj.password.label }}</label>
                    {{ form_obj.password }}
                </div>
                <div class="form-group">
                    <label for="v-code" style="display: block">验证码</label >
                    <input type="text" class="form-control" style=" 250px; display: inline-block">
                    <img src="/v_code/" style="float: right">
                </div>
                <p id="error-p" class="err-text"></p>
                <button id="login-btn" type="button" class="btn btn-success">登陆</button>
            </form>
            </div>
        </div>
    </div>
    <script src="/static/js/jquery.js"></script>
    <script>
        $('#login-btn').click(function () {
            let username = $('#id_username').val();
            let pwd = $('#id_password').val();
            let csr_token = $("[name='csrfmiddlewaretoken']").val();
            $.ajax({
                url:'/login/',
                type:'post',
                data:{
                    username:username,
                    password:pwd,
                    csrfmiddlewaretoken: csr_token,
                },
                success:function (res) {
                    console.log(res);
                    if(res['code'] !== 0){
                        $('#error-p').text(res['msg'])
                    } else{
                        location.href= '/index/'
                    }
                }
            });
            $('.form-control').focus(function () {
                 $('#error-p').text('')
            })
        })
    </script>
    </body>
    </html>

    views.py代码:

    from django.shortcuts import render,HttpResponse
    from django import views
    from blog.forms import LoginForm
    from django.contrib.auth import authenticate,logout,login
    from django.http import JsonResponse
    # Create your views here.
    
    def index(request):
        return render(request,'index.html')
    
    
    
    class Login(views.View):
        def get(self,request):
            form_obj = LoginForm()
            return render(request,'login.html',{'form_obj':form_obj})
        def post(self,request):
            res = {'code':0}
            username = request.POST.get('username')
            pwd = request.POST.get('password')
            # 校验用户名密码是否正确
            user = authenticate(username=username,password=pwd)
            if user:
                # 正确就登陆
                login(request,user)
            else:
                # 用户名或密码错误
                res['code'] = 1
                res['msg'] = '用户名或密码错误'
            return JsonResponse(res)

    这些就是之前将的登陆,现在我们在这个登陆上面加上验证码:

    首先验证码的图片应该是随机生成的:

    #随机生成图片
    from PIL import Image
    import random
        image_obj = Image.new(
            'RGB',#生成图片的模式
            (250,35),#图片大小
            (random.randint(0,255),random.randint(0,255),random.randint(0,255)),
        )
        with open('static/img/xx.png','wb')as f:
            image_obj.save(f)

    这样生成的图片就会不一样

    但是这样生成图片的方法很麻烦,因为每次都要打开一个文件,把图片写进去,然后用的时候在打开文件拿出来。

    def v_code(request):
        # 随机生成图片
        from PIL import Image,ImageFont,ImageDraw
        import random
        image_obj = Image.new(
            'RGB',#生成图片的模式
            (250,35),#图片大小
            (random.randint(0,255),random.randint(0,255),random.randint(0,255)),
        )
     #直接将生成的图片保存在内存中
        from io import BytesIO
        f = BytesIO()
        image_obj.save(f,'png')
        #从内存中读取图片数据
        data = f.getvalue()
        return HttpResponse(data, content_type='image/png')

    接下来我们做图片上歪歪扭扭的字体:

    #生成一个准备写字的画笔
        draw_obj = ImageDraw.Draw(image_obj) # 在哪里写
        font_obj = ImageFont.truetype('static/font/kumo.ttf',size=28)# 加载本地的字体文件
    #专门用来返回验证码图片的试图
    def v_code(request):
        # 随机生成图片
        from PIL import Image,ImageFont,ImageDraw
        import random
        #生成图片对象
        image_obj = Image.new(
            'RGB',#生成图片的模式
            (250,35),#图片大小
            (random.randint(0,255),random.randint(0,255),random.randint(0,255)),
        )
        #生成一个准备写字的画笔
        draw_obj = ImageDraw.Draw(image_obj) # 在哪里写
        font_obj = ImageFont.truetype('static/font/kumo.ttf',size=28)# 加载本地的字体文件

      #生成随机验证码
      tmp = []
      for i in range(5):
      n = str(random.randint(0,9)) # 随机数字
      l = chr(random.randint(65,90)) # 随机英文小写
      r = chr(random.randint(97,122)) # 随机英文大写
      r = random.choice([n,l,r])
      tmp.append(r)
      # 每一次取到要写的东西之后,往图片上写
      draw_obj.text(
      (i * 45 + 25, 0 ), # 坐标
      r, # 内容
      fill=(random.randint(0,255),random.randint(0,255),random.randint(0,255)),# 颜色
      font=font_obj,# 字体
      )

    #将上一步生成的图片保存在本地的static目录下 #每一次都在硬盘中保存在读取涉及到io操作,很慢 # with open('static/img/xx.png','wb')as f: # image_obj.save(f) # # with open('static/img/v_code.png','rb')as f: # data = f.read() #直接将生成的图片保存在内存中 from io import BytesIO f = BytesIO() image_obj.save(f,'png') #从内存中读取图片数据 data = f.getvalue() return HttpResponse(data, content_type='image/png')

    这样生成的图片上面就有文字了。

    最后一步在全局定义一个 V_CODE = ‘’,

        v_code = ''.join(tmp) # c84yR 取到最后的验证码

        global V_CODE
        V_CODE = v_code # 将他赋值给全局的V_CODE中,在全局就可以用了

    整个代码:

    #专门用来返回验证码图片的试图
    def v_code(request):
        # 随机生成图片
        from PIL import Image,ImageFont,ImageDraw
        import random
        #生成图片对象
        image_obj = Image.new(
            'RGB',#生成图片的模式
            (250,35),#图片大小
            (random.randint(0,255),random.randint(0,255),random.randint(0,255)),
        )
        #生成一个准备写字的画笔
        draw_obj = ImageDraw.Draw(image_obj) # 在哪里写
        font_obj = ImageFont.truetype('static/font/kumo.ttf',size=28)# 加载本地的字体文件
    
        #生成随机验证码
        tmp = []
        for i in range(5):
            n = str(random.randint(0,9)) # 随机数字
            l = chr(random.randint(65,90)) # 随机英文小写
            r = chr(random.randint(97,122)) # 随机英文大写
            r = random.choice([n,l,r])
            tmp.append(r)
            # 每一次取到要写的东西之后,往图片上写
            draw_obj.text(
                (i * 45 + 25, 0 ), # 坐标
                r, # 内容
                fill=(random.randint(0,255),random.randint(0,255),random.randint(0,255)),# 颜色
                font=font_obj,# 字体
            )
        print(tmp) # ['c', '8', '4', 'y', 'R']
        v_code = ''.join(tmp) # c84yR   取到最后的验证码
        global V_CODE
        V_CODE = v_code # 将他赋值给全局的V_CODE中,在全局就可以用了
        #将上一步生成的图片保存在本地的static目录下
        #每一次都在硬盘中保存在读取涉及到io操作,很慢
        # with open('static/img/xx.png','wb')as f:
        #     image_obj.save(f)
        #
        # with open('static/img/v_code.png','rb')as f:
        #     data = f.read()
    
        #直接将生成的图片保存在内存中
        from io import BytesIO
        f = BytesIO()
        image_obj.save(f,'png')
        #从内存中读取图片数据
        data = f.getvalue()
        return HttpResponse(data, content_type='image/png')
    class Login(views.View):
        def get(self,request):
            form_obj = LoginForm()
            return render(request,'login.html',{'form_obj':form_obj})
        def post(self,request):
            res = {'code':0}
            username = request.POST.get('username')
            pwd = request.POST.get('password')
            v_code = request.POST.get('v_code')
            # 先判断验证码是否正确
            if v_code.upper() != V_CODE.upper():
                res['code'] = 1
                res['msg'] = '验证码错误'
            else:
                # 校验用户名密码是否正确
                user = authenticate(username=username,password=pwd)
                if user:
                    # 正确就登陆
                    login(request,user)
                else:
                    # 用户名或密码错误
                    res['code'] = 1
                    res['msg'] = '用户名或密码错误'
            return JsonResponse(res)

    如果有多个人同时访问login页面,那验证码就会出问题,这时候就要加上session了。

    不同的用户在访问login页面时,就会有不同的验证码,不一样的数据就应该存在session里。所以保存在全局中是不行的。

      v_code = "".join(tmp)  # 得到最终的验证码
        # global V_CODE
        # V_CODE = v_code  # 保存在全局变量不行!!!
        # 将该次请求生成的验证码保存在该请求对应的session数据中
        request.session['v_code'] = v_code.upper()

    最后整理一下:

    #专门用来返回验证码图片的试图
    def v_code(request):
        # 随机生成图片
        from PIL import Image,ImageFont,ImageDraw
        import random
        def random_color():
            return random.randint(0,255),random.randint(0,255),random.randint(0,255)
        #生成图片对象
        image_obj = Image.new(
            'RGB',#生成图片的模式
            (250,35),#图片大小
            random_color(),# 图片颜色,写成了函数
        )
        #生成一个准备写字的画笔
        draw_obj = ImageDraw.Draw(image_obj) # 在哪里写
        font_obj = ImageFont.truetype('static/font/kumo.ttf',size=28)# 加载本地的字体文件
    
        #生成随机验证码
        tmp = []
        for i in range(5):
            n = str(random.randint(0,9)) # 随机数字
            l = chr(random.randint(65,90)) # 随机英文小写
            r = chr(random.randint(97,122)) # 随机英文大写
            r = random.choice([n,l,r])
            tmp.append(r)
            # 每一次取到要写的东西之后,往图片上写
            draw_obj.text(
                (i * 45 + 25, 0 ), # 坐标
                r, # 内容
                fill=random_color(),# 颜色
                font=font_obj,# 字体
            )
        print(tmp) # ['c', '8', '4', 'y', 'R']
        v_code = ''.join(tmp) # c84yR   取到最后的验证码
        # global V_CODE # 保存到全局不行
        # V_CODE = v_code # 将他赋值给全局的V_CODE中,在全局就可以用了
        request.ssession['v_code'] = v_code.upper()
        #将上一步生成的图片保存在本地的static目录下
        #每一次都在硬盘中保存在读取涉及到io操作,很慢
        # with open('static/img/xx.png','wb')as f:
        #     image_obj.save(f)
        #
        # with open('static/img/v_code.png','rb')as f:
        #     data = f.read()
    
        #直接将生成的图片保存在内存中
        from io import BytesIO
        f = BytesIO()
        image_obj.save(f,'png')
        #从内存中读取图片数据
        data = f.getvalue()
        return HttpResponse(data, content_type='image/png')
    class Login(views.View):
        def get(self,request):
            form_obj = LoginForm()
            return render(request,'login.html',{'form_obj':form_obj})
        def post(self,request):
            res = {'code':0}
            username = request.POST.get('username')
            pwd = request.POST.get('password')
            v_code = request.POST.get('v_code')
            # 先判断验证码是否正确
            # if v_code.upper() != V_CODE.upper():
            if v_code.upper != request.session.get('v_code',''):
                res['code'] = 1
                res['msg'] = '验证码错误'
            else:
                # 校验用户名密码是否正确
                user = authenticate(username=username,password=pwd)
                if user:
                    # 正确就登陆
                    login(request,user)
                else:
                    # 用户名或密码错误
                    res['code'] = 1
                    res['msg'] = '用户名或密码错误'
            return JsonResponse(res)

    还可以在图片中加一些干扰线和干扰点:

     # 加干扰线
        width = 250  # 图片宽度(防止越界)
        height = 35
        for i in range(5):  # 干扰线数量
            x1 = random.randint(0, width)
            x2 = random.randint(0, width)
            y1 = random.randint(0, height)
            y2 = random.randint(0, height)
            draw_obj.line((x1, y1, x2, y2), fill=random_color())
    
        # 加干扰点
        for i in range(40):  # 干扰点数量
            draw_obj.point([random.randint(0, width), random.randint(0, height)], fill=random_color())
            x = random.randint(0, width)
            y = random.randint(0, height)
            draw_obj.arc((x, y, x+4, y+4), 0, 90, fill=random_color())

     最后再加一步:

    就是点击图片时,就刷新验证码:

    $("#i1").click(function () {
            this.src += "?"
        })

    加问号的效果就是既不改变原来的访问页面,同时也能实现刷新。

  • 相关阅读:
    如何使用Python创建可视化对象
    Power Query中如何解析累积总计
    如何快速复制度量值?
    如何使用文本字段进行条件格式设置
    Power BI 3-4月功能更新培训合集
    2019微软Power BI 每月功能更新系列——Power BI 4月版本功能完整解读
    送你一份堆积柱形图小点心,请收下~
    命令行编译带外部包依赖的java源文件 [以JDBC MySQL8为例]
    JavaScript (JS)常用方法
    JDK8过渡到JDK11
  • 原文地址:https://www.cnblogs.com/yb635238477/p/9471221.html
Copyright © 2020-2023  润新知