Django學習筆記(16)——擴充套件Django自帶User模型,實現使用者註冊與登入

戰爭熱誠發表於2019-06-08

一,專案題目:擴充套件Django自帶User模型,實現使用者註冊與登入

  我們在開發一個網站的時候,無可避免的需要設計實現網站的使用者系統。此時我們需要實現包括使用者註冊,登入,使用者認證,登出,修改密碼等功能。Django作為一個完美主義者的終極框架,當然也會想到使用者的這些痛點,它內建了強大的使用者認證系——auth,所以本文在不建立User模型的情況下實現使用者的註冊,登入和認證。另外對Django Auth自帶的User模型進行擴充套件,執行使用者新增更多的個人資訊。

   我在之前的Django學習筆記(9)——開發使用者註冊與登入系統 中已經完成了這個註冊與登入系統,那麼下面在這裡寫主要是向基於Django自帶的User模型重新建立自己使用者的模型。

二,專案需求

2.1 實現的功能需求

  我們用Django2.0開發一個叫users的APP,來實現以下六項功能:

  1. 使用者註冊:註冊完成後轉到登入頁面
  2. 使用者登入:登入完成後轉到使用者資料頁面
  3. 使用者資料頁面:檢視使用者註冊資訊,並提供編輯資料按鈕
  4. 使用者資料編輯:編輯完成後轉到使用者資料檢視頁面
  5. 使用者密碼重置
  6. 使用者退出登入

