1. 程式人生 > >Django博客項目之登錄和註冊系統

Django博客項目之登錄和註冊系統

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>

技術分享圖片

三、生成驗證碼的幾種方式

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>

技術分享圖片



Django博客項目之登錄和註冊系統