前情回顾
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 += "?" })
加问号的效果就是既不改变原来的访问页面,同时也能实现刷新。