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 += "?"
    })

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

原文地址:https://www.cnblogs.com/yb635238477/p/9471221.html