Django项目实战:BBS+Blog项目开发之一登录和注册功能
一. 博客系统之功能需求
1.1 项目流程:
1 搞清楚需求 2 设计表结构 3 按照每一个功能进行开发 4 功能测试 5 项目部署上线
1.2 搞清楚需求 仿照博客园, 注意:微信朋友圈也是博客
1 基于用户认证组件和Ajax实现登录验证(图片验证码)
2 基于forms组件和Ajax实现注册功能
3 设计系统首页(文章列表渲染)
4 设计个人站点页面
5 文章详情页
6 实现文章点赞功能
7 实现文章的评论
----文章评论
---- 评论的评论
8 富文本编辑框和防止xss攻击
二. 博客系统之表结构设计1 用户表,针对需求1,2
一个用户对应一个站点
from django.contrib.auth.models import User, AbstractUser # AbstractUser是用户父表. User对应auth_user表, User继承自AbstractUser. class UserInfo(AbstractUser): # 扩展用户表,同样继承自AbstractUser, 注意这里没有user,pwd等字段,因为这些字段在Django自带的auth_user表中 """用户信息"""
nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to='avatars/', default='/avatars/default.png') # 头像 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) # 用户创建时间(园龄) blog = models.OneToOneField(to='Blog', to_field='nid', null=True) def __str__(self): return self.username
三. 博客系统值表结构设计2 博客信息表(个人站点表), 个人文章分类表, 标签表
一个站点对应多个文章分类, 一个站点对应多个标签(关键字)
from django.db import models from django.contrib.auth.models import User, AbstractUser class Blog(models.Model): """博客信息表(个人站点表)""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='个人博客标题', max_length=64) site_name = models.CharField(verbose_name="站点名称", max_length=64) theme = models.CharField(verbose_name='博客主题', max_length=32) def __str__(self): return self.title class Category(models.Model): """博主个人文章分类表""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='分类标题', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid') def __str__(self): return self.title class Tag(models.Model): """标签,相当于论文的关键字""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='标签名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid') def __str__(self): return self.title
四. 博客系统值表结构设计3 文章详细表, 文章与标签关系表,点赞反对表
class Article(models.Model): """文章详细表""" nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章描述') # 文章摘要 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.TextField() # 文章详细内容 comment_count = models.IntegerField(default=0) # 评论数 up_count = models.IntegerField(default=0) # 点赞数 down_count = models.IntegerField(default=0) # 反对数 user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid') # 作者与文章是一对多的关系 category = models.ForeignKey(to='Category', to_field='nid', null=True) # 分类与文章是一对多的关系(博客园网站实际是多对多的关系) # 标签与文章是多对多的关系 tags = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag')) # 自主创建关系表 def __str__(self): return self.title class Article2Tag(models.Model): """文章与标签的多对多关系表""" nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='文章', to='Article', to_field='nid') tag = models.ForeignKey(verbose_name='标签', to='Tag', to_field='nid') class Meta: unique_together = [ # 联合唯一 ('article', 'tag'), ] def __str__(self): v = self.article.title + '---' + self.tag.title return v class ArticleUpDown(models.Model): """点赞表(反对表)""" nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True) # 用户ID article = models.ForeignKey('Article', null=True) # 文章ID is_up = models.BooleanField(default=True) # True是点赞, False是反对 class Meta: unique_together = [ # 联合唯一 ('article', 'user'), ]
五. 博客系统值表结构设计4 评论表
class Comment(models.Model): """ 评论表 1根评论:对文章的评论 2子评论:对评论的评论 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey(verbose_name='评论者', to="UserInfo", to_field='nid') article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid') create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.CharField(verbose_name='评论内容', max_length=255) parent_comment = models.ForeignKey('self', null=True) # 自关联, 创建父评论ID, parent_comment_id def __str__(self): return self.content
对于自关联的解释,就是parent_comment字段关联了本表的nid.父评论与子评论是一对多的关系.
下面的444评论,它是111评论的子评论,存储在表中是第4条记录, 它的父评论parent_comment_id是记录1.也就是它的父评论在第1条记录中.
下面的555评论,它是444评论的子评论.存储在表中是第5条记录, 它的父评论praent_comment_id是记录4.也就是它飞父评论在第4条记录中.
六. 博客系统值表结构设计5 理清所有表之间的关系
注意在Article表中的三个字段, 是每次用户新增一个评论后就在此处为总评论数加1.这里牺牲了增加的效率,而提高了查询效率.
comment_count = models.IntegerField(default=0) # 评论数
up_count = models.IntegerField(default=0) # 点赞数
down_count = models.IntegerField(default=0) # 反对数
注意在Comment表中的自关联,关系是,父评论对子评论是一对多的关系.
完整的表模型代码:迁移时:一对一,一对多的表关系中需要加入on_delete=models.CASCADE不然出错
from django.db import models from django.contrib.auth.models import User, AbstractUser class UserInfo(AbstractUser): """ 用户信息 扩展用户表,同样继承自AbstractUser, 注意这里没有user,pwd等字段,因为这些字段在Django自带的auth_user表中 """ nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to='avatars/', default='/avatars/default.png') # 头像 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) # 用户创建时间(园龄) blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.CASCADE) def __str__(self): return self.username class Blog(models.Model): """博客信息表(个人站点表)""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='个人博客标题', max_length=64) site_name = models.CharField(verbose_name="站点名称", max_length=64) theme = models.CharField(verbose_name='博客主题', max_length=32) def __str__(self): return self.title class Category(models.Model): """博主个人文章分类表""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='分类标题', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid', on_delete=models.CASCADE) def __str__(self): return self.title class Tag(models.Model): """标签,相当于论文的关键字""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='标签名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid', on_delete=models.CASCADE) def __str__(self): return self.title class Article(models.Model): """文章详细表""" nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章描述') # 文章摘要 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.TextField() # 文章详细内容 comment_count = models.IntegerField(default=0) # 评论数 up_count = models.IntegerField(default=0) # 点赞数 down_count = models.IntegerField(default=0) # 反对数 user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid', on_delete=models.CASCADE) # 作者与文章是一对多的关系 category = models.ForeignKey(to='Category', to_field='nid', null=True, on_delete=models.CASCADE) # 分类与文章是一对多的关系 # 标签与文章是多对多的关系 tags = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag')) # 自主创建关系表 def __str__(self): return self.title class Article2Tag(models.Model): """文章与标签的多对多关系表""" nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='文章', to='Article', to_field='nid', on_delete=models.CASCADE) tag = models.ForeignKey(verbose_name='标签', to='Tag', to_field='nid', on_delete=models.CASCADE) class Meta: unique_together = [ # 联合唯一 ('article', 'tag'), ] def __str__(self): v = self.article.title + '---' + self.tag.title return v class ArticleUpDown(models.Model): """点赞表(反对表)""" nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True, on_delete=models.CASCADE) # 用户ID article = models.ForeignKey('Article', null=True, on_delete=models.CASCADE) # 文章ID is_up = models.BooleanField(default=True) # True是点赞, False是反对 class Meta: unique_together = [ # 联合唯一 ('article', 'user'), ] class Comment(models.Model): """ 评论表 1根评论:对文章的评论 2子评论:对评论的评论 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey(verbose_name='评论者', to="UserInfo", to_field='nid', on_delete=models.CASCADE) article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid', on_delete=models.CASCADE) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.CharField(verbose_name='评论内容', max_length=255) parent_comment = models.ForeignKey('self', null=True, on_delete=models.CASCADE) # 自关联, 创建父评论ID, parent_comment_id def __str__(self): return self.content
七. 博客系统值创建项目与迁移
项目名称cnblog, app名称blog.
# settings.py中加上下面这个设置 AUTH_USER_MODEL = 'blog.UserInfo' # UserInfo表是auth_user表的扩展表,这里需要增加这个设置
教程中使用mysql.这个为了方便暂时使用sqlite数据库
数据库迁移2个命令python manage.py makemigrations python manage.py migrate
迁移完成后生成的10个表如下:
如果使用mysql,需要做以下工作:settings.py中修改数据库连接配置
在pycharm中配置mysql数据库连接读取,方便以后直接查看管理数据库.
八. 博客系统值登录页面的设计
8.1 课程中下载bootstrap到本地,在根目录新建static目录,并配置了静态目录 静态目录用于存放bootstrap,jquery,Font字体,自定义的css,js等文件
# settings.py增加配置的静态目录 STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
8.2 设计效果
8.3 登录页面源代码, 这里注意我们将要使用的是Ajax,所以尽管有form表单标签,但这里仅起代码编写提示作用.input标签未设置name也是这个原因.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>用户登录</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <input type="button" class="btn btn-default login_btn pull-right" value="submit"> </form> </div> </div> </div> </body> </html>
九. 博客系统之验证码图片生成1
9.1 验证码图片渲染在模板的关键代码
<div class="form-group"> <label for="valid_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="valid_code" class="form-control"> </div> <div class="col-md-6"> <img width="270" height="35" src="/get_validCode_img/" alt="验证码"> </div> </div> </div>
9.2 生成验证码图片的视图关键代码(存储在硬盘,是不成熟方案)
def get_validCode_img(request): """生成验证码图片""" def get_random_color(): # 生成随机的颜色数值 import random return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 生成图片 from PIL import Image img = Image.new('RGB', (270, 40), color=get_random_color()) with open('validCode.png', 'wb') as f: img.save(f, 'png') # 读取图片 with open('validCode.png', 'rb') as f: data = f.read() # 将图片渲染到模板页面 return HttpResponse(data)
9.3 最终效果
十. 博客系统之验证码图片生成2 生成随机数字字母写入到图片中
10.1 生成随机字母数字的验证码, 在内存中生成png, 不再将图片保存在磁盘中,
def get_validCode_img(request): """生成验证码图片""" import random def get_random_color(): # 生成随机的RGB颜色数值 return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 生成图片 from PIL import Image, ImageDraw, ImageFont # ImageDraw画笔工具 from io import BytesIO # 引入内存管理工具 img = Image.new('RGB', (270, 40), color=get_random_color()) # 在img上画画 draw = ImageDraw.Draw(img) fzjz_font = ImageFont.truetype("static/font/fzjz.TTF", size=30) # 方正剪纸字体, 字体保存在项目的静态文件夹中 # 生成5个数字字母组合 for i in range(5): # 生成随机字母 random_num = str(random.randint(0, 9)) # 随机数字 random_low_alpha = chr(random.randint(97, 122)) # 随机小写字母 random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字母 random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) print(random_char) # --------书写位置x, y--------书写内容---------文字颜色-------------字体--------- draw.text((i*55+15, 5), random_char, get_random_color(), font=fzjz_font) # 写字 # draw.line() # 画线 # draw.point() # 画点 # 在内存中生成png图片 f = BytesIO() img.save(f, 'png') data = f.getvalue() # 将图片渲染到模板页面 return HttpResponse(data)
10.2 最终效果
十一. 博客系统之验证码图片的噪点燥线
11.1 关键代码
# 增加干扰线\燥点\画圆圈 width = 270 height = 40 for i in range(10): x1 = random.randint(0, width) x2 = random.randint(0, width) y1 = random.randint(0, height) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(100): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color())
11.2 视图完整代码
from django.shortcuts import render, HttpResponse # Create your views here. def login(request): return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" import random def get_random_color(): # 生成随机的RGB颜色数值 return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 生成图片 from PIL import Image, ImageDraw, ImageFont # ImageDraw画笔工具 from io import BytesIO # 引入内存管理工具 img = Image.new('RGB', (270, 40), color=get_random_color()) # 在img上画画 draw = ImageDraw.Draw(img) fzjz_font = ImageFont.truetype("static/font/fzjz.TTF", size=30) # 方正剪纸字体 # 生成5个数字字母组合 for i in range(5): # 生成随机字母 random_num = str(random.randint(0, 9)) # 随机数字 random_low_alpha = chr(random.randint(97, 122)) # 随机小写字母 random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字母 random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) print(random_char) # --------书写位置x, y--------书写内容---------文字颜色-------------字体--------- draw.text((i*55+15, 5), random_char, get_random_color(), font=fzjz_font) # 写字 # draw.line() # 画线 # draw.point() # 画点 # 增加干扰线\燥点\画圆圈 width = 270 height = 40 for i in range(10): x1 = random.randint(0, width) x2 = random.randint(0, width) y1 = random.randint(0, height) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(100): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color()) # 在内存中生成png图片 f = BytesIO() img.save(f, 'png') data = f.getvalue() # 将图片渲染到模板页面 return HttpResponse(data)
11.3 最终效果
十二. 博客系统之验证码刷新 点击验证码图片刷新验证码
12.1 关键代码 login.html $('#valid_code_img')[0].src += '?'该代码可以自动刷新. 这个刷新也是局部刷新. 类似于Ajax的局部刷新
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script> $('#valid_code_img').click(function () { $(this)[0].src += '?' }) </script>
12.2 完整代码 login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>用户登录</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="valid_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="valid_code" class="form-control"> </div> <div class="col-md-6"> <img width="270" height="35" src="/get_validCode_img/" id='valid_code_img' alt="验证码"> </div> </div> </div> <input type="button" class="btn btn-default login_btn pull-right" value="submit"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> $('#valid_code_img').click(function () { $(this)[0].src += '?' }) </script> </body> </html>
十三. 博客系统之保存验证码字符串 保存在session中,校验时从session中提取
再次体验跟踪会话技术session. 每个客户端将验证码存储到自己所属的session中,校验时再冲session中提取,就不会和别的客户端冲突了.
13.1 将生成的验证码字符串保存到session
def get_validCode_img(request): """生成验证码图片""" import random def get_random_color(): return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) from PIL import Image, ImageDraw, ImageFont from io import BytesIO img = Image.new('RGB', (270, 40), color=get_random_color()) draw = ImageDraw.Draw(img) fzjz_font = ImageFont.truetype("static/font/fzjz.TTF", size=30) valid_code_str = '' # 存储生成的验证码字符串 for i in range(5): random_num = str(random.randint(0, 9)) random_low_alpha = chr(random.randint(97, 122)) random_upper_alpha = chr(random.randint(65, 90)) random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) draw.text((i*55+15, 5), random_char, get_random_color(), font=fzjz_font) valid_code_str += random_char # 拼接验证码字符串 # 将验证码字符串写入到session中 request.session['valid_code'] = valid_code_str # 虽然是简单的一句代码,其实完成了三部操作哦. print(valid_code_str) width = 270 height = 40 for i in range(10): x1 = random.randint(0, width) x2 = random.randint(0, width) y1 = random.randint(0, height) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(100): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color()) f = BytesIO() img.save(f, 'png') data = f.getvalue() return HttpResponse(data)
13.2 读取session中的验证码字符串, 用户输入验证码, 然后两个进行比对
def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 response['msg'] = '验证码正确' return JsonResponse(response) # 直接传递json数据 需要导入from django.http import JsonResponse # 传递json数据类型 else: response['msg'] = '验证码输入错误' return JsonResponse(response) return render(request, 'login.html')
from django.shortcuts import render, HttpResponse from django.http import JsonResponse # 传递json数据类型 # Create your views here. def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 response['msg'] = '验证码正确' return JsonResponse(response) # 直接传递json数据 else: response['msg'] = '验证码输入错误' return JsonResponse(response) return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" import random def get_random_color(): # 生成随机的RGB颜色数值 return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 生成图片 from PIL import Image, ImageDraw, ImageFont # ImageDraw画笔工具 from io import BytesIO # 引入内存管理工具 img = Image.new('RGB', (270, 40), color=get_random_color()) # 在img上画画 draw = ImageDraw.Draw(img) fzjz_font = ImageFont.truetype("static/font/fzjz.TTF", size=30) # 方正剪纸字体 # 生成5个数字字母组合 valid_code_str = '' # 存储生成的验证码字符串 for i in range(5): # 生成随机字母 random_num = str(random.randint(0, 9)) # 随机数字 random_low_alpha = chr(random.randint(97, 122)) # 随机小写字母 random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字母 random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) # --------书写位置x, y--------书写内容---------文字颜色-------------字体--------- draw.text((i*55+15, 5), random_char, get_random_color(), font=fzjz_font) # 写字 valid_code_str += random_char # 拼接验证码字符串 # 将验证码字符串写入到session中 request.session['valid_code'] = valid_code_str # 虽然是简单的一句代码,其实完成了三部操作哦. print(valid_code_str) # 增加干扰线\燥点\画圆圈 width = 270 height = 40 for i in range(10): x1 = random.randint(0, width) x2 = random.randint(0, width) y1 = random.randint(0, height) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(100): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color()) # 在内存中生成png图片 f = BytesIO() img.save(f, 'png') data = f.getvalue() # 将图片渲染到模板页面 return HttpResponse(data)
13.3 login.html中提交登录验证的ajax代码, 注意csrfmiddlewaretoken必须要有
// 登录验证
$('.login_btn').click(function () {
$.ajax({
url:"",
type:"post",
data:{
user: $('#user').val(),
pwd: $('#pwd').val(),
valid_code: $('#valid_code').val(),
csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
},
success: function (data) {
console.log(data)
}
})
})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>用户登录</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="valid_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="valid_code" class="form-control"> </div> <div class="col-md-6"> <img width="270" height="35" src="/get_validCode_img/" id='valid_code_img' alt="验证码"> </div> </div> </div> <input type="button" class="btn btn-default login_btn pull-right" value="submit"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> $('#valid_code_img').click(function () { $(this)[0].src += '?' }); // 登录验证 $('.login_btn').click(function () { $.ajax({ url:"", type:"post", data:{ user: $('#user').val(), pwd: $('#pwd').val(), valid_code: $('#valid_code').val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { console.log(data) } }) }) </script> </body> </html>
十四. 博客系统之登录验证 验证用户名密码并给出错误提示
14.1 为数据库增加一个超级用户
命令: python manage.py createsuperuser
14.2 用户登录验证视图函数,注意这里我们用的是ajax方式提交数据的,所以用户验证成功(或失败)后的返回json用户数据到login模板,而不是直接跳转index.因为模板中的ajax只接收json数据.
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # 验证成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,有模板进行跳转. return render(request, 'login.html')
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" import random def get_random_color(): # 生成随机的RGB颜色数值 return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 生成图片 from PIL import Image, ImageDraw, ImageFont # ImageDraw画笔工具 from io import BytesIO # 引入内存管理工具 img = Image.new('RGB', (270, 40), color=get_random_color()) # 在img上画画 draw = ImageDraw.Draw(img) fzjz_font = ImageFont.truetype("static/font/fzjz.TTF", size=30) # 方正剪纸字体 # 生成5个数字字母组合 valid_code_str = '' # 存储生成的验证码字符串 for i in range(5): # 生成随机字母 random_num = str(random.randint(0, 9)) # 随机数字 random_low_alpha = chr(random.randint(97, 122)) # 随机小写字母 random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字母 random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) # --------书写位置x, y--------书写内容---------文字颜色-------------字体--------- draw.text((i*55+15, 5), random_char, get_random_color(), font=fzjz_font) # 写字 valid_code_str += random_char # 拼接验证码字符串 # 将验证码字符串写入到session中 request.session['valid_code'] = valid_code_str # 虽然是简单的一句代码,其实完成了三部操作哦. print(valid_code_str) # 增加干扰线\燥点\画圆圈 width = 270 height = 40 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.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(20): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color()) # 在内存中生成png图片 f = BytesIO() img.save(f, 'png') data = f.getvalue() # 将图片渲染到模板页面 return HttpResponse(data) def index(request): return render(request, 'index.html')
14.3 登录模板login.html, 注意验证成功后的跳转,和验证失败后的显示错误提示.
// 登录验证 $('.login_btn').click(function () { $.ajax({ url:"", type:"post", data:{ user: $('#user').val(), pwd: $('#pwd').val(), valid_code: $('#valid_code').val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { {#console.log(data)#} if (data.user){ {# location.href跳转到 首页 #} location.href = '/index/' } else { $('#login_error').text(data.msg) } } })
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>用户登录</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="valid_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="valid_code" class="form-control"> </div> <div class="col-md-6"> <img width="270" height="35" src="/get_validCode_img/" id='valid_code_img' alt="验证码"> </div> </div> </div> <span id="login_error" style="color: red; margin-right: 10px"></span> <input type="button" class="btn btn-default login_btn pull-right" value="submit"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> $('#valid_code_img').click(function () { $(this)[0].src += '?' }); // 登录验证 $('.login_btn').click(function () { $.ajax({ url:"", type:"post", data:{ user: $('#user').val(), pwd: $('#pwd').val(), valid_code: $('#valid_code').val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { {#console.log(data)#} if (data.user){ {# location.href跳转到 首页 #} location.href = '/index/' } else { $('#login_error').text(data.msg) } } }) }) </script> </body> </html>
"""cnblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path from blog import views urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('get_validCode_img/', views.get_validCode_img), ]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>网站首页</h3> <h3>欢迎:{{ request.user.username }}</h3> </body> </html>
14.4 博客系统登录验证总结
主要实现功能1: 基于用户认证组件和Ajax实现登录验证(图片验证码)
总结:
1. 一次请求伴随多次请求 2. PIL 3. session存储 4. 验证码刷新
十五. 博客系统之登录验证码代码优化 解耦合
15.1 将视图文件中的验证码图片生成函数解耦
在blog文件夹中建立utils工具模块文件夹,新建validCode.py文件,用于存放生成图片验证码的函数.
import random def get_random_color(): # 生成随机的RGB颜色数值 return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) def get_validCode_img(request): """生成验证码图片""" # 生成图片 from PIL import Image, ImageDraw, ImageFont # ImageDraw画笔工具 from io import BytesIO # 引入内存管理工具 img = Image.new('RGB', (270, 40), color=get_random_color()) # 在img上画画 draw = ImageDraw.Draw(img) fzjz_font = ImageFont.truetype("static/font/fzjz.TTF", size=30) # 方正剪纸字体 # 生成5个数字字母组合 valid_code_str = '' # 存储生成的验证码字符串 for i in range(5): # 生成随机字母 random_num = str(random.randint(0, 9)) # 随机数字 random_low_alpha = chr(random.randint(97, 122)) # 随机小写字母 random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字母 random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) # --------书写位置x, y--------书写内容---------文字颜色-------------字体--------- draw.text((i*55+15, 5), random_char, get_random_color(), font=fzjz_font) # 写字 valid_code_str += random_char # 拼接验证码字符串 # 将验证码字符串写入到session中 request.session['valid_code'] = valid_code_str # 虽然是简单的一句代码,其实完成了三部操作哦. print(valid_code_str) # 增加干扰线\燥点\画圆圈 width = 270 height = 40 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.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(20): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color()) # 在内存中生成png图片 f = BytesIO() img.save(f, 'png') data = f.getvalue() return data
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img # 导入自定义的生成验证码功能函数 data = get_validCode_img(request) return HttpResponse(data) def index(request): return render(request, 'index.html')
15.2 为登录页面模板中增加错误提示信息的消失时间,在回调函数中使用setTimeout()
success: function (data) { if (data.user){ location.href = '/index/' } else { $('#login_error').text(data.msg); {#错误提示一秒钟后消失#} setTimeout(function () { $('#login_error').text("") },1000) }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>用户登录</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="valid_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="valid_code" class="form-control"> </div> <div class="col-md-6"> <img width="270" height="35" src="/get_validCode_img/" id='valid_code_img' alt="验证码"> </div> </div> </div> <span id="login_error" style="color: red; margin-right: 10px"></span> <input type="button" class="btn btn-default login_btn pull-right" value="submit"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> $('#valid_code_img').click(function () { $(this)[0].src += '?' }); // 登录验证 $('.login_btn').click(function () { $.ajax({ url:"", type:"post", data:{ user: $('#user').val(), pwd: $('#pwd').val(), valid_code: $('#valid_code').val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { {#console.log(data)#} if (data.user){ {# location.href跳转到 首页 #} location.href = '/index/' } else { $('#login_error').text(data.msg); {#错误提示一秒钟后消失#} setTimeout(function () { $('#login_error').text("") },1000) } } }) }) </script> </body> </html>
十六. 博客系统值滑动验证码作业 待学习熟练后再做
滑动验证码是一个公司的产品.
从网上下载,嵌入到自己的系统中即可
参考;https://blog.csdn.net/qq_26877377/article/details/80452086
十七. 博客系统值基于forms组件的注册页面设计1
17.1 最终效果
17.2 form组件的验证规则,以及注册视图函数
from django import forms # 引入表单验证的模块 from django.forms import widgets # forms组件的参数配置模块 class RegForm(forms.Form): """验证规则创建, 这里只选取了部分字段""" user = forms.CharField(max_length=32, label='用户名', widget=widgets.TextInput(attrs={'class': 'form-control'},)) pwd = forms.CharField(max_length=32, label='密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'},)) re_pwd = forms.CharField(max_length=32, label='确认密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'},)) email = forms.CharField(max_length=32, label='电子邮件', widget=widgets.TextInput(attrs={'class': 'form-control'},)) def reg(request): """用户注册视图""" form = RegForm() # 带入验证规则 return render(request, 'reg.html', {"form": form})
17.2 reg.html页面的关键代码, 注意头像单独渲染
<div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} {% for field in form %} <div class="form-group"> <label>{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> {# 头像无需放在视图中的forms组件中进行检验,所以单独渲染 #} <label for="avatar">头像</label> <input type="file"> </div> <input type="button" class="btn btn-default login_btn pull-right btn-success" value="注册"> </form>
<div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} {% for field in form %} <div class="form-group"> <label>{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> {# 头像无需放在视图中的forms组件中进行检验,所以单独渲染 #} <label for="avatar">头像</label> <input type="file"> </div> <input type="button" class="btn btn-default login_btn pull-right btn-success" value="注册"> </form>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>用户登录</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="valid_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="valid_code" class="form-control"> </div> <div class="col-md-6"> <img width="270" height="35" src="/get_validCode_img/" id='valid_code_img' alt="验证码"> </div> </div> </div> <input type="button" class="btn btn-default login_btn" value="登录"> <span id="login_error" style="color: red; margin-left: 10px"></span> <input type="button" class="btn btn-default login_btn pull-right btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> $('#valid_code_img').click(function () { $(this)[0].src += '?' }); // 登录验证 $('.login_btn').click(function () { $.ajax({ url:"", type:"post", data:{ user: $('#user').val(), pwd: $('#pwd').val(), valid_code: $('#valid_code').val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { {#console.log(data)#} if (data.user){ {# location.href跳转到 首页 #} location.href = '/index/' } else { $('#login_error').text(data.msg); {#错误提示一秒钟后消失#} setTimeout(function () { $('#login_error').text("") },1000) } } }) }) </script> </body> </html>
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 from django import forms # 引入表单验证的模块 from django.forms import widgets # forms组件的参数配置模块 class RegForm(forms.Form): """验证规则创建, 这里只选取了部分字段""" user = forms.CharField(max_length=32, label='用户名', widget=widgets.TextInput(attrs={'class': 'form-control'},)) pwd = forms.CharField(max_length=32, label='密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'},)) re_pwd = forms.CharField(max_length=32, label='确认密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'},)) email = forms.CharField(max_length=32, label='电子邮件', widget=widgets.TextInput(attrs={'class': 'form-control'},)) def reg(request): """用户注册视图""" form = RegForm() # 带入规则 return render(request, 'reg.html', {"form": form}) def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img # 导入自定义的生成验证码功能函数 data = get_validCode_img(request) return HttpResponse(data) def index(request): return render(request, 'index.html')
十八. 博客系统之注册页面的默认头像 隐藏上传文件框,点击头像更换头像
18.1 最终效果, 点击头像弹出打开文件对话框. 注意在静态文件夹static中建立img文件夹用于存放默认头像.jpg
18.2 关键代码 reg.html
<form> {% csrf_token %} {% for field in form %} <div class="form-group"> {# 注意 {{ field.auto_id }}就是自动生成的每一field的ID #} <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> {# 点击下面这个label就打开隐藏的input-file #} <label for="avatar"> 头像 <img id="avatar_img" height="60" width="60" src="/static/img/none.jpg"> </label> {# input框是隐藏的 #} <input type="file" id="avatar"> </div> <input type="button" class="btn btn-default btn-success" value="注册"> </form>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ {#隐藏input上传文件框#} display: none; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} {% for field in form %} <div class="form-group"> {# 注意 {{ field.auto_id }}就是自动生成的每一field的ID #} <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> {# 点击下面这个label就打开隐藏的input-file #} <label for="avatar"> 头像 <img id="avatar_img" height="60" width="60" src="/static/img/none.jpg"> </label> {# input框是隐藏的 #} <input type="file" id="avatar"> </div> <input type="button" class="btn btn-default btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> </script> </body> </html>
十九. 博客系统之注册页面的头像预览功能
19.1 在客户端预览图像的思路, 选择本地的图片,就能显示在客户端上.效果如下.
19.2 reg.html的关键代码
<script> {# 注意这里是change事件,不是click. 当选择一个文件后就是change事件 #} $('#avatar').change(function () { // 获取用户选择的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象的路径, 需要使用文件阅读器FileReader var reader = new FileReader(); reader.readAsDataURL(file_obj); // 修改img的src属性, src=文件对象的路径. // reader.onload的意思就是等待reader加载读取完毕后(从本地加载到服务器需要时间),再执行修改img的src属性. 如果没有onload将不等待,并行处理下面的代码,所以无法显示出头像 reader.onload = function(){ $("#avatar_img").attr('src', reader.result) } }) </script>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ {#隐藏input上传文件框#} display: none; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} {% for field in form %} <div class="form-group"> {# 注意 {{ field.auto_id }}就是自动生成的每一field的ID #} <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> {# 点击下面这个label就打开隐藏的input-file #} <label for="avatar"> 头像 <img id="avatar_img" height="60" width="60" src="/static/img/none.jpg"> </label> {# input框是隐藏的 #} <input type="file" id="avatar"> </div> <input type="button" class="btn btn-default btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> {# 注意这里是change事件,不是click. 当选择一个文件后就是change事件 #} $('#avatar').change(function () { // 获取用户选择的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象的路径, 需要使用文件阅读器FileReader var reader = new FileReader(); reader.readAsDataURL(file_obj); // 修改img的src属性, src=文件对象的路径. // reader.onload的意思就是等待reader加载读取完毕后(从本地加载到服务器需要时间),再执行修改img的src属性. 如果没有onload将不等待,并行处理下面的代码,所以无法显示出头像 reader.onload = function(){ $("#avatar_img").attr('src', reader.result) } }) </script> </body> </html>
二十. 博客系统之基于Ajax提交formdata数据
20.1 效果:用户点击注册,然后显示注册中的错误提示
20.2 关键代码模板reg.html:
// 基于ajax提交数据
$('.reg_btn').click(function () {
// 获取数据, 组装数据, 因为有上传文件数据,所以用form_data格式
var form_data = new FormData(); // formdata这种数据类型可以带文件
form_data.append('user', $('#id_user').val());
form_data.append('pwd', $('#id_pwd').val());
form_data.append('re_pwd', $('#id_re_pwd').val());
form_data.append('email', $('#id_email').val());
form_data.append('avatar', $('#avatar')[0].files[0]);
form_data.append('csrfmiddlewaretoken', $("[name='csrfmiddlewaretoken']").val()); // 不要丢掉, 否则会报jquery错误
// 提交数据, 验证数据
$.ajax({
url: '',
type: 'post',
contentType: false, // 因为有上传文件数据
processData: false, // 因为有上传文件数据
data: form_data, // 因为有上传文件数据,所以用form_data格式
success: function (data) {
console.log(data)
}
})
})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ {#隐藏input上传文件框#} display: none; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} {% for field in form %} <div class="form-group"> {# 注意 {{ field.auto_id }}就是自动生成的每一field的ID #} <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> {# 点击下面这个label就打开隐藏的input-file #} <label for="avatar"> 头像 <img id="avatar_img" height="60" width="60" src="/static/img/none.jpg"> </label> {# input框是隐藏的 #} <input type="file" id="avatar"> </div> <input type="button" class="btn btn-default btn-success reg_btn" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> {# 注意这里是change事件,不是click. 当选择一个文件后就是change事件 #} $('#avatar').change(function () { // 获取用户选择的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象的路径, 需要使用文件阅读器FileReader var reader = new FileReader(); reader.readAsDataURL(file_obj); // 修改img的src属性, src=文件对象的路径. // reader.onload的意思就是等待reader加载读取完毕后,再执行修改img的src属性. 如果没有onload将不等待,并行处理下面的代码,所以无法显示出头像 reader.onload = function(){ $("#avatar_img").attr('src', reader.result) } }); // 基于ajax提交数据 $('.reg_btn').click(function () { // 获取数据, 组装数据, 因为有上传文件数据,所以用form_data格式 var form_data = new FormData(); form_data.append('user', $('#id_user').val()); form_data.append('pwd', $('#id_pwd').val()); form_data.append('re_pwd', $('#id_re_pwd').val()); form_data.append('email', $('#id_email').val()); form_data.append('avatar', $('#avatar')[0].files[0]); form_data.append('csrfmiddlewaretoken', $("[name='csrfmiddlewaretoken']").val()); // 提交数据, 验证数据 $.ajax({ url: '', type: 'post', contentType: false, // 因为有上传文件数据 processData: false, // 因为有上传文件数据 data: form_data, // 因为有上传文件数据,所以用form_data格式 success: function (data) { console.log(data) } }) }) </script> </body> </html>
20.3 视图函数的数据校验
def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样,因为这里是ajax提交的数据 print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 respone = {'user': None, 'msg': None} if form.is_valid(): respone['user'] = form.claned_data.get('user') pass else: print(form.cleaned_data) # 所有校验成功的信息 print(form.errors) # 所有校验失败的信息 respone['msg'] = form.errors return JsonResponse(respone) form = RegForm() return render(request, 'reg.html', {'form': form})
from django.shortcuts import render, HttpResponse from django.contrib import auth from django.http import JsonResponse from django import forms from django.forms import widgets # Create your views here. class RegForm(forms.Form): user = forms.CharField(max_length=32, label='用户名', widget=widgets.TextInput(attrs={'class': 'form-control'},)) pwd = forms.CharField(max_length=32, label='密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'},)) re_pwd = forms.CharField(max_length=32, label='确认密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'},)) email = forms.CharField(max_length=32, label='电子邮箱', widget=widgets.EmailInput(attrs={'class': 'form-control'},)) def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样 print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 respone = {'user': None, 'msg': None} if form.is_valid(): respone['user'] = form.changed_data.get('user') pass else: print(form.cleaned_data) # 所有校验成功的信息 print(form.errors) # 所有校验失败的信息 respone['msg'] = form.errors return JsonResponse(respone) form = RegForm() return render(request, 'reg.html', {'form': form}) def login(request): if request.method == 'POST': response = {'user': None, 'msg': None} user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') valid_code_str = request.session.get('valid_code') if valid_code.upper() == valid_code_str.upper(): user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) response['user'] = user.username else: response['msg'] = '用户名或密码错误' else: response['msg'] = '验证码错误' return JsonResponse(response) return render(request, 'login.html') def index(request): return render(request, 'index.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img data = get_validCode_img(request) # 将图片渲染到模板页面 return HttpResponse(data)
二一. 博客系统之基于Ajax提交formdata数据的优化 提交的字段很多时使用
21.1 在reg.html模板中, 使用serializeArray()方法, 该方法能自动提取出表单中的name和value值
下图是console.log($('#form').serializeArray());后的效果, 发现是个数组.
21.2 关键代码, 两种方法比对.
// ajax提交数据 $('.login_btn').click(function () { var form_data = new FormData(); {#console.log($('#form').serializeArray());#} var request_data = $('#form').serializeArray(); // 获取form数据对象 $.each(request_data, function (index, data) { // 遍历form数据对象,提取出name, value,添加到form_data数组对象中(组装数据). form_data.append(data.name, data.value) }); form_data.append('avatar', $('#avatar')[0].files[0]); // 文件数据特殊,需要单独做. // 下面是原来的做法,效果和上面一样. {#form_data.append('user', $('#id_user').val());#} {#form_data.append('pwd', $('#id_pwd').val());#} {#form_data.append('re_pwd', $('#id_re_pwd').val());#} {#form_data.append('email', $('#id_email').val());#} {#form_data.append('avatar', $('#avatar')[0].files[0]);#} {#form_data.append('csrfmiddlewaretoken', $('[name="csrfmiddlewaretoken"]').val());#} $.ajax({ url: '', type: 'post', contentType: false, processData: false, data: form_data, success: function (data) { console.log(data) } })
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ display: none; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> {# 头像无需放在视图中的forms组件中进行检验,所以单独渲染 #} <label for="avatar"> 头像 <img id="avatar_img" width="60" height="60" src="/static/img/none.jpg"> </label> <input id="avatar" type="file"> </div> <input type="button" class="btn btn-default login_btn btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> // 预览头像 $('#avatar').change(function () { var file_obj = $(this)[0].files[0]; var reader = new FileReader(); reader.readAsDataURL(file_obj); reader.onload = function () { $('#avatar_img').attr('src', reader.result) } }); // ajax提交数据 $('.login_btn').click(function () { var form_data = new FormData(); {#console.log($('#form').serializeArray());#} var request_data = $('#form').serializeArray(); // 获取form数据对象 $.each(request_data, function (index, data) { // 遍历form数据对象,提取出name, value,添加到form_data数组对象中(组装数据). form_data.append(data.name, data.value) }); form_data.append('avatar', $('#avatar')[0].files[0]); // 下面是原来的做法,效果和上面一样. {#form_data.append('user', $('#id_user').val());#} {#form_data.append('pwd', $('#id_pwd').val());#} {#form_data.append('re_pwd', $('#id_re_pwd').val());#} {#form_data.append('email', $('#id_email').val());#} {#form_data.append('avatar', $('#avatar')[0].files[0]);#} {#form_data.append('csrfmiddlewaretoken', $('[name="csrfmiddlewaretoken"]').val());#} $.ajax({ url: '', type: 'post', contentType: false, processData: false, data: form_data, success: function (data) { console.log(data) } }) }) </script> </body> </html>
二二. 博客系统值基于Ajax在注册页面显示错误信息1
22.1 回调函数返回服务器的错误信息数组对象
success: function (data) {
// data.msg是服务器返回的错误信息数组对象, 下面对它的键值进行遍历
console.log(data.msg);
$.each(data.msg, function (field, msg) {
console.log(field, msg)
})
}
下图是客户端显示的错误数组效果.下面红框是遍历效果显示. 键和值
22.2 将错误提示的值显示在对应的表单字段下方, 即写在后面的span标签中. 注意{{ field }}其实是一个<input>输入框,"#id_"+field就是这个input框的id值
<div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }}<span class="error pull-right"></span> </div>
success: function (data) { // data.msg是服务器返回的错误信息数组对象, 下面对它的键值进行遍历 $.each(data.msg, function (field, msg) { $("#id_"+field).next().html(msg[0]) // 找到<span>标签,将错误信息写入.注意id_+field可定位到输入框, .next定位到输入框的后面的span }) }
上图是错误信息显示的最终效果.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ display: none; } .error{ color: red; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }}<span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> {# 头像无需放在视图中的forms组件中进行检验,所以单独渲染 #} <label for="avatar"> 头像 <img id="avatar_img" width="60" height="60" src="/static/img/none.jpg"> </label> <input id="avatar" type="file"> </div> <input type="button" class="btn btn-default login_btn btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> // 预览头像 $('#avatar').change(function () { var file_obj = $(this)[0].files[0]; var reader = new FileReader(); reader.readAsDataURL(file_obj); reader.onload = function () { $('#avatar_img').attr('src', reader.result) } }); // ajax提交数据 $('.login_btn').click(function () { var form_data = new FormData(); var request_data = $('#form').serializeArray(); // 获取form数据对象 $.each(request_data, function (index, data) { // 遍历form数据对象,提取出name, value,添加到form_data数组对象中(组装数据). form_data.append(data.name, data.value) }); form_data.append('avatar', $('#avatar')[0].files[0]); $.ajax({ url: '', type: 'post', contentType: false, processData: false, data: form_data, success: function (data) { // data.msg是服务器返回的错误信息数组对象, 下面对它的键值进行遍历 $.each(data.msg, function (field, msg) { $("#id_"+field).next().html(msg[0]) // 找到<span>标签,将错误信息写入. }) } }) }) </script> </body> </html>
二三. 博客系统值基于Ajax在注册页面显示错误信息2 清空上次的错误信息, 为错误的框加红色框线
23.1 效果
23.2 关键代码
success: function (data) { // 错误信息清除, 先清除一下错误信息, 因为这里会记录上次的结果, 如果下次提交有新的只会覆盖掉, 不会自动清除. $(".error").html(""); $(".error").parent().removeClass('has-error'); // 去除上次显示的错误的红框 // 展示此次的错误信息 $.each(data.msg, function (field, msg) { $("#id_"+field).next().html(msg[0]); // 找到<span>标签,将错误信息写入. // 让错误的input框显示红色提示框线 $("#id_"+field).parent().addClass('has-error') }) }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ display: none; } .error{ color: red; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }}<span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> {# 头像无需放在视图中的forms组件中进行检验,所以单独渲染 #} <label for="avatar"> 头像 <img id="avatar_img" width="60" height="60" src="/static/img/none.jpg"> </label> <input id="avatar" type="file"> </div> <input type="button" class="btn btn-default login_btn btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> // 预览头像 $('#avatar').change(function () { var file_obj = $(this)[0].files[0]; var reader = new FileReader(); reader.readAsDataURL(file_obj); reader.onload = function () { $('#avatar_img').attr('src', reader.result) } }); // ajax提交数据 $('.login_btn').click(function () { var form_data = new FormData(); var request_data = $('#form').serializeArray(); // 获取form数据对象 $.each(request_data, function (index, data) { // 遍历form数据对象,提取出name, value,添加到form_data数组对象中(组装数据). form_data.append(data.name, data.value) }); form_data.append('avatar', $('#avatar')[0].files[0]); $.ajax({ url: '', type: 'post', contentType: false, processData: false, data: form_data, success: function (data) { // 错误信息清除, 先清除一下错误信息, 因为这里会记录上次的结果, 如果下次提交有新的只会覆盖掉, 不会自动清除. $(".error").html(""); // 展示此次的错误信息 $.each(data.msg, function (field, msg) { $("#id_"+field).next().html(msg[0]); // 找到<span>标签,将错误信息写入. // 让错误的input框显示红色提示框线 $("#id_"+field).parent().addClass('has-error') }) } }) }) </script> </body> </html>
二四. 博客系统值forms组件的局部钩子与全局钩子的应用 局部钩子验证用户名是否注册过, 全局钩子验证两次输入密码是否一致
24.1 验证规则中,字段不能为空的代码
class RegForm(forms.Form): """验证规则创建, 这里只选取了部分字段""" user = forms.CharField(max_length=32, error_messages={'required': '该字段不能为空!'}, label='用户名', widget=widgets.TextInput(attrs={'class': 'form-control'},) )
24.2 客户端显示的局部钩子与全局钩子效果, 画红框部分是全局钩子, 上面是局部钩子
24.3 局部钩子, 验证用户名是否已经被注册过
# 局部钩子, 验证用户名是否已经注册过. def clean_user(self): # 注意该函数的命名规则, clean_+字段名 val = self.cleaned_data.get('user') # 从干净数据中再次取出user数据.所谓干净数据就是已经被上面的规则初步验证通过的数据. user = UserInfo.objects.filter(username=val).first() # 到数据库中筛选该用户名 if not user: return val # 返回用户名 else: raise ValidationError('该用户名已经被注册!')
24.4 全局钩子, 验证两次输入的密码是否一致
# 全局钩子, 验证两次输入的密码是否一致 def clean(self): # 全局钩子只能用这个名字 pwd = self.cleaned_data.get('pwd') # 从干净数据中取出密码 re_pwd = self.cleaned_data.get('re_pwd') if pwd == re_pwd: return self.cleaned_data # 把干净数据原封不动再次传递 else: raise ValidationError('两次输入的密码不一致')
注意,全局钩子还需要在模板reg.html中加入__all__代码.如果data.msg中包含有全局钩子错误,那么其键将是__all__
// 展示此次的错误信息 $.each(data.msg, function (field, msg) { // 全局钩子错误显示, 两次密码输入不一致. console.log(field, msg); if (field=='__all__'){ // 链式, 既显示错误提示,又显示红色框. $("#id_re_pwd").next().html(msg[0]).parent().addClass('has-error') }; $("#id_"+field).next().html(msg[0]); // 找到<span>标签,将错误信息写入. // 让错误的input框显示红色提示框线 $("#id_"+field).parent().addClass('has-error') })
24.5 完整代码. 注意这里将验证的部分从视图中分离出来. 在blog下新建myforms.py文件.
# -*- coding: utf-8 -*- # programming environment:windows7_64 + python3.8.3_64 from django import forms from django.forms import widgets from django.core.exceptions import ValidationError from blog.models import UserInfo # Create your views here. class RegForm(forms.Form): user = forms.CharField(max_length=32, label='用户名', error_messages={'required': '该字段不能为空!'}, widget=widgets.TextInput(attrs={'class': 'form-control'},) ) pwd = forms.CharField(max_length=32, label='密码', error_messages={'required': '该字段不能为空!'}, widget=widgets.PasswordInput(attrs={'class': 'form-control'},) ) re_pwd = forms.CharField(max_length=32, label='确认密码', error_messages={'required': '该字段不能为空!'}, widget=widgets.PasswordInput(attrs={'class': 'form-control'},) ) email = forms.CharField(max_length=32, label='电子邮箱', error_messages={'required': '该字段不能为空!'}, widget=widgets.EmailInput(attrs={'class': 'form-control'},) ) # 局部钩子, 验证用户名是否被注册过 def clean_user(self): val = self.cleaned_data.get('user') user = UserInfo.objects.filter(username=val).first() if not user: return val # 用户名没有注册过, 直接返回该用户名. else: raise ValidationError('该用户已被注册!') # 全局钩子, 验证两次输入的密码是否一致 def clean(self): pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd and re_pwd: if pwd != re_pwd: raise ValidationError('两次输入的密码不一致!') else: return self.cleaned_data else: return self.cleaned_data
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 from blog.myforms import RegForm # 导入自定义forms验证规则 def reg(request): """用户注册视图""" if request.is_ajax(): form = RegForm(request.POST) respone = {'user': None, 'msg': None} if form.is_valid(): respone['user'] = form.changed_data.get('user') else: respone['msg'] = form.errors return JsonResponse(respone) form = RegForm() # 带入规则 return render(request, 'reg.html', {"form": form}) def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img # 导入自定义的生成验证码功能函数 data = get_validCode_img(request) return HttpResponse(data) def index(request): return render(request, 'index.html')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ display: none; } .error{ color: red; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }}<span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> {# 头像无需放在视图中的forms组件中进行检验,所以单独渲染 #} <label for="avatar"> 头像 <img id="avatar_img" width="60" height="60" src="/static/img/none.jpg"> </label> <input id="avatar" type="file"> </div> <input type="button" class="btn btn-default login_btn btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> // 预览头像 $('#avatar').change(function () { var file_obj = $(this)[0].files[0]; var reader = new FileReader(); reader.readAsDataURL(file_obj); reader.onload = function () { $('#avatar_img').attr('src', reader.result) } }); // ajax提交数据 $('.login_btn').click(function () { var form_data = new FormData(); var request_data = $('#form').serializeArray(); // 获取form数据对象 $.each(request_data, function (index, data) { // 遍历form数据对象,提取出name, value,添加到form_data数组对象中(组装数据). form_data.append(data.name, data.value) }); form_data.append('avatar', $('#avatar')[0].files[0]); $.ajax({ url: '', type: 'post', contentType: false, processData: false, data: form_data, success: function (data) { // 错误信息清除, 先清除一下错误信息, 因为这里会记录上次的结果, 如果下次提交有新的只会覆盖掉, 不会自动清除. $(".error").html(""); // 展示此次的错误信息 $.each(data.msg, function (field, msg) { // 全局钩子错误显示, 两次密码输入不一致. console.log(field, msg); if (field=='__all__'){ // 链式, 既显示错误提示,又显示红色框. $("#id_re_pwd").next().html(msg[0]).parent().addClass('has-error') }; $("#id_"+field).next().html(msg[0]); // 找到<span>标签,将错误信息写入. // 让错误的input框显示红色提示框线 $("#id_"+field).parent().addClass('has-error') }) } }) }) </script> </body> </html>
二十五. 博客系统之FileField字段(或ImageFiled) 对上传文件的处理
25.1 接上面, 改进一下全局钩子,当有一个密码框没有输入时,不进行两次是否一致的校验
# 全局钩子, 验证两次输入的密码是否一致 def clean(self): pwd = self.cleaned_data.get('pwd') # 从干净数据中取出密码 re_pwd = self.cleaned_data.get('re_pwd') if pwd and re_pwd: # 只有当两个密码框都输入值时,才进行是否一致的校验 if pwd == re_pwd: return self.cleaned_data # 把干净数据原封不动再次传递 else: raise ValidationError('两次输入的密码不一致') else: return self.cleaned_data
25.2 错误处理
25.3 前面讲的校验钩子等都是对错误的处理.下面开始, 如果用户输入都正确, 则应当在数据库创建输入的数据.
对FileField字段(或ImageFiled)的处理不同于CharField等字段,存储到数据库会做两件事: 1. 在服务器中保存文件, 2.在数据库中存放该文件的相对路径
25.4 在reg.html页面中增加注册成功,跳转到登录页面的代码
success: function (data) { if (data.user){ // 注册成功, 跳转到登录页面 location.href='/login/' } else{ // 注册失败 $('.error').html(''); $('.error').parent().removeClass('has-error'); $.each(data.msg, function (field, msg) { console.log(field, msg); if (field == '__all__'){ $('#id_re_pwd').next().html(msg[0]).parent().addClass('has-error') }; $('#id_'+field).next().html(msg[0]); $('#id_'+field).parent().addClass('has-error') }) } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ {#隐藏input上传文件框#} display: none; } .error{ color: red; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> {# 注意 {{ field.auto_id }}就是自动生成的每一field的ID #} <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }}<span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> {# 点击下面这个label就打开隐藏的input-file #} <label for="avatar"> 头像 <img id="avatar_img" height="60" width="60" src="/static/img/none.jpg"> </label> {# input框是隐藏的 #} <input type="file" id="avatar"> </div> <input type="button" class="btn btn-default btn-success reg_btn" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> {# 注意这里是change事件,不是click. 当选择一个文件后就是change事件 #} $('#avatar').change(function () { // 获取用户选择的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象的路径, 需要使用文件阅读器FileReader var reader = new FileReader(); reader.readAsDataURL(file_obj); // 修改img的src属性, src=文件对象的路径. // reader.onload的意思就是等待reader加载读取完毕后,再执行修改img的src属性. 如果没有onload将不等待,并行处理下面的代码,所以无法显示出头像 reader.onload = function(){ $("#avatar_img").attr('src', reader.result) } }); // 基于ajax提交数据 $('.reg_btn').click(function () { // 获取数据, 组装数据, 因为有上传文件数据,所以用form_data格式 var form_data = new FormData(); var request_data = $('#form').serializeArray(); console.log(request_data); $.each(request_data, function (index, data) { console.log('index:', index, '----', 'data:', data); form_data.append(data.name, data.value) }); form_data.append('avatar', $('#avatar')[0].files[0]); // 提交数据, 验证数据 $.ajax({ url: '', type: 'post', contentType: false, // 因为有上传文件数据 processData: false, // 因为有上传文件数据 data: form_data, // 因为有上传文件数据,所以用form_data格式 success: function (data) { if (data.user){ // 注册成功, 跳转到登录页面 location.href='/login/' } else{ // 注册失败 $('.error').html(''); $('.error').parent().removeClass('has-error'); $.each(data.msg, function (field, msg) { console.log(field, msg); if (field == '__all__'){ $('#id_re_pwd').next().html(msg[0]).parent().addClass('has-error') }; $('#id_'+field).next().html(msg[0]); $('#id_'+field).parent().addClass('has-error') }) } } }) }) </script> </body> </html>
25.5 在views.py的注册视图中,增加向数据库添加记录. 注意这里对用户上传头像的处理.
def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样 # print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 response = {'user': None, 'msg': None} if form.is_valid(): response['user'] = form.cleaned_data.get('user') # 生成一条用户记录 user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') # 注意凡是客户端传过来的文件,都从FILES中取 # 注册信息写入数据库, 注意avatar_obj是以文件的相对路径存储在数据库中的, 实体文件会存放到models.py中avatar字段指定的目录中. user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) else: response['msg'] = form.errors return JsonResponse(response)
from django.shortcuts import render, HttpResponse from django.contrib import auth from django.http import JsonResponse from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义验证 def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样 # print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 response = {'user': None, 'msg': None} if form.is_valid(): response['user'] = form.cleaned_data.get('user') # 生成一条用户记录 user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') # 注意凡是客户端传过来的文件,都从FILES中取 # 注册信息写入数据库, 注意avatar_obj是以文件的相对路径存储在数据库中的, 实体文件会存放到models.py中avatar字段指定的目录中. user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) else: # print(form.cleaned_data) # 所有校验成功的信息 # print(form.errors) # 所有校验失败的信息 response['msg'] = form.errors return JsonResponse(response) form = RegForm() return render(request, 'reg.html', {'form': form}) def login(request): if request.method == 'POST': response = {'user': None, 'msg': None} user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') valid_code_str = request.session.get('valid_code') if valid_code.upper() == valid_code_str.upper(): user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) response['user'] = user.username else: response['msg'] = '用户名或密码错误' else: response['msg'] = '验证码错误' return JsonResponse(response) return render(request, 'login.html') def index(request): return render(request, 'index.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img data = get_validCode_img(request) # 将图片渲染到模板页面 return HttpResponse(data)
25.6 头像最终存储效果,
二六. 博客系统之media配置1 静态文件夹media用于存储用户的上传文件
本节解决两个问题: 1. 用户上传的头像存储位置, 2. 用户没有上传头像怎么处理,(使用默认头像)
26.1 settings.py中配置media静态文件夹
# 配置用户上传的文件存放位置, 项目根目录下的media目录. 当有了此配置后,Django会将用户上传的文件存储在此. MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
26.2 创建media文件夹, 创建default.png默认头像
26.3 增加视图函数中对是否上传头像的处理
if avatar_obj: # 当用户上传了头像时 user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) else: # 当用户没有上传头像时,使用models.py中配置的默认头像 user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email)
26.4 对media的说明
二七. 博客系统之media配置2 配置后可通过url直接访问media中文件
Django中,非静态文件夹中的文件,是无法直接通过url访问的,
27.1 最终效果, url直接访问用户上传的图片
27.2 配置settings.py
# 配置用户上传的文件存放位置, 项目根目录下的media目录. 当有了此配置后,Django会将用户上传的文件存储在此. MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' # 设置后用于可在外网直接通过url访问MEDIA_ROOT设置的文件夹
27.3 配置urls.py
from django.urls import path, re_path from django.views.static import serve # serve器 from cnblog import settings # media配置, 外网直接访问用户上传文件的media文件夹的路由 re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}) # 固定写法
27.4 完整代码
""" Django settings for cnblog project. Generated by 'django-admin startproject' using Django 3.1.7. For more information on this file, see https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ from pathlib import Path import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '43@l3&_w6qq@q3@$5u8gpzbz7ti_dgd2ppwcse%m8@t#zn*l#j' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog.apps.BlogConfig', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'cnblog.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'cnblog.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ] AUTH_USER_MODEL = 'blog.UserInfo' # 配置用户上传的文件存放位置, 项目根目录下的media目录. 当有了此配置后,Django会将用户上传的文件存储在此. MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' # 设置后用于可在外网直接通过url访问MEDIA_ROOT设置的文件夹
"""cnblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path, re_path from django.views.static import serve # serve器 from blog import views from cnblog import settings urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('get_validCode_img/', views.get_validCode_img), path('reg/', views.reg), # media配置, 外网直接访问用户上传文件的media文件夹的路由 re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}) # 固定写法 ]
from django.shortcuts import render, HttpResponse from django.contrib import auth from django.http import JsonResponse from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义验证 def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样 # print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 response = {'user': None, 'msg': None} if form.is_valid(): response['user'] = form.cleaned_data.get('user') # 生成一条用户记录 user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') # 注意凡是客户端传过来的文件,都从FILES中取 if avatar_obj: # 当用户上传了头像时 user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) else: # 当用户没有上传头像时,使用models.py中配置的默认头像 user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email) else: # print(form.cleaned_data) # 所有校验成功的信息 # print(form.errors) # 所有校验失败的信息 response['msg'] = form.errors return JsonResponse(response) form = RegForm() return render(request, 'reg.html', {'form': form}) def login(request): if request.method == 'POST': response = {'user': None, 'msg': None} user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') valid_code_str = request.session.get('valid_code') if valid_code.upper() == valid_code_str.upper(): user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) response['user'] = user.username else: response['msg'] = '用户名或密码错误' else: response['msg'] = '验证码错误' return JsonResponse(response) return render(request, 'login.html') def index(request): return render(request, 'index.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img data = get_validCode_img(request) # 将图片渲染到模板页面 return HttpResponse(data)
# -*- coding: utf-8 -*- # programming environment:windows7_64 + python3.8.3_64 from django import forms from django.forms import widgets from django.core.exceptions import ValidationError from blog.models import UserInfo # Create your views here. class RegForm(forms.Form): user = forms.CharField(max_length=32, label='用户名', error_messages={'required': '该字段不能为空!'}, widget=widgets.TextInput(attrs={'class': 'form-control'},) ) pwd = forms.CharField(max_length=32, label='密码', error_messages={'required': '该字段不能为空!'}, widget=widgets.PasswordInput(attrs={'class': 'form-control'},) ) re_pwd = forms.CharField(max_length=32, label='确认密码', error_messages={'required': '该字段不能为空!'}, widget=widgets.PasswordInput(attrs={'class': 'form-control'},) ) email = forms.CharField(max_length=32, label='电子邮箱', error_messages={'required': '该字段不能为空!'}, widget=widgets.EmailInput(attrs={'class': 'form-control'},) ) # 局部钩子, 验证用户名是否被注册过 def clean_user(self): val = self.cleaned_data.get('user') user = UserInfo.objects.filter(username=val).first() if not user: return val # 用户名没有注册过, 直接返回该用户名. else: raise ValidationError('该用户已被注册!') # 全局钩子, 验证两次输入的密码是否一致 def clean(self): pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd and re_pwd: if pwd != re_pwd: raise ValidationError('两次输入的密码不一致!') else: return self.cleaned_data else: return self.cleaned_data
from django.db import models from django.contrib.auth.models import User, AbstractUser class UserInfo(AbstractUser): """ 用户信息 扩展用户表,同样继承自AbstractUser, 注意这里没有user,pwd等字段,因为这些字段在Django自带的auth_user表中 """ nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to='avatars/', default='/avatars/default.png') # 头像 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) # 用户创建时间(园龄) blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.CASCADE) def __str__(self): return self.username class Blog(models.Model): """博客信息表(个人站点表)""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='个人博客标题', max_length=64) site_name = models.CharField(verbose_name="站点名称", max_length=64) theme = models.CharField(verbose_name='博客主题', max_length=32) def __str__(self): return self.title class Category(models.Model): """博主个人文章分类表""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='分类标题', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid', on_delete=models.CASCADE) def __str__(self): return self.title class Tag(models.Model): """标签,相当于论文的关键字""" nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='标签名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid', on_delete=models.CASCADE) def __str__(self): return self.title class Article(models.Model): """文章详细表""" nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章描述') # 文章摘要 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.TextField() # 文章详细内容 comment_count = models.IntegerField(default=0) # 评论数 up_count = models.IntegerField(default=0) # 点赞数 down_count = models.IntegerField(default=0) # 反对数 user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid', on_delete=models.CASCADE) # 作者与文章是一对多的关系 category = models.ForeignKey(to='Category', to_field='nid', null=True, on_delete=models.CASCADE) # 分类与文章是一对多的关系 # 标签与文章是多对多的关系 tags = models.ManyToManyField(to='Tag', through='Article2Tag', through_fields=('article', 'tag')) # 自主创建关系表 def __str__(self): return self.title class Article2Tag(models.Model): """文章与标签的多对多关系表""" nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='文章', to='Article', to_field='nid', on_delete=models.CASCADE) tag = models.ForeignKey(verbose_name='标签', to='Tag', to_field='nid', on_delete=models.CASCADE) class Meta: unique_together = [ # 联合唯一 ('article', 'tag'), ] def __str__(self): v = self.article.title + '---' + self.tag.title return v class ArticleUpDown(models.Model): """点赞表(反对表)""" nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True, on_delete=models.CASCADE) # 用户ID article = models.ForeignKey('Article', null=True, on_delete=models.CASCADE) # 文章ID is_up = models.BooleanField(default=True) # True是点赞, False是反对 class Meta: unique_together = [ # 联合唯一 ('article', 'user'), ] class Comment(models.Model): """ 评论表 1根评论:对文章的评论 2子评论:对评论的评论 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey(verbose_name='评论者', to="UserInfo", to_field='nid', on_delete=models.CASCADE) article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid', on_delete=models.CASCADE) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.CharField(verbose_name='评论内容', max_length=255) parent_comment = models.ForeignKey('self', null=True, on_delete=models.CASCADE) # 自关联, 创建父评论ID, parent_comment_id def __str__(self): return self.content
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> #avatar_img{ margin-left: 10px; } #avatar{ {#隐藏input上传文件框#} display: none; } .error{ color: red; } </style> </head> <body> <h3>用户注册</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> {# 注意 {{ field.auto_id }}就是自动生成的每一field的ID #} <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }}<span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> {# 点击下面这个label就打开隐藏的input-file #} <label for="avatar"> 头像 <img id="avatar_img" height="60" width="60" src="/static/img/none.jpg"> </label> {# input框是隐藏的 #} <input type="file" id="avatar"> </div> <input type="button" class="btn btn-default btn-success reg_btn" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> {# 注意这里是change事件,不是click. 当选择一个文件后就是change事件 #} $('#avatar').change(function () { // 获取用户选择的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象的路径, 需要使用文件阅读器FileReader var reader = new FileReader(); reader.readAsDataURL(file_obj); // 修改img的src属性, src=文件对象的路径. // reader.onload的意思就是等待reader加载读取完毕后,再执行修改img的src属性. 如果没有onload将不等待,并行处理下面的代码,所以无法显示出头像 reader.onload = function(){ $("#avatar_img").attr('src', reader.result) } }); // 基于ajax提交数据 $('.reg_btn').click(function () { // 获取数据, 组装数据, 因为有上传文件数据,所以用form_data格式 var form_data = new FormData(); var request_data = $('#form').serializeArray(); console.log(request_data); $.each(request_data, function (index, data) { console.log('index:', index, '----', 'data:', data); form_data.append(data.name, data.value) }); form_data.append('avatar', $('#avatar')[0].files[0]); // 提交数据, 验证数据 $.ajax({ url: '', type: 'post', contentType: false, // 因为有上传文件数据 processData: false, // 因为有上传文件数据 data: form_data, // 因为有上传文件数据,所以用form_data格式 success: function (data) { if (data.user){ // 注册成功, 跳转到登录页面 location.href='/login/' } else{ // 注册失败 $('.error').html(''); $('.error').parent().removeClass('has-error'); $.each(data.msg, function (field, msg) { console.log(field, msg); if (field == '__all__'){ $('#id_re_pwd').next().html(msg[0]).parent().addClass('has-error') }; $('#id_'+field).next().html(msg[0]); $('#id_'+field).parent().addClass('has-error') }) } } }) }) </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>用户登录</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="valid_code">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" id="valid_code" class="form-control"> </div> <div class="col-md-6"> <img width="270" height="35" src="/get_validCode_img/" id='valid_code_img' alt="验证码"> </div> </div> </div> <input type="button" class="btn btn-default login_btn" value="登录"> <label id="login_error" style="color: red; margin-left: 10px"></label> <input type="button" class="btn btn-default pull-right btn-success" value="注册"> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script> $('#valid_code_img').click(function () { $(this)[0].src += '?' }); // 登录验证 $('.login_btn').click(function () { $.ajax({ url:'', type:'post', data:{ user: $('#user').val(), pwd: $('#pwd').val(), valid_code: $('#valid_code').val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, success: function (data) { if (data.user){ location.href = '/index/' } else{ $('#login_error').text(data.msg); setTimeout(function () { $('#login_error').text('') }, 1000) } } }) }) </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>网站首页</h3> <h3>欢迎, {{ request.user.username }}!</h3> </body> </html>
# -*- coding: utf-8 -*- # programming environment:windows7_64 + python3.8.3_64 import random def get_random_color(): # 生成随机的RGB颜色数值 return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) def get_validCode_img(request): """生成验证码图片""" # 生成图片 from PIL import Image, ImageDraw, ImageFont # ImageDraw画笔工具 from io import BytesIO # 引入内存管理工具 img = Image.new('RGB', (270, 40), color=get_random_color()) # 在img上画画 draw = ImageDraw.Draw(img) fzjz_font = ImageFont.truetype("static/font/fzjz.TTF", size=30) # 方正剪纸字体 # 生成5个数字字母组合 valid_code_str = '' for i in range(5): # 生成随机字母 random_num = str(random.randint(0, 9)) # 随机数字 random_low_alpha = chr(random.randint(97, 122)) # 随机小写字母 random_upper_alpha = chr(random.randint(65, 90)) # 随机大写字母 random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) # --------书写位置x, y--------书写内容---------文字颜色-------------字体--------- draw.text((i*55+15, 5), random_char, get_random_color(), font=fzjz_font) # 写字 # draw.line() # 画线 # draw.point() # 画点 valid_code_str += random_char request.session['valid_code'] = valid_code_str # 增加干扰线\燥点\画圆圈 width = 270 height = 40 for i in range(2): x1 = random.randint(0, width) x2 = random.randint(0, width) y1 = random.randint(0, height) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=get_random_color()) for i in range(10): draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x+4, y+4), 0, 90, fill=get_random_color()) # 在内存中生成png图片 f = BytesIO() img.save(f, 'png') data = f.getvalue() # 将图片渲染到模板页面 return data
27.5 小结
二八. 博客系统之生成用户对象的代码优化
28.1 改进一处views.py,判断用户是否上传了头像,的不同处理.
extra = {} if avatar_obj: # 当用户上传了头像时 extra['avatar'] = avatar_obj UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
from django.shortcuts import render, HttpResponse from django.contrib import auth from django.http import JsonResponse from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义验证 def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样 # print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 response = {'user': None, 'msg': None} if form.is_valid(): response['user'] = form.cleaned_data.get('user') # 生成一条用户记录 user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') # 注意凡是客户端传过来的文件,都从FILES中取 extra = {} if avatar_obj: # 当用户上传了头像时 extra['avatar'] = avatar_obj UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) else: # print(form.cleaned_data) # 所有校验成功的信息 # print(form.errors) # 所有校验失败的信息 response['msg'] = form.errors return JsonResponse(response) form = RegForm() return render(request, 'reg.html', {'form': form}) def login(request): if request.method == 'POST': response = {'user': None, 'msg': None} user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') valid_code_str = request.session.get('valid_code') if valid_code.upper() == valid_code_str.upper(): user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) response['user'] = user.username else: response['msg'] = '用户名或密码错误' else: response['msg'] = '验证码错误' return JsonResponse(response) return render(request, 'login.html') def index(request): return render(request, 'index.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img data = get_validCode_img(request) # 将图片渲染到模板页面 return HttpResponse(data)
29.2 格式化代码, 去除不要的print等等
$('.error').parent().removeClass('has-error');