Django学习笔记之利用Form和Ajax实现注册功能

时间:2022-06-02
本文章向大家介绍Django学习笔记之利用Form和Ajax实现注册功能,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、注册相关的知识点

1、Form组件

我们一般写Form的时候都是把它写在views视图里面,那么他和我们的视图函数也不影响,我们可以吧它单另拿出来,在应用下面建一个forms.py的文件来存放

2、局部钩子函数

 def clean_username(self):
        username = self.cleaned_data.get("username")
        valid = models.UserInfo.objects.filter(username = username).first()
        if valid:
            raise ValidationError("用户名已存在")
        return username

3、全局钩子函数

#自定义全局钩子:验证两次密码是否一致
 def clean(self):
   if self.cleaned_data.get("password") == self.cleaned_data.get("password_again"):
        return self.cleaned_data
   else:
        raise  ValidationError("两次密码不一致")

4、 jQuery的属性操作相关的

attr:
    一个参数是获取属性的值,两个参数是设置属性值
removeAttr(属性名):
    删除属性值
prop:
    适应于属性的返回值是布尔类型的(单选,反选,取消的例子)
removePorp:
    删除属性的值

5、循环的两种方式:

$.each(数组/对象,function(i,v){})
$("div").each(function(i,v){})

6、css中的三种隐藏:

1、display:none  隐藏所有内容
2、visibility:hidden  隐藏内容
3、overflow:hidden   隐藏溢出内容
三者都是用来隐藏的:
区别在于:
   visibility虽然隐藏了,但是被隐藏的内容依然占据这空间,这段隐藏了的内容却保留空间的位置会在网页中显示空白
   而display:隐藏了不占用空间
我们在注册的时候不用display:none,不然选择文件的那个功能就没有了,我们可以吧透明度

7、提交二进制数据用FormData

var formData=new FormData();
formData.append("username",$("#id_username").val()); 
formData.append("email",$("#id_email").val());
formData.append("tel",$("#id_tel").val());
formData.append("password",$("#id_password").val());
formData.append("password_again",$("#id_password_again").val());
formData.append("avatar_img",$("#avatar")[0].files[0]);

记得要加上

contentType:false
processData:false

8、可以用下面的方法判断是什么请求

if request.ajax():    #如果ajax请求
if request,method=="POST":   #如果是POST请求

9、上传文件有一个固定的配置参数media,和static相似 但又不同

步骤如下:

- 首先在settings中配置:

# ============media配置===============
MEDIA_URL="/media/"  #别名
MEDIA_ROOT=os.path.join(BASE_DIR,"app01","media","uploads")   #具体路径

- 在url中配置

url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

用处:

用处一:
-----      avatar = models.FileField(verbose_name='头像', upload_to='avatar', default="/avatar/default.png")
会把接收的文件放在media指代的路径与upload_to的拼接:BASE_DIR+blog+media+uploads+avatar/a.png
avatar字段在数据库中保存的是:avatar/a.png
 
用处二:
                                           
------     <img src="/media/avatar/a.png">

如果上传成功会把图片自动保存在这里

