Python学习之路―2018/7/10
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>博客开发系统</title> <link rel="stylesheet" href="/static/blog/css/bootstrap.min.css"> <link rel="icon" href="/static/blog/image/favicon.ico"> <style type="text/css"> body { background: url(../static/blog/image/bk.jpeg) no-repeat; background-size: 100%; overflow: hidden; } h3 { padding: 10px; border-bottom: 1px solid #ddd; } </style> </head> <body> <div class="row"> <div class="col-md-4 col-md-offset-4" style="margin-top: 100px"> <form> {% csrf_token %} <div class="well"> <h3>登录</h3> <div class="form-group"> <label for="username">用户名</label> <input type="text" class="form-control" id="username" placeholder="用户名" autocomplete="off"> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" class="form-control" id="password" placeholder="密码" autocomplete="off"> </div> <div class="row form-group"> <div class="col-md-6"> <label for="password">验证码</label> <input type="text" class="form-control" id="verify_code" placeholder="请输入验证码" autocomplete="off"> </div> <div class="col-md-6"> <label for="picture"></label> <img src="/verify_code" style="height: 40px;width: 183px" id="verify_code_img"> </div> </div> <div class="form-group"> <span id="error"></span> <input type="button" class="btn btn-success form-control login-btn" value="登录"> </div> </div> </form> </div> </div> </body> <script type="text/javascript" src="/static/blog/js/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $("#verify_code_img").click(function () { $(this)[0].src += "?" }); $(".login-btn").click(function () { $.ajax({ url: "/login/", type: "POST", data: { "username": $("#username").val(), "password": $("#password").val(), "verify_code": $("#verify_code").val(), }, success:function (data) { if(data["user"]){ location.href = "/index/" } else { $("#error").html(data["msg"]).css({"color": "red"}) } } }) }) </script> </html>
views.py
from django.shortcuts import render, HttpResponse from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.contrib import auth from blog.utils.verify_code import * @csrf_exempt def login(request): if request.method == "GET": return render(request, "login.html") elif request.method == ‘POST‘: username = request.POST.get("username") password = request.POST.get("password") verify_codes = request.POST.get("verify_code") verify_code_str = request.session.get("verify_code_str") response = {"user": None, "msg": None} if verify_codes.upper() == verify_code_str.upper(): user = auth.authenticate(username=username, password=password) if user: auth.login(request, user) response["user"] = user.username else: response["msg"] = "用户名或者密码错误!" else: response["msg"] = "验证码错误!" return JsonResponse(response) def verify_code(request): data = get_verify_code(request) return HttpResponse(data) def index(request): return render(request, "index.html")
verify_code.py
import random from PIL import Image, ImageDraw, ImageFont, ImageFilter from io import BytesIO def random_color(): color = (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255)) return color def random_color2(): color = (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127)) return color def random_char(): """ 随机数/字母 """ random_num = str(random.randint(0, 9)) random_low = chr(random.randint(97, 122)) # a~z random_upper = chr(random.randint(65, 90)) # A~Z random_chars = random.choice([random_num, random_low, random_upper]) return random_chars def get_verify_code(request): """ 动态生成验证码 """ image = Image.new("RGB", (183, 40), (255, 255, 255)) image_font = ImageFont.truetype("static/blog/font/Arial.ttf", 32) draw = ImageDraw.Draw(image) # 给每个坐标填充颜色,填充噪点 for x in range(183): for y in range(40): draw.point((x, y), fill=random_color()) verify_code_str = "" for i in range(5): random_chars = random_char() verify_code_str += random_chars draw.text((20+i*30, 0), random_chars, font=image_font, fill=random_color2()) image = image.filter(ImageFilter.BLUR) # 模糊处理 # 放到磁盘中,但是速度比较慢,推荐放在内存中 # with open("verify_code.png", "wb") as f: # image.save(f) # # with open("verify_code.png", "rb") as f: # data = f.read() request.session["verify_code_str"] = verify_code_str f = BytesIO() image.save(f, "png") data = f.getvalue() return data
登录界面效果如下图所示:
- 一次请求伴随着多次静态的请求,例如css文件的请求
- 利用PIL模块动态制作验证码
- 验证码使用session存储(session首先会生成一个sessionid,接着会在cookie中存储sessionid,最后想session表中插入数据,包括sessionid以及session内容,一个浏览器对应一条session数据)
- 验证码的刷新,每次点击验证功能图片时,在其src的末尾加上 ?,这样每次点击图片便会刷新验证码
- 遵循“高耦合低内聚”原则,将动态生成验证码的功能存放在另一个py文件中
首先需要安装social-auth-app-django模块
pip install social-auth-app-django
接着从https://github.com/GeeTeam/gt-python-sdk/ 下载geetest.py ,将其放入项目中
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>博客开发系统</title> <link rel="stylesheet" href="/static/blog/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/blog/css/blog.css"> <link rel="icon" href="/static/blog/image/favicon.ico"> <style type="text/css"> body { background: url(../static/blog/image/bk.jpeg) no-repeat; background-size: 100%; overflow: hidden; } </style> </head> <body> <div class="row"> <div class="col-md-4 col-md-offset-4" style="margin-top: 100px"> <form> {% csrf_token %} <div class="well"> <div class="login-head"> {% block head %} <h3 style="display: inline-block">登录</h3> <a id="register">立即注册</a> {% endblock head %} </div> {% block content %}} <div class="form-group"> <label for="username">用户名</label> <input type="text" class="form-control" id="username" placeholder="用户名" autocomplete="off"> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" class="form-control" id="password" placeholder="密码" autocomplete="off"> </div> {% endblock content %} <div class="form-group"> <span id="error"></span> {% block submit %} <input type="button" class="btn btn-success form-control login-btn" value="登录"> {% endblock submit %} </div> <div id="popup-captcha"></div> </div> </form> </div> </div> </body> <script type="text/javascript" src="/static/blog/js/jquery-3.3.1.min.js"></script> <script src="https://static.geetest.com/static/tools/gt.js"></script> <script src="/static/blog/js/blog.js"></script> </html>
blog.css
a:hover { cursor: pointer; text-decoration: none; } .login-head { margin-bottom: 10px; border-bottom: 1px solid #ddd; } .login-head a { margin-left: 287px; color: #5cb85c; }
blog.js
var handlerPopup = function (captchaObj) { // 成功的回调 captchaObj.onSuccess(function () { var validate = captchaObj.getValidate(); $.ajax({ url: "/login/", // 进行二次验证 type: "post", dataType: "json", data: { username: $(‘#username‘).val(), password: $(‘#password‘).val(), geetest_challenge: validate.geetest_challenge, geetest_validate: validate.geetest_validate, geetest_seccode: validate.geetest_seccode }, success: function (data) { if(data["user"]){ location.href = "/index/" } else { $("#error").html(data["msg"]).css({"color": "red"}) //提示错误信息 } } }); }); $(".login-btn").click(function () { captchaObj.show(); }); // 将验证码加到id为captcha的元素里 captchaObj.appendTo("#popup-captcha"); // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html }; // 验证开始需要向网站主后台获取id,challenge,success(是否启用failback) $.ajax({ url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存 type: "get", dataType: "json", success: function (data) { // 使用initGeetest接口 // 参数1:配置参数 // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件 initGeetest({ gt: data.gt, challenge: data.challenge, product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效 offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注 // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config }, handlerPopup); } });
views.py
from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from blog.utils.geetest import GeetestLib from django.contrib import auth @csrf_exempt def login(request): if request.method == "GET": return render(request, "login.html") elif request.method == ‘POST‘: # 滑动验证码,利用geetest插件 gt = GeetestLib(pc_geetest_id, pc_geetest_key) status = request.session[gt.GT_STATUS_SESSION_KEY] username = request.POST.get("username") password = request.POST.get("password") response = {"user": None, "msg": None} if status: user = auth.authenticate(username=username, password=password) if user: auth.login(request, user) response["user"] = user.username else: response["msg"] = "用户名或者密码错误!" return JsonResponse(response)
展示效果:
首先在项目中创建media文件夹,在media目录下创建avatars文件夹,这个文件主要存放用户上传的文件
Django有两种静态文件,分别是static以及media,static主要存放js,css,img等服务器自己的文件,而media主要存放用户上传的文件。
我们需要配置settings中的media信息MEDIA_ROOT = os.path.join(BASE_DIR, "media")
,配置好以后每次用户上传的文件Django会存放到/media/avatars目录下,如果用户没有创建avatars目录,Django会自动创建该目录
register.html
{% extends "login.html" %} {% block head %} <h3 style="text-align: center">注册</h3> {% endblock %} {% block content %} {% for foo in form %} <div class="form-group"> <label for="{{ foo.auto_id }}">{{ foo.label }}</label> {{ foo }} <span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> <label for="avatar"> 头像 <img src="/static/blog/avatars/default.gif" id="avatar_img"> </label> <input type="file" id="avatar" style="display: none"> </div> {% endblock content %} {% block submit %} <input type="button" class="btn btn-success form-control" value="注册"> {% endblock submit %}
blog.css
#avatar_img { width: 60px; height: 60px; margin-left: 20px; } .error{ color: red; }
blog.js
// input标签内容改变事件,即头像上传 $("#avatar").change(function () { var file = $(this)[0].files[0]; // 获取图片路径 var reader = new FileReader(); // 文本阅读器 reader.readAsDataURL(file); // 读取文件的路径 reader.onload = function () { $("#avatar_img").attr("src", reader.result) // 将图片的路径写入img标签的src中 } }); // 注册用户 $(".reg-btn").click(function () { var formdata = new FormData(); var request_list = $("#form").serializeArray(); // 包含form表单中所有标签的name和value $.each(request_list, function (index, data) { formdata.append(data.name, data.value) /* 相当于: formdata.append("username",$("#id_username").val()); formdata.append("password",$("#id_password").val()); formdata.append("re_password",$("#id_re_password").val()); formdata.append("email",$("#id_email").val()); */ }); formdata.append("avatar", $("#avatar")[0].files[0]); $.ajax({ url: "/register/", type: "POST", contentType: false, // 必加参数 processData: false, // 必加参数 data: formdata, success: function (data) { $("span.error").html(""); // 移除上一次的错误信息 $(".has-error").removeClass("has-error"); // 移除上一次错误信息框样式 if (data.user) { location.href = "/login/" //注册成功则返回登录界面 } else { var error_list = data.msg; $.each(error_list, function (field, error) { if (field == "__all__"){ $("#id_re_password").next().html(error[0]).parent().addClass("has-error") // 对全局钩子的单独处理 } $("#id_" + field).next().html(error[0]); // 找寻对应id的input标签后面的span标签,将错误信息写入 $("#id_" + field).parent().addClass("has-error"); // 给含有错误信息的input标签添加css样式 }) } } }) });
my_forms.py
from django import forms from .models import * from django.core.exceptions import ValidationError wid_1 = forms.widgets.TextInput(attrs={"class": "form-control", "autocomplete": "off"}) wid_2 = forms.widgets.PasswordInput(attrs={"class": "form-control"}) class UserForm(forms.Form): username = forms.CharField(max_length=32, label="用户名", widget=wid_1, error_messages={"required": "该字段不能为空!"}) password = forms.CharField(min_length=8, label="密码", widget=wid_2, error_messages={"min_length": "密码长度至少8位!", "required": "该字段不能为空!"}) re_password = forms.CharField(min_length=8, label="确认密码", widget=wid_2, error_messages={"min_length": "密码长度至少8位!", "required": "该字段不能为空!"}) email = forms.EmailField(label="邮箱", widget=wid_1, error_messages={"invalid": "邮箱格式错误!"}) def clean_username(self): username = self.cleaned_data.get("username") user = User.objects.filter(username=username) if not user: return username else: raise ValidationError("该用户已经被注册!") def clean(self): password = self.cleaned_data.get("password") re_password = self.cleaned_data.get("re_password") if password == re_password: return self.cleaned_data else: raise ValidationError("两次密码不一致!")
views.py
from blog.my_forms import * def register(request): user = UserForm() if request.method == "GET": return render(request, "register.html", locals()) elif request.method == "POST": response = {"user": None, "msg": None} user = UserForm(request.POST) if user.is_valid(): username = user.cleaned_data.get("username") response["user"] = username password = user.cleaned_data.get("password") email = user.cleaned_data.get("email") avatar_obj = request.FILES.get("avatar") if avatar_obj: # 如果用户上传了头像,则创建用户时将其传入,没有则传递其他填写的信息 user_obj = User.objects.create_user(username, email, password, avatar=avatar_obj) else: user_obj = User.objects.create_user(username, email, password) else: response["msg"] = user.errors # 包含了所有的错误信息 return JsonResponse(response)
展示效果:
- label标签添加for属性(内容为某个标签的id),点击label相当于点击for属性中的标签。利用此特性,将文件上传input隐藏,添加一个label与其绑定,接着在label中添加img,这样点击图片便可以实现文件上传。
- 文件的路径通过
$(xx)[0].files[0]
来获取 - 实现上传头像的预览主要分为三步,第一获取图片的路径,第二步读取图片的路径,第三步将图片路径写入img标签中
- 文本阅读器中的readAsDataURL是异步的,所以会出现还没有读取图片路径就写入了,从而产生一个unknow对象,所以需要用到onload方法,onload会在所有资源加载完之后运行。
- 上传文件时需要用到FormData,使用FormData时必须加ajax中加
contentType: false,processData: false
这两个参数 - 向FormData中插入数据的时候,可以通过
$("xxx").serializeArray()
(包含了form表单中所有输入框的name以及value)循环添加,减少代码量 - Media的配置,将服务器的文件存放在static中,用户上传的文件存放在media/avatars中