12. Django 博客项目01 表设计&注册功能&登入功能

1. 项目需求

主要功能:
1.表设计	
2.注册功能
    forms组件使用
    头像动态展示
    错误信息提示
3.登陆功能
    图片验证码
    登入装饰器
3.首页展示
	media配置
	主动暴露任意资源接口
	密码修改
4.个人站点展示
    侧边栏展示
    侧边栏筛选
    侧边栏inclusion_tag
5.文章详情页
    点赞点踩
    评论
6.后台管理
	Kind编辑器模块
	添加文章
	修改文章
	删除文章
	头像修改

2. 环境准备

前端 HTML + CSS + JavaScript
后端   Python 3.6
架构   Django 1.11.11
数据库 Mysql  5.6.47
2.1 新建Django项目

12. Django 博客项目01 表设计&注册功能&登入功能_第1张图片

2.2 解决路径问题
项目名目录下的settings 58.    
	# 0. 修改静态文件路径已知Bug
    'DIRS': [BASE_DIR, 'templates']
2.3 建立bbs库
由于django自带的sqlite数据库对日期不敏感,所以我们换成MySQL.
使用Navicat创建bbs库.
数据库的名称 bbs 字符集 utf8mb4
# 命令行创建
CREATE DATABASE `bbs` CHARACTER SET 'utf8mb4';

12. Django 博客项目01 表设计&注册功能&登入功能_第2张图片

2.4 Django连接MySQL
# 1. 数据配置文件
DATABASES = {
    'default': {
        # 1.0 输入的数据库引擎
        'ENGINE': 'django.db.backends.mysql',
        # 1.1 IP
        'HOST': '127.0.0.1',
        # 1.2 端口
        'POST': 3306,
        # 1.3 登入的用户名
        'USER': 'root',
        # 1.4 登入用户的密码
        'PASSWORD': 123,
        # 1.5 连接的库
        'NAME': 'bbs',
        # 1.6 字符编码
        'CHARSET': 'UTF8'
    }
}
在app01 应用下 __init__.py 中配置 pymysql 模块连接数据库.
import pymysql
pymysql.install_as_MySQLdb()
2.5 开放静态文件
0. 在项目目录下创建 static 静态文件目录.
1. 在static目录下 创建 js 目录, 将jQuery文件复制到 js 目录中.
2. 复制bootstarp框架文件到 static 目录中.
3. 复制SweetAlert框架文件到 SweetAlert , (后面加上的).
4. 去项目名文件下settings.py 中设置开放静态文件.

12. Django 博客项目01 表设计&注册功能&登入功能_第3张图片

# settings.py 
STATIC_URL = '/static/'

# 2. 开放静态文件路径

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'STATIC')
]
2.6 访问测试
0. 启动项目
1. 在浏览器中输入: 127.0.0.1:8000

12. Django 博客项目01 表设计&注册功能&登入功能_第4张图片

3. 表设计

一个项目中最重要的不是业务逻辑的书写
而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺.
在app01 下的 models.py 使用ORM模块, 创建映射表的类.
先写普通的字段, 在写外键字段.
3.1用户表 User

20220323122604

用户表: 记录用户的信息
继承AbstractUser 使用Auth模块
	
扩展字段:
    avatar       用户头像        FileField    文件类型
    create_time  用户创建时间     DateField    日期 年月日
 
外键字段: 
	用户表一对一个人站点表, 外键字段建在查询评论多的一方.
	blog        外键绑定个人博客表的id
from django.db import models

# Create your models here.

# 0. 用户表
# 0.1 导入 AbstractUser 类
from django.contrib.auth.models import AbstractUser


# 0.2 用户信息表   继承Auth表 拓展额外的字段
class UserInfo(AbstractUser):

    # 0.3 头像 文件类型
    avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='头像')
    # 0.4 用户创建时间   自动获取当前 年月日   (UTC 时间)
    create_time = models.DateField(auto_now_add=True, verbose_name='用户创建时间')
    # 0.5 外键字段 blog_id 关联 个人博客表id  在写表数据的时候可以不对此字段写数据
    blog = models.OneToOneField(to='Blog', null=True, verbose_name='关联博客表id')
# 项目名目录下 settings.py 
# 3. 配置 AUTH 模块使用的表
AUTH_USER_MODEL = 'app01.UserInfo'
avatar 字段存放的文件路径 avatar/xxx.pnh
upload_to='avatar/',           upload_to  参数 文本保存的位置, 
default='avatar/default.png'   用户不上传头像,使用 default 参数设置的默认头像.    
在项目下创建avatar目录, 找一个图片设置为 default.png 默认头像.

12. Django 博客项目01 表设计&注册功能&登入功能_第5张图片

3.2 个人博客表 Blog
个人博客/个人站点表: 记录每个用户个人网站, 访问时查询有没有该有户的个人站点存在.
每个人站点有自己的站点名称, 站点标题, 站点样式.
https://www.cnblogs.com/python

12. Django 博客项目01 表设计&注册功能&登入功能_第6张图片

https://www.cnblogs.com/java

12. Django 博客项目01 表设计&注册功能&登入功能_第7张图片

