先把github鏈接放上來:
https://github.com/q100036q/python_demo_learning_logs
上一篇文章從創(chuàng)建一個django環(huán)境開始,一直到制作了一個網(wǎng)站,可以保存主題和內(nèi)容。這篇文章主要描述使用賬號來管理這個網(wǎng)站。
第一:增加新的主題和內(nèi)容:
1.首先引入forms模塊中的ModelForm來創(chuàng)建一個新的表單格式,代碼如下:
#forms.py
from django import forms;
from .models import Topic,Entry;
class TopicForm(forms.ModelForm):
class Meta:
model = Topic;
fields = ['text'];
labels = {'text':''};
class EntryForm(forms.ModelForm):
class Meta:
model = Entry;
fields = ['text'];
labels = {'text':''};
widgets = {'text':forms.Textarea(attrs = {'cols':80})};#這段代碼作用是
這里針對主題和內(nèi)容分別定制了兩個表單,它內(nèi)嵌Meta 類,告訴Django根據(jù)哪個模型創(chuàng)建表單,以及在表單中包含哪些字段。
2.接下來,修改urls文件去添加主題:
#urls.py
from django.conf.urls import url;
from . import views;
app_name='learning_logs'
urlpatterns = [
url(r'^$',views.index,name = 'index'),
url(r'^topics/$',views.topics,name = 'topics'),
url(r'^topics/(?P<topic_id>\d+)/$',views.topic,name = 'topic'),
url(r'^new_topic/&',views.new_topic,name = 'new_topic'),
url(r'^new_entry/(?P<topic_id>\d+)/$',views.new_entry,name = 'new_entry'),
#url(r'^edit_entry/(?P<entry_id>\d+)/$',views.edit_entry,name = 'edit_entry'),這段代碼之后再編輯內(nèi)容的時候去解開注釋
]
這里就是分別對http://localhost:8000/new_topic/ 和 http://localhost:8000/new_entry/1/分別進行匹配,然后去視圖層注冊這兩個函數(shù)。
3.修改視圖層
#views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Topic,Entry;
from .forms import TopicForm,EntryForm;
--snip--#由于代碼太多,這里使用這個表示之前的其他代碼
def new_topic(request):
"""添加新主題"""
if request.method != 'POST':
# 未提交數(shù)據(jù):創(chuàng)建一個新表單
form = TopicForm()
else:
# POST提交的數(shù)據(jù),對數(shù)據(jù)進行處理
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs:topics'))
context = {'form': form}
return render(request, 'learning_logs/new_topic.html', context)
def new_entry(request,topic_id):
topic = Topic.objects.get(id = topic_id);
if request.method != 'POST':
form = EntryForm();
else:
form = EntryForm(data = request.POST);
if form.is_valid():
new_entry = form.save(commit = False);
new_entry.topic = topic;
new_entry.save();
return HttpResponseRedirect(reverse('learning_logs:topic',args = [topic_id]));
context = {'topic':topic,'form':form};
return render(request,'learning_logs/new_entry.html',context);
這兩個函數(shù)的最終的作用就是當新的表單內(nèi)容提交后,通過HttpResponseRedirect模塊分別重新定向到topics.html和topic.html頁面。首先對請求類型進行判斷,是GET請求,就創(chuàng)建一個可被用戶編輯的空表單,經(jīng)過字典的方式傳遞給模板。若是POST請求,則對表單數(shù)據(jù)進行處理,使用用戶輸入的數(shù)據(jù)并保存,在定向到之前顯示數(shù)據(jù)的界面。
關于new_entry函數(shù)內(nèi)容,稍有些不同的地方:調(diào)用save() 時,我們傳遞了實參commit=False ,讓Django創(chuàng)建一個新的條目對象,并將其存儲到new_entry 中,但不將它保存到數(shù)據(jù)庫中。我們將new_entry 的屬性topic 設置為在這個函數(shù)開頭從數(shù)據(jù)庫中獲取的主題,然后調(diào)用save() ,且不指定任何實參。這將把條目保存到數(shù)據(jù)庫,并將其與正確的主題相關聯(lián)。
我們將用戶重定向到顯示相關主題的頁面。調(diào)用reverse() 時,需要提供兩個實參:要根據(jù)它來生成URL的URL模式的名稱;列表args ,其中包含要包含在URL中的所有實參。在這里,列表args 只有一個元素——topic_id 。接下來,調(diào)用HttpResponseRedirect() 將用戶重定向到顯示新增條目所屬主題的頁面,用戶將在該頁面的條目列表中看到新添加的條目。
4.創(chuàng)建模板頁面
根據(jù)以往經(jīng)驗,視圖層結束之后就是模板了,下面把兩個新增加內(nèi)容代碼寫出來:
<!--new_topic.html-->
{% extends 'learning_logs/base.html' %}
{% block content %}
<p>Add a new topic:</p>
<form action="{% url 'learning_logs:new_topic' %}" method = 'post'>
{% csrf_token %}
{{ form.as_p }}
<button name = 'submit'>add topic</button>
</form>
{% endblock content %}
<!--new_entry.html-->
{% extends "learning_logs/base.html" %}
{% block content %}
<p><a href = "{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>
<p>Add a new entry:</p>
<form action="{% url 'learning_logs:new_entry' topic.id %}"method = 'post'>
{% csrf_token %}
{{ form.as_p }}
<button name = 'submit'>add entry</button>
</form>
{% endblock content %}
首先定義了一個HTML的表單,參數(shù)action告訴我們將表單傳遞到哪,參數(shù)method告知這次傳遞使用的是POST的方式,{% csrf_token %}標簽防止攻擊者利用表單來對服務器進行未授權的訪問,模板變量{{ form.as_p }} 可讓Django自動創(chuàng)建顯示表單所需的全部字段。修飾符as_p 讓Django以段落格式渲
染所有表單元素。最后在創(chuàng)建一個提交按鈕來執(zhí)行這些內(nèi)容。
new_entry.html的內(nèi)容和之前類似,多了一個超鏈接回到上一層頁卡,action中多傳遞了一個參數(shù),給表單進行使用,能在第一步的第3條中通過它獲得指定的數(shù)據(jù)。
5.編輯內(nèi)容文本
為了給每個條目的內(nèi)容進行修改,新增加一個模塊來完成它,老規(guī)矩,先去urls.py中進行注冊:
#urls.py
--snip--#這里偷下懶,不再將全部代碼拷貝,后文都用這個代替已有代碼
urlpatterns = [
--snip--
# 用于編輯條目的頁面
url(r'^edit_entry/(?P<entry_id>\d+)/$',views.edit_entry,name = 'edit_entry'),
]
上面代碼匹配http://localhost:8000/edit_entry/1/這樣的一個路徑,接著,去views.py中增加視圖函數(shù):
#views.py
--snip--
def edit_entry(request,entry_id):
entry = Entry.objects.get(id = entry_id);
topic = entry.topic;
if request.method != 'POST':
form = EntryForm(instance=entry);
else:
form = EntryForm(instance=entry,data = request.POST);
if form.is_valid():
form.save();
return HttpResponseRedirect(reverse('learning_logs:topic',args = [topic.id]));
context = {'entry':entry,'topic':topic,'form':form};
return render(request,'learning_logs/edit_entry.html',context);
這個函數(shù)和上面new_entry()函數(shù)很像,先通過id獲得數(shù)據(jù)庫內(nèi)容,然后判斷請求來創(chuàng)建表單,不同的是,這次,創(chuàng)建表單的函數(shù)傳遞了instance參數(shù),目的是根據(jù)這個參數(shù)內(nèi)容來填充表單。因為這個內(nèi)容肯定是指定了條目topic的,所以不需要和之前那樣保存本地指定條目再保存,可以直接保存到數(shù)據(jù)庫。接下來,去編寫新的html文件使用它。
<!--edit_entry.html-->
{% extends 'learning_logs/base.html'%}
{% block content %}
<p><a href="{% url 'learning_logs:topic' topic.id %}">{{topic}}</a></p>
<p>Edit entry:</p>
<form action="{% url 'learning_logs:edit_entry' entry.id %}" method = 'post'>
{% csrf_token %}
{{form.as_p}}
<button name = 'submit'>save changes</button>
</form>
{% endblock content %}
內(nèi)容和之前差不多,多了一個鏈接去返回到條目的頁面。
最后給用戶提供一個編輯已有內(nèi)容的入口,修改下topic.html:
#topic.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Topics: {{ topic }}</p>
<p>Entries:</p>
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
</p>
<ul>
{% for entry in entries %}
<li>
<p>{{ entry.date_add|date:'M d,Y H:i'}}</p>
<p>{{ entry.text|linebreaks }}</p>
<p>
<a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a>
</p>
</li>
{% empty %}
<li>There are no entries for this topic yet.</li>
{% endfor %}
</ul>
{% endblock content %}
至此,給我們的項目進行新增條目和新增內(nèi)容,并編輯內(nèi)容都完成了,接下來將為項目增加賬戶控制。
第二:創(chuàng)建用戶賬戶
為了單獨管理賬戶系統(tǒng),我們新建一個應用程序來處理相關功能。回想一下之前創(chuàng)建learning_logs的方法,使用命令startapp 來創(chuàng)建一個名為users 的應用程序,其結構與應用程序learning_logs 相同:
python manage.py startapp users
接著修改主項目learning_log文件夾下的settings.py和urls.py,將users加入到項目中來。
#setting.py
--snip--
INSTALLED_APPS = (
--snip--
# 我的應用程序
'learning_logs',
'users',
)
--snip--
#urls.py
--snip--
url(r'^users/',include('users.urls',namespace='users')),
--snip--
下面我們將依次對登錄,注銷,注冊三個功能進行處理。
1.登錄
注意,之后基本都是修改users文件夾下的內(nèi)容,若修改別的文件夾,會特殊說明。
修改urls.py添加登錄頁面的url模式:
#urls.py
from django.conf.urls import url
from django.contrib.auth.views import LoginView
from . import views
app_name='users'#一定要加這個
urlpatterns = [
url(r'^login/$',LoginView.as_view(template_name='users/login.html'),name='login'),
#url(r'^logout/$',views.logout_view,name='logout'),暫時注釋注銷路徑
#url(r'^register/$',views.register,name='register'),暫時注釋注冊路徑
]
登錄頁面的URL模式與URL http://localhost:8000/users/login/匹配。這里我們使用django提供的默認登錄視圖,就不用為了登錄再去創(chuàng)建修改views文件了,為此引入了模塊LoginView,django的類視圖擁有自動查找指定方法的功能, 通過調(diào)用是通過as_view()方法實現(xiàn),所以,這里這么寫的意思就是給默認的視圖提供模板。
關于as_view()的工作流程可以看這個文章:http://www.itdecent.cn/p/17860becea09
之后,去完成這個模板文件新建文件夾template,在其中建立文件夾users。新建文件login.html:
<!--login.html-->
{% extends "learning_logs/base.html" %}
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match.Please try again.</p>
{% endif %}
<form method="post" action="{% url 'users:login' %}">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Log in</button>
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
</form>
{% endblock content %}
如果表單的errors 屬性被設置,我們就顯示一條錯誤消息,指出輸入的用戶名—密碼對與數(shù)據(jù)庫中存儲的任何用戶名—密碼對都不匹配。最后面的input type="hidden"證明這是一個隱藏表單,傳遞的參數(shù)value告知django,登陸之后重定向到主頁。
下面,修改一下base.html文件,增加一個登錄的鏈接入口。
<!--learning_logs/base.html-->
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>-
<a href="{% url 'learning_logs:topics' %}">Topics</a>-
{% if user.is_authenticated %}
Hello,{{ user.username }}.
<!--<a href="{% url 'users:logout' %}">Log out</a>暫時注釋掉注銷的入口-->
{% else %}
<!--<a href="{% url 'users:register' %}">register</a>-暫時注釋掉注冊的入口-->
<a href="{% url 'users:login' %}">Log in</a>
{% endif %}
</p>
{% block content %}{% endblock content %}
在Django身份驗證系統(tǒng)中,每個模板都可使用變量user ,這個變量有一個is_authenticated 屬性:如果用戶已登錄,該屬性將為True ,否則為False 。
現(xiàn)在可以訪問http://localhost:8000/users/login/,若登錄了顯示的是你的用戶名,否則,點擊登錄按鈕跳轉到登錄界面試試吧。若已經(jīng)登錄了,可以先注銷,一般保存在瀏覽器的設置里面,清空之后再進去就等點擊登錄了。
2.注銷
修改urls.py,指定索引url路徑,解開上面第1段中的注釋。下面去編寫視圖函數(shù)logout_view()。
#views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect as HRR
from django.urls import reverse
from django.contrib.auth import logout
# Create your views here.
def logout_view(request):
logout(request)
return HRR(reverse('learning_logs:index'))
我們從django.contrib.auth中導入了函數(shù)logout() ,它要求將request 對象作為實參,會將存儲在用戶session的數(shù)據(jù)全部清空。然后,我們重定向到主頁。接著修改base.html,增加注銷的入口鏈接。這個在第1段登錄的里面已經(jīng)寫過被注釋了,解開就可以。
3.注冊
雖然注冊比上面麻煩一些,但是基礎流程是沒有變化的,首先去改urls.py,這里也把上面的注釋解開就行,匹配的是http://localhost:8000/users/register/
修改views.py,這里面要導入一些別的模塊:
#views.py
--snip--
from django.contrib.auth import logout,login,authenticate
from django.contrib.auth.forms import UserCreationForm
--snip--
def register(request):
if request.method != 'POST':
form = UserCreationForm()
else:
form = UserCreationForm(data=request.POST)
if form.is_valid():
new_user = form.save()
authenticated_user = authenticate(username=new_user.username,password=request.POST['password1'])
login(request,authenticated_user)
return HRR(reverse('learning_logs:index'))
context = {'form':form}
return render(request,'users/register.html',context)
使用 authenticate() 函數(shù)認證給出的用戶名和密碼, 而要登錄一個用戶,使用 login() 。該函數(shù)接受一個 HttpRequest 對象和一個 User 對象作為參數(shù)并使用Django的會話( session )框架把用戶的ID保存在該會話中。UserCreationForm是提供的默認表,包括username和兩個password。
關于login函數(shù)和authenticate函數(shù)看這里:https://www.cnblogs.com/ccorz/p/6357815.html
下面,給這個注冊視圖增加模板:
<!--register.html-->
{% extends "learning_logs/base.html" %}
{% block content %}
<form method="post" action="{% url 'users:register' %}">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">register</button>
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
</form>
{% endblock content %}
和login.html幾乎一樣,最后去修改base.html,解開注釋的代碼,增加注冊賬號的入口。
第三:屬于用戶自己的數(shù)據(jù)
1.裝飾器@login_required限制訪問
我們將修改模型Topic ,讓每個主題都歸屬于特定用戶。Django提供了裝飾器@login_required ,對于某些頁面,只允許已登錄的用戶訪問它們。這里開始,代碼回到learning_logs應用程序內(nèi),先去給views.py里面除了index()主頁函數(shù)之外的每一個函數(shù)的頭部都增加裝飾器:
#views.py
--snip--
from django.contrib.auth.decorators import login_required
--snip--
@login_required
def topics(request):
"""顯示所有的主題"""
--snip--
login_required() 的代碼檢查用戶是否已登錄,僅當用戶已登錄時,Django才運行topics() 的代碼。如果用戶未登錄,就重定向到登錄頁面。
為實現(xiàn)這種重定向,我們需要修改settings.py,讓Django知道到哪里去查找登錄頁面。請在settings.py末尾添加如下代碼:
#learning_log/setting.py
""" 項目learning_log的Django設置
--snip--
# 我的設置
LOGIN_URL = '/users/login/'
現(xiàn)在,如果未登錄的用戶請求裝飾器@login_required 的保護頁面,Django將重定向到settings.py中的LOGIN_URL 指定的URL?,F(xiàn)在在未登錄的情況下嘗試訪問這些頁面,將被重定向到登錄頁面。
2.關聯(lián)用戶主題
下面來修改模型Topic ,在其中添加一個關聯(lián)到用戶的外鍵。因為涉及到修改models,我們必須對數(shù)據(jù)庫進行遷移。最后,我們必須對有些視圖進行修改,使其只顯示與當前登錄的用戶相關聯(lián)的數(shù)據(jù)。
#models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Topic(models.Model):
text = models.CharField(max_length = 200);
date_added = models.DateTimeField(auto_now_add = True);
owner = models.ForeignKey(User, on_delete=models.CASCADE);
def __str__(self):
return self.text;
--snip--
這里引入User模型,并且作為一個外鍵生成owner字段。
注意:python3.7下使用models.ForeignKey時一定要傳入實參on_delete=models.CASCADE,作用是刪除主表,數(shù)據(jù)跟著一起刪除。接下來進行數(shù)據(jù)庫遷移:
python manage.py makemigrations learning_logs
此時因為我們代碼中沒有給owner一個默認值,會有報錯提示,提供默認值或者退出回到models.py中填寫默認值,這里輸入1,立刻給一個默認數(shù)據(jù),再輸入一次1,將之前創(chuàng)建的超級用戶作為owner,這里可以不使用超級用戶,在shell會話中可以查看之前注冊過的用戶名和id,新建一個工作窗口,輸入以下shell語句,查看用戶id:
python manage.py shell
from django.contrib.auth.models import User
User.objects.all()
若是覺得之前創(chuàng)建的用戶過多,使用如下語句重置數(shù)據(jù)庫,并在這之后重新去創(chuàng)建超級用戶:
python manage.py flush
3.訪問自己的主題
當前,不管你以哪個用戶的身份登錄,都能夠看到所有的主題。我們來改變這種情況,只向用戶顯示屬于自己的主題?,F(xiàn)在修改views.py的topics()函數(shù):
#views.py
--snip--
@login_required
def topics(request):
topics = Topic.objects.filter(owner=request.user).order_by('date_added');
context = {'topics':topics}
return render(request,'learning_logs/topics.html',context)
--snip--
用戶登錄后,request 對象將有一個user 屬性,這個屬性存儲了有關該用戶的信息。代碼Topic.objects.filter(owner=request.user) 讓Django只從數(shù)據(jù)庫中獲取owner 屬性為當前用戶的Topic 對象?,F(xiàn)在主題列表頁面已經(jīng)被限制了,只能顯示當前登錄用戶所有的主題,但是如果更換用戶,使用url的方式訪問這個主題的內(nèi)容頁面,比如http://localhost:8000/topics/1/還是可以進入,所以下面對每個主題的內(nèi)容頁面進行限制:
#views.py
from django.shortcuts import render;
from django.http import HttpResponseRedirect,Http404;
from django.urls import reverse;
# Create your views here.
from django.contrib.auth.decorators import login_required;
from .models import Topic,Entry;
from .forms import TopicForm,EntryForm;
def check_topic_owner(request,topic):
if topic.owner != request.user:
raise Http404;
@login_required
def topic(request,topic_id):
topic = Topic.objects.get(id = topic_id);
check_topic_owner(request,topic);
entries = topic.entry_set.order_by('-date_added');
context = {'topic':topic,'entries':entries}
return render(request,'learning_logs/topic.html',context);
引入Http404的目的是為了訪問失敗的時候給一個異常報錯,這里當該主題的用戶不是登錄用戶,就報這個異常。edit_entry這個函數(shù)也和topic函數(shù)一樣需要限制,這里就不重復了。
4.將新增加的主題關聯(lián)用戶
創(chuàng)建新主題時,你必須指定其owner 字段的值,否則會報錯。這里要修改new_topic函數(shù):
#views.py
--snip--
@login_required
def new_topic(request):
if request.method != 'POST':
form = TopicForm();
else:
form = TopicForm(data = request.POST);
if form.is_valid():
new_topic = form.save(commit=False);
new_topic.owner = request.user;
new_topic.save();
return HttpResponseRedirect(reverse('learning_logs:topics'));
context = {'form':form};
return render(request,'learning_logs/new_topic.html',context);
--snip--
這里對比之前寫的new_topic函數(shù)修改了保存數(shù)據(jù)庫內(nèi)表單的代碼,具體參考第一第3段。原來代碼表單如果正確就保存進數(shù)據(jù)庫,現(xiàn)在為了增加一個owner的值,save函數(shù)內(nèi)傳遞commit=False字段,先保存到new_topic里,設置好owner之后在保存。
new_entry函數(shù)也同樣進行限制,只需要把check_topic_owner這個函數(shù)調(diào)用就行。至此,給這個項目創(chuàng)建一個賬戶管理系統(tǒng)就完成了。