19.1 用戶可以輸入數據html
咱們不想讓用戶與管理網站交互,所以咱們將使用Django的表單來建立讓用戶可以輸入數據的頁面。python
19.1.1 添加新主題數據庫
建立基於表單的頁面的方法交互與前面建立網頁同樣:定義一個URL,編寫一個視圖函數並編寫一個模板。一個主要差異是,須要導入包含表單的模塊forms.py。django
一、用於添加主題的表單服務器
在Django中,建立表單的最簡單的方式是使用ModelForm。建立一個名爲forms.py所在的目錄中,並在其中編寫你的第一個表單:app
from django import forms from .models import Topic class TopicForm(forms.ModelForm): class Meta: model = Topic fields = ['text'] labels = {'text':''}
最簡單的ModelForm版本只包含一個內嵌的Meta類,它告訴Django根據哪一個模型建立表單,以及在表單中包含哪些字段。函數
二、URL模式new_topicpost
這個新網頁的URL應簡短而具備描述性,所以當用戶要添加新主題時,咱們將切換到http://localhost:8000/new_topic。學習
"""定義learning_logs的URL模式""" from django.conf.urls import url from .import views 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'), ]
三、視圖函數網站
函數new_topic()須要處理兩種情形:剛進入new_topic網頁(應顯示一個空表單),對提交的表單數據進行處理,並將用戶重定向到網頁topics:
from django.shortcuts import render from .models import Topic from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .forms import TopicForm # Create your views here. --ship-- def new_topic(request): """添加新主題""" if request.method != 'POST': # 未提交數據:建立一個新表單 form = TopicForm() else: # POST提交的數據,對數據處理 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)
四、模板new_topic
{% 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 %}
Django使用模板標籤{% csrf_token %}來防止攻擊者利用表單來得到對服務器未經受權的訪問(這種攻擊被稱爲跨站請求僞造)。 只須要包含模板變量{{ form.as_p}},就可以讓Django自動建立顯示錶單所需的所有字段。修飾符as_p讓Django以段落格式渲染全部表單元素,這是一種整潔地顯示錶單的簡單方式。
六、連接到頁面new_topic
接下來,咱們在頁面topics中添加一個到頁面new_topic的連接:
{% extends "learning_logs/base.html" %} {% block content %} <p>Topics</p> <ul> {% for topic in topics %} <li> <a href="{% url 'learning_logs:topic' topic.id %}">{{topic}}</a> </li> {% empty %}} <li>No topics have been added yet.</li> {% endfor %} </ul> <a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a> {% endblock content %}
19.1.2 添加新條目
一、用於添加新條目的表單
咱們須要建立一個與模型Entry相關聯的表單,但這個表單的定製程度比TopicForm要高一些:
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})}
小部件(widget)是一個HTML表單元素,如單行文本框、多行文本區域或下拉列表。經過讓Django使用forms.Textarea,咱們定製了字段‘text’的輸入小部件,將文本區域的寬度設置爲80,而不是默認的40列。
二、URL模式new_entry
# 用於添加新條目的頁面 url(r'^new_entry/(?Ptopic_id>\d+)/$',views.new_entry,name='new_entry')
三、視圖函數new_entry
def new_entry(request,topic_id): """在特定的主題中添加新條目""" topic = Topic.objects.get(id=topic_id) if request.method !='POST': # 未提交數據,建立一個空表單 form = EntryForm() else: # POST提交的數據,對數據進行處理 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)
調用save()時,咱們傳遞了實參commit=False,讓Django建立一個新的條目對象,並將其存儲到new_entry中,但不將它保存到數據庫中。咱們將new_entry的屬性topic設置爲在這個函數開頭從數據庫中獲取的主題,而後調用save(),且不指定任何參數。這將把條目保存到數據庫中,並將其與正確的主題相關聯。
從新定向顯示相關主題的頁面、調用reverse()時,須要提供兩個實參:要根據它來生成URL模式的URL模式的名稱;列表args,其中包含要包含在URL中的全部實參。在這裏,列表args只有一個元素——topic_id。接下來調用HttpResponseRedirect()將用戶從新定向到顯示增長條目所屬主題的頁面,用戶將在該頁面的條目列表中看到新添加的條目。
四、模板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"> {% crsf_token %} {{ form.as_p}} <button name="submit">add entry</button> </form> {% endblock content %}}
咱們在頁面頂端顯示了主題,讓用戶知道他是在哪一個主題中添加條目;該主題名也是一個連接,可用於返回該主題的主頁面。
表單的實參action包含URL中的topic_id值,讓視圖函數可以添加新條目關聯到正確的主題。
五、連接到頁面new_entry
{% extends "learning_logs/base.html" %} {% block content %} <p>Topic:{{ topic }}</p> <p>Entries:</p> <p> <a href="{% url 'learning_logs:new_entry' topic.id %}">add new</a> </p> <ul> --ship--</ul> {% endblock content %}
19.1.3 編輯條目
下面建立一個頁面,讓用戶可以編輯既有的條目。
一、URL模式edit_entry
這個頁面的URL須要傳遞要編輯的條目的ID。修改後delearning_logs/urls.py以下:
# 用於編輯條目的頁面 url(r'^edit_entry/(P<entry_id>\d+)/$',views.edit_entry, name='edit_entry'),
二、視圖函數edit_entry()
頁面edit_entry收到GET請求時,edit_entry()返回一個表單,讓用戶可以對條目進行編輯。該頁面收到POST請求時,將修改後的文本保存到數據庫中:
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: # POST提交的數據,對數據進行處理 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.himl',context)
三、模板edit_entry
{% 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 %}}
四、連接到頁面edit_entry
{% extends "learning_logs/base.html" %} {% block content %} <p>Topic:{{ 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_added|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 %}
將編輯連接放在每一個條目的日期和文本後面。
19.2 建立用戶帳戶
19.2.1 應用程序user
首先使用命令startapp來建立一個名爲user的應用程序:
(11_env) D:\learning_log>python manage.py startapp users
這個命令新建了一個名爲user的目錄,其結構與應用程序learning_logs相同。
一、將應用程序users添加到settings.py中
# My apps 'learning_logs', 'users',
二、包含應用程序users的URL
接下來,須要修改項目根目錄中的urls.py,使其包含咱們將爲應用程序users定義的URL:
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/',include('user.urls',namespace='users')), url(r'',include('learning_logs.urls',namespace='learning_logs')), ]
19.2.2 登陸頁面
首先實現登陸頁面,爲此使用Django提供的默認登陸視圖,所以URL模式會稍有不一樣。早目錄learning_log/users/中,新建一個名爲urls.py的文件,並在其中添加以下代碼:
"""爲應用程序users定義URL模式""" from django.conf.urls import url from django.contrib.auth.views import login from .import views urlpatterns = [ # 登陸頁面 url(r'^login/$',login,{'template_name':'users/login.html'},name='login') ]
首先導入默認視圖login。登陸頁面的URL模式與URL http://localhost:8000/users/login匹配。這個URL中的單詞users讓那個Django在users/urls.py中查找,而單詞login讓它將請求發送給Django默認視圖login(視圖實參爲login,而不是views.login)。
一、模板login.html
在目錄learning_log/users/中,建立一個名爲templates的目錄,並在其中建立一個名爲users的目錄。一下是模板login.html,你應將其存儲到目錄learning_log/users/templates/users中:
{% 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 %}
二、連接到登陸頁面
在base.html中添加到登陸頁面的連接,讓全部頁面都包含它。用戶已登陸時,咱們不想顯示這個連接,所以將它嵌套在一個{% if %}標籤中:
<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 }}. {% else %} <a href="{% url 'user.login' %}}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
三、使用登陸頁面
前面創建了一個用戶帳戶,下面來登陸一下,看看登陸頁面是否管用。訪問http://localhost:8000/admin/,若是你依然是以管理員身份登陸的,請在頁眉上找到註銷連接並單擊。
註銷後,訪問http://localhost:8000/users/login,再登陸。
19.2.3 註銷
不建立註銷的頁面,而讓用戶只需單擊一個連接就能註銷並返回到主頁。爲此,咱們將爲註銷連接定義一個URL模式,編寫一個視圖函數,並在base.html中添加一個註銷連接。
一、註銷URL
# 註銷 url(r'^logout/$',views.logout_view,name='logout'),
二、視圖函數logout_view()
from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib.auth import logout # Create your views here. def logout_view(request): """註銷用戶""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index'))
三、連接到註銷視圖
<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:login' %}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
19.2.4 註冊頁面
下面建立一個讓新用戶可以註冊的頁面。咱們將使用Django提供的表單UserCreationForm,但編寫本身的視圖函數和模板。
一、註冊頁面的URL模式
# 註冊頁面 url(r'^register/$',views.register,name='register'),
二、視圖函數register()
在註冊頁面首次被請求時,視圖函數register()須要顯示一個空的註冊表單,並在用戶提交填寫好的註冊表單時對其進行處理。若是註冊成功,這個函數還須要讓用戶自動登陸。
def logout_view(request): """註銷用戶""" logout(request) return HttpResponseRedirect(reverse('learning_logs:index')) def register(request): """註冊新用戶""" if request.method !='POST': # 顯示空的註冊表單 form = UserCreationForm(date=request.POST) 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 HttpResponseRedirect(reverse('learning_logs:index')) context = {'form':form} return render(request,'users/register.html',context)
三、註冊模板
{% 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 %}
這裏也使用了as_p,讓Django在表單中正確地顯示全部的字段,包括錯誤消息——若是用戶沒有正確地填寫表單。
四、連接到註冊頁面
<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 <a href="{% url 'users:login' %}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
19.3 讓用戶擁有本身的數據
19.3.1 使用@login_required限制訪問
Django提供了裝飾器@login_required:對於某些頁面,只容許已登錄的用戶訪問它們。裝飾器是放在函數定義前面的命令,Python在函數運行前,根據它來修改函數代碼的行爲:
一、限制對topics頁面的訪問
在learning_logs/views.py中添加以下代碼:
--ship-- from .forms import TopicForm,EntryForm from django.contrib.auth.decorators import login_required # Create your views here. def index(request): """學習筆記的主頁""" return render(request,'learning_logs/index.html') @login_required def topics(request): --ship--
爲實現這種重定向,咱們須要修改setting.py,讓Django知道到哪去查找登陸頁面。在settings.py末尾添加:
# 個人設置 LOGIN_URL = '/users/login/'
二、全面限制對項目「學習筆記」的訪問
在項目「學習筆記中」,咱們將不限制對主頁、註冊頁和註銷頁面的訪問,並限制對其餘全部頁面的訪問。
在下面的learning_logs/views.py中,對除index()外的每一個視圖都應用了裝飾器@login_required:
若是你在未登陸的狀況下嘗試訪問這些頁面,將被重定向到登陸頁面。另外,你還不能單擊到new_topic等頁面的連接。
19.3.2 將數據關聯到用戶
下面修改模型Topic,在其中添加一個關聯到用戶的外鍵。這樣作後,咱們必須對數據庫進行遷移。最後,咱們必須對有些視圖進行修改,使其只顯示與當前登陸的用戶相關聯的數據。
一、修改模型Topic
from django.db import models from django.contrib.auth.models import User # 定義了一個名爲Topic的類,它繼承了Model—Django中一個定義了模型基本功能的類 # 屬性text是一個CharField—由字符或文本組成的數據。須要存儲少許的文本,如名稱、標題或城市時,可使用 # date_added是一個DateTimeField—記錄日期和時間的數據。 class Topic(models.Model): """用戶學習主題""" text = models.CharField(max_length=200) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User)
--ship--
二、肯定當前有哪些用戶
三、遷移數據庫
爲驗證遷移符合預期,可在Shell會話中像下面這樣作:
19.3.3 只容許用戶訪問本身的主題
在views.py中,對函數topics()作以下修改:
@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)
19.3.4 保護用戶的主題
@login_required def topic(request,topic_id): """顯示單個主題及其全部的條目""" topic = Topic.objects.get(id=topic_id) # 確認請求的主題屬於當前用戶 if topic.owner != request.user: raise Http404 entries = topic.entry_set.order_by('-date_added') context = {'topic':topic,'entries':entries} return render(request,'learning_logs/topic.html',context)
19.3.5 保護頁面edit_entry
@login_required def edit_entry(request,entry_id): """編輯既有條目""" entry = Entry.objects.get(id=entry_id) topic = entry.topic if topic.owner != request.user: raise Http404 if request.method !='POST': --ship--
19.3.6 將新主題關聯到當前用戶
@login_required def new_topic(request): """添加新主題""" if request.method != 'POST': # 未提交數據:建立一個新表單 form = TopicForm() else: # POST提交的數據,對數據處理 form = TopicForm(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)