2.2  擴充套件User模型的內容

  由於Django Auth自帶的User模型欄位有限,我們還需要自定義模型UserProfile對其擴充套件。

  (Auth元件的相關內容可以參考:Django學習筆記(13)——Django的使用者認證元件,檢視層和QuerySet API

  首先我們看一下Django Auth模組自帶User模型所包含欄位:

username:使用者名稱

email: 電子郵件

password:密碼

first_name:名

last_name:姓

is_active: 是否為活躍使用者。預設是True

is_staff: 是否為員工。預設是False

is_superuser: 是否為管理員。預設是False

date_joined: 加入日期。系統自動生成。

  下面展示我們自定義的UserProfile模型

user: 與User是1對1關係

org:使用者名稱

telephone: 電話

mod_date: 最後修改日期。系統自動生成

  

2.3  專案不足與改進

  整個使用者註冊和登入非常簡單,比如沒有郵箱驗證,也不能通過第三方APP授權登入,我做的頁面也比較醜陋、一個更好的方式是使用以及成熟的第三方Django Package(比如django-allauth)來實現使用者註冊和登入。

 

三,編碼規範需求

編碼規範需求:

    1. 程式碼規範遵守pep8 (https://python.org/dev/peps/pep-0008/)

    2. 函式有相應的註釋

    3. 程式有文件說明檔案(README.md)

    4. 程式的說明文件必須包含的內容:程式的開發環境(django版本)、程式的實
現的功能、程式的啟動方式、登入使用者資訊、程式的執行效果

    5. 程式設計的流程圖:

  

四,專案思路

  基本的專案開啟什麼的都在這裡不再闡述了,我直接寫專案的主要思路,基礎的Django操作流程看我之前的部落格就行。

4.1  建立模型(Model)

  到目前為止,我學習Django,第一步都是建立模型。為什麼呢?因為大多數語言都遵循軟體開發的設計模式MVC。

  這裡我們建立一個名稱為UserProfile的模型,程式碼如下:

from django.db import models

# Create your models here.
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE,
                                related_name='profile')
    # 模型類中設定:blank=True,表示程式碼中建立資料庫記錄時該欄位可傳空白(空串,空字串)
    org = models.CharField('Organization', max_length=128, blank=True)
    telephone = models.CharField('Telephone', max_length=50, blank=True)
    mod_data = models.DateTimeField('Last modified', auto_now=True)

    class Meta:
        verbose_name = 'User profile'

    def __str__(self):
        # return self.user.__str__()
        return "{}".format(self.user.__str__())

  注意:我們並沒改變Django Auth 自帶的User模型,也沒有建立新的User模型。而UserProfile只是對User模型的擴充套件。找到users/models.py,並建立上面的UserProfile模型。由於我們引入了Django Auth自帶的User模型,所以我們必須開始先把它import進來。

  自定義的UserProfile模型新增欄位的意思如下:

user: 與User是1對1關係

org:使用者名稱

telephone: 電話

mod_date: 最後修改日期。系統自動生成

  

4.2  配置URL

  我比較習慣先寫URL,再寫檢視View,從URL配置上,你應該可以理解我們想實現的六個功能。

  下面是users/urls.py程式碼內容:

from django.urls import re_path, path
from users import views

app_name = 'users'

urlpatterns = [
    re_path(r'^register/$', views.register, name='register'),
    re_path(r'^login/$', views.login, name='login'),
    re_path(r'^user/(?P<pk>\d+)/profile/$', views.profile, name='profile'),
    re_path(r'^user/(?P<pk>\d+)/profile/update/$', views.profile_update, name='profile_update'),
    re_path(r'^user/(?P<pk>\d+)/pwd_change/$', views.pwd_change, name='pwd_change'),
    re_path(r"^logout/$", views.logout, name='logout'),
]

  上面,我們使用 name= '?' ,比如第一個:我們這是給動態連結 /register/取了個名字 ‘register’,這樣我們就可以在HTML模板裡面通過 {% url 'users:register' %} 來呼叫這個連結了。

4.3  編寫檢視函式(View)

  我們需要編寫六個檢視函式,正如上面url路徑所指。

  由於這六個檢視中,register , login,profile , pwdChange這個四個檢視都需要用到表單,所以我們先建立表單程式碼。

  (表單程式碼我們可以分離出去,在users目錄下建立MyForms.py檔案,然後再在裡面建立form表單,最後將其匯入檢視函式,這樣程式碼顯得工整,清晰,而且後期容易維護。還有MyForms.py可以通過clean方法自定義表單驗證,非常便捷,而且邏輯更清晰)。

from django import forms
from django.contrib.auth.models import User
import re

def email_check(email):
    
    pattern = re.compile(r"\"?([-a-zA-Z0-9.'?{}]+@\w+\.\w+)\"?")
    return re.match(pattern, email)

class RegistrationForm(forms.Form):
    username = forms.CharField(label='Username', max_length=50)
    email = forms.EmailField(label='Email')
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

    #user clean methods to define custom validation rules

    def clean_username(self):
        username = self.cleaned_data.get('username')
        if len(username) < 3:
            raise forms.ValidationError("your username must be at least 3 characters log")
        elif len(username) > 20:
            raise forms.ValidationError("your username is too long")
        else:
            filter_result = User.objects.filter(username__exact=username)
            if len(filter_result) > 0:
                raise forms.ValidationError('your username already exists')
        return username

    def clean_email(self):
        email = self.cleaned_data.get('email')
        if email_check(email):
            filter_result = User.objects.filter(email__exact=email)
            if len(filter_result) > 0:
                raise forms.ValidationError("your email already exists")
        else:
            raise forms.ValidationError("Please enter a valid email")

        return email

    def clean_password1(self):
        password1 = self.cleaned_data.get('password1')
        if len(password1) < 3:
            raise forms.ValidationError("your password is too short")
        elif len(password1) > 20:
            raise forms.ValidationError("your password is too long")

        return password1

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')

        if password1 and password2 and password1 != password2:
            raise forms.ValidationError('Password mismatch Please enter again')

        return password2

class LoginForm(forms.Form):
    username = forms.CharField(label='Username', max_length=50)
    password = forms.CharField(label='Password', widget=forms.PasswordInput)

    # use clean methods to define custom validation rules

    def clean_username(self):
        username = self.cleaned_data.get('username')
        if email_check(username):
            filter_result = User.objects.filter(email__exact=username)
            if not filter_result:
                raise forms.ValidationError('This emial does not exist')
        else:
            filter_result = User.objects.filter(username__exact=username)
            if not filter_result:
                raise forms.ValidationError('This username does not exist Please register first')

        return username
    
    
class ProfileForm(forms.Form):
    first_name = forms.CharField(label='First Name', max_length=50, required=False)
    last_name = forms.CharField(label='Last Name', max_length=50, required=False)
    org = forms.CharField(label='Organization', max_length=50, required=False)
    telephone = forms.CharField(label='Telephone', max_length=50, required=False)


class PwdChangeForm(forms.Form):
    old_password = forms.CharField(label='Old Password', widget=forms.PasswordInput)

    password1 = forms.CharField(label='New Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

    # use clean methods to define custom validation rules

    def clean_password1(self):
        password1 = self.cleaned_data.get('password1')

        if len(password1) < 6:
            raise forms.ValidationError("your password is too short")
        elif len(password1) > 20:
            raise forms.ValidationError("your password is too long")

        return password1

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')

        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Password mismatch Please enter again")

        return password2

  

  上面的程式碼之所以非常長,是因為我們用clean方法加入了很多表單驗證項。比如檢查使用者名稱是否過短,是否過長,是否已經存在等等。

  我們在之前,Django學習筆記(14)——AJAX與Form元件知識補充(區域性鉤子和全域性鉤子詳解) ,一文中詳細學習了區域性鉤子和全域性鉤子,也就是clean方法的使用,如果不懂的話可以先學習這一篇博文。畢竟真實的網站開發都是需要加上這些驗證規則的。

  廢話就說這麼多,當然你也可以在View檢視裡面寫Form驗證規則。隨你。下面直接看我的檢視users/views.py的內容:

from django.shortcuts import render, HttpResponse, get_object_or_404
from .MyForms import RegistrationForm, LoginForm, ProfileForm, PwdChangeForm
from .models import UserProfile
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.contrib import auth
from django.urls import reverse
from django.contrib.auth.decorators import login_required

# Create your views here.

@login_required
def profile(request, pk):
    user = get_object_or_404(User, pk=pk)
    return render(request, 'users/profile.html', {'user': user})

@login_required
def profile_update(request, pk):
    user = get_object_or_404(User, pk=pk)
    user_profile = get_object_or_404(UserProfile, user=user)

    if request.method == 'POST':
        form = ProfileForm(request.POST)

        if form.is_valid():
            user.first_name = form.cleaned_data['first_name']
            user.last_name = form.cleaned_data['last_name']
            user.save()

            user_profile.org = form.cleaned_data['org']
            user_profile.telephone = form.cleaned_data['telephone']
            user_profile.save()

            return HttpResponseRedirect(reverse('users:profile', args=[user.id]))
    else:
        default_data = {'first_name': user.first_name, 'last_name': user.last_name,
                        'org': user_profile.org, 'telephone': user_profile.telephone,}
        form = ProfileForm(default_data)

    return render(request, 'users/profile_update.html', {'form': form, 'user': user})


def register(request):
    if request.method == 'POST':
        
        form = RegistrationForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            email = form.cleaned_data['email']
            password = form.cleaned_data['password2']
            
            # 使用內建User自帶create_user方法建立使用者,不需要使用save()
            user = User.objects.create_user(username=username, password=password, email=email)

            # 如果直接使用objects.create()方法後不需要使用save()
            user_profile = UserProfile(user=user)
            user_profile.save()

            return HttpResponseRedirect("/accounts/login/")
    else:
        form = RegistrationForm()
    return render(request, 'users/registration.html', {'form': form})


def login(request):
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']

            user = auth.authenticate(username=username, password=password)

            if user is not None and user.is_active:
                auth.login(request, user)
                return HttpResponseRedirect(reverse('users:profile', args=[user.id]))
            else:
                # 登入失敗
                return render(request, 'users/login.html', {'form': form, 'message':'Wrong password Please Try agagin'})
    else:
        form = LoginForm()

    return render(request, 'users/login.html', {'form': form})

@login_required
def logout(request):
    auth.logout(request)
    return HttpResponseRedirect("/accounts/login/")

@login_required
def pwd_change(request, pk):
    user = get_object_or_404(User, pk=pk)

    if request.method == "POST":
        form = PwdChangeForm(request.POST)

        if form.is_valid():
            password = form.cleaned_data['old_password']
            username = user.username

            user = auth.authenticate(username=username, password=password)

            if user is not None and user.is_active:
                new_password = form.cleaned_data['password2']
                user.set_password(new_password)
                user.save()
                return HttpResponseRedirect('/accounts/login/')

            else:
                return render(request, 'users/pwd_change.html', {'form': form,
                        'user': user, 'message': 'Old password is wrong Try again'})
    else:
        form = PwdChangeForm()

    return render(request, 'users/pwd_change.html', {'form': form, 'user': user})

  

 下面我們分別看看幾個檢視函式是如何工作的:

register檢視函式:

  1. 當使用者通過POST方法提交表單,我們先驗證表單RegistrationForm的資料是否有效。如果有效,我們先用Django User模型自帶的 create_user方法建立user物件,再建立 user_profile。使用者通過一張表單提交資料,我們實際上分別儲存在兩張表裡。
  2. 如果使用者註冊成功,我們通過HttpResponseRedirect方法轉到登入頁面。
  3. 如果使用者沒有提交表單或者不是通過POST方法提交表單,我們轉到註冊頁面,生成一張空的RegistrationForm。

login檢視函式:

  1. 當使用者通過POST方法提交表單,我們先驗證表單LoginForm的資料是否有效。如果有效,我們呼叫Django自帶的auth.authenticate()來驗證使用者名稱和密碼是否正確。如果正確且使用者是活躍的,我們呼叫auth.login() 來進行登入。
  2. 如果使用者登入失敗,會重新轉到登入頁面,並返回錯誤資訊。
  3. 如果使用者登入成功,我們通過HttpResponseRedirect方法轉到使用者個人資訊頁面
  4. 如果使用者沒有提交表單或不是通過POST方法提交表單,我們轉到登入頁面,生成一個空的LoginForm。

profile檢視函式:

  1. 我們先從url獲取user的主鍵pk(id),利用get_object_or_404方法獲取需要修改個人資料的使用者物件user,然後利用user獲取一一對應的 user_profile 物件。
  2. 當使用者通過POST方法提交個人資料修改表單,我們先驗證表單ProfileForm的資料是否有效。如果有效,我們將更新過的first_name 和 last_name 資料存入 user,再將更新過的 telephone 和 org 資料存入 user_profile,更新成功後返回個人資訊頁。
  3. 如果使用者沒有提交表單或者不是通過POST方法提交表單,我們先獲取現有資料生成 default_data,再利用ProfileForm顯示。

pwd_change檢視函式:

  1. 首先我們用@login_required 裝飾器確定使用者是否已經登入,只有登入的才能修改個人密碼。
  2. 我們先從url獲取user的主鍵pk(id),利用 get_object_or_404 方法獲取需要修改密碼的使用者物件user。
  3. 當使用者通過POST方法提交密碼修改表單,我們先驗證表單PwdChangeForm的資料是否有效。如果老的密碼是正確的,我們將更新過的密碼通過set_password 方法資料存入 user。修改密碼成功後返回登入頁面。
  4. 如果使用者沒有提交表單或不是通過POST方法提交表單,我們生成一張空的PwdChangeForm。

4.4  編寫HTML模板(Template)

  在users目錄下建立/templates/users/資料夾,編寫HTML模板。其目錄結構如下:

   展示一個 login.html 程式碼:

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}<h1 class="my-con1">Login Page</h1>{% endblock %}
{% block css %}
    <link rel="stylesheet" href="{% static '/CSS/login.css' %}">
{% endblock %}


{% block content %}

    {% if message %}
    {{ message }}
    {% endif %}
    <div class="form-group">
        <form method="post" action="" enctype="multipart/form-data">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group ">
                    {{ field.errors }}
                    {{ field.label_tag }}{{ field }}
                    {% if field.help_text %}
                        <p class="help">{{ field.help_text|safe }}</p>
                    {% endif %}
                </div>
            {% endfor %}
            <div class="btn">
                <input type="submit" value="Login">
            </div>
        </form>
        <h2 class="my-href pull-right">
            <a href="/accounts/register" >Register Link</a>
        </h2>

    </div>

{% endblock %}

  

五,注意事項

5.1 Django網站開發是如何遵循軟體設計MVC模式

  如果你要開發一個好的網站或者網路應用,你就必須瞭解經典的軟體開發所遵循的MVC設計模式。Django作為最優秀的基於Python語言的網站開發框架,當然也遵循了這種設計模型。下面就學習一下什麼是MVC框架以及Django網站開發是如何遵循這種軟體開發設計模型的。

5.1.1  什麼是MVC模式?它有什麼優點?

  MVC是Model-View-Controller(模型-試圖-控制器)模式

  • Model(模型)簡而言之就是資料模型。模型不是資料本身(比如資料庫裡的資料),而是抽象的描述資料的構成和邏輯關係。通常模型包括了資料表的各個欄位(比如人的年齡和出生日期)和相關關係(單對單,單對多關係等)。資料庫裡的表會根據模型的定義來生成建立。
  • View(檢視)主要用於顯示資料,用來展示使用者可以看到內容或者提供使用者可以輸入或者操作的介面。資料來源於哪裡?當然是資料庫。那麼使用者輸入的資料給誰?當然是給控制器了。
  • Controller(控制器)是應用程式中處理使用者互動的部分。通常控制器負責從檢視讀取資料,控制使用者輸入,並向模型傳送資料(比如增加或者更新資料表)。

  如果把MVC比喻成一個粽子,那麼View就是最外面一層的綠色玉米葉,是我們可以直接看到的。Controller就是中間那層熟糯米,而粽子的的核心自然然是最裡面的那一層的肉餡Model模型了。

  MVC最大的優點是實現了軟體或網路應用開發過程中資料,業務邏輯和介面的分離,使軟體開發更清晰,也是維護變得更容易。這與靜態網頁設計中使用HTML和CSS實現了內容和樣式的分離是同一個道理。

5.1.2  Django網站開發是如何遵循MVC設計模型的?

  Django網站開發全靠四件套:Model(模型),URL(連結),View(檢視)和Template(模板)。他們看似與MVC設計模式不太一致,其實本質是相同的。但是Django的View和經典的View確實有非常大的不同。Django四件套與經典的MVC對應關係如下。

  • Django Model(模型):這個與經典MVC模式下的Model差不多。
  • Django URL+View(檢視):這個合起來就與經典MVC下的Controller更像。原因在於Django的URL和View合起來才能向Template傳遞正確的資料。使用者輸入提供的資料也需要Django的View來處理。
  • Django Template(模板):這個與經典MVC模式下的View一致。Django模板用來呈現Django View傳來的資料,也決定了使用者介面的外觀。Template裡面也包含了表單,可以用來收集使用者的輸入。

5.1.3  Django網站開發應先寫URL還是寫View?

  使用Django開發網站的第一步絕對是定義模型(Model),如果寫個不需要使用資料庫的小應用,也完全可以不定義模型,直接寫URL和View。當然先寫URL還是View,其實都可以,完全取決於個人偏好。一般來說,從上向下思考問題的話,先寫URL。不過不影響。

 

5.2  擴充套件Django自帶的User Admin

  Django自帶的User Admin 只能編輯管理基礎欄位如使用者名稱和電子郵件。我們現在希望能擴充套件User Admin,在編輯 User的同時編輯我們User Profile 裡的額外欄位(如電話)。去實現這個功能,我們需要按照如下程式碼修改 users/admin.py:

from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from .models import UserProfile

admin.site.unregister(User)


class UserProfileInline(admin.StackedInline):
    model = UserProfile


class UserProfileAdmin(UserAdmin):
    inlines = [UserProfileInline, ]


admin.site.register(User, UserProfileAdmin)

  下面是實際效果,我們會發現UserAdmin後多了一欄 user Profiles。

進入後臺的方法(前提是專案URL等東西要配置好):
 
1,啟動專案
    python manage.py runserver
 
2,在瀏覽器中開啟地址:
    127.0.0.1:8000/users/admin

   (我們進入admin後臺,向下拉,會發現)

 

 

六,知識擴充套件

6.1:如何引入Bootstrap

  根目錄下新建一個static目錄,並將其解壓後的Bootstrap-3.3.7目錄,整體拷貝到static目錄中,而且由於Bootstrap依賴於jQuery,所以我們需要引入jQuery,並且在static目錄下,新建一個CSS和JS目錄,作為以後的樣式檔案和JS檔案的存放地,最後如下圖所示:

  然後開啟專案的settings檔案,在最下面新增配置,用於指定靜態檔案的搜尋目錄:

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

  

6.1.1  建立 base.html 模板

  一個網站要有自己的統一風格和公共部分,可以將這部分內容集中到一個基礎模板 base.html中。現在,在根目錄下的template中建一個 base.html 檔案作為站點的基礎模板。

  在Bootstrap文件中,為我們提供了一個非常簡單而且又實用的基礎模板,程式碼如下:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支援 HTML5 元素和媒體查詢(media queries)功能 -->
    <!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <h1>你好,世界!</h1>

    <!-- jQuery (Bootstrap 的所有 JavaScript 外掛都依賴 jQuery,所以必須放在前邊) -->
    <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
    <!-- 載入 Bootstrap 的所有 JavaScript 外掛。你也可以根據需要只載入單個外掛。 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

  我們將其整體拷貝到 base.html檔案中。因為我們將Bootstrap的目錄都拷貝下來了,所以我們不需要cdn引入,我們可以將路徑更改為本地的檔案路徑。

   更改後的程式碼如下:

{% load staticfiles %}

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->

    <!-- Bootstrap -->
    <link href="/static/bootstrap-3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支援 HTML5 元素和媒體查詢(media queries)功能 -->
    <!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->

  </head>
  <body>

    <!-- jQuery (Bootstrap 的所有 JavaScript 外掛都依賴 jQuery,所以必須放在前邊) -->
    <script src="/static/JS/jquery-3.2.1.js"></script>
    <!-- 載入 Bootstrap 的所有 JavaScript 外掛。你也可以根據需要只載入單個外掛。 -->
    <script src="/static/bootstrap-3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

  

  注意:Bootstrap的所有JavaScript外掛都依賴jQuery,所以必須將jQuery放在前面。

    <!-- jQuery (Bootstrap 的所有 JavaScript 外掛都依賴 jQuery,所以必須放在前邊) -->
    <script src="/static/JS/jquery-3.2.1.js"></script>

    <!-- 載入 Bootstrap 的所有 JavaScript 外掛。你也可以根據需要只載入單個外掛。 -->
    <script src="/static/bootstrap-3.3.7/dist/js/bootstrap.min.js"></script>

  

6.1.2  使用Bootstrap靜態檔案

  {%  static ‘相對路徑’ %}  這個Django為我們提供的靜態檔案載入方法,可以將頁面與靜態檔案連結起來。

  {%  block title%} AAA {%  endblock %} 設定了專門的 title。

  通過block css 引入了針對性的 css樣式檔案。

  更改後的 base.html 內容如下:

{% load staticfiles %}

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->
      {% block title %}
          <title>base</title>
      {% endblock %}

    <!-- Bootstrap -->
    <link href="/static/bootstrap-3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支援 HTML5 元素和媒體查詢(media queries)功能 -->
    <!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->

        <link rel="stylesheet" href="/static/CSS/base.css">

      {% block css %}
      {% endblock %}
  </head>
  <body>



  <div class="container my-con">
        {% block content %}
        {% endblock %}
  </div>

    <!-- jQuery (Bootstrap 的所有 JavaScript 外掛都依賴 jQuery,所以必須放在前邊) -->
    <script src="/static/JS/jquery-3.2.1.js"></script>
    <!-- 載入 Bootstrap 的所有 JavaScript 外掛。你也可以根據需要只載入單個外掛。 -->
    <script src="/static/bootstrap-3.3.7/dist/js/bootstrap.min.js"></script>
  </body>
</html>

  

6.1.3  完成自己的HTML程式碼

  因為有四五個,這裡只舉其中一個例子來說。其他的大同小異。(注意:我的前端水平有點差,所以頁面不是那麼美觀,見諒見諒啊)

  比如登入頁面,首先我們繼承 base.html的內容,然後將自己的後端邏輯程式碼填入其中,如下:

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}<h1 class="my-con1">Login Page</h1>{% endblock %}
{% block css %}
    <link rel="stylesheet" href="{% static '/CSS/login.css' %}">
{% endblock %}