10、头像图片预览

   //头像预览
        $(".avatar_file").change(function () {
            var ele_file = $(this)[0].files[0]; //当前选中的文件
            var reader = new FileReader();
            reader.readAsDataURL(ele_file); //对应找到打开的url
            reader.onload=function () {
{#                方式一#}
                $(".avatar_img").attr("src",this.result) ; //this.result是上面找到的url
{#                方式二#}
{#                 $(".avatar_img")[0].src=this.result; //设置图片属性#}
            }
        })

11、form自动生成的错误信息

当你定义了全局钩子的时候,而且正好出现你的那个全局钩子函数中的错(比如两次密码输入不一致),这样你打印错误信息的时候

会有一个__all__对象,这个就是你设置的全局钩子生成的。

所以还要单独判断一下,现在全局钩子只有一个,你可以这样判断,但是,当全局钩子多的时候就得一个一个分开来判断

  if (i=="__all__"){
        $("#id_password_again").after($span)
   }

二、具体实现注册操作

 url.py

from django.conf.urls import url,include
from django.contrib import admin
from django.conf import settings
from django.views.static import serve

from blog import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    url(r'^index/', views.index),
    url(r'^v_code/', views.v_code),
    url(r'^login2/', views.login2),
    url(r'^pcgetcaptcha/', views.pcgetcaptcha),

    # 以上是bbs day75的内容

    url(r'^reg/$', views.reg),
    url(r'^media/(?P<path>.*)$', serve, {"document_root":settings.MEDIA_ROOT})

]


if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        url(r'^__debug__', include(debug_toolbar.urls))
    ] + urlpatterns

views.py

def reg(request):
    logger.info("又来了,小伙子")
    collect_logger.info("小伙子又来洗.脚.了")

    if request.method == "POST":

        ret = {"ret":0}

        form_obj = forms.RegForm(request.POST)
        logger.debug(request.FILES)

        if form_obj.is_valid():
            # 数据经过校验没问题
            logger.debug(form_obj.cleaned_data)
            avatar_obj = request.FILES.get("avatar")
            # 创建用户
            form_obj.cleaned_data.pop("re_password", "")
            models.UserInfo.objects.create_user(
                avatar=avatar_obj,
                **form_obj.cleaned_data
            )
            ret["data"] = "/login/"
        else:
            # 数据校验失败
            ret["code"] = 1
            ret["data"] = form_obj.errors

        return JsonResponse(ret)

    form_obj = forms.RegForm()

    return render(request,"reg.html",{"form_obj":form_obj})

forms.py

from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from blog import models


# 登陆Form
class LoginForm(forms.Form):

    username = forms.CharField(
        label="用户名",
        min_length=3,
        max_length=12,
        error_messages={
            "required": "用户名不能为空",
            "min_length": "用户名最短为3位",
            "max_length": "用户名最长12位",
        },
        widget=forms.widgets.TextInput(
            attrs={"class": "form-control"}
        )
    )

    password = forms.CharField(
        label="密码",
        min_length=4,
        max_length=12,
        error_messages={
            "required": "用户名不能为空",
            "min_length": "用户名最短3位",
            "max_length": "用户名最长12位",
        },
        widget=forms.widgets.PasswordInput(
            attrs={"class": "form-control"}
        )
    )


# 注册Form
class RegForm(forms.Form):

    username = forms.CharField(
        label="用户名",
        min_length=3,
        max_length=12,
        error_messages={
            "required": "用户名不能为空!",
            "min_length": "用户名最短3位",
            "max_length": "用户名最长12位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )

    password = forms.CharField(
        label="密码",
        min_length=4,
        max_length=12,
        error_messages={
            "required": "密码不能为空!",
            "min_length": "密码最短4位",
            "max_length": "密码最长12位"
        },
        widget=forms.widgets.PasswordInput(attrs={"class": "form-control"})
    )

    re_password = forms.CharField(
        label = "确认密码",
        min_length = 4,
        max_length= 12,
        error_messages={
            "required": "确认密码不能为空!",
            "min_length": "密码最短4位",
            "max_length": "密码最长12位"
        },
        widget=forms.widgets.PasswordInput(attrs={"class": "form-control"})
    )

    phone = forms.CharField(
        label = "手机",
        min_length = 11,
        max_length= 11,
        validators= [
            RegexValidator(r'^d{11}$', "手机号必须是数字"),
            RegexValidator(r'^1[356789][0-9]{9}$', "无效的手机号码")
        ],
        error_messages={
            "required":"手机不能为空!",
            "min_length":"手机号码11位",
            "max_length":"手机号码11位"
        },
        widget=forms.widgets.TextInput(
            attrs={"class":"form-control"}
        )
    )



    # 局部钩子
    def clean_username(self):
        value = self.cleaned_data.get("username", "")
        if "金/瓶/梅" in value:
            raise ValidationError("不符/合社/会/主/义/核心价值观")
        elif models.UserInfo.objects.filter(username=value):
            raise ValidationError("用户名已存在")
        else:
            return value

    # 全局钩子
    def clean(self):
        pwd = self.cleaned_data.get("password", "")
        re_pwd = self.cleaned_data.get("re_password", "")

        if re_pwd and pwd == re_pwd:
            return self.cleaned_data
        else:
            err_msg = "两次输入密码不一致"
            self.add_error("re_password",err_msg)
            raise ValidationError(err_msg)

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎注册</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <style>
        .reg-form {
            margin-top: 70px;
        }
        #show-avatar {
            width: 80px;
            height: 80px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3 reg-form">
            <form class="form-horizontal" autocomplete="off" novalidate>
                <div class="form-group">
                    <label for="{{ form_obj.username.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.username.label }}</label>
                    <div class="col-sm-10">
                        {{ form_obj.username }}
                        <span class="help-block"></span>
                    </div>
                </div>
                <div class="form-group">
                    <label for="{{ form_obj.password.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.password.label }}</label>
                    <div class="col-sm-10">
                        {{ form_obj.password }}
                        <span class="help-block"></span>
                    </div>
                </div>
                <div class="form-group">
                    <label for="{{ form_obj.re_password.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.re_password.label }}</label>
                    <div class="col-sm-10">
                        {{ form_obj.re_password }}
                        <span class="help-block"></span>
                    </div>
                </div>
                <div class="form-group">
                    <label for="{{ form_obj.phone.id_for_label }}"
                           class="col-sm-2 control-label">{{ form_obj.phone.label }}</label>
                    <div class="col-sm-10">
                        {{ form_obj.phone }}
                        <span class="help-block"></span>
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">头像</label>
                    <div class="col-sm-10">
                        <input accept="image/*" type="file" id="id_avatar" name="avatar" style="display: none">
                        <label for="id_avatar"><img src="/static/img/default.png" id="show-avatar"></label>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                        <button type="button" class="btn btn-default" id="reg-button">注册</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>

<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/setupAjax.js"></script>

<script>
    // 找到注册按钮绑定点击事件
    $("#reg-button").click(function () {
        let dataObj = new FormData();
        dataObj.append("username",$("#id_username").val());
        dataObj.append("password",$("#id_password").val());
        dataObj.append("re_password",$("#id_re_password").val());
        dataObj.append("phone",$("#id_phone").val());
        dataObj.append("avatar",$("#id_avatar")[0].files[0]);
        $.ajax({
            url: "/reg/",
            type: "POST",
            processData: false,
            contentType: false,
            data: dataObj,
            success: function (data) {
                console.log(data);
                if(data.data) {
                    // 如果有报错信息, 应该在页面的对应的位置展示出来
                    let errMsgObj = data.data;
                    $.each(errMsgObj, function(k,v){
                        $("#id_"+k).next(".help-block").text(v[0]).parent().parent().addClass("has-error");
                    })
                }else{
                    console.log(data.data);
                    location.href = data.data || "/login/"
                }
            }
        })
    });

    // 给每一个input标签绑定focus事件,移除当前的错误提示信息
    $("input.form-control").focus(function() {
        $(this).next(".help-block").text("").parent().parent().removeClass("has-error")
    });

    //头像预览
    $("#id_avatar").change(function() {
        // 找到你选中的那个头像文件
        let fileObj = this.files[0];
        console.log(fileObj);
        // 读取文件路径
        let fileReader = new FileReader();
        fileReader.readAsDataURL((fileObj));
        // 等图片被读取完毕后,在进行操作
        fileReader.onload = function() {
            // 设置预览图片
            $("#show-avatar").attr("src",fileReader.result);
        };
    })

</script>
</body>
</html>

models.py

from django.db import models

# Create your models here.

from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    '''
    用户信息表
    '''
    nid = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=11, null=True, unique=True)
    avatar = models.FileField(upload_to="avatars/", default="avatars/default.png")

    blog = models.OneToOneField(to="Blog", to_field="nid", null=True)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = "用户信息"
        verbose_name_plural = verbose_name


class Blog(models.Model):
    '''
    博客信息
    '''
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=64)  # 个人博客主题
    theme = models.CharField(max_length=32)  # 每个博客主题

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "博客"
        verbose_name_plural = verbose_name


class Category(models.Model):
    '''
    个人文章分类
    '''
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)  # 分类主题
    blog = models.ForeignKey(to="Blog", to_field="nid")  # 外键关联博客,一个博客可以有多个分类

    def __str__(self):
        return "{}-{}".format(self.blog.title, self.title)

    class Meta:
        verbose_name = "文章分类"
        verbose_name_plural = verbose_name


