博客园--登录注册
表结构:
注意:UserInfo表继承了AbstractUser,需要在settings 中写入:AUTH_USER_MODEL ="项目名称.UserInfo"
class UserInfo(AbstractUser): #settings :AUTH_USER_MODEL ="项目名称.UserInfo"
'''用户信息表'''
nid = models.BigAutoField(primary_key=True)
nickname =models.CharField(max_length=32,verbose_name="昵称",unique=True)
tel = models.IntegerField(verbose_name="电话",unique=True,null=True,blank=True)
email = models.EmailField(max_length=64,verbose_name="邮箱")
avatar = models.FileField(verbose_name="头像",upload_to="avatar",default="/avatar/default.png")
create_time = models.DateTimeField(verbose_name="创建时间",auto_now_add=True)
class Meta:
verbose_name_plural = "用户信息表"
def __str__(self):
return self.username
class Article(models.Model):
'''
文章表
'''
title = models.CharField(max_length=64,verbose_name="文章标题")
summary = models.CharField(max_length=244, verbose_name="文章概要")
create_time = models.DateTimeField(verbose_name="创建时间")
update_time = models.DateTimeField(verbose_name="修改时间",auto_now=True)
poll_count = models.IntegerField(verbose_name="点赞数",default=0)
comment_count = models.IntegerField(verbose_name="评论数",default=0)
read_count = models.IntegerField(verbose_name="阅读数",default=0)
# is_essence = models.BooleanField(verbose_name="是否精华",default=0)
# is_top = models.BooleanField(verbose_name="是否置顶",default=0)
user = models.ForeignKey(to="UserInfo",verbose_name="所属作者",null=True,blank=True)
classify = models.ForeignKey(to="Classfication",verbose_name="所属类别",null=True,blank=True)
tags = models.ManyToManyField(to="Tag",through="Article2tag",through_fields=('article', 'tag'),verbose_name="所属标签")
site_category_article=models.ForeignKey("SiteArticle",verbose_name="左侧菜单文章类别",null=True)
class Meta:
verbose_name_plural = "文章表"
def __str__(self):
return self.title+"+"+self.user.username
class Article_detail(models.Model):
'''文章细节表'''
article = models.OneToOneField(to="Article",verbose_name="所属文章")
content =models.TextField(verbose_name="文章内容")
class Meta:
verbose_name_plural = "文章细节表"
def __str__(self):
return self.article.title
class Tag(models.Model):
'''标签表'''
name = models.CharField(max_length=32,verbose_name="标签名")
blog = models.ForeignKey(to="Blog",verbose_name="所属博客")
class Meta:
verbose_name_plural = "标签表"
def __str__(self):
return self.name+"+"+self.blog.title
class Article2tag(models.Model):
article = models.ForeignKey(verbose_name="文章",to="Article")
tag = models.ForeignKey(verbose_name="标签",to="Tag")
class Meta:
'''联合唯一'''
unique_together = [
("article","tag")
]
def __str__(self):
return self.article.title+" + "+self.tag.name
class Comment(models.Model):
'''评论表'''
time = models.DateTimeField(verbose_name="评论时间",auto_now_add=True)
content = models.CharField(max_length=265,verbose_name="评论内容")
up_count = models.IntegerField(default=0)
user = models.ForeignKey(to="UserInfo",verbose_name="评论人",null=True,blank=True)
article = models.ForeignKey(to="Article",verbose_name="评论文章",null=True,blank=True)
farther_comment = models.ForeignKey(to="Comment",verbose_name="父级评论",null=True,blank=True)
# farther_comment = models.ForeignKey("self",verbose_name="父级评论",null=True,blank=True)
class Meta:
verbose_name_plural = "评论表"
def __str__(self):
return self.user.nickname+"----"+self.article.title+"+"+self.article.user.nickname
class Article_poll(models.Model):
'''文章点赞表'''
time = models.DateTimeField(verbose_name="点赞时间",auto_now_add=True)
article = models.ForeignKey(to="Article",verbose_name="点赞文章",null=True,blank=True) #一个文章可以有多个赞
user = models.ForeignKey(to="UserInfo",verbose_name="点赞人",null=True,blank=True)
# is_positive = models.BooleanField(default=1,verbose_name="点赞或踩")
class Meta:
'''联合唯一'''
unique_together = ("user", "article",)
verbose_name_plural = "文章点赞表"
def __str__(self):
return self.user.nickname+"---"+self.article.title+"+"+self.article.user.nickname
class Comment_poll(models.Model):
'''评论点赞表'''
time=models.DateTimeField(verbose_name="点赞时间",auto_now_add=True)
# is_positive = models.BooleanField(verbose_name="点赞或踩",default=1)
user = models.ForeignKey(to="UserInfo",verbose_name="点赞用户",null=True,blank=True)
comment = models.ForeignKey(to="Comment",verbose_name="点赞所属评论",null=True,blank=True) #一个评论可以有多个赞
class Meta:
'''联合唯一'''
unique_together = ("user","comment",)
verbose_name_plural = "评论点赞表"
class Blog(models.Model):
'''个人站点表'''
title = models.CharField(max_length=32,verbose_name="个人博客标题")
url = models.CharField(max_length=64,verbose_name="个人博客后缀",unique=True)
theme = models.CharField(max_length=32,verbose_name="博客主题")
user = models.OneToOneField(to="UserInfo", verbose_name="所属用户")
class Meta:
'''通过admin录入数据的时候个中文显示'''
verbose_name_plural = "个人站点表"
def __str__(self):
return self.title
class Classfication(models.Model):
'''博主个人文章分类表'''
title = models.CharField(max_length=32, verbose_name="分类标题")
blog = models.ForeignKey(to="Blog",verbose_name="所属博客")
class Meta:
verbose_name_plural = "个人分类表"
def __str__(self):
return self.title
class SiteCategory(models.Model):
name=models.CharField(max_length=32)
class Meta:
verbose_name_plural = "网站分类表"
def __str__(self):
return self.name
class SiteArticle(models.Model):
name=models.CharField(max_length=32)
site_category=models.ForeignKey("SiteCategory")
class Meta:
verbose_name_plural = "左侧菜单具体分类表"
def __str__(self):
return self.name
登录
返回登录页面
from django.contrib import auth
def login(request):
if request.method=='POST':
username=request.POST.get("username")
password=request.POST.get("password")
login_response={"is_login":False,"error_msg":None}
user=auth.authenticate(username=username,password=password)
if user:#用户名密码正确
login_response["is_login"]=True
auth.login(request,user) # 相当于session request.session[is_login]=True
else:
login_response["error_msg"] = "username or password error"
# print(login_response)
return HttpResponse(json.dumps(login_response))
return render(request, 'login.html')
通过ajax提交数据,根据视图函数返回不同结果,如果response["is_login"]=True,则显示验证码插件
反之,则在登录框下发返回response["error_msh"]里面的报错信息。
注意:POST请求处理,每一个ajax函数都要写:headers:{"X-CSRFToken":$.cookie('csrftoken')},
登录Ajax:
var handlerPopup = function (captchaObj) {
// 成功的回调
captchaObj.onSuccess(function () {
var validate = captchaObj.getValidate();
$.ajax({
url: "/pc-geetest/ajax_validate", // 进行二次验证
type: "post",
dataType: "json",
data: {
username: $('#username1').val(),
password: $('#password1').val(),
geetest_challenge: validate.geetest_challenge,
geetest_validate: validate.geetest_validate,
geetest_seccode: validate.geetest_seccode
},
headers:{"X-CSRFToken":$.cookie('csrftoken')},
success: function (data) {
if (data && (data.status === "success")) {
location.href="/index/";
} else {
$(".error").html(response["error_msg"]).css("color", "red");
}
}
});
});
// 将验证码加到id为captcha的元素里
captchaObj.appendTo("#popup-captcha");
// 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
$("#popup-submit").click(function () {
$.ajax({
url: "/login/",
type: "POST",
data: {
"username": $("#username").val(),
"password": $("#password").val(),
"csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(),
},
headers:{"X-CSRFToken":$.cookie('csrftoken')},
success: function (data) {
var response = JSON.parse(data);
console.log(captchaObj);
if (response["is_login"]) {
console.log(response["is_login"]);
captchaObj.show();
} else {
$(".error").html(response["error_msg"]).css("color", "red")
}
}
});
})
};
// 验证开始需要向网站主后台获取id,challenge,success(是否启用failback)
$.ajax({
url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
type: "get",
dataType: "json",
headers:{"X-CSRFToken":$.cookie('csrftoken')},
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);
}
});
插件url:
url(r'^pc-geetest/register',views.pcgetcaptcha),
url(r'^pc-geetest/validate$',views.pcvalidate),
url(r'^pc-geetest/ajax_validate',views.pcajax_validate),
插件视图函数:
from app.geetest import GeetestLib
pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"
mobile_geetest_id = "7c25da6fe21944cfe507d2f9876775a9"
mobile_geetest_key = "f5883f4ee3bd4fa8caec67941de1b903"
def pcgetcaptcha(request):
user_id = 'test'
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
status = gt.pre_process(user_id)
request.session[gt.GT_STATUS_SESSION_KEY] = status
request.session["user_id"] = user_id
response_str = gt.get_response_str()
return HttpResponse(response_str)
def pcvalidate(request):
if request.method == "POST":
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
challenge = request.POST.get(gt.FN_CHALLENGE, '')
validate = request.POST.get(gt.FN_VALIDATE, '')
seccode = request.POST.get(gt.FN_SECCODE, '')
status = request.session[gt.GT_STATUS_SESSION_KEY]
user_id = request.session["user_id"]
print("status",status)
if status:
result = gt.success_validate(challenge, validate, seccode, user_id)
else:
result = gt.failback_validate(challenge, validate, seccode)
result = "<html><body><h1>登录成功</h1></body></html>" if result else "<html><body><h1>登录失败</h1></body></html>"
return HttpResponse(result)
return HttpResponse("error")
def pcajax_validate(request):
if request.method == "POST":
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
challenge = request.POST.get(gt.FN_CHALLENGE, '')
validate = request.POST.get(gt.FN_VALIDATE, '')
seccode = request.POST.get(gt.FN_SECCODE, '')
status = request.session[gt.GT_STATUS_SESSION_KEY]
user_id = request.session["user_id"]
print("status",status)
if status:
result = gt.success_validate(challenge, validate, seccode, user_id)
else:
result = gt.failback_validate(challenge, validate, seccode)
result = {"status":"success"} if result else {"status":"fail"}
return HttpResponse(json.dumps(result))
return HttpResponse("error")
import sys
import random
import json
import requests
import time
from hashlib import md5
if sys.version_info >= (3,):
xrange = range
VERSION = "3.0.0"
class GeetestLib(object):
FN_CHALLENGE = "geetest_challenge"
FN_VALIDATE = "geetest_validate"
FN_SECCODE = "geetest_seccode"
GT_STATUS_SESSION_KEY = "gt_server_status"
API_URL = "http://api.geetest.com"
REGISTER_HANDLER = "/register.php"
VALIDATE_HANDLER = "/validate.php"
JSON_FORMAT = False
def __init__(self, captcha_id, private_key):
self.private_key = private_key
self.captcha_id = captcha_id
self.sdk_version = VERSION
self._response_str = ""
def pre_process(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""):
"""
验证初始化预处理.
//TO DO arrage the parameter
"""
status, challenge = self._register(user_id,new_captcha,JSON_FORMAT,client_type,ip_address)
self._response_str = self._make_response_format(status, challenge,new_captcha)
return status
def _register(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""):
pri_responce = self._register_challenge(user_id,new_captcha,JSON_FORMAT,client_type,ip_address)
if pri_responce:
if JSON_FORMAT == 1:
response_dic = json.loads(pri_responce)
challenge = response_dic["challenge"]
else:
challenge = pri_responce
else:
challenge=" "
if len(challenge) == 32:
challenge = self._md5_encode("".join([challenge, self.private_key]))
return 1,challenge
else:
return 0, self._make_fail_challenge()
def get_response_str(self):
return self._response_str
def _make_fail_challenge(self):
rnd1 = random.randint(0, 99)
rnd2 = random.randint(0, 99)
md5_str1 = self._md5_encode(str(rnd1))
md5_str2 = self._md5_encode(str(rnd2))
challenge = md5_str1 + md5_str2[0:2]
return challenge
def _make_response_format(self, success=1, challenge=None,new_captcha=1):
if not challenge:
challenge = self._make_fail_challenge()
if new_captcha:
string_format = json.dumps(
{'success': success, 'gt':self.captcha_id, 'challenge': challenge,"new_captcha":True})
else:
string_format = json.dumps(
{'success': success, 'gt':self.captcha_id, 'challenge': challenge,"new_captcha":False})
return string_format
def _register_challenge(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""):
if user_id:
register_url = "{api_url}{handler}?gt={captcha_ID}&user_id={user_id}&json_format={JSON_FORMAT}&client_type={client_type}&ip_address={ip_address}".format(
api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id, user_id=user_id,new_captcha=new_captcha,JSON_FORMAT=JSON_FORMAT,client_type=client_type,ip_address=ip_address)
else:
register_url = "{api_url}{handler}?gt={captcha_ID}&json_format={JSON_FORMAT}&client_type={client_type}&ip_address={ip_address}".format(
api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id,new_captcha=new_captcha,JSON_FORMAT=JSON_FORMAT,client_type=client_type,ip_address=ip_address)
try:
response = requests.get(register_url, timeout=2)
if response.status_code == requests.codes.ok:
res_string = response.text
else:
res_string = ""
except:
res_string = ""
return res_string
def success_validate(self, challenge, validate, seccode, user_id=None,gt=None,data='',userinfo='',JSON_FORMAT=1):
"""
正常模式的二次验证方式.向geetest server 请求验证结果.
"""
if not self._check_para(challenge, validate, seccode):
return 0
if not self._check_result(challenge, validate):
return 0
validate_url = "{api_url}{handler}".format(
api_url=self.API_URL, handler=self.VALIDATE_HANDLER)
query = {
"seccode": seccode,
"sdk": ''.join( ["python_",self.sdk_version]),
"user_id": user_id,
"data":data,
"timestamp":time.time(),
"challenge":challenge,
"userinfo":userinfo,
"captchaid":gt,
"json_format":JSON_FORMAT
}
backinfo = self._post_values(validate_url, query)
if JSON_FORMAT == 1:
backinfo = json.loads(backinfo)
backinfo = backinfo["seccode"]
if backinfo == self._md5_encode(seccode):
return 1
else:
return 0
def _post_values(self, apiserver, data):
response = requests.post(apiserver, data)
return response.text
def _check_result(self, origin, validate):
encodeStr = self._md5_encode(self.private_key + "geetest" + origin)
if validate == encodeStr:
return True
else:
return False
def failback_validate(self, challenge, validate, seccode):
"""
failback模式的二次验证方式.在本地对轨迹进行简单的判断返回验证结果.
"""
if not self._check_para(challenge, validate, seccode):
return 0
validate_result = self._failback_check_result(
challenge, validate,)
return validate_result
def _failback_check_result(self,challenge,validate):
encodeStr = self._md5_encode(challenge)
if validate == encodeStr:
return True
else:
return False
def _check_para(self, challenge, validate, seccode):
return (bool(challenge.strip()) and bool(validate.strip()) and bool(seccode.strip()))
def _md5_encode(self, values):
if type(values) == str:
values = values.encode()
m = md5(values)
return m.hexdigest()
注册
form组件:
from django.forms import Form
from django.forms import fields
from django.forms import widgets
from django.forms import ValidationError
from app import models
class Reg_Form(Form):
username=fields.CharField(
required=True,
min_length=3,
max_length=10,
error_messages={
"required": "不能为空","min_length":"至少3位","max_length":"至多10位"},
widget=widgets.TextInput(attrs={"class":"form-control input-lg","placeholder":"Username"})
)
password=fields.CharField(
required=True,
min_length=4,
max_length=10,
error_messages={
"required": "不能为空","min_length":"至少4位","max_length":"至多12位"},
widget=widgets.PasswordInput(attrs={"class":"form-control input-lg","placeholder":"Password"})
)
ret_password=fields.CharField(
required=True,
min_length=4,
max_length=10,
error_messages={
"required": "不能为空","min_length":"至少4位","max_length":"至多12位"},
widget=widgets.PasswordInput(attrs={"class":"form-control input-lg","placeholder":"Password"})
)
nickname = fields.CharField(
required=True,
min_length=2,
max_length=8,
error_messages={
"required": "不能为空", "min_length": "至少2位", "max_length": "至多8位"},
widget=widgets.TextInput(attrs={"class": "form-control input-lg", "placeholder": "Nickname"})
)
tel=fields.CharField(
required=True,
min_length=11,
error_messages={
"required": "不能为空", "min_length": "手机号需要11位", },
widget=widgets.TextInput(attrs={"class": "form-control input-lg", "placeholder": "Phone"})
)
email=fields.EmailField(
required=True,
error_messages={
"required": "不能为空" },
widget=widgets.EmailInput(attrs={"class": "form-control input-lg", "placeholder": "Email"})
)
def clean_username(self):
ret=models.UserInfo.objects.filter(username=self.cleaned_data.get("username"))
if not ret:
return self.cleaned_data.get("username")
else:
raise ValidationError("用户名已注册")
def clean_password(self):
data=self.cleaned_data.get("password")
if not data.isdigit():
return self.cleaned_data.get("password")
else:
raise ValidationError("密码不能全是数字")
def clean(self):
if self.cleaned_data.get("password") == self.cleaned_data.get("ret_password"):
return self.cleaned_data
else:
raise ValidationError("两次密码不一致")
def __init__(self,request,*args,**kwargs):
super().__init__(*args,**kwargs)
self.request=request
首先:用户请求,返回注册表单,通过ajax提交数据,因为有头像:二进制文件,用FormData提交
注册Ajax:
/**
* Created by 张宁阳 on 2017/11/21.
*/
/**
* Created by 张宁阳 on 2017/11/21.
*/
$("#popup-submit").click(function () {
var formdata=new FormData();
formdata.append("username",$("#id_username").val());
formdata.append("password",$("#id_password").val());
formdata.append("ret_password",$("#id_ret_password").val());
formdata.append("nickname",$("#id_nickname").val());
formdata.append("tel",$("#id_tel").val());
formdata.append("email",$("#id_email").val());
formdata.append("avatar_file",$("#avatar_file")[0].files[0]);
$.ajax({
url:"/register/",
type:"post",
data:formdata,
contentType:false,
processData:false,
headers:{"X-CSRFToken":$.cookie('csrftoken')},
success:function (data) {
var data=JSON.parse(data);
if (data["is_success"]){
location.href="/login/"
}else{
$.each(data["err_msg"],function (x,y) {
$span=$("<span>");
$span.addClass("pull-right").css("color","red");
$span.html(y[0]);
$("#id_"+x).after($span).parent().addClass("has-error");
if (x=="__all__"){
$("#id_ret_password").after($span)
}
})
}
}
})
});
$("#avatar_file").change(function () {
var file=$(this)[0].files[0];
var reader=new FileReader();
reader.readAsDataURL(file);
reader.onload=function () {
$("#img")[0].src=this.result
}
});
"############################################"
ajax
通过FormData获取前端页面input值(form组件自动创建的input标签id默认为id_字段名称)
通过ajax传送
success:
反序列化数据
如果data["is_success"]为True:
跳转到登录页面
反之:
$.each(data["err_msg"],function (x,y) {
$span=$("<span>");
$span.addClass("pull-right").css("color","red");
$span.html(y[0]);
$("#id_"+x).after($span).parent().addClass("has-error");
if (x=="__all__"){
$("#id_ret_password").after($span)
}
})
注释: x为出错form组件字段、y为报错
each循环报错信息、新建span标签,给其增加样式(右漂浮,红色)
span的内容为报错信息
给出错的input标签后边添加span标签,并使其父级标签has-error
如果x使__all__:将报错添加到重新输入密码后边的span中
注意:配置media
1、settings中
MEDIA_ROOT=os.path.join(BASE_DIR,"app","media","upload")
MEDIA_URL="/media/"
2、urls中
from django.views.static import serve
from django.conf import settings
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
将头像作为上传文件input框
头像:
</div>
<div class="form-group avatar">
<label for="avatar">头像:</label><img src="/static/img/default.png" id="img">
<input type="file" class="input-lg" id="avatar_file" >
</div>
默认图片路径
将图片跟上传文件的input框修改为块级标签,设置为同等长和宽
将图片跟上传文件的input框的父级标签相对定位,然后绝对定位自己
相对定位: position: relative;
绝对定位: position: absolute;
上传文件的input框透明度改为0: opacity: 0;
注册视图函数
if request.method=="GET":
form =Reg_Form(request)
return render(request,"register.html",{"form":form})
else:
form=Reg_Form(request,request.POST)
response={"is_success":False,"err_msg":None}
if form.is_valid():
username=form.cleaned_data["username"]
password=form.cleaned_data["password"]
# password=make_password(password)
nickname=form.cleaned_data["nickname"]
tel=form.cleaned_data["tel"]
email=form.cleaned_data["email"]
avatar_file=request.FILES.get("avatar_file")
models.UserInfo.objects.create_user(username=username,password=password,nickname=nickname,tel=tel,email=email,avatar=avatar_file)
response["is_success"]=True
else:
response["err_msg"]=form.errors
return HttpResponse(json.dumps(response))
注册时,因为需要跟数据库密文密码比对,所以给密码加密:
from django.contrib.auth.hashers import make_password
password=form.cleaned_data["password"]
password=make_password(password)
因为继承了AbstractUser类:
创建时create_user即可

浙公网安备 33010602011771号