ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

2022.5.26 BBS项目之注册登录

2022-05-27 00:33:15  阅读:215  来源: 互联网

标签:26 password obj form models data avatar 2022.5 BBS


2022.5.26 BBS项目之注册登录

  • BBS项目之模型层
  • 注册用户form校验组件
  • 注册登录之视图层
  • 注册登录之模板层

一、模型层

1、用户表继承AbstractUser

目的是为了让auth自动在数据库自动创建的user用户表加以拓展,符合用户注册的需求;

from django.contrib.auth.models import AbstractUser

class UserInfo(AbstractUser):
    """用户表"""
    phone = models.BigIntegerField(verbose_name='手机号', null=True)
    avatar = models.FileField(verbose_name='头像', upload_to='avatar/', default='avatar/default.jpg')
    '''给该字段传文件对象 会保存到upload_to指定的位置 然后该字段存储该位置的路径'''
    create_time = models.DateField(verbose_name='注册时间', auto_now_add=True)

    blog = models.OneToOneField(to='Blog', null=True)  # 外键设置null=True便于后期录入数据的方便

创建好user的拓展表后,需要在settings文件中注册:

AUTH_USER_MODEL = 'app01.UserInfo'

总结:

(1)File字段:up_load='文件夹路径',字段获取的文件数据的保存位置;

(2)File字段:default='文件路径',设置文件字段的默认值,这里头像设置为默认头像;

(3)blog字段:null=True,外键字段设置便于后期录入数据

2、文章表字段优化

针对文章的评论数、点赞数、点踩数都可以跨表查询得到;
但是页面上有很多文章,如果每一篇都跨表查询,效率极低;
我们可以在文章表里面设计三个普通字段专门记录:
文章评论数:每次评论让该字段数值自增1;
文章点赞数:每次点赞让该字段数值自增1;
文章点踩数:每次点踩让该字段数值自增1;

class Article(models.Model):
    """文章表"""
    title = models.CharField(verbose_name='文章标题', max_length=32)
    desc = models.CharField(verbose_name='文章简介', max_length=255)
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(verbose_name='创作时间', auto_now_add=True)
    # 优化字段
    up_num = models.IntegerField(verbose_name='点赞数', default=0)
    down_num = models.IntegerField(verbose_name='点踩数', default=0)
    comment_num = models.IntegerField(verbose_name='评论数', default=0)
    # 外键字段
    blog = models.ForeignKey(to='Blog', null=True)
    category = models.ForeignKey(to='Category', null=True)
    tags = models.ManyToManyField(to='Tag',
                                  through='Article2Tag',
                                  through_fields=('article', 'tag')
                                  )

3、评论表设置自关联外键

class Comment(models.Model):
    """评论表"""
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(verbose_name='评论内容', max_length=255)
    comment_time = models.DateTimeField(verbose_name='评论时间', auto_now_add=True)
    # 自关联
    parent = models.ForeignKey(to='self', null=True)  # 有些评论就是根评论

总结:

to='self':意思是设置当前表为关联的外键字段,因为该表中的每个评论可以作为根评论,也可以作为子评论

二、Forms组件校验

from django import forms
from app01 import models


class RegisterForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8, label='用户名',
                               error_messages={
                                   'min_length': '用户名最少三位',
                                   'max_length': '用户名最多八位',
                                   'required': '用户名不能为空'
                               },
                               widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                               )
    
    password = forms.CharField(...
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}))  # ...省略代码
    
    confirm_password = forms.CharField(...
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}))
    
    email = forms.EmailField(...
                               widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}))
    
    '''针对用户头像字段 单独处理!!!'''
    # 局部钩子 校验用户名是否已存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        is_exist = models.UserInfo.objects.filter(username=username)
        if is_exist:
            self.add_error('username', '用户名已存在')
        return username

    # 全局钩子 校验两次密码是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password', '两次密码不一致')
        return self.cleaned_data

三、视图层

1、注册

from django.shortcuts import render,HttpResponse
# Create your views here.
from app01 import myforms
from app01 import models
from django.http import JsonResponse