{% block content %}

    {% if message %}
    {{ message }}
    {% endif %}
    <div class="form-group">
        <form method="post" action="" enctype="multipart/form-data">
            {% csrf_token %}
            {% for field in form %}
                <div class="form-group ">
                    {{ field.errors }}
                    {{ field.label_tag }}{{ field }}
                    {% if field.help_text %}
                        <p class="help">{{ field.help_text|safe }}</p>
                    {% endif %}
                </div>
            {% endfor %}
            <div class="btn">
                <input type="submit" value="Login">
            </div>
        </form>
        <h2 class="my-href pull-right">
            <a href="/accounts/register" >Register Link</a>
        </h2>

    </div>

{% endblock %}

   然後,我們寫樣式的話,需要在 static/CSS目錄下新建一個樣式檔案,然後裡面寫樣式(注意:我就寫了簡單的樣式,哈哈哈哈哈)。

body{
    background: #e6e6e6;
}

.my-con1{
    text-align: center;
    color: steelblue;
}

.my-href{
    margin-top: 50px;
}

  這樣一個簡單的登入頁面就出來了:

 

 

6.2:Model中的Meta選項

6.2.1,ModelForm的Meta類中定義的fields

  預設的Field是Model中定義的Field,如需更改,可在Form類內以同名欄位覆蓋,比如自定義widget和required屬性等。

  下面來學習class Meta內嵌類的所有後設資料選項(meta options),