https://www.cnblogs.com/python_21

12. Django 博客项目01 表设计&注册功能&登入功能_第8张图片

字段:
    site_name    站点名称   CharField  字符串类型
    site_title 	 站点标题   CharField  字符串类型
    site_theme	 站点样式   CharField  字符串类型
# 1. 个人博客表
class Blog(models.Model):
    # 1.0 id 字段自动生成
    # 1.1 站点名称
    site_name = models.CharField(max_length=32, verbose_name='站点名称')
    # 1.2 站点标题
    site_title = models.CharField(max_length=32, verbose_name='站点标题')
    # 1.3 站点样式 样式的路径
    site_theme = models.CharField(max_length=32, verbose_name='站点样式')
站点样式 字段存放css/js文件的路径, 简单的操作下样式的切换.
3.3 文章表 Article
文章表记录文字的内容.

12. Django 博客项目01 表设计&注册功能&登入功能_第9张图片

字段:
    文章的标题   title          CharField   字符串类型
    文章简介     desc           CharField   字符串类型
    文章内容     content        TextField   文本字段适合存大量文本信息 不需要指定 max_length
    文章发布时间  create_time    DateField   日期 年月日
	
	查询优化:
	虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率低下,
	直接在文章表中创建三个普通的字段, 点赞点踩, 评论表, 记录数据的时候同步文章表的这三个字段.
	
    文章的点赞数   up_num        BigIntegerField    极大整数值 8位表示
    文章的点踩数   down_num      BigIntegerField    极大整数值 8位表示
    文字的评论数   comment_num   BigIntegerField    极大整数值 8位表示

    * 访问数不做, 换为点踩.

外键字段:
	站点表      一对多    文章表   外键    blog_id 绑定站点表的id
	文章标签表  多对多    文章表   虚拟字段 tag  半自动建立第三张表
	文章分类表  一对一    文章表	外键   sort_id 绑定分类表id  

# 2. 文章表
class Article(models.Model):
    # 2.1 id 字段自动生成
    # 2.2 文章标题
    title = models.CharField(max_length=32, verbose_name='标题')
    # 2.3 文章简介
    desc = models.CharField(max_length=256, verbose_name='简介')
    # 2.4 文章内容 
    content = models.TextField(verbose_name='文章内容')
    # 2.5 文章创建时间 创建文章的时候会自动获取UTC时间
    create_time = models.DateField(auto_now_add=True, verbose_name='文章创建时间')

    # 数据库的字段优化
    # 2.6 点赞数
    up_num = models.BigIntegerField(default=0, verbose_name='点赞数')
    # 2.7 点踩数
    down_num = models.BigIntegerField(default=0, verbose_name='点踩数')
    # 2.8 评论数
    comment_num = models.BigIntegerField(default=0, verbose_name='评论数')
    # 2.9 外键 blog_id 关联 博客表的id
    blog = models.ForeignKey(to='Blog', null=True, verbose_name='关联博客表id')
    # 2.10 外键 sort_id 关联分类表id
    sort = models.OneToOneField(to='Sort', null=True, verbose_name='关联分类表id')
    # 2.11 虚拟字段端 半自动创建第三张表
    tag = models.ManyToManyField(to='Tag', through='ArticleToTag',
                                 # 第三张表中 article字段与tag字段关联
                                 through_fields=('article', 'tag'))
# 7. 文章表多对多标签表的第三张表
class ArticleToTag(models.Model):
    # 7.1 id字段自动生成
    # 7.2 外键 article_id 绑定 文章表的id
    article = models.ForeignKey(to='Article')
    # 7.2 外键 tag_id 绑定 标签表的id
    tag = models.ForeignKey(to='Tag')
3.4 文章的标签 Tag
一个文章可以打上很多个标签.
字段:
	标签的名字  name   CharField  字符串类型
	
外键字段: (这个字段存在的原因是需要在个人博客表中,侧边栏可以通过标签去查找对应的文章)
	个人站点表 一对多 文章标签表
	blog_id 绑定博客表的id
# 3. 标签表
class Tag(models.Model):
    # 3.1 id 字段自动生成
    # 3.2 标签名称
    name = models.CharField(max_length=32, verbose_name='文章标签')
    # 3.3 外键
    blog = models.ForeignKey(to='Blog', null=True, verbose_name='绑定博客表的id')
3.5 文章的分类 Sort
一个文章属于一个类型.(一个文章就一个类, 不搞多个.)

字段:
	类的名字 name   CharField  字符串类型
	
外键字段:(这个字段存在的原因是需要在个人站点中, 侧边栏可以通过分类去查找对应的文章)
	个人站点表 一对多 文章分类表
	blog_id 绑定博客表的id
# 4. 分类表
class Sort(models.Model):
    # 4.1 id 字段自动生成
    # 4.2 分类的名称
    name = models.CharField(max_length=32, verbose_name='文章分类')
    # 4.3 外键 blog_绑定 博客表的id
    blog = models.ForeignKey(to='Blog', null=True, verbose_name='绑定博客表的id')
3.6 点赞点踩表 UpAndDown
文章点赞点踩表: 记录那个用户给哪篇文章点了赞还是点了踩
字段:
	用户名        user
	文章名        article
    点赞/点踩      is_up

