一,項目題目:擴展Django自帶User模型,實現用戶注冊與登錄
我們在開發一個網站的時候,無可避免的需要設計實現網站的用戶系統。此時我們需要實現包括用戶注冊,登錄,用戶認證,注銷,修改密碼等功能。Django作為一個完美主義者的終極框架,當然也會想到用戶的這些痛點,它內置了強大的用戶認證系——auth,所以本文在不建立User模型的情況下實現用戶的注冊,登錄和認證。另外對Django Auth自帶的User模型進行擴展,運行用戶添加更多的個人信息。
我在之前的Django學習筆記(9)——開發用戶注冊與登錄系統 中已經完成了這個注冊與登錄系統,那么下面在這里寫主要是向基於Django自帶的User模型重新建立自己用戶的模型。
二,項目需求
2.1 實現的功能需求
我們用Django2.0開發一個叫users的APP,來實現以下六項功能:
- 用戶注冊:注冊完成后轉到登錄頁面
- 用戶登錄:登錄完成后轉到用戶資料頁面
- 用戶資料頁面:查看用戶注冊信息,並提供編輯資料按鈕
- 用戶資料編輯:編輯完成后轉到用戶資料查看頁面
- 用戶密碼重置
- 用戶退出登錄
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視圖函數:
- 當用戶通過POST方法提交表單,我們先驗證表單RegistrationForm的數據是否有效。如果有效,我們先用Django User模型自帶的 create_user方法創建user對象,再創建 user_profile。用戶通過一張表單提交數據,我們實際上分別存儲在兩張表里。
- 如果用戶注冊成功,我們通過HttpResponseRedirect方法轉到登錄頁面。
- 如果用戶沒有提交表單或者不是通過POST方法提交表單,我們轉到注冊頁面,生成一張空的RegistrationForm。
login視圖函數:
- 當用戶通過POST方法提交表單,我們先驗證表單LoginForm的數據是否有效。如果有效,我們調用Django自帶的auth.authenticate()來驗證用戶名和密碼是否正確。如果正確且用戶是活躍的,我們調用auth.login() 來進行登錄。
- 如果用戶登錄失敗,會重新轉到登錄頁面,並返回錯誤信息。
- 如果用戶登錄成功,我們通過HttpResponseRedirect方法轉到用戶個人信息頁面
- 如果用戶沒有提交表單或不是通過POST方法提交表單,我們轉到登錄頁面,生成一個空的LoginForm。
profile視圖函數:
- 我們先從url獲取user的主鍵pk(id),利用get_object_or_404方法獲取需要修改個人資料的用戶對象user,然后利用user獲取一一對應的 user_profile 對象。
- 當用戶通過POST方法提交個人資料修改表單,我們先驗證表單ProfileForm的數據是否有效。如果有效,我們將更新過的first_name 和 last_name 數據存入 user,再將更新過的 telephone 和 org 數據存入 user_profile,更新成功后返回個人信息頁。
- 如果用戶沒有提交表單或者不是通過POST方法提交表單,我們先獲取現有數據生成 default_data,再利用ProfileForm顯示。
pwd_change視圖函數:
- 首先我們用@login_required 裝飾器確定用戶是否已經登錄,只有登錄的才能修改個人密碼。
- 我們先從url獲取user的主鍵pk(id),利用 get_object_or_404 方法獲取需要修改密碼的用戶對象user。
- 當用戶通過POST方法提交密碼修改表單,我們先驗證表單PwdChangeForm的數據是否有效。如果老的密碼是正確的,我們將更新過的密碼通過set_password 方法數據存入 user。修改密碼成功后返回登錄頁面。
- 如果用戶沒有提交表單或不是通過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