def register(request):
    """前后端使用ajax交互 一般采用字典(json格式数据)作为数据媒介"""
    back_dic = {'code':10000,'msg':''}  # code模拟响应状态码  msg模拟数据
    form_obj = myforms.RegisterForm()  # 获取写好的forms校验组件
    if request.method == 'POST':
        # 1.将request.POST里面的数据全部交给forms类校验
        form_obj = myforms.RegisterForm(request.POST)  # 多余的字段默认就不校验
        # 2.判断是否符合条件
        if form_obj.is_valid():
            cleaned_data = form_obj.cleaned_data  # username password confirm_password email
            # 3.移除创建用户数据时没有用的键值对confirm_password
            cleaned_data.pop('confirm_password')  # username password email
            # 4.获取用户头像
            avatar_obj = request.FILES.get('avatar')
            # 5.判断用户是否自定义了头像
            if avatar_obj:
                cleaned_data['avatar'] = avatar_obj  # username password email avatar
            # 6.创建用户对象
            models.UserInfo.objects.create_user(**cleaned_data)
            back_dic['msg'] = '用户注册成功'
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 10001
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request, 'register.html', locals())

注意:

(1)cleaned_data获取的数据,需要判断数据的多少,比如模型层并没有confirm_password字段,因此应该去除这一字段:

cleaned_data.pop('confirm_password')

并且cleaned_data是一个容器类型的对象,可以使用**打散:

models.UserInfo.objects.create_user(**cleaned_data)

(2)由于前端js需要判断后端获取数据需要判断是否成功登录并且返回错误信息,因此需要模拟响应状态码,登录失败返回错误信息:

back_dic = {'code':10000,'msg':''}  # code模拟响应状态码  msg模拟数据

# 登录成功
back_dic['msg'] = '用户注册成功'
back_dic['url'] = '/login/'

# 登录失败
back_dic['code'] = 10001  # 更改状态码
back_dic['msg'] = form_obj.errors  # 返回forms校验组件中写好的errors

(3)不管登录成功或者失败返回的back_dic都需要使用JsonResponse模块进行返回,因为前端需要获取数据:

from django.http import JsonResponse
...
return JsonResponse(back_dic)

2、登录

def login(request):
    return render(request,'login.html')


"""
图形化相关模块:pillow
    pip3 install pillow
"""
from PIL import Image,ImageFont,ImageDraw
"""
Image       生成图片对象
ImageDraw   生成画笔对象 可以在图片上乱涂乱画
ImageFont   如果写文字 可以控制字体样式
"""

from io import BytesIO,StringIO
"""
BytesIO     内存中保存数据 并且取的时候返回bytes类型
StringIO    内存中保存数据 并且取的时候返回字符串类型
"""

# 专门随机生成三个数
import random
def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)

def get_code(request):
    # 1.推导步骤1:直接读取图片的二进制数据返回
    # with open(r'avatar/555.jpg','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
    # 2.推导步骤2:利用模块产生图片
    # img_obj = Image.new('RGB',(350, 35),'yellow')
    # with open(r'xxx.png','wb') as f:
    #     img_obj.save(f,'png')
    # with open(r'xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
    # 3.推导步骤3:图片颜色随机产生
    # img_obj = Image.new('RGB',(350, 35),get_random())
    # with open(r'xxx.png','wb') as f:
    #     img_obj.save(f,'png')
    # with open(r'xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
    # 4.推导步骤4:使用内存管理器临时存取图片  (前端处理)用户点击图片刷新验证码
    img_obj = Image.new('RGB', (350, 35), get_random())  # 生成随机图片
    io_obj = BytesIO()  # IO对象
    img_obj.save(io_obj,'png')  # IO操作保存图片
    res = io_obj.getvalue()  # 获取图片
    return HttpResponse(res)  #  返回

总结:

(1)图形化相关模块:pillow

# 下载方式:cmd窗口
pip3 install pillow

# 模块导入
from PIL import Image,ImageFont,ImageDraw

Image       生成图片对象
ImageDraw   生成画笔对象 可以在图片上乱涂乱画
ImageFont   如果写文字 可以控制字体样式

eg:
	 img_obj = Image.new('RGB', (350, 35), get_random())  # RGB代表三基色,(350,35)代表长宽,get_random()为获取随机的三个数字的视图函数
    img_obj.save(io_obj,'png')  # 内存中保存图片

(2)io模块

# 模块导入
from io import BytesIO,StringIO

BytesIO     内存中保存数据 并且取的时候返回bytes类型
StringIO    内存中保存数据 并且取的时候返回字符串类型

StringIO的使用