12. Django 博客项目01 表设计&注册功能&登入功能_第10张图片

id    user_id    article_id    is_up
1        1           1           1         用户1 给第一篇文章点了赞
2        2           1           1         点赞 统计 article的 is_up 1 数量
3        3           1           1         点踩 统计 article的 is_up 1 数量
# 5. 点赞点踩表
class UpAndDown(models.Model):
    # 5.1 id 字段自动生成
    # 5.2 外键 user_id    关联用户id  _id自动增加
    user = models.ForeignKey(to='UserInfo', verbose_name='关联用户id')
    # 5.3 外键 article_id  关联文章id _id自动增加
    article = models.ForeignKey(to='Article', verbose_name='关联文章id')
    # 5.4 是否点赞  布尔值 存的是0/1
    is_up = models.BooleanField(verbose_name='是否点赞')
3.7 文字评论表 Comment
文章的评论表: 记录那个用户给哪篇文字评论了什么内容

字段: 
	用户名    user
	文章名    article
	评论内容  content
	评论时间  comment_time
根评论子评论的概念
	根评论就是直接评论当前发布的内容的
		
	子评论是评论别人的评论
		1.PHP是世界上最牛逼的语言
			1.1 PHP是世界上最牛逼的语言.py
				1.1.1 java才是.py
				1.2.1 go才是.py
		
根评论与子评论是一对多的关系
自关联
parent					ForeignKey(to="Comment",null=True)		
ORM专门提供的自关联写法	
parent					ForeignKey(to="self",null=True)
id	user_id    article_id    parent_id
1	 1		      1			(可以不评论)						
2    2			  1				1 (代表这个评论是给这个表第一个内容的)		

12. Django 博客项目01 表设计&注册功能&登入功能_第11张图片

# 6. 评论表
class Comment(models.Model):
    # 6.1 id字段自动生成
    # 6.2 外键  user_id     关联用户id  _id自动增加
    user = models.ForeignKey(to='UserInfo', verbose_name='关联用户id')
    # 6.3 外键 article_id  关联文章id _id自动增加
    article = models.ForeignKey(to='Article', verbose_name='关联文章id')
    # 6.4 评价内容
    content = models.CharField(max_length=256, verbose_name='评价内容')
    # 6.5 评论时间 年月日 时分秒 UTC 时间
    comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')

    # 6.6 自关联 parent_id关联这张表的id
    parent = models.ForeignKey(to='self', null=True, verbose_name='自关联')
只要关联了就代表它是子评论, 否则就是根评论, null=True, 不写就只能是评论了

12. Django 博客项目01 表设计&注册功能&登入功能_第12张图片

3.8数据库迁移
python manage.py makemigrations
python manage.py migrate

12. Django 博客项目01 表设计&注册功能&登入功能_第13张图片

4. 用户注册功能

4.1 路由层
from django.conf.urls import url
from django.contrib import admin
# 0. 导入 视图层
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 1. 注册功能
    url(r'^register/', views.register)
]
4.2 forms组件
0. 在app01应用程序下创建 forms_module 目录
1.  forms_module 目录下创建 register_froms.py 文件
# 0. 导入 forms 组件
from django import forms
# 1. 导入 models 模型层
from app01 import models


# 1. 创建注册表forms组件 继承 forms.Form
class RegisterForms(forms.Form):
    # 1.1 名称
    username = forms.CharField(
        # 展示名称, 限制
        label='名称', min_length=3, max_length=8,
        # 错误信息
        error_messages={
            'required': '名称不能为空',
            'min_length': '名称不能少于3位',
            'max_length': '名称不能超过8位'},
        # 样式 input框 border:none 无边框  background-color:transparent; 背景颜色透明 字体颜色
        widget=forms.widgets.TextInput(
            attrs=(
            {'class': 'form-control input-lg', 'style': 'border:none; background-color:transparent; color: #66AFE9;'})
        )
    )

    # 1.2 密码
    password = forms.CharField(
        # 展示名称, 限制
        label='密码', min_length=3, max_length=8,
        # 错误信息
        error_messages={
            'required': '密码不能为空',
            'min_length': '密码不能少于3位',
            'max_length': '密码不能超过8为'},
        # 样式
        widget=forms.widgets.PasswordInput(
            attrs=(
            {'class': 'form-control input-lg', 'style': 'border:none; background-color:transparent; color: #ffaa00;'})
        )
    )

    # 1.3 确定密码
    confirm_password = forms.CharField(
        # 展示名称, 限制
        label='确认密码', min_length=3, max_length=8,
        # 错误信息
        error_messages={
            'required': '确认密码不能为空',
            'min_length': '确认密码不能少于3位',
            'max_length': '确认密码不能超过8位'},
        # 样式
        widget=forms.widgets.PasswordInput(
            attrs={'class': 'form-control input-lg',
                   'style': 'border:none; background-color:transparent; color: #ffaa00;'}
        )
    )

    # 1.4 邮箱
    email = forms.EmailField(
        # 展示名称
        label='邮箱',
        # 错误信息
        error_messages={
            'required': '邮箱不能为空',
            'invalid': '邮箱格式不正确'},
        # 样式
        widget=forms.widgets.EmailInput(
            attrs=(
            {'class': 'form-control input-lg', 'style': 'border:none; background-color:transparent; color: #ffaa00;'})
        )
    )

    # 1.5 局部钩子 判断用户名是否存在
    def clean_username(self):
        # 1.5.1 获取用户名
        username = self.cleaned_data.get('username')
        # 1.5.2 去用户表中匹配数据
        is_exist = models.UserInfo.objects.filter(username=username)

        # print(is_exist)
        # 用户不存在显示 

        # 1.5.3 判断返回的数据 如果存在
        if is_exist:
            self.add_error('username', '名称已经存在')

        # 1.5.4 返回钩子函数
        return username

    # 1.6 全局钩子 判断两次密码是否一致
    def clean(self):
        # 1.6.1 获取密码
        password = self.cleaned_data.get('password')
        # 1.6.2 获取确认密码
        confirm_password = self.cleaned_data.get('confirm_password')
        # 1.6.3 判断是否一致
        if password != confirm_password:
            # 1.6.4 添加错误提示信息
            self.add_error('confirm_password', '两次密码不一致')

        # 1.6.6 返回全局钩子
        return self.cleaned_data