6.2.2,可用的Meta選項

  abstract

Optional.abstract
    
    如果abstract = True ,這個model就是一個抽象基類

  

  app_label

Options.app_label
    
    如果一個model定義在預設的models.py之外(例如,如果你的APP的models在myapp.models
子模組下),你必須定義app_label 讓DJango知道他屬於哪一個APP

app_label = 'myapp'

  

  db_table

Options.db_table
    
    定義該model在資料中的表名稱:
    
    db_table = 'music_album'

  

6.2.3,資料庫中表的名稱

  (注意:在MySQL中使用小寫字母作為資料庫表名稱)

  為了節省時間,Django會自動的使用你的model class的名稱和包含這個model的APP名稱來構建資料庫的表名稱,一個model的資料庫表名稱是通過將‘app label’ (你在manage.py startapp 中使用的名稱 和model的類名稱,加上一個下劃線在他們之間來構成)。

  例如,如果你有一個APP叫做bookstore(使用manage.py startapp bookstore建立),以及一個model定義為class Book這樣將會建立一個名為bookstore_book的資料庫表。

  如果想自定義資料庫的表名稱,需要在class Meta 使用db_table引數來自定義。

  如果你的資料庫表名稱是一個SQL保留字,或者它包含不允許出現在Python變數中的字元(比如連字元)這是沒問題的,因為Django會自動給列名新增引號。

