Django博客项目之登录和注册系统

一、环境准备

1、配置文件

settings.py文件:

增加一项内容实现UserInfo表继承Django用户认证的表
AUTH_USER_MODEL="blog.UserInfo"

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',
            ],
        },
    },
]


2、表关系设计

models.py文件内容:


from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    """
    用户信息
    """
    nid = models.AutoField(primary_key=True)
    telephone = models.CharField(max_length=11, null=True, unique=True)
    avatar = models.FileField(upload_to='avatars/', default="/avatars/default.png")
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    blog = models.OneToOneField(to='Blog', to_field='nid', null=True)
    def __str__(self):
        return self.username
class Blog(models.Model):
    """
    博客(个人站点)信息
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='个人博客标题', max_length=64)
    site = models.CharField(verbose_name='个人博客后缀', max_length=32, unique=True)
    theme = models.CharField(verbose_name='博客主题', max_length=32)
    def __str__(self):
        return self.title


3、数据库实例化

python3 manage.py makemigrations

python3 manage.py migrate


二、登录系统

urls.py文件内容:

from django.conf.urls import url
from django.contrib import admin
from blog import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    url(r'^valid_img/', views.valid_img),
    url(r'^log_out/$', views.log_out),
    url(r'^index/', views.index),
]


views.py文件内容:

from django.shortcuts import render,HttpResponse,redirect
import json
def login(reqeust):
    if reqeust.is_ajax():
       res={"user":None,"msg":None}
       user=reqeust.POST.get("user")
       pwd=reqeust.POST.get("pwd")
       valid=reqeust.POST.get("valid")
       print(reqeust.POST)
       random_str=reqeust.session.get("random_str")               #取出session保存的random_str对应的验证码字符串
       if valid.upper()==random_str.upper():
           from django.contrib import auth
           user=auth.authenticate(username=user,password=pwd)
           if user:
               auth.login(request, user)
               res["user"]=user.username
           else:
               res["msg"]="用户名或者密码错误"
       else:
           res["msg"]="验证码失败"                              #先判断验证码是否正确,正确后接着判断用户名和密码
       return HttpResponse(json.dumps(res))
    return render(reqeust,"login.html")
def valid_img(request):
    from PIL import Image                   #需要安装pillow模块:pip3 install pillow
    from PIL import ImageDraw,ImageFont
    from io import BytesIO
    image=Image.new("RGB",(250,36),color=get_random_color())              #随机创建图片,长度为250px,宽度为36px
    draw=ImageDraw.Draw(image)
    font = ImageFont.truetype("blog/static/font/kumo.ttf", size=32)      #定义验证码的字体格式
    random_str=""
    for i in range(5):                                                      #生成一个5位的字符串
        random_num=str(random.randint(0,9))
        random_low_alpha=chr(random.randint(97,122))
        random_up_alpha=chr(random.randint(65,90))
        random_char=random.choice([random_num,random_low_alpha,random_up_alpha])
        draw.text((35+i*40,0),random_char,get_random_color(),font=font)
        random_str+=random_char
    print(random_str)
    request.session["random_str"]=random_str                              #得到的验证码字符串设置session以用来保存在内存
    # 噪点噪线
    width=250
    height=36
    for i in range(10):
        x1=random.randint(0,width)
        x2=random.randint(0,width)
        y1=random.randint(0,height)
        y2=random.randint(0,height)
        draw.line((x1,y1,x2,y2),fill=get_random_color())
    for i in range(100):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color())
    f=BytesIO()
    image.save(f,"png")
    data=f.getvalue()
    return HttpResponse(data)
def index(request):
    #验证是不是当前进来的那个用户,如果用户已经登录了就可以看到页面
    # 如果没有登录就不让看见主页面,就直接返回登录页面
    if not request.user.is_authenticated():
        return redirect("/login/")
    else:
        return render(request, "index.html")
def log_out(request):
    auth.logout(request)
    return redirect("/login/")


login.html文件内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
    <script src="/static/js/jquery-3.2.1.min.js"></script>
    <style>
        .error{
            color: red;
            margin-left: 20px;
        }
    </style>
</head>
<body>
<h3>登录页面</h3>
<div>
    <div>
        <div class="col-md-6 col-md-offset-3">
            <form enctype="application/x-www-form-urlencoded">
                {% csrf_token %}
                <div>
                    <label for="">用户名</label>
                    <input type="text" id="user">
                </div>
                 <div>
                    <label for="">密码</label>
                    <input type="password" id="pwd" >
                </div>
                  <div>
                    <label for="">验证码</label>
                      <div>
                          <div>
                              <input type="text"id="valid" >
                          </div>
                          <div>
                              <img width="250" height="36" src="/valid_img/" alt="">
                          </div>
                      </div>
                </div>
                <input type="button" value="submit" class="login_btn btn btn-default">
                <span></span>
            </form>
        </div>
    </div>
</div>
<script>
     // 登录验证
     $(".login_btn").click(function () {
         $.ajax({
             url:"",
             data:{
                 user:$("#user").val(),                  {# 取到用户输入的用户名 #}
                 pwd:$("#pwd").val(),                    {# 取到用户输入的密码 #}
                 valid:$("#valid").val(),                 {# 取到用户输入的验证码 #}
                 csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()  {# 防止出现404错误 #}
             },
             type:"post",
             success:function (data) {
                 console.log(data);
                 var data=JSON.parse(data);              {# 接收到的data进行反序列化 #}
                 if(data.user){
                     location.href = "/index/"            {#跳转到内容页#}
                 }else{
                   $(".error").html(data.msg)
                 }
             }
         })
     });
    // 验证码局部刷新
    $(".valid_img").click(function () {
        $(this)[0].src+="?"
    })
</script>
</body>
</html>


index.html文件内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width">
    <title>Title</title>
</head>
<body>
<h1>hello{{ request.user.username }}</h1>
<button><a href="/log_out/">注销</a></button>
</body>
</html>

登录.png

三、生成验证码的几种方式

1、方式一:这样的方式吧路径写死了,只能是那一张图片

import os
path = os.path.join(settings.BASE_DIR,"static","image","3.jpg")
with open(path,"rb") as f:
    data = f.read()
return HttpResponse(data)

2、方式二:每次都显示不同的图片,利用pillow模块,安装一个pillow模块,图片保存到硬盘上

from PIL import Image
img = Image.new(mode="RGB",size=(120,40),color="green")          #首先自己创建一个图片,参数size=(120,40) 代表长和高
f = open("validcode.png","wb")                          #然后把图片放在一个指定的位置
img.save(f,"png")                                         #保存图片
f.close()
with open("validcode.png","rb") as f:
    data = f.read()
return HttpResponse(data)


3、方式三:我们可以把图片保存到内存中,完了自动清除,那么就引入了方式三:利用BytesIO模块

from io import BytesIO
from PIL import Image
img = Image.new(mode="RGB",size=(120,40),color="blue")
f = BytesIO()                                           #内存文件句柄
img.save(f,"png")                                      #保存文件
data = f.getvalue()                                     #打开文件(相当于python中的f.read())
return HttpResponse(data)

4、方式四:添加画笔,在图片上写上一些文字和噪点噪线

from PIL import Image                               # 需要安装pillow模块:pip3 install pillow
from PIL import ImageDraw, ImageFont
from io import BytesIO
image = Image.new("RGB", (250, 36), color=get_random_color())        # 随机创建图片,长度为250px,宽度为36px
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("blog/static/font/kumo.ttf", size=32)  # 定义验证码的字体格式
random_str = ""
for i in range(5):                                   #生成一个5位的字符串
    random_num = str(random.randint(0, 9))           # 随机取出一个数字
    random_low_alpha = chr(random.randint(97, 122))  # 随机取出一个小写字母
    random_up_alpha = chr(random.randint(65, 90))    # 随机取出一个大写字母
    random_char = random.choice([random_num, random_low_alpha, random_up_alpha])      # 随机取出一个小写字母或者大写字母或者数字
    draw.text((35 + i * 40, 0), random_char, get_random_color(), font=font)          # 文字间距是40px,第一个到左边框距离是35px
    random_str += random_char                         # 将取到的字符串内容放到random_str值的后面,每次循环加一个字符串
print(random_str)
request.session["random_str"] = random_str           # 得到的验证码字符串设置session以用来保存在内存
# 噪点噪线
width=250
height=36
for i in range(10):
    x1=random.randint(0,width)
    x2=random.randint(0,width)
    y1=random.randint(0,height)
    y2=random.randint(0,height)
    draw.line((x1,y1,x2,y2),fill=get_random_color())
for i in range(100):
    draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())
    x = random.randint(0, width)
    y = random.randint(0, height)
    draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color())
f = BytesIO()
image.save(f, "png")
data = f.getvalue()
return HttpResponse(data)

四、注册相关内容

1、Form组件

我们一般写Form的时候都是把它写在views视图里面,还可以在应用下面建一个forms.py的文件来存放

from django.forms import widgets
from blog import models
from django import forms
from django.core.validators import ValidationError
class UserForm(forms.Form):
    user=forms.CharField(
        required=True,
        max_length=16,
        min_length=3,
        error_messages={
            "required": "用户名不能为空",
            "max_length": "长度不能大于16",
            "min_length": "长度不能小于3",
        },
        widget=widgets.TextInput({"placeholder":"用户名","class":"form-control"}))
    pwd=forms.CharField(
        required=True,
        max_length=16,
        min_length=3,
        error_messages={
            "required": "密码不能为空",
            "max_length": "长度不能大于16",
            "min_length": "长度不能小于3",
        },
        widget=widgets.PasswordInput({"placeholder": "密码", "class": "form-control"})
    )
    tel = forms.CharField(
        required=True,
        max_length=11,
        min_length=11,
        error_messages={
            "required": "手机号码不能为空",
            "max_length": "长度必须是11位,请你正确输入",
            "min_length": "长度必须是11位,请你正确输入",
        },
        widget=widgets.TextInput({"placeholder": "手机号码", "class": "form-control"})
    )
    email=forms.EmailField(
        required=True,
        error_messages={
            "required": "邮箱不能为空",
            "invalid": "邮箱格式有误"
        },
        widget=widgets.EmailInput({"placeholder": "邮箱", "class": "form-control"})
    )

2、局部钩子函数

# 自定义用户名验证:
def clean_user(self):
    user = self.cleaned_data.get("user")
    valid = models.UserInfo.objects.filter(username=user).first()
    if valid:
        raise ValidationError("用户名已存在")
    return user
# 自定义手机号码验证:
def clean_tel(self):
    tel = self.cleaned_data.get("tel")
    if len(tel) == 11:
        return tel
    else:
        raise ValidationError("手机号码格式错误")

3、全局钩子函数


# 自定义密码验证:
def clean_pwd(self):
    password = self.cleaned_data.get("pwd")
    if password.isdigit():
        raise ValidationError("密码需要数字字母组合")
    else:
        return password

4、css中的三种隐藏:

(1)display:none       #隐藏所有内容

(2)visibility:hidden   #隐藏内容

(3)overflow:hidden   #隐藏溢出内容

三者都是用来隐藏的:

区别在于:

   visibility虽然隐藏了,但是被隐藏的内容依然占据这空间,这段隐藏了的内容却保留空间的位置会在网页中显示空白,而display:隐藏了不占用空间


5、 jQuery的属性操作相关的

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

6、提交二进制数据用FormData

$(".reg_btn").click(function () {
        var formdata=new FormData();
        formdata.append("user",$("#user").val());
        formdata.append("pwd",$("#pwd").val());
        formdata.append("email",$("#email").val());
        formdata.append("tel",$("#tel").val());
        formdata.append("repeat_pwd",$("#repeat_pwd").val());
        formdata.append("avatar_img",$("#avatar")[0].files[0]);
        $.ajax({
             url:"",
             type:"post",
             contentType:false,
             processData:false,
             data:formdata,                          {#ajax上传有文件用到formdata#}
             success:function (data) {
                 // console.log(data);
                 var data=JSON.parse(data);           {#反序列化json#}
                 if (data.user){
                     console.log("OK")
                 }else{
                     $("form span.error").html("")
                     $.each(data.msg,function (filed,error_list) {
                         $("#"+filed).next().html(error_list[0])
                     })
                 }
             }
        })
    })


7、上传文件有一个固定的配置参数media

(1)首先在settings中配置:

MEDIA_URL="/media/"                                                                       #别名

MEDIA_ROOT=os.path.join(BASE_DIR,"app01","media","uploads")   #具体路径


(2)在url中配置:

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

(3)作用

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">


(4)文件保存的位置:app01项目下------->media目录下------->uploads目录下------->avatar目录下


五、用户注册系统

views.py文件内容:

from django.shortcuts import render,HttpResponse
from django.forms import widgets
from blog import models
from django import forms
import json
from django.core.validators import ValidationError
class UserForm(forms.Form):
    user=forms.CharField(
        required=True,
        max_length=16,
        min_length=3,
        error_messages={
            "required": "用户名不能为空",
            "max_length": "长度不能大于16",
            "min_length": "长度不能小于3",
        },
        widget=widgets.TextInput({"placeholder":"用户名","class":"form-control"}))
    pwd=forms.CharField(
        required=True,
        max_length=16,
        min_length=3,
        error_messages={
            "required": "密码不能为空",
            "max_length": "长度不能大于16",
            "min_length": "长度不能小于3",
        },
        widget=widgets.PasswordInput({"placeholder": "密码", "class": "form-control"})
    )
    repeat_pwd=forms.CharField(
        required=True,
        max_length=16,
        min_length=3,
        error_messages={
            "required": "密码不能为空",
            "max_length": "长度不能大于16",
            "min_length": "长度不能小于3",
        },
        widget=widgets.PasswordInput({"placeholder": "确认密码", "class": "form-control"})
    )
    tel = forms.CharField(
        required=True,
        max_length=11,
        min_length=11,
        error_messages={
            "required": "手机号码不能为空",
            "max_length": "长度必须是11位,请你正确输入",
            "min_length": "长度必须是11位,请你正确输入",
        },
        widget=widgets.TextInput({"placeholder": "手机号码", "class": "form-control"})
    )
    email=forms.EmailField(
        required=True,
        error_messages={
            "required": "邮箱不能为空",
            "invalid": "邮箱格式有误"
        },
        widget=widgets.EmailInput({"placeholder": "邮箱", "class": "form-control"})
    )
    #自定义用户名验证:局部钩子
    def clean_user(self):
        user = self.cleaned_data.get("user")
        valid = models.UserInfo.objects.filter(username = user).first()
        if valid:
            raise ValidationError("用户名已存在")
        return user
    #自定义手机号码验证:
    def clean_tel(self):
        tel = self.cleaned_data.get("tel")
        if len(tel)== 11:
            return tel
        else:
            raise ValidationError("手机号码格式错误")
    #自定义密码验证:
    def clean_pwd(self):
        password = self.cleaned_data.get("pwd")
        if password.isdigit():
            raise ValidationError("密码需要数字字母组合")
        else:
            return password
    #自定义全局钩子:验证两次密码是否一致
    def clean(self):
        if self.cleaned_data.get("pwd") == self.cleaned_data.get("repeat_pwd"):
            return self.cleaned_data
        else:
            raise  ValidationError("两次密码不一致")
def reg(request):
    if request.method=="POST":
        print(request.POST)
        print(request.FILES)
        form=UserForm(request.POST)
        res={"user":None,"msg":None}
        if form.is_valid():
            username=form.cleaned_data.get("user")
            password=form.cleaned_data.get("pwd")
            email=form.cleaned_data.get("email")
            telephone=form.cleaned_data.get("tel")
            avatar_img = request.FILES.get("avatar_img")
            print(username,password,telephone)
            models.UserInfo.objects.create_user(user=username, pwd=password, email=email, avatar=avatar_img)
        else:
            res["msg"]=form.errors
        return HttpResponse(json.dumps(res))
    return render(request,"reg.html")

reg.html文件内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
    <script src="/static/js/jquery-3.2.1.min.js"></script>
    <style>
        #avatar{
            display: none;               {#隐藏input文件选择框#}
        }
        .avatar_img{
            width: 60px;
            height: 60px;
            margin-left: 10px;
        }
        .error{
            color: red;
        }
    </style>
</head>
<body>
<h3>注册页面</h3>
<div>
    <div>
        <div class="col-md-6 col-md-offset-3">
            <form action="/reg/" method="post" novalidate enctype="multipart/form-data">
                {% csrf_token %}
                <div>
                    <label for="user">用户名</label>
                    <input type="text" id="user"><span class="error pull-right"></span>
                </div>
                 <div>
                    <label for="pwd">密码</label>
                    <input type="password" id="pwd" ><span class="error pull-right"></span>
                </div>
                <div>
                    <label for="repeat_pwd">确认密码</label>
                    <input type="password" id="repeat_pwd" ><span class="error pull-right"></span>
                </div>
                <div>
                    <label for="email">手机</label>
                    <input type="email" id="tel" ><span class="error pull-right"></span>
                </div>
                <div>
                    <label for="email">邮箱</label>
                    <input type="email" id="email" ><span class="error pull-right"></span>
                </div>
                 <div>
                     <label for="avatar">头像 <img src="/static/img/default.png" alt=""></label>
                     <input type="file" id="avatar" >
                </div>
                <input type="button" value="submit" class="reg_btn btn btn-default">
                <span></span>
            </form>
        </div>
    </div>
</div>
<script>
    // 头像预览
   $("#avatar").change(function () {            {#绑定change事件#}
       var reader=new FileReader();             {#文件阅读器实例化对象 #}
       var choose_file=$(this)[0].files[0];       {#取到用户选中的文件对象#}
       reader.readAsDataURL(choose_file);           {#拿到文件url路径#}
       reader.onload=function(){                 {#等reader阅读器加载完后才执行里面的代码#}
             $(".avatar_img").attr("src",reader.result)
       };
   })
     // 注册
    $(".reg_btn").click(function () {
        var formdata=new FormData();
        formdata.append("user",$("#user").val());
        formdata.append("pwd",$("#pwd").val());
        formdata.append("email",$("#email").val());
        formdata.append("tel",$("#tel").val());
        formdata.append("repeat_pwd",$("#repeat_pwd").val());
        formdata.append("avatar_img",$("#avatar")[0].files[0]);
        $.ajax({
             url:"",
             type:"post",
             contentType:false,
             processData:false,
             data:formdata,                          {#ajax上传有文件用到formdata#}
             success:function (data) {
                 // console.log(data);
                 var data=JSON.parse(data);           {#反序列化json#}
                 if (data.user){
                     location.href = "/login/"
                 }else{
                     // 清空操作
                     $("form span.error").html("")
                     console.log(data.msg)
                     $.each(data.msg,function (filed,error_list) {        {#循环data.msg,其中filed是键,对应的值是一个字典类型的数据#}
                         $span = $("<span>");
                         $span.addClass("pull-right").css("color","red");
                         $span.html(error_list[0]);
                         $("#"+filed).next().html(error_list[0])         {# "#"+filed拼接标签,然后在下一个span标签填充取到的错误信息#}
                         if (filed=="__all__"){                       {#filed=="__all__"表示全局钩子取到了错误信息 #}
                             $("#repeat_pwd").next().html($span)
                         }
                     })
                 }
             }
        })
    })
</script>
</body>
</html>

注册.png



猜你喜欢

转载自blog.51cto.com/qidian510/2117570