4.3 视图层
# 0. 导入django 三板斧
from django.shortcuts import render, redirect, HttpResponse

# 1.导入 注册表单forms组件
from app01.forms_module.register_forms import RegisterForms


# 2.注册功能
def register(request):
    # 2.0 生成 forms组件对象
    forms_obj = RegisterForms()

    # 2.1 返回注册页面 与 forms组件对象
    return render(request, 'register.html', locals())
4.4 模板层
在templates目录下创建 register.html
form表单 autocomplete="off" 浏览器不自动填充数据.
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面title>
    
    {% load static %}
    
    <script src="{% static 'js/jquery-3.6.0.min.js' %}">script>
    
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}">script>

    <style>
        /* 13. body背景设置 */
        body {
            /* 网页背景 */
            background-image: url("{% static 'background/register.jpg'%}");
            /*  不缩放  */
            background-size: cover;
            /*  不平铺  */
            background-repeat: no-repeat;
            /* 字体颜色 */
            color: #d4ff00;
            font-size: 18px;
        }

        /* 14. 自动填充值不留白 */
        input:-webkit-autofill,
        input:-webkit-autofill:hover,
        input:-webkit-autofill:focus,
        input:-webkit-autofill:active {
            -webkit-transition-delay: 111111s;
            -webkit-transition: color 11111s ease-out, background-color 111111s ease-out;
        }
    style>

head>
<body>

<div class="container-fluid">
    
    <div class="row">
        
        <div class="col-md-6 col-md-offset-3">
            
            <h1 class="text-center">注册h1>
            
            <form action="" autocomplete="off" id="form_table">
                
                {% csrf_token %}
                {% for input in forms_obj %}
                    <div class="form-group">
                        
                        <label for="{{ input.auto_id }}">{{ input.label }}label>
                        {{ input }}

                        
                        <span style="color: red; " class="pull-right">span>
                    div>
                {% endfor %}
                
                <label for="my_avatar">头像
                    <img src="{% static 'img/default.png' %}" alt="" id='my_img' width="80px" style="margin-left:20px">
                label>

                <input type="file" name="avatar" id="my_avatar" style="display: none">

                
                <p>
                    <input type="button" id='btn1' value="注册" class="btn-primary btn-lg btn-block"
                           style="border: none; opacity:0.3; color: orange; font-size: 20px">
                p>

            form>
        div>
    div>
