bbs项目
数据库表设计:
models.py
from django.db import models from django.contrib import auth from django.contrib.auth.models import AbstractUser # Create your models here. ''' 先写普通字段 之后在写外键字段 ''' class UserInfo(AbstractUser): phone = models.BigIntegerField(null=True,verbose_name="手机号") # 头像 avatar = models.FileField(upload_to="avatar/",default='avatar/default.png') """ 给avatar字段传文件对象 该文件会自动存储到avatar文件夹 然后avatar字段只保存文件路径avatar/default.png """ create_time = models.DateTimeField(auto_now_add=True) blog = models.OneToOneField(to='Blog',null=True) class Blog(models.Model): site_name = models.CharField(max_length=32,verbose_name='站点名称') site_title = models.CharField(max_length=32,verbose_name='站点标题') # 简单模拟 认识样式内部原理的操作 site_theme = models.CharField(max_length=64,verbose_name='站点样式') # 存css/js的文件路径 class Category(models.Model): name = models.CharField(max_length=32,verbose_name='文章分类') blog = models.ForeignKey(to='Blog',null=True) class Tag(models.Model): name = models.CharField(max_length=32,verbose_name='文章标签') blog = models.ForeignKey(to='Blog',null=True) tags = models.ManyToManyField(to='Article', through='Article2Tag', through_fields=('tag','article') ) class Article(models.Model): title = models.CharField(max_length=64,verbose_name='文章标题') desc = models.CharField(max_length=255,verbose_name='文章简介') # 文章内容有很多 一般情况下都是使用TextField content = models.TextField(verbose_name='文章内容') create_time = models.DateTimeField(auto_now_add=True) # 数据库字段设计优化 up_num = models.BigIntegerField(default=0,verbose_name='点赞数') down_num = models.BigIntegerField(default=0,verbose_name='点踩数') comment_num = models.BigIntegerField(default=0,verbose_name='评论数') # 外键字段 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') ) class Article2Tag(models.Model): article = models.ForeignKey(to='Article') tag = models.ForeignKey(to='Tag') class UpAndDown(models.Model): user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article') is_up = models.BooleanField() # 传布尔值存0/1 class Comment(models.Model): user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article') content = models.CharField(max_length=255,verbose_name="评论内容") create_time = models.DateTimeField(auto_now_add=True,verbose_name='评论时间') # 自关联 parent = models.ForeignKey(to='self',null=True) # 一定要加null=True,因为有些评论就是根评论
settings.py
""" Django settings for BBS14 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 = '7vht@lf-*t@weir@mcdg^-r+lmyg4qboyy1i_xf^#mwieg!(&x' # 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', 'app01.apps.App01Config', ] 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 = 'BBS14.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [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 = 'BBS14.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bbs14', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': 3306, 'CHARSET': 'utf8' } } # 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/' # 告诉django你要用UserInfo替代auth_user(******) AUTH_USER_MODEL = 'app01.UserInfo'
注册功能
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), # 注册功能 url(r'^register/', views.register, name='register') ]
myforms.py
from django import forms # forms组件所需模块 from app01 import models class MyRegForm(forms.Form): username = forms.CharField(label="用户名", max_length=8, min_length=3, error_messages={ 'required': '用户名不能为空', 'max_length': '用户名最多8位', 'min_length': '用户名最少3位', }, widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) ) password = forms.CharField(label="密码", max_length=8, min_length=3, error_messages={ 'required': '密码不能为空', 'max_length': '密码最多8位', 'min_length': '密码最少3位', }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) confirm_password = forms.CharField(label="确认密码", max_length=8, min_length=3, error_messages={ 'required': '确认密码不能为空', 'max_length': '确认密码最多8位', 'min_length': '确认密码最少3位', }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) ) email = forms.EmailField(label='邮箱', error_messages={ 'required': '邮箱不能为空', 'invalid': '邮箱格式不正确', }, widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}) ) # 钩子函数 def clean_username(self): username = self.cleaned_data.get('username') # 数据库中校验 is_exists = models.UserInfo.objects.filter(username=username) if is_exists: # 提示信息 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
views.py
from django.shortcuts import render, HttpResponse from app01.myforms import MyRegForm from app01 import models from django.http import JsonResponse # Create your views here. def register(request): form_obj = MyRegForm() if request.method == "POST": back_dic = {"code": 1000, "msg": ""} # 校验数据是否合法 form_obj = MyRegForm(request.POST) if form_obj.is_valid(): # print(form_obj.cleaned_data) # {'username': 'alias', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'} clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量 clean_data.pop("confirm_password") # {'username': 'alias', 'password': '123', 'email': '123@qq.com'} # 用户头像 file_obj = request.FILES.get("avatar") """针对用户头像一定要判断是否传值 不能直接添加到字典中""" if file_obj: clean_data['avatar'] = file_obj # 操作数据库保存数据 models.UserInfo.objects.create_user(**clean_data) back_dic["url"] = "/login/" else: back_dic["code"] = 2000 back_dic["msg"] = form_obj.errors return JsonResponse(back_dic) return render(request, "register.html", locals())
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- Bootstrap3 核心 CSS 文件 --> <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- jQuery文件。务必在bootstrap.min.js 之前引入 --> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <!-- Bootstrap3 核心 JavaScript 文件 --> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> <!-- font-awesome.min.css图标库4.7版本 --> <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">注册</h1> <form id="myform"> <!--我们不用form表单提交数据 只是单纯用一下form标签而已--> {% for form in form_obj %} <div class="from-group"> <!--form.auto_id获取forms渲染的input框的id--> <label for="{{ form.auto_id }}">{{ form.label }}</label> {{ form }} <span style="color: red" class="pull-right"></span> </div> {% endfor %} <div class="form-group" style="margin-top: 10px"> <label for="myfile">头像 {% load static %} <img src="{% static "img/default.png" %}" alt="" id="myimg" width="100px" style="margin-left: 10px"> </label> <input type="file" id="myfile" name="avatar" style="display: none"> </div> <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"> </form> </div> </div> </div> <script src="{% static "js/mysteup.js" %}/"></script> <script> // 选择头像实时显示 $("#myfile").change(function () { // 文件阅读器对象 // 1、先生成一个文件阅读器对象 let myFileReaderObj = new FileReader(); // 2、获取用户上传的头像文件 let fileObj = $(this)[0].files[0]; // 3、将文件对象交给阅读器对象读取 myFileReaderObj.readAsDataURL(fileObj); // 这步操作是异步 // 4、利用文件阅读器将文件展示到前端页面 修改img的src属性 // 等待myFileReaderObj文件阅读器加载完毕之后再执行 myFileReaderObj.onload = function(){ $("#myimg").attr("src",myFileReaderObj.result) } }); $("#id_commit").click(function () { // 发送ajax请求 我们发送的数据中即包含普通键值也包含文件 let formDataObj = new FormData(); // 添加普通键值对 // console.log($("#myform").serializeArray()) // [{},{},{},...] 只包含普通键值对 $.each($("#myform").serializeArray(),function (index,obj) { // console.log(index,obj) // obj = {} formDataObj.append(obj.name,obj.value) }); // 添加文件 formDataObj.append('avatar',$("#myfile")[0].files[0]); // 发送ajax请求 $.ajax({ url: '', type: 'post', dataType: 'json', data: formDataObj, contentType: false, processData: false, success: function (args) { if (args.code == 1000){ // 跳转到登录页面 window.location.href = args.url }else{ // 如何将对应的错误信息提示展示到对应的input框下面,因为用的ajax所以无法像之前在span标签里面写form.errors.0 // forms组件渲染的标签的id值都是 id_字段名 $.each(args.msg,function (index,obj) { {#console.log(index,obj) // username ["用户名不能为空"]#} let targetId = '#id_' + index; // input标签 {#$(targetId).next().text(obj[0]).parent().addClass('has-error')#} $(targetId).next().text(obj[0]).prev().css("border","solid red") }) } } }) }) // 给所有的input框绑定获取焦点事件 $('input').focus(function () { // 将input下面的span标签和input外面的div标签修改内容及属性 $(this).next().text('').prev().removeAttr("style",""); }) </script> </body> </html>
登录功能
验证码推导:
# urls.py from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), # 注册功能 url(r'^register/', views.register, name='register'), # 登录功能 url(r'^login/', views.login, name='login'), # 验证码 url(r'^get_code/', views.get_code, name='gc'), ] # views.py from django.shortcuts import render, HttpResponse from app01.myforms import MyRegForm from app01 import models from django.http import JsonResponse # Create your views here. def register(request): form_obj = MyRegForm() if request.method == "POST": back_dic = {"code": 1000, "msg": ""} # 校验数据是否合法 form_obj = MyRegForm(request.POST) if form_obj.is_valid(): # print(form_obj.cleaned_data) # {'username': 'alias', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'} clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量 clean_data.pop("confirm_password") # {'username': 'alias', 'password': '123', 'email': '123@qq.com'} # 用户头像 file_obj = request.FILES.get("avatar") """针对用户头像一定要判断是否传值 不能直接添加到字典中""" if file_obj: clean_data['avatar'] = file_obj # 操作数据库保存数据 models.UserInfo.objects.create_user(**clean_data) back_dic["url"] = "/login/" else: back_dic["code"] = 2000 back_dic["msg"] = form_obj.errors return JsonResponse(back_dic) return render(request, "register.html", locals()) def login(request): return render(request, 'login.html') """ 图片相关的模块 # pip3 install pillow """ from PIL import Image,ImageDraw,ImageFont,ImageFilter """ Image:生成图片 ImageDraw:能够在图片上乱涂乱画 ImageFont:控制字体样式 ImageFilter:控制图片模糊度 """ from io import BytesIO,StringIO """ 内存管理器模块 BytesIO:临时的帮你存储数据 返回的时候数据就是二进制格式 StringIO:临时的帮你存储数据 返回的时候数据就是字符串格式 """ import random def get_random(): return random.randint(0,255),random.randint(0,255),random.randint(0,255) def get_code(request): # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端 # with open(r"static/img/111.jpg", "rb") as f: # data = f.read() # return HttpResponse(data) # # 推导步骤2:利用pillow模块动态产生图片(文件存储繁琐IO操作效率低) # img_obj = Image.new('RGB', (430, 35), "red") # 第二个参数图片尺寸(要和前端划定的尺寸一致),第三个参数还可以放三基色参数 # img_obj = Image.new('RGB', (430, 35), get_random()) # 先将图片对象保存起来 # with open("xxx.png",'wb') as f: # img_obj.save(f,"png") # 文件句柄,图片格式 # # 再将图片对象读取出来 # with open('xxx.png','rb') as f: # data = f.read() # return HttpResponse(data) # 推导步骤3:借助于内存管理模块 # img_obj = Image.new('RGB', (430, 35), get_random()) # io_obj = BytesIO() # 生成一个内存管理器对象,可以看成是文件句柄 # img_obj.save(io_obj,'png') # 要指定图片的格式 # return HttpResponse(io_obj.getvalue()) # io_obj.getvalue()读取出文件,返回的是二进制格式 # 最终步骤:绘制图片验证码 img_obj = Image.new('RGB', (430, 35), get_random()) img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象 img_font = ImageFont.truetype("static/font/222.TTF",30) # 字体样式,以及字体大小 # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 code = '' for i in range(5): random_upper = chr(random.randint(65,90)) random_lower = chr(random.randint(97,122)) random_int = str(random.randint(0,9)) # 从上面三个里面随机选一个 tmp = random.choice([random_upper,random_lower,random_int]) # 将产生的随机字符串写入图片 """ 为什么一个个写而不是生成好了之后再写 因为一个个写能够控制每个字体的间隙 而生成好之后再写的话 间隙就没法控制了 """ img_draw.text((i*45+100,-3),tmp,get_random(),img_font) # 拼接随机字符串 code += tmp # print(code) # 随机验证码在登录的视图函数中需要用到 要比对,所以要找地方存起来且其他视图函数也能拿到(可以放在auth_session表中) request.session['code'] = code io_obj = BytesIO() # img_obj = img_obj.filter(ImageFilter.GaussianBlur) img_obj.save(io_obj,"png") return HttpResponse(io_obj.getvalue()) # login.py <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- Bootstrap3 核心 CSS 文件 --> <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- jQuery文件。务必在bootstrap.min.js 之前引入 --> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <!-- Bootstrap3 核心 JavaScript 文件 --> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> <!-- font-awesome.min.css图标库4.7版本 --> <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> {% load static %} </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">登录</h1> <div class="form-group"> <label for="username">用户名</label> <input type="text" name="username" id="username" class="form-control"> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" name="password" id="password" class="form-control"> </div> <div class="form-group"> <label for="">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" name="code" id="id_code" class="form-control"> </div> <div class="col-md-6"> <!--这个页面只要一加载出来,就会朝这个/get_code/路径发get请求--> <img src="/get_code/" alt="" width="430px" height="35px" id="id_img"> </div> </div> </div> <input type="button" class="btn btn-success btn-block" value="登陆"> </div> </div> </div> <script> $("#id_img").click(function () { // 1 先获取标签之前的src let oldVal = $(this).attr('src'); $(this).attr('src',oldVal += '?') }) </script> </body> </html>
登录注册完整代码
目录
settings.py
""" Django settings for BBS14 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 = '7vht@lf-*t@weir@mcdg^-r+lmyg4qboyy1i_xf^#mwieg!(&x' # 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', 'app01.apps.App01Config', ] 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 = 'BBS14.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [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 = 'BBS14.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bbs14', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': 3306, 'CHARSET': 'utf8' } } # 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"), ] # 告诉django你要用UserInfo替代auth_user(******) AUTH_USER_MODEL = 'app01.UserInfo'
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), # 注册功能 url(r'^register/', views.register, name='register'), # 登录功能 url(r'^login/', views.login, name='login'), # 验证码 url(r'^get_code/', views.get_code, name='gc'), # 首页 url(r'^home/', views.home), ]
common.py验证码
import random from PIL import Image, ImageDraw, ImageFont, ImageFilter def get_chars_str(): ''' :return:验证码字符集合 ''' _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z _upper_cases = _letter_cases.upper() # 大写字母 _numbers = ''.join(map(str, range(3, 10))) # 数字 init_chars = ''.join((_letter_cases, _upper_cases, _numbers)) return init_chars def create_validate_code(size=(430, 35), chars=get_chars_str(), img_type="GIF", mode="RGB", bg_color=(255, 255, 255), fg_color=(0, 0, 255), font_size=20, font_type="static/font/222.TTF", length=4, draw_lines=True, n_line=(1, 2), draw_points=True, point_chance=2): """ @todo: 生成验证码图片 @param size: 图片的大小,格式(宽,高),默认为(120, 30) @param chars: 允许的字符集合,格式字符串 @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG @param mode: 图片模式,默认为RGB @param bg_color: 背景颜色,默认为白色 @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF @param font_size: 验证码字体大小 @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf @param length: 验证码字符个数 @param draw_lines: 是否划干扰线 @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效 @param draw_points: 是否画干扰点 @param point_chance: 干扰点出现的概率,大小范围[0, 100] @return: [0]: PIL Image实例 @return: [1]: 验证码图片中的字符串 """ width, height = size # 宽高 # 创建图形 img = Image.new(mode, size, bg_color) draw = ImageDraw.Draw(img) # 创建画笔 def get_chars(): """生成给定长度的字符串,返回列表格式""" return random.sample(chars, length) def create_lines(): """绘制干扰线""" line_num = random.randint(*n_line) # 干扰线条数 for i in range(line_num): # 起始点 begin = (random.randint(0, size[0]), random.randint(0, size[1])) # 结束点 end = (random.randint(0, size[0]), random.randint(0, size[1])) draw.line([begin, end], fill=(0, 0, 0)) def create_points(): """绘制干扰点""" chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] for w in range(width): for h in range(height): tmp = random.randint(0, 100) if tmp > 100 - chance: draw.point((w, h), fill=(0, 0, 0)) def create_strs(): """绘制验证码字符""" c_chars = get_chars() strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开 font = ImageFont.truetype(font_type, font_size) font_width, font_height = font.getsize(strs) draw.text(((width - font_width) / 3, -1), strs, font=font, fill=fg_color) return ''.join(c_chars) if draw_lines: create_lines() if draw_points: create_points() strs = create_strs() # 图形扭曲参数 params = [1 - float(random.randint(1, 2)) / 100, 0, 0, 0, 1 - float(random.randint(1, 10)) / 100, float(random.randint(1, 2)) / 500, 0.001, float(random.randint(1, 2)) / 500 ] img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) return img, strs
models.py
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserInfo(AbstractUser): phone = models.BigIntegerField(null=True, verbose_name="手机号") # 头像 avatar = models.FileField(upload_to="avatar/", default='avatar/default.png') """ 给avatar字段传文件对象 该文件会自动存储到avatar文件夹 然后avatar字段只保存文件路径avatar/default.png """ create_time = models.DateTimeField(auto_now_add=True) blog = models.OneToOneField(to='Blog', null=True) class Blog(models.Model): site_name = models.CharField(max_length=32, verbose_name='站点名称') site_title = models.CharField(max_length=32, verbose_name='站点标题') # 简单模拟 认识样式内部原理的操作 site_theme = models.CharField(max_length=64, verbose_name='站点样式') # 存css/js的文件路径 class Category(models.Model): name = models.CharField(max_length=32, verbose_name='文章分类') blog = models.ForeignKey(to='Blog', null=True) class Tag(models.Model): name = models.CharField(max_length=32, verbose_name='文章标签') blog = models.ForeignKey(to='Blog', null=True) tags = models.ManyToManyField(to='Article', through='Article2Tag', through_fields=('tag', 'article') ) class Article(models.Model): title = models.CharField(max_length=64, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章简介') # 文章内容有很多 一般情况下都是使用TextField content = models.TextField(verbose_name='文章内容') create_time = models.DateTimeField(auto_now_add=True) # 数据库字段设计优化 up_num = models.BigIntegerField(default=0, verbose_name='点赞数') down_num = models.BigIntegerField(default=0, verbose_name='点踩数') comment_num = models.BigIntegerField(default=0, verbose_name='评论数') # 外键字段 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') ) class Article2Tag(models.Model): article = models.ForeignKey(to='Article') tag = models.ForeignKey(to='Tag') class UpAndDown(models.Model): user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article') is_up = models.BooleanField() # 传布尔值存0/1 class Comment(models.Model): user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article') content = models.CharField(max_length=255, verbose_name="评论内容") create_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间') # 自关联 parent = models.ForeignKey(to='self', null=True) # 一定要加null=True,因为有些评论就是根评论
views.py
from django.shortcuts import render, HttpResponse from app01.myforms import MyRegForm from app01 import models from django.http import JsonResponse from app01.lib.common import create_validate_code from io import BytesIO from django.contrib import auth # Create your views here. def register(request): form_obj = MyRegForm() if request.method == "POST": back_dic = {"code": 1000, "msg": ""} # 校验数据是否合法 form_obj = MyRegForm(request.POST) if form_obj.is_valid(): clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量 clean_data.pop("confirm_password") # {'username': 'alias', 'password': '123', 'email': '123@qq.com'} # 用户头像 file_obj = request.FILES.get("avatar") """针对用户头像一定要判断是否传值 不能直接添加到字典中""" if file_obj: clean_data['avatar'] = file_obj # 操作数据库保存数据 models.UserInfo.objects.create_user(**clean_data) back_dic["url"] = "/login/" else: back_dic["code"] = 2000 back_dic["msg"] = form_obj.errors print(form_obj.errors) return JsonResponse(back_dic) return render(request, "register.html", locals()) def login(request): if request.is_ajax(): if request.method == "POST": back_dic = {"code": 1000, "msg": ""} username = request.POST.get("username") password = request.POST.get("password") code = request.POST.get("code") # 校验验证码是否正确 自己决定是否忽略验证码大小写 统一转大写或小写即可 if request.session.get("code").lower() == code.lower(): # 校验用户名和密码 user_obj = auth.authenticate(request, username=username, password=password) if user_obj: # 保存用户状态 auth.login(request, user_obj) back_dic["url"] = '/home/' print(request.user, type(request.user)) else: back_dic["code"] = 2000 back_dic["msg"] = "用户名或密码错误" else: back_dic["code"] = 3000 back_dic["msg"] = "验证码错误" return JsonResponse(back_dic) return render(request, 'login.html') def get_code(request): f = BytesIO() # 创建一个内存地址存放图片 img, code = create_validate_code() # 调用方法生成图片对象和验证码 request.session['code'] = code # 设置session print(code) img.save(f, 'PNG') # 保存图片 return HttpResponse(f.getvalue()) # 返回图片 def home(request): return HttpResponse('home')
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- Bootstrap3 核心 CSS 文件 --> <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- jQuery文件。务必在bootstrap.min.js 之前引入 --> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <!-- Bootstrap3 核心 JavaScript 文件 --> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> <!-- font-awesome.min.css图标库4.7版本 --> <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">注册</h1> <form id="myform"> <!--我们不用form表单提交数据 只是单纯用一下form标签而已--> {% for form in form_obj %} <div class="from-group"> <!--form.auto_id获取forms渲染的input框的id--> <label for="{{ form.auto_id }}">{{ form.label }}</label> {{ form }} <span style="color: red" class="pull-right"></span> </div> {% endfor %} <div class="form-group" style="margin-top: 10px"> <label for="myfile">头像 {% load static %} <img src="{% static "img/default.png" %}" alt="" id="myimg" width="100px" style="margin-left: 10px"> </label> <input type="file" id="myfile" name="avatar" style="display: none"> </div> <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"> </form> </div> </div> </div> <script src="{% static "js/mysteup.js" %}/"></script> <script> // 选择头像实时显示 $("#myfile").change(function () { // 文件阅读器对象 // 1、先生成一个文件阅读器对象 let myFileReaderObj = new FileReader(); // 2、获取用户上传的头像文件 let fileObj = $(this)[0].files[0]; // 3、将文件对象交给阅读器对象读取 myFileReaderObj.readAsDataURL(fileObj); // 这步操作是异步 // 4、利用文件阅读器将文件展示到前端页面 修改img的src属性 // 等待myFileReaderObj文件阅读器加载完毕之后再执行 myFileReaderObj.onload = function () { $("#myimg").attr("src", myFileReaderObj.result) } }); $("#id_commit").click(function () { // 发送ajax请求 我们发送的数据中即包含普通键值也包含文件 let formDataObj = new FormData(); // 添加普通键值对 // console.log($("#myform").serializeArray()) // [{},{},{},...] 只包含普通键值对 $.each($("#myform").serializeArray(), function (index, obj) { // console.log(index,obj) // obj = {} formDataObj.append(obj.name, obj.value) }); // 添加文件 formDataObj.append('avatar', $("#myfile")[0].files[0]); // 发送ajax请求 $.ajax({ url: '', type: 'post', dataType: 'json', data: formDataObj, contentType: false, processData: false, success: function (args) { if (args.code == 1000) { // 跳转到登录页面 window.location.href = args.url } else { // 如何将对应的错误信息提示展示到对应的input框下面,因为用的ajax所以无法像之前在span标签里面写form.errors.0 // forms组件渲染的标签的id值都是 id_字段名 $.each(args.msg, function (index, obj) { {#console.log(index,obj) // username ["用户名不能为空"]#} let targetId = '#id_' + index; // input标签 {#$(targetId).next().text(obj[0]).parent().addClass('has-error')#} $(targetId).next().text(obj[0]).prev().css("border", "solid red") }) } } }) }) // 给所有的input框绑定获取焦点事件 $('input').focus(function () { // 将input下面的span标签和input外面的div标签修改内容及属性 $(this).next().text('').prev().removeAttr("style", ""); }) </script> </body> </html>
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- Bootstrap3 核心 CSS 文件 --> <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- jQuery文件。务必在bootstrap.min.js 之前引入 --> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <!-- Bootstrap3 核心 JavaScript 文件 --> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> <!-- font-awesome.min.css图标库4.7版本 --> <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> {% load static %} </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="text-center">登录</h1> <div class="form-group"> <label for="id_username">用户名</label> <input type="text" name="username" id="id_username" class="form-control"> </div> <div class="form-group"> <label for="id_password">密码</label> <input type="password" name="password" id="id_password" class="form-control"> </div> <div class="form-group"> <label for="">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" name="code" id="id_code" class="form-control"> </div> <div class="col-md-6"> <!--这个页面只要一加载出来,就会朝这个/get_code/路径发get请求--> <img src="/get_code/" alt="" width="430px" height="35px" id="id_img"> </div> </div> </div> <input type="button" class="btn btn-success" value="登陆" id="id_commit"> <span style="color: red;font: bold 16px 微软雅黑" id="error"></span> </div> </div> </div> <script> $("#id_img").click(function () { // 1 先获取标签之前的src let oldVal = $(this).attr('src'); $(this).attr('src', oldVal += '?') }) // 点击登录按钮发送ajax请求 $("#id_commit").click(function () { $.ajax({ url: '', type: 'post', data: { 'username': $("#id_username").val(), 'password': $("#id_password").val(), 'code': $("#id_code").val(), 'csrfmiddlewaretoken': '{{ csrf_token }}', }, dataType: 'json', success: function (args) { if (args.code == 1000) { // 跳转到首页 window.location.href = args.url } else { //渲染错误信息 $("#error").text(args.msg) } } }) }) </script> </body> </html>