# 类似文件的缓冲区
from io import StringIO
cache_file = StringIO()
print(cache_file.write('hello world'))  # 11
print(cache_file.seek(0))  # 0
print(cache_file.read())  # hello world
print(cache_file.close())   # 释放缓冲区
  • StringIO经常被用来作字符串的缓存,因为StringIO的一些接口和文件操作是一致的,也就是说同样的代码,可以同时当成文件操作或者StringIO操作;
  • 要读取StringIO,可以用一个str初始化StringIO,然后像读文件一样读取;
  • 当使用read()方法读取写入的内容时,则需要先用seek()方法让指针移动到最开始的位置,否则读取不到内容(写入后指针在最末尾);
  • getvalue()方法:直接获得写入后的str;
  • close()方法:在关闭文件的缓冲区之后就不能再进行读写操作了;

BytesIO的使用

# 类似文件的缓冲区
from io import BytesIO
bytes_file = BytesIO()
bytes_file.write(b'hello world')
bytes_file.seek(0)
print(bytes_file.read()) # b'hello world'
bytes_file.close()

bytes_file.getvalue()  # 获取写入的二进制数据

StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO;

BytesIO实现了在内存中读写bytes,写入的不是str,而是经过UTF-8编码的bytes;

要读取BytesIO,可以用一个bytes初始化BytesIO,然后像读文件一样读取;

四、模板层

1、注册页面

<!-- register.html -->

<body>
<div class="container-fluid">
    <div class="row">
        <h2 class="text-center">用户注册</h2>
        <div class="col-md-8 col-md-offset-2">
            <form id="form">
                {% csrf_token %}
                <!--获取普通数据-->
                {% for form in form_obj %}
                    <div class="form-group">
                        <label for="{{ form.auto_id }}">{{ form.label }}</label>  <!--form.auto_id自动产生对应绑定的标签id值-->
                        {{ form }}
                        <span class="errors pull-right" style="color: red"></span>
                    </div>
                {% endfor %}
                <!--获取用户头像-->
                <div class="form-group">
                    <p><label for="avatar">头像
                        <img src="/static/img/default.jpg" alt="" width="120" id="img">
                    </label></p>

                    <input type="file" name="avatar" id="avatar" style="display: none">
                </div>  <!--该div仅仅是增加了上下两个标签的距离 更好看一些-->

                <input type="button" value='注册' class="btn btn-primary btn-block" id="subBtn">
            </form>  <!--仅仅用一个form标签  不使用它的提交功能-->
        </div>
    </div>