6.2.4,verbose_name

  Django模型中的verbose_name我們常常可能需要使用,比如將資料庫裡面的資料匯出成csv檔案。那麼,csv檔案的表頭的名字可以通過取每個欄位的verbose_name來獲取,資料可以通過queryset語句來獲取。這樣製作出來的csv表就能像資料庫一樣,欄位名和欄位值一一對應了。

Options.verbose_name
    
    指明一個易於理解和表述的物件名稱,單數形式:
    verbose_name = 'user_name'

    如果這個值沒有設定,Django將會使用該model的類名的分詞形式作為其物件的表述名
    CamelCase將會轉換為camel case

  

6.2.5,常見的Django Model META類選項

from django.db import models

class Meta:
    # 按 Priority降序, order_date升序排列
    get_latest_by = ['-priority', 'order_date']
    # 自定義資料庫裡表格的名稱
    db_table = 'music_album'
    # 按照什麼排序
    ordering = ['pub_date']
    # 定義APP的標籤
    app_label = 'myapp'
    # 宣告此類是否抽象
    abstract = True
    # 新增授權
    permissions = (('Can deliver pizzas', 'Can deliver pizzas'))

  

6.3:URL命名及reverse() 方法

  假設我們需要在模板中通過連結指向一篇具體的文字,下面哪種方式更好?