class Tag(models.Model):
    '''
    标贴
    '''
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)  # 标签
    blog = models.ForeignKey(to="Blog", to_field="nid")  # 所属的博客

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "标签"
        verbose_name_plural = verbose_name


class Article(models.Model):
    '''
    文章
    '''
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=64)  # 文章标题
    desc = models.CharField(max_length=255)  # 文章描述
    create_time = models.DateTimeField(auto_now_add=True)  # 创建时间
    category = models.ForeignKey(to="Category", to_field="nid", null=True)

    user = models.ForeignKey(to="UserInfo", to_field="nid")

    tag = models.ManyToManyField(
        to="Tag",
        through="Article2Tag",
        through_fields=("article", "tag")
    )

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "文章"
        verbose_name_plural = verbose_name


class ArticleDetail(models.Model):
    '''
    文章详情表格
    '''
    nid = models.AutoField(primary_key=True)
    content = models.TextField()
    article = models.OneToOneField(to="Article", to_field="nid")

    class Meta:
        verbose_name = "文章详情"
        verbose_name_plural = verbose_name


class Article2Tag(models.Model):
    '''
    文章和标签多对多的关系
    '''
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(to="Article", to_field="nid")
    tag = models.ForeignKey(to="Tag", to_field="nid")

    def __str__(self):
        return "{}-{}".format(self.article, self.tag)

    class Meta:
        unique_together = (("article", "tag"),)
        verbose_name = "文章-标签"
        verbose_name_plural = verbose_name


class ArticleUpDown(models.Model):
    '''
    点赞表
    '''
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey(to="UserInfo", null=True)
    article = models.ForeignKey(to="Article", null=True)
    is_up = models.BooleanField(default=True)

    class Meta:
        unique_together = (("article","user"))
        verbose_name = "点赞"
        verbose_name_plural = verbose_name


class Comment(models.Model):
    '''
    评论表
    '''
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(to="Article", to_field="nid")
    user = models.ForeignKey(to="UserInfo", to_field="nid")
    content = models.CharField(max_length=255)
    create_time = models.DateTimeField(auto_now_add=True)
    parent_comment = models.ForeignKey("self", null=True)

    def __str__(self):
        return self.content

    class Meta:
        verbose_name = "评论"
        verbose_name_plural = verbose_name

settings.py(带logging模块)

"""
Django settings for bbs_d76 project.

Generated by 'django-admin startproject' using Django 1.11.11.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'jng4g!)5pve-0s7)6lv(nty7$jn^!p7ov+$-*hdn@!!kkg7q#e'

# 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 = 'bbs_d76.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 = 'bbs_d76.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbs_d76',
        'USER': 'root',
        'PASSWORD': '1234567890',
        'HOST': '127.0.0.1',
        'PORT': 3306,
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.11/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/1.11/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/1.11/howto/static-files/

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static")
]

# 指定一下认证使用  自定义的UserInfo表
AUTH_USER_MODEL = "blog.UserInfo"

# 用户上传的文件配置项
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

# 日志配置
BASE_LOG_DIR = os.path.join(BASE_DIR, "log")
LOGGING = {
    'version': 1,
    # 禁用已经存在的logger实例
    'disable_existing_loggers': False,
    # 定义日志 格式化的 工具
    'formatters': {
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    # 过滤
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    # 日志处理器
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],  # 只有在Django debug为True时才在屏幕打印日志
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },

        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 3,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },

        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },

        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    # logger实例
    'loggers': {
       # 默认的logger应用如下配置
        '': {
            'handlers': ['default', 'console', 'error'],  # 上线之后可以把'console'移除
            'level': 'DEBUG',
            'propagate': True,
        },
        # 名为 'collect'的logger还单独处理
        'collect': {
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}

INTERNAL_IPS = ['127.0.0.1',]

参考