</div>
<script>
    // 用户头像实时展示
    $('#avatar').change(function () {
        // 1.产生一个文件阅读器对象
        let myFileReader = new FileReader();
        // 2.获取用户上传的头像文件
        let avatarObj = this.files[0];
        // 3.将文件对象交给阅读器加载
        myFileReader.readAsDataURL(avatarObj)  // IO操作 需要消耗时间 但是是异步
        // 4.修改img标签的src属性
        // 等待文件阅读器对象加载完毕之后再修改src属性
        myFileReader.onload = function(){
            $('#img').attr('src',myFileReader.result)
        }
    })

    // 绑定点击事件 提交数据
    $('#subBtn').click(function () {
        // ajax携带文件数据 需要利用内置对象
        let myFormData = new FormData();
        // 添加普通数据  利用form标签自带的序列化功能(一次性获取所有的普通键值对数据)
        {#console.log($('#form').serializeArray())#}
        $.each($('#form').serializeArray(),function (index,dictObj) {
            myFormData.append(dictObj.name,dictObj.value)
        })
        // 添加文件数据
        myFormData.append('avatar',$('#avatar')[0].files[0])
        // 发送ajax请求
        $.ajax({
            url:'',
            type:'post',
            data:myFormData,
            contentType:false,
            processData:false,
            success:function (args) {
                if (args.code===10000){
                    // 注册成功跳转到登录页面
                    window.location.href = args.url
                }else{
                    // 获取到了所有的字段错误提示 如何对应展示???
                    // 研究发现 渲染出来的标签id值都是 id_字段名  而后端返回的错误提示键是字段名 所以拼接即可
                    $.each(args.msg, function (i,j) {
                        let eleId = '#id_' + i;
                        $(eleId).next().text(j[0]).parent().addClass('has-error')
                    })
                }
            }
        })
    })

    // 绑定聚焦事件 点击移除错误提示
    $('input').focus(function () {
        $(this).next().text('').parent().removeClass('has-error')
    })
</script>
</body>

XZPsrn.png

总结:

(1)循环展示forms表单

{% for form in form_obj %}
     <div class="form-group">
         <label for="{{ form.auto_id }}">{{ form.label }}</label>  <!--form.auto_id自动产生对应绑定的标签id值-->
         {{ form }}
         <span class="errors pull-right" style="color: red"></span>
     </div>
{% endfor %}

(2)文件阅读器对象

为了让前端能够识别上传的文件,需要使用一个文件阅读器对象

$('#avatar').change(function () {
    // 1.产生一个文件阅读器对象
    let myFileReader = new FileReader();
    // 2.获取用户上传的头像文件
    let avatarObj = this.files[0];
    // 3.将文件对象交给阅读器加载
    myFileReader.readAsDataURL(avatarObj)  // IO操作 需要消耗时间 但是是异步
    // 4.修改img标签的src属性
    // 等待文件阅读器对象加载完毕之后再修改src属性
    myFileReader.onload = function(){
        $('#img').attr('src',myFileReader.result)
    }
})

(3)FormData内置对象

ajax不能直接携带文件数据,需要使用内置对象FormData

let myFormData = new FormData();
// 添加普通数据  利用form标签自带的序列化功能(一次性获取所有的普通键值对数据)
{#console.log($('#form').serializeArray())#}
$.each($('#form').serializeArray(),function (index,dictObj) {
    myFormData.append(dictObj.name,dictObj.value)
})
// 添加文件数据
myFormData.append('avatar',$('#avatar')[0].files[0])

$.ajax({
    ...
    data:myFormData,
    ...
})

(4)用户注册成功/失败后的异步回调函数

后端会返回一个back_dic字典,内部模拟的响应状态码、登录成功后的url、注册失败后的errors(forms校验后的数据),我们利用异步回调函数加以操作:

$.ajax({
    ...
    success:function (args) {
        if (args.code===10000){
            // 注册成功跳转到登录页面
            window.location.href = args.url
        }else{
            // 获取到了所有的字段错误提示 如何对应展示???
            // 研究发现 渲染出来的标签id值都是 id_字段名  而后端返回的错误提示键是字段名 所以拼接即可
            $.each(args.msg, function (i,j) {
                let eleId = '#id_' + i;
                $(eleId).next().text(j[0]).parent().addClass('has-error')
            })
        }
    }
})

(5)输入框错误提示开启和关闭

在异步回调函数获取到返回数据,如果注册失败,则通过字符串的拼接找到对应的标签,然后在更改对应标签的内容并加上属性has-error(输入框变红提示):

# 异步回调函数内对应span标签更改内容,添加has-error属性
$(eleId).next().text(j[0]).parent().addClass('has-error')

给输入框绑定聚焦事件,点击移除错误提示:

$('input').focus(function () {
    $(this).next().text('').parent().removeClass('has-error')
})

2、登录页面

<!-- login.html -->

<body>
<div class="container">
    <div class="row">
        <h2 class="text-center">用户登录</h2>
        <div class="col-md-8 col-md-offset-2">
            <form action="" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <p>用户名:
                        <input type="text" name="username" class="form-control">
                    </p>
                </div>
                <div class="form-group">
                    <p>密码:
                        <input type="password" name="password" class="form-control">
                    </p>
                </div>
                <div class="form-group">
                    <p>验证码:
                        <div class="row">
                            <div class="col-md-6 ">
                                <input type="text" name="username" class="form-control">
                            </div>
                            <div class="col-md-6 ">
                                <img src="/get_code/" alt="" width="350" height="35" id="img">
                            </div>
                </div>
                    </p>
                </div>
                <input type="submit" class="btn btn-success btn-block">
            </form>
        </div>
    </div>
</div>
<script>
    $('#img').click(function () {
        // 1.获取img标签原来的src属性
        let oldSrc = $(this).attr('src');
        // 2.重新赋值即可  只要src跟原来的不一样 就会重新发请求
        $(this).attr('src',oldSrc+'?')
    })
</script>
</body>

标签:26,password,obj,form,models,data,avatar,2022.5,BBS
来源: https://www.cnblogs.com/williama/p/16315983.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有