方法1: 使用命名URL

<a href="{%  url  'article'  id %}Article</a>


方法2:使用常規URL——不建議

<a href="blog/article/id">Article</a>

  如果你還沒有意識到方法1的好處,那麼想想假設你需要把全部模板連結由 blog/article/id 改為blog/articles/id,那種方法更快?,改所有模板,還是改URL配置裡的一個字母?

  可惜的是命名的URL一般只在模板裡使用,不能直接在檢視裡使用。如果我們有了命名的URL,我們如何把它轉化為常規的URL在檢視裡使用呢?Django提供的reverse()方法很容易實現這點。假設不同的APP(比如news和blog)裡都有article這個命名URL,我們怎麼樣區分呢?我們只需要在article前面加上blog這個名稱空間即可。

from django.urls import reverse

# output blog/article/id
reverse('blog:article', args=[id])

  而且,我們需要在urls.py 裡面,給動態連結 取名字,比如:

from django.urls import re_path
from . import views

app_name = 'blog'
urlpatterns = [
    re_path(r'^articles/$', views.articles_content, name='articles'),

]

  這樣我們就可以在HTML模板中通過{%   url 'arrticle'  id %} 來呼叫這個連結了。

 

6.4  Django建立物件的create和save方法

  Django的模型(Model)的本質是類,並不是一個具體的物件(object)。當你設計好模型後,你就可以對Model進行例項化從而建立一個具體的物件。Django對於建立物件提供了兩種不同的save與create方法,下面來具體分析一下兩者的不同。

  我們先來看一下下面的例子,我們已經設計好了一個Person的模型:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

  

