可能你已經發現了,Django自帶的User模型很是實用,以致於咱們沒有寫用戶管理相關的任何模型。html
可是自帶的User畢竟可用的字段較少。比方說很是重要的電話號碼、頭像等都沒有。解決的方法有不少,你能夠不使用User,本身從零寫用戶模型;也能夠對User模型進行擴展。python
博客網站的用戶信息並不複雜,所以擴展User就足夠了。git
擴展User模型又有不一樣的方法。在大多數狀況下,使用模型一對一連接的方法是比較適合的。github
編寫userprofile/models.py
以下:shell
userprofile/models.py
from django.db import models
from django.contrib.auth.models import User
# 引入內置信號
from django.db.models.signals import post_save
# 引入信號接收器的裝飾器
from django.dispatch import receiver
# 用戶擴展信息
class Profile(models.Model):
# 與 User 模型構成一對一的關係
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
# 電話號碼字段
phone = models.CharField(max_length=20, blank=True)
# 頭像
avatar = models.ImageField(upload_to='avatar/%Y%m%d/', blank=True)
# 我的簡介
bio = models.TextField(max_length=500, blank=True)
def __str__(self):
return 'user {}'.format(self.user.username)
# 信號接收函數,每當新建 User 實例時自動調用
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
# 信號接收函數,每當更新 User 實例時自動調用
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
複製代碼
每一個Profile
模型對應惟一的一個User
模型,造成了對User的外接擴展,所以你能夠在Profile
添加任何想要的字段。這種方法的好處是不須要對User
進行任何改動,從而擁有徹底自定義的數據表。模型自己沒有什麼新的知識,比較神奇的是用到的信號機制。數據庫
Django包含一個「信號調度程序」,它能夠在框架中的某些位置發生操做時,通知其餘應用程序。簡而言之,信號容許某些發送者通知一組接收器已經發生了某個動做。當許多代碼可能對同一事件感興趣時,信號就特別有用。django
這裏引入的post_save
就是一個內置信號,它能夠在模型調用save()
方法後發出信號。bash
有了信號以後還須要定義接收器,告訴Django應該把信號發給誰。裝飾器receiver
就起到接收器的做用。每當User
有更新時,就發送一個信號啓動post_save
相關的函數。服務器
經過信號的傳遞,實現了每當User
建立/更新時,Profile
也會自動的建立/更新。session
固然你也能夠不使用信號來自動建立Profile表,而是採用手動方式實現。
爲何刪除User表不須要信號?答案是二者的關係採用了models.CASCADE級聯刪除,已經帶有關聯刪除的功能了。
avatar字段用來存放頭像,暫且無論它,下一章講解。
前面講過,每次改動模型後都須要進行數據的遷移。因爲avatar
字段爲圖像字段,須要安裝第三方庫Pillow
來支持:
(env) E:\django_project\my_blog> pip install Pillow
複製代碼
安裝成功後,經過makemigrations
、migrate
遷移數據:
(env) E:\django_project\my_blog>python manage.py makemigrations
Migrations for 'userprofile':
userprofile\migrations\0001_initial.py
- Create model Profile
複製代碼
(env) E:\django_project\my_blog>python manage.py migrate
Operations to perform:
Apply all migrations: admin, article, auth, contenttypes, sessions, userprofile
Running migrations:
Applying userprofile.0001_initial... OK
複製代碼
**遷移好數據後,若是你試圖登陸用戶,會獲得報錯。**這是由於以前建立的User
數據都沒有對應的Profile
模型,違背了現有的模型。一種解決辦法就是乾脆刪除舊的數據,所以就須要用到Django的shell
命令。
shell
是Django提供的互動解釋器,你能夠在這個指令模式中試驗代碼是否可以正確執行,是至關方便的工具。
在虛擬環境中輸入python manage.py shell
就能夠進入shell:
(env) E:\django_project\my_blog>python manage.py shell
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
複製代碼
看到>>>
表示成功進入shell。
輸入下面兩行指令就能夠輕鬆刪除User數據庫:
>>> from django.contrib.auth.models import User
>>> User.objects.all().delete()
複製代碼
注意由於前面寫的article
模型中,與User
的外鍵也採用了models.CASCADE
級聯刪除模式,所以隨着User的刪除,相關的文章也一併刪除了。
輸入exit()
退出shell
,輸入指令python manage.py createsuperuser
,從新建立管理員帳戶。
對新手來講,修改數據庫常常會致使各類頭疼的問題,好比說字段失效、新字段爲null、賦值錯誤、外鍵連接出錯等等,最終致使整個業務邏輯報錯。所以個人建議是,在設計數據庫時儘可能考慮周全,避免頻繁修改模型。
若是實在要修改,而且已經致使數據庫混亂了,不妨刪除掉
/app/migrations/
目錄下最新的幾個文件,清空相關數據庫,從新遷移數據。
接下來編寫MTV模式的剩餘部分。
有了擴展的Profile
模型後,須要新建一個表單類去編輯它的內容:
userprofile/forms.py
...
# 引入 Profile 模型
from .models import Profile
...
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('phone', 'avatar', 'bio')
複製代碼
而後在userprofile/views.py
中寫處理用戶信息的視圖函數:
userprofile/views.py
...
# 別忘了引入模塊
from .forms import ProfileForm
from .models import Profile
...
# 編輯用戶信息
@login_required(login_url='/userprofile/login/')
def profile_edit(request, id):
user = User.objects.get(id=id)
# user_id 是 OneToOneField 自動生成的字段
profile = Profile.objects.get(user_id=id)
if request.method == 'POST':
# 驗證修改數據者,是否爲用戶本人
if request.user != user:
return HttpResponse("你沒有權限修改此用戶信息。")
profile_form = ProfileForm(data=request.POST)
if profile_form.is_valid():
# 取得清洗後的合法數據
profile_cd = profile_form.cleaned_data
profile.phone = profile_cd['phone']
profile.bio = profile_cd['bio']
profile.save()
# 帶參數的 redirect()
return redirect("userprofile:edit", id=id)
else:
return HttpResponse("註冊表單輸入有誤。請從新輸入~")
elif request.method == 'GET':
profile_form = ProfileForm()
context = { 'profile_form': profile_form, 'profile': profile, 'user': user }
return render(request, 'userprofile/edit.html', context)
else:
return HttpResponse("請使用GET或POST請求數據")
複製代碼
業務邏輯與之前寫的處理表單的視圖很是類似(還記得嗎),就不贅述了。
須要注意下面幾個小地方:
user_id
是外鍵自動生成的字段,用來表徵兩個數據表的關聯。你能夠在SQLiteStudio中查看它。redirect()
是如何攜帶參數傳遞的。而後就是新建模板文件/templates/userprofile/edit.html
:
/templates/userprofile/edit.html
{% extends "base.html" %} {% load staticfiles %}
{% block title %} 用戶信息 {% endblock title %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-12">
<br>
<div class="col-md-4">用戶名: {{ user.username }}</div>
<br>
<form method="post" action=".">
{% csrf_token %}
<!-- phone -->
<div class="form-group col-md-4">
<label for="phone">電話</label>
<input type="text" class="form-control" id="phone" name="phone" value="{{ profile.phone }}">
</div>
<!-- bio -->
<div class="form-group col-md-4">
<label for="bio">簡介</label>
<textarea type="text" class="form-control" id="bio" name="bio" rows="12">{{ profile.bio }}</textarea>
</div>
<!-- 提交按鈕 -->
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</div>
{% endblock content %}
複製代碼
User
、Profile
對象的value
屬性設置了初始值,而多行文本則直接設置{{ profile.bio }}
最後配置熟悉的userprofile/urls.py
:
userprofile/urls.py
...
urlpatterns = [
...
# 用戶信息
path('edit/<int:id>/', views.profile_edit, name='edit'),
]
複製代碼
啓動服務器,輸入地址查看功能是否正常。注意舊的用戶都刪除了(id=1的用戶已經沒有了),這裏的/<int:id>
必須爲新建立的用戶的id。
頁面雖然簡陋,可是方法是相似的。能夠在這個基礎上,擴展爲一個美觀、詳細的用戶信息頁面。
**固然最好再給我的信息添加一個入口。**修改/templates/header.html
:
/templates/header.html
...
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href='{% url "userprofile:edit" user.id %}'>我的信息</a>
...
</div>
...
複製代碼
在前面新建article的章節中,因爲沒有用戶管理的知識,存在一些問題:
new_article.author = User.objects.get(id=1)
強行把做者指定爲id=1的用戶,這顯然是不對的。所以稍加修改def article_create()
:
/article/views.py
...
from django.contrib.auth.decorators import login_required
...
# 檢查登陸
@login_required(login_url='/userprofile/login/')
def article_create(request):
...
# 指定目前登陸的用戶爲做者
new_article.author = User.objects.get(id=request.user.id)
...
複製代碼
重啓服務器,文章正確匹配到登陸的用戶,又能夠愉快的寫文章了。
實際上,刪除文章
article_delete()
、更新文章article_update()
都應該對用戶身份進行檢查。就請讀者嘗試修改吧。
前面咱們已經嘗試過將article
配置到admin後臺,方法是很是簡單的,直接在admin.py
中寫入admin.site.register(Profile)
就能夠了。可是這樣寫會致使User
、Profile
是兩個分開的表,不方便不說,強迫症的你怎麼能受得了。
咱們但願可以在admin中將User
、Profile
合併爲一張完整的表格。方法以下:
/userprofile/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from .models import Profile
# 定義一個行內 admin
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = 'UserProfile'
# 將 Profile 關聯到 User 中
class UserAdmin(BaseUserAdmin):
inlines = (ProfileInline,)
# 從新註冊 User
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
複製代碼
打開admin中的User
表,發現Profile
的數據已經堆疊在底部了:
本章使用一對一連接的方式,擴展並更新了用戶信息。
下一章將學習對圖片的簡單處理。
轉載請告知做者並註明出處。