div>
<script>
    // 10. 绑定文本域变化事件  文本阅读器对象, 将上传的头像文件用阅读器获取出来
    $('#my_avatar').change(function () {
        // 10.1 生成一个文本器阅读对象
        let FileReaderObj = new FileReader()

        // 10.2 获取用户上传的文件  (this)[0]对象转为  DOM对象  files[0]取文件值
        let UpFileObj = $(this)[0].files[0];

        // 10.3 将文件对象交给阅读器对象读取
        FileReaderObj.readAsDataURL(UpFileObj)  // 异步操作, IO操作

        // 10.4 绑定加载事件 等待文件阅读完毕之后再执行事假 修改img标签的值
        FileReaderObj.onload = function () {
            // 10.5 attr标签属性值 属性   值(人本阅读器的结果)
            $('#my_img').attr('src', FileReaderObj.result)
            console.log(FileReaderObj.result)
        }

    })

    // 11. 绑定点击事件 获取表单数据 使用ajax提交表单数据
    $('#btn1').on('click', function () {
        // 11.1 创建一个表单对象
        let FormDataObj = new FormData()

        // 11.2 获取form表单的所有文本数据
        let Text_Data = $('#form_table').serializeArray()

        // console.log(Text_Data)
        // 获取的数据是列表套字典 [{'name': '表单name值', 'value': '表单value值'}, {}, {}]


        // 11.3 遍历form表单的文本数据 添加到表单对象中 each(可迭代的值, 函数(索引, 索引对应的值){})
        $.each(Text_Data, function (index, obj) {

            // console.log(index, obj)
            //  0 {name: 'username', value: 'kid'} ...

            // 11.4 添加数据到 表单对象中
            FormDataObj.append(obj.name, obj.value)
        })

        // 11.5 表单对象添加文件对象
        FormDataObj.append('avatar', $('#my_avatar')[0].files[0])

        // 11.6 发送ajax请求
        $.ajax({
            // 11.6.1 提交地址
            url: '',
            // 11.6.2 提交方式
            type: 'post',
            // 11.6.3 上传的数据
            data: FormDataObj,

            // 11.6.4 上传文件必须设置的参数
            contentType: false,  //  不需要任何编码django能自动识别表单对象
            processData: false,  // 告诉浏览器不要对数据进行任何处理

            // 11.6.5 回调函数
            success: function (args) {
                // 11.6.6 判断 状态码
                if (args.code === 200) {
                    // 11.6.7 用户注册成功后, 跳转网页  http://127.0.0.1:8000/login/
                    window.location.href = args.url


                } else {

                    // console.log(args.error)
                    // {username: Array(1)..)}  confirm_password: ['确认密码不能为空']... 一个大字典 each 将 k:v 传递给函数

                    // 11.6.8 在input框后面的span后面展示错信息
                    $.each(args.error, function (key, value) {

                        console.log(key, value)
                        // confirm_password ['确认密码不能为空']

                        // 11.6.9 获取input标签 input的id 优 forms组件创建, 他的id值特点是 id='id_name值'
                        let InputId = '#id_' + key
                        // 11.6.10 移除 input框的 border属性值 将 border: none 移除 展示出边框
                        $(InputId).css('border', '')
                        // 11.6.11 input后面的标签添加错误提示信息, 它的父标签添加一个样式 红色边框 has-error input框变红色 链式擦操作
                        $(InputId).next().text(value[0]).parent().addClass('has-error')
                    })

                }
            }

        })
    })

    // 12. 绑定获取焦点事假
    $('input').focus(function () {
        // 12.1 清除 错误信息和input款的颜色
        $(this).next().text('').parent().removeClass('has-error')
        // 12.2 input框添加 border属性值 设置为没有边框
        $(this).css('border', 'none')
    })
script>
body>
html>

        // 11.2 获取form表单的所有文本数据
        let Text_Data = $('#form_table').serializeArray()

12. Django 博客项目01 表设计&注册功能&登入功能_第14张图片