6.4.1  用save方法建立物件

  用save方法建立一個名為 james 的具體物件,我們可以這麼做。記住你只有用了save()方法後,Django才會將這個物件的資訊儲存到資料庫中。

james_obj = Person(name="LeBron james")
james_obj.save()

  

6.4.2  用create方法建立物件

  正因為用save方法建立物件有兩步,而且程式設計師容易忘記加上 save(),Django提供了一個更便捷的create方法,如下:如果你使用create方法,無需再加上save(),create方法不僅建立了新的物件,而且直接將資訊儲存到資料庫裡。

james_obj = Person.objects.create(name="LeBron james")

  

6.4.3  save和create方法比較

  create只能用於建立新的物件,在資料庫層總是執行 insert 的操作。save不僅用於建立新的物件,也能用於更新物件的現有資料,在資料庫總是先執行update,找不到具體物件後再執行 insert 的操作,對於建立全新的物件,兩者都可以。如果更新已有物件資訊,只能用save()方法。

 

6.4.4  User自帶的create_user方法

  如果你要用Auth自帶的User模型建立新物件,你需要使用create_user方法,而不是create方法,如下所示。(create_user方法很有用,自動會給密碼加hash )。

user1 = User.objects.create_user(username=username, username=password)

  

七,結果展示

註冊頁面展示

  當我們註冊一個harden,註冊成功後會自動跳轉到登入頁面。

登入頁面展示

  下面我們登入harden的資訊,登入成功後跳轉到harden的使用者資訊頁面。

使用者資訊頁面展示

  進入詳細資訊頁面,我們會發現下面會有三個按鈕,分別是編輯,修改密碼,退出按鈕。首先我們點選編輯按鈕。進入編輯頁面。更新資訊,結果如下:

  下面我們進入修改密碼頁面:

  當我們點選退出,就會重新進入登入頁面。再次進入個人資訊頁面,我們就會發現新的資訊。

   如果登入的使用者名稱不存在,會出現下面報錯資訊:

  其他的就不多做演示了。

 

八,程式碼 

8.1  完整的專案程式碼 

  請移步小編的GitHub:傳送門

 

 參考文獻: https://blog.csdn.net/weixin_42134789/article/details/80194532

相關文章