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
models.py

七. 博客系统值创建项目与迁移

项目名称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)
views.py

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>
login.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)
views.py

 

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>
login.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')
views.py

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>
login.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),
]
urls.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>网站首页</h3>
    <h3>欢迎:{{ request.user.username }}</h3>

</body>
</html>
index.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
vialdCode.py
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')
views.py 注意get_validCode_img视图函数的变化

 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>
login.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>
reg.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="登录">
                    <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>
login.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')
views.py

 十八. 博客系统之注册页面的默认头像  隐藏上传文件框,点击头像更换头像

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>
reg.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>
reg.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>
reg.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)
views.py

 二一. 博客系统之基于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>
reg.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>
reg.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>
reg.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
blog下的myforms.py
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')
views.py
<!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>
reg.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>
reg.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)
views.py

 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设置的文件夹
settings.py
"""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})  # 固定写法

]
urls.py
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)
views.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
myforms.py
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
models.py
<!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>
reg.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>
login.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>
index.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
validCode.py

 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)
views.py

29.2 格式化代码, 去除不要的print等等

 

 

 

 

 

 

 

 

 

 

 

 

$('.error').parent().removeClass('has-error');
posted @ 2021-02-23 20:10  蓝蓝的白云天!  阅读(409)  评论(0编辑  收藏  举报