each遍历列表获取到两个参数,第一个是index索引, 第二个是索引对应的值.
        // 11.3 遍历form表单的文本数据 添加到表单对象中 each(可迭代的值, 函数(索引, 索引对应的值){})
        $.each(Text_Data, function (index, obj) {
                     console.log(index, obj)...

image-20220325092452231

// ajax接收 后端返回 检验不通过的数据
args.error

image-20220325092942973

each遍历对象获取到两个参数,第一个是属性, 第二个是属性值
                    // 11.6.8 在input框后面的span后面展示错信息
                    $.each(args.error, function (key, value) {
                        console.log(key, value) ...

image-20220325093205739

forms组件 表单错误信息提示

4.5 业务逻辑
# 0. 导入django 三板斧
from django.shortcuts import render, redirect, HttpResponse

# 1.导入 注册表单forms组件
from app01.forms_module.register_forms import RegisterForms

# 2.9 时 导入models 模型
from app01 import models

# 2.11 时 导入 JsonResponse
from django.http import JsonResponse


# 2.注册功能
def register(request):
    # 2.0 生成 forms组件对象
    forms_obj = RegisterForms()
    # 2.2 判断请求方式
    if request.is_ajax():
        # 2.3 检验数据是否合法
        forms_obj = RegisterForms(request.POST)

        # 2.4 如果数据合法
        if forms_obj.is_valid():
            # 2.5 获取所有合法数据 赋值给一个变量 是一个字典
            cleaned_data = forms_obj.cleaned_data

            # print(cleaned_data)
            # {'username': 'kid', 'password': '123', 'confirm_password': '123', 'email': '[email protected]'}

            # 2.6将 confirm_password的数据弹出
            cleaned_data.pop('confirm_password')

            # 2.7 获取文件数据
            avatar_obj = request.FILES.get('avatar')

            # print(avatar_obj)
            # 如果没有自己选头像获取到 None  选了头像 文件对象 头像1.png

            # 2.8 判断头像是否为空
            if avatar_obj:
                cleaned_data['avatar'] = avatar_obj

            # 2.9 写入数据 **将字典作还原关键字参数形式
            models.UserInfo.objects.create_user(**cleaned_data)
            print('写入数据成功!')

            # 2.10 数据写入成功后 返回的数据 状态码200 跳转的页面, 格式一定要是 /login/
            back_dir = {'code': 200, 'url': '/login/'}

        else:
            # 2.11 数据检验不合法 返回的数据  状态码400 错误的提示信息
            back_dir = {'code': 400, 'error': forms_obj.errors}

            # print(forms_obj.errors)
            # 错误的信息 
  • confirm_password
    • 两次密码不一致
# 2.12 返回json格式数据 返回一个字典数据给ajax, 前端拿到的就一个对象 # print(back_dir) return JsonResponse(back_dir) # 2.1 返回注册页面 与 forms组件对象 return render(request, 'register.html', locals())

5. 登入功能

5.1 路由层
    # 2. 登入功能
    url(r'^login/', views.login),
5.2 视图层
# 3. 登入功能
def login(request):
    # 3.1 返回 登入页面
    return render(request, 'login.html')
5.3 模板层
img标签的src属性
1. 图片路径
2. url
3. 图片的二进制数据
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登入页面title>
    
    {% load static %}
    
    <script src="{% static 'js/jquery-3.6.0.min.js' %}">script>
    
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}">script>
    
    <script src="{% static 'bootstrap-sweetalert-master/dist/sweetalert.min.js' %}">script>
    
    <link rel="stylesheet" href="{% static 'bootstrap-sweetalert-master/dist/sweetalert.css' %}">

    <style>
        /* 12 背景*/
        body {
            /* 网页背景 */
            background-image: url("{% static 'background/login.jpeg'%}");
            /*  不缩放  */
            background-size: cover;
            /*  不平铺  */
            background-repeat: no-repeat;
            /* 字体颜色 */
            color: #364046;
            font-size: 18px;
        }

        /* 13 input 框 透明*/
        .transparent {
            background-color: transparent;
            border: none;
            color: #01fdfc;
        }

        /* 14. 自动填充值不留白 */
        input:-webkit-autofill,
        input:-webkit-autofill:hover,
        input:-webkit-autofill:focus,
        input:-webkit-autofill:active {
            -webkit-transition-delay: 111111s;
            -webkit-transition: color 11111s ease-out, background-color 111111s ease-out;
        }

        /* 15 sweetalert 框样式 */
        .sweetAlert {
            width: 22em;
            color: red;
            background-color: transparent;
        }
    style>
head>
<body>

<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            
            <form action="" id="id_form">
                <h1 class="text-center">登入h1>
                
                {% csrf_token %}

                <div class="form-group">
                    <label for="id_username">账户label>
                    <input type="text" id="id_username" name="username" minlength="3" maxlength="8"
                           class="form-control transparent">
                div>

                <div class="form-group">
                    <label for="id_password">密码label>
                    <input type="password" id="id_password" name="password" class="form-control transparent">
                div>

                <label for="id_code">验证码(点击图片刷新)label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" id="id_code" name="code" class="form-control transparent">
                    div>

                    <div class="col-md-6">
                        
                        <img src="/get_code/?num=0" id="refresh" alt="" width="445" height="35" style="opacity: 0.5;">
                    div>
                div>
                <input type="button" value="登入" class="btn-info btn-block" style="opacity: 0.3; margin-top: 30px">
            form>
        div>
    div>
div>
<script>
    // 10. 绑定事件, 每次点击验证码图片就
    $('#refresh').on('click', function () {
        // 10.1 获取img标签的src值 /get_code/?num=0
        let url = $(this).attr('src')

        // 对url后面的参数做自增操作

        // 10.2 字符串切分
        let UrlList = url.split('=')  // 以=切分

        // console.log(UrlList)  // ['/get_code/?num', '0']

        // 10.3 将字符串类型 数值类型
        let num = parseInt(UrlList[1])

        // 10.4 自增
        num += 1  // 每刷新一次就加1

        // 10.5 将整数转为字符串
        UrlList[1] = num.toString()

        // console.log(UrlList)  // ['/get_code/?num', '0']

        // 10.6 数组中 字符串元素拼接, 以=拼接
        url = UrlList.join('=')

        // 10.7 修改 img标签的src属性, 这个时候会向服务器发送请求, 返回的数据被展示出来.
        $(this).attr('src', url)
    })

    // 11. 绑定点击事件
    $('input:button').on('click', function () {
        // 11.1 获取表单数据
        let form_data = $('#id_form').serializeArray()

        // console.log(form_data)

        // 11.2 定义一个对象
        let dict_obj = new Object()

        // 11.3 遍历对象 [{}. {}]
        $.each(form_data, function (index, obj) {
            // 9.4 为对象的属性写入值
            dict_obj[obj.name] = obj.value
        })
        // console.log(dict_obj)
        // 11.5 ajax提交数据
        $.ajax({
            // 11.6 向当前url提交数据
            url: '',
            // 11.7 请求方式
            type: 'post',
            // 11.8 提交的数据
            data: dict_obj,
            // 11.9 回调函数
            success: function (args) {
                // 11.10 判断响应状态码
                if (args.code === 200) {
                    // 11.11 跳转到主页
                    window.location.href = args.url
                } else {
                    // 11.12 二次弹框 展示错错误的提示信息 

                    swal({
                        title: args.error_msg,
                        type: 'error',
                        customClass: "sweetAlert" // 样式
                    })
                }
            }
        })
    })
script>
body>
html>
每次修改img标签src属性的值都会重新获取图片的数据.
在访问的路径后面修改任意的参数, 就能不影响访问地址的情况下重新获取一次图片数据, 
当前登入页面的表单信息不被刷新.
    // 8. 绑定事件, 每次点击验证码图片就
    $('#refresh').on('click', function () {
        // 8.1 获取img标签的src值 /get_code/?num=0
        let url = $(this).attr('src')

        // 对url后面的参数做自增操作

        // 8.2 字符串切分
        let UrlList = url.split('=')  // 以=切分

        // console.log(UrlList)  // ['/get_code/?num', '0']

        // 8.3 将字符串类型 数值类型
        let num = parseInt(UrlList[1])

        // 8.4 自增
        num += 1  // 每刷新一次就加1

        // 8.5 将整数转为字符串
        UrlList[1] = num.toString()

        // console.log(UrlList)  // ['/get_code/?num', '0']

        // 8.6 数组中 字符串元素拼接, 以=拼接
        url = UrlList.join('=')

        // 8.7 修改 img标签的src属性, 这个时候会向服务器发送请求, 返回的数据被展示出来.
        $(this).attr('src', url)
    })

12. Django 博客项目01 表设计&注册功能&登入功能_第15张图片

                    // 11.12 二次弹框 展示错错误的提示信息 
                    swal({
                        title: args.error_msg,
                        type: 'error',
                        customClass: "sweetAlert" // 样式
                    })

5.4 业务逻辑
# 3. 登入功能
def login(request):
    # 3.2 判断请求方式
    if request.is_ajax():

        # 3.3 获取session表中 中的验证码
        table_code = request.session.get('code')

        # 3.4 获取提交的验证码
        submit_code = request.POST.get('code')
        print(f'表中的验证码{table_code}  提交的验证码{submit_code}')

        # 3.5 判断验证码是否正确 忽略大小写
        if table_code.upper() == submit_code.upper():
            # 如果验证码正确
            # 3.6 获取提交的用户名称&密码
            username = request.POST.get('username')
            password = request.POST.get('password')

            # 3.7 导入 auth 模块 在文件首部导入

            # 3.8 密码加密之后再去userinfo表中比对数据, 匹配成功 返回有值的request对象, 否则放回一个空的request对象
            is_exists = auth.authenticate(username=username, password=password)

            # 3.9 判断是名称或密码是否正确
            if is_exists:
                # 3.10 登入成功将保存登入状态
                auth.login(request, is_exists)

                # 3.11 组成登入成功的返回数据 状态码200 主页路由
                back_dir = {'code': 200, 'url': '/home/'}

            else:
                # 3.12 账户或密码不正确放回的信息
                back_dir = {'code': 400, 'error_msg': '名称或密码错误'}

        else:
            # 3.13 验证码不正确返回的信息
            back_dir = {'code': 400, 'error_msg': '验证码错误'}

        # 3.14 返回信息给回调函数 前端拿到的一个自定义对象
        print(back_dir)
        return JsonResponse(back_dir)

    # 3.1 返回 登入页面
    return render(request, 'login.html')

6. 验证码功能

6.1 路由层
    # 3. 获取图片验证码
    url(r'^get_code/', views.get_code),
6.2 视图层
ps: Django 是由 mange.py 启动的, 其他的子程序都是以模块的形式被到过来运行的
os.getcwd() 获取当前路径, 获取的都是项目的绝对路径.
    import  os
    print(os.getcwd())  # F:\synchro\Project\BBS
1. 验证码返回测试
返回一张验证码图片用于测试, 查看前端的效果.
# 4. 验证码
def get_code(request):
    # 4.1 二进制模式读取图片数据
    with open('static/img/code_test.jpg', mode='rb') as rf:
        data = rf.read()

    # 4.2 返回二进制数据给 img标签的src属性
    return HttpResponse(data)

12. Django 博客项目01 表设计&注册功能&登入功能_第16张图片

2. 随机背景图片1
pillow 图片相关模块
安装:
pip install pillow

导入:
from PIL import Image, ImageDraw, ImageFont
Image: 生成图片
ImageDraw: 在图片上写字
ImageTont: 控制字体的颜色
在PyChrm的Terminal中输入下载包的命令:
pip3.6 install pillow

12. Django 博客项目01 表设计&注册功能&登入功能_第17张图片

Image的方法:

.new(mode, size, color=0) 生成图片对象
    mode 模式 RGB
    size 大小 (, ) 是一个数组, 单位px.
    color 颜色, 可以是三基色数组, 颜色的单词.
    
.save(fp, format=None)
	fp: 文件句柄
	format: 文件格式
# 4.验证码
# 4.1 导入 PIL 模块
from PIL import Image, ImageDraw, ImageFont


# 4.2 随机生成 三基色数组 函数
def get_color():
    # 4.2.1 导入随机模块
    from random import randint
    # 4.2.2 返回一个随机三位数的数组
    return randint(0, 255), randint(0, 255), randint(0, 255)


# 4.3 验证码背景图片
def get_code(request):
    # 4.3.1 产生图片对象
    image_obj = Image.new('RGB', (440, 35), get_color())

    # 产生图片对象之后需要保存

    # 4.3.2 保存图片
    with open('static/img/random_img.png', mode='wb') as wf:
        image_obj.save(wf, format='png')

    # 4.3.3 读取图片
    with open('static/img/random_img.png', mode='rb') as rf:
        data = rf.read()

    # 4.3.4 返回图片数据
    return HttpResponse(data)

缺点: IO操作频繁, 每次获取验证图片都要进行读写操作.
3. 随机背景图片2
优化: 避免频繁的IO操作.
内存管理模块(内置)
BytesIO: 临时存储数据, 返回的数据是二进制.
StringIO: 临时存储数据, 返回的数据是字符串.
导入模块
from IO import BytesIO, StringIO
生成对象
io_obj = BytesIO()
读数据:
io_obj.getvalue()
# 4.验证码
# 4.1 导入 PIL 模块
from PIL import Image, ImageDraw, ImageFont


# 4.2 随机生成 三基色数组 函数
def get_color():
    # 4.2.1 导入随机模块
    from random import randint
    # 4.2.2 返回一个随机三位数的数组
    return randint(0, 255), randint(0, 255), randint(0, 255)


# 4.3 验证码背景图片
def get_code(request):
    # 4.3.1 产生图片对象
    image_obj = Image.new('RGB', (440, 35), get_color())

    # 产生图片对象之后需要保存

    # 4.3.2 导入 临时存储模块
    from io import BytesIO

    # 4.3.4 生成io对象
    io_obj = BytesIO()  # 得到一个文件句柄

    # 4.3.5 保存图片 (文件句柄, 文件格式)
    image_obj.save(io_obj, format='png')

    # 4.3.6 读取文件数据, 返回图片数据
    return HttpResponse(io_obj.getvalue())
效果与上面一致.
4. 图片上写字
ps: 电脑上展示的文字格式对应了一个.ttf/ .itf文件.

下载字体文件, 选一个免费商用, 找一个自己喜欢的字体点击下载.
http://www.zhaozi.cn/ai/2019/fontlist.php?ph=1&classid=32&softsq=%E5%85%8D%E8%B4%B9%E5%95%86%E7%94%A8

12. Django 博客项目01 表设计&注册功能&登入功能_第18张图片

在static目录下新建一个font目录.
将下载好的字体文件复制到font目录中.
图片对象
image_obj = Image.new('RGB', (440, 35), get_color())

画笔对象 = ImageDraw.Draw(图片对象)
img_draw = ImageDraw.Draw(image_obj)

产生一个字体对象        
img_font = ImageFont.truetype(字体文件位置, 字体大小)

画笔对象将一个个字符写在图片上
img_draw.text ( (x 水平, y 垂直), 需要写的文字, 文字颜色, 字体对象)
# 4.验证码
# 4.1 导入 PIL 模块
from PIL import Image, ImageDraw, ImageFont

# 4.2 随机生成 三基色数组 函数
# 4.2.1 导入随机模块
from random import randint, choice


def get_color():
    # 4.2.2 返回一个随机三位数的数组
    return randint(0, 255), randint(0, 255), randint(0, 255)


# 4.3 验证码背景图片
def get_code(request):
    # 4.3.1 产生图片对象
    image_obj = Image.new('RGB', (440, 35), get_color())

    # 4.3.2 产生画笔对象 .Draw(图片对象)
    img_draw = ImageDraw.Draw(image_obj)

    # 4.3.3 产生一个字体对象        (字体文件位置, 字体大小)
    img_font = ImageFont.truetype('static/font/思源黑体TW-Normal.otf', 30)

    # 4.3.4 定义一个字符串存在字符串用于登入时检验
    code = ''

    # 4.3.5 生成一个5位数的随机验证码 大小写字母 和 数字的任意组合

    for i in range(5):
        # .1 大写字母 --> 字符串
        letter_upper = chr(randint(65, 90))
        # .2 小写字母 --> 字符串
        letter_lower = chr(randint(97, 122))
        # .3 字符串数字
        str_number = str(randint(0, 9))

        # .4 三选一 可以是  大小写字母 和 数字的任意一个
        character = choice([letter_upper, letter_lower, str_number])

        # .5 画笔对象将一个个字符写在图片上 ( (x 水平, y 垂直), 需要写的文字, 文字颜色, 字体对象)
        img_draw.text((i * 80+50, -5), character, get_color(), img_font)
        """ 字体文字 图片位置 440 
        i  0 ~ 4
        0 * 80 + 40 = 40  1 * 80 + 40 = 120  2 * 80 + 40 = 200  3 * 80 + 40 = 280  4 * 80 + 40 = 360
        ---> 文字排序开来 40   120  200   280  360 --->
        """

        # .6 拼接 字符串
        code += character

    # 4.3.6 将随机验证码保存到session表中
    request.session['code'] = code
    print(f'验证码是:{code}')

    # 临时存续图片
    # 4.3.7 导入 BytesIO 模块
    from io import BytesIO
    # 4.3.8 生成一个临时存储对象, 是一个文件句柄
    io_obj = BytesIO()

    # 4.3.9 将存放到 临时存储对象中 ('文件句柄', '文件的存储格式')
    image_obj.save(io_obj, format='png')

    # 4.3.10 读取文件数据, 返回图片数据
    return HttpResponse(io_obj.getvalue())

你可能感兴趣的:(3.Django,django,python,前端)