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',]
参考
分类:
Django学习笔记
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?