1. 讓用戶能夠輸入數據
添加幾個頁面,讓用戶能夠輸入數據,能夠添加新主題,添加新條目以及編輯既有條目
只有超級用戶能夠通過管理網站輸入數據,為了不讓用戶與管理網站交互,因此使用Django的表單創建工具來創建讓用戶能夠輸入數據的頁面
- 添加新主題
創建基于表單的頁面方法與創建網頁方法基本一致
定義一個URL
編寫一個視圖函數
編寫一個模版
基于表單的頁面需要導入包含表單的模塊forms.py
創建一個名為forms.py的文件,將其存儲到models.py所在的目錄中,并在其中編寫第一個表單
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
"""內嵌Meta類,告訴Django根據那個模型創建表單,這里根據Topic創建一個表單"""
model = Topic
#該表單只包含字段text
fields = ['text']
#讓Django不要為字段text生成標簽
labels = {'text':''}
URL模式new_topic(http://localhost:8000/new_topic/),修改learning_logs/urls.py中
"""定義learning_logs的URL模式"""
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模式將請求交給視圖函數new_topic()
視圖函數new_topic()
from django.shortcuts import render
from django.http import HttpResponseRedirect
#django.core.urlresolvers在django2.0中合并到django.urls中了
#from django.core.urlresolvers import reverse
from django.urls import reverse
from .models import Topic
from .forms import TopicForm
# Create your views here.
def index(request):
"""學習筆記的主頁"""
return render(request,'learning_logs/index.html')
passxxxxx
def topics(request):
"""顯示所有的主題"""
topics = Topic.objects.order_by('date_added')
context = {'topics':topics}
return render(request,'learning_logs/topics.html',context)
def topic(request, topic_id):
"""顯示單個主題及其所有的條目"""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by('-date_added')
context = {'topic': topic, 'entries': entries}
return render(request, 'learning_logs/topic.html', context)
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)
導入了HttpResponseRedirect類,用戶提交主題后將使用這個類將用戶重定向到網 頁topics。函數reverse()根據指定的URL模型確定URL,這意味著Django將在頁面被請求時生成 URL。
函數new_topic()將請求對象作為參數,用戶初次請求網頁時,其瀏覽器將發送GET請求:用戶填寫并提交表單時,其瀏覽器將發送POST請求,根據請求類型,我們可以確定用戶請求的是空表單(GET請求)還是要求對填寫好的表單進行處理(POST請求)
函數is_valid()合適用戶填寫了所有必不可少的字段,且輸入的數據與要求的字段類型一致,如果所有字段都有效,調用save(),將表單中的數據寫入數據庫,保存數據后,可以離開這個頁面了,使用reverse()獲取頁面topics的URL,并將其傳遞給HttpResponseRedirect(),將用戶的瀏覽器重新定向到頁面topics,在頁面topics中,這主題列表中就可以看到剛才輸入的主題
創建模版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 %}
Django使用模版標簽{% csrf_token %}來防止攻擊者利用表單來獲取對服務器未經授權的訪問(跨站請求偽造)
只需包含模板變量{{ form.as_p }},就可讓Django自動創建顯 示表單所需的全部字段。修飾符as_p讓Django以段落格式渲染所有表單元素,這是一種整潔地顯 示表單的簡單方式。
Django不會為表單創建提交按鈕,因此使用button定義一個這樣的按鈕
鏈接到頁面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 %}
- 添加新條目
為了想添加新條目,再次定義URL,編寫視圖函數和模版,并鏈接到添加新條目的網頁
在forms.py中再添加一個類,創建一個與模型Entry相關聯的表單,這個表單的定制程度比TopicForm要高一些
from django import forms
from .models import Topic,Entry
class TopicForm(forms.ModelForm):
class Meta:
"""內嵌Meta類,告訴Django根據那個模型創建表單,這里根據Topic創建一個表單"""
model = Topic
#該表單只包含字段text
fields = ['text']
#讓Django不要為字段text生成標簽
labels = {'text':''}
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text':''}
widgets = {'text':forms.Textares(attrs={'cols':80})}
屬性widgets,小部件(widget)是一個HTML表單元素,如但行文本框,多行文本區域或下拉列表。通過設置屬性widgets,可覆蓋Django選擇的默認小部件。通過讓 Django使用forms.Textarea,定制字段'text'的輸入小部件,將文本區域的寬度設置為80列,而不是默認的40列。這給用戶提供了足夠的空間,可以編寫有意義的條目。
URL模式new_entry
"""定義learning_logs的URL模式"""
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')
]
視圖函數new_entry()
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Topic
from .forms import TopicForm,EntryForm
# Create your views here.
def index(request):
"""學習筆記的主頁"""
return render(request,'learning_logs/index.html')
passxxxxx
def topics(request):
"""顯示所有的主題"""
topics = Topic.objects.order_by('date_added')
context = {'topics':topics}
return render(request,'learning_logs/topics.html',context)
def topic(request, topic_id):
"""顯示單個主題及其所有的條目"""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by('-date_added')
context = {'topic': topic, 'entries': entries}
return render(request, 'learning_logs/topic.html', context)
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)
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)
模版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 %}
鏈接到頁面new_entry,修改topic.html
{% 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>
</li>
{% empty %}
<li>
There are no entries for this topic yet.
</li>
{% endfor %}
</ul>
{% endblock content %}
- 編輯條目,讓用戶能夠編輯既有條目
URL模式edit_entry
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import Topic
from .forms import TopicForm,EntryForm
# Create your views here.
def index(request):
"""學習筆記的主頁"""
return render(request,'learning_logs/index.html')
passxxxxx
def topics(request):
"""顯示所有的主題"""
topics = Topic.objects.order_by('date_added')
context = {'topics':topics}
return render(request,'learning_logs/topics.html',context)
def topic(request, topic_id):
"""顯示單個主題及其所有的條目"""
topic = Topic.objects.get(id=topic_id)
entries = topic.entry_set.order_by('-date_added')
context = {'topic': topic, 'entries': entries}
return render(request, 'learning_logs/topic.html', context)
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)
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)
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.html',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 %}
2. 創建用戶賬戶
建立一個用戶注冊和身份驗證系統,讓用戶能夠注冊賬戶,進而登錄和注銷,將創建一個新的應用程序,包含與處理賬戶相關的所有功能,對模型Topic稍做修改,讓每個主題都歸屬于特定用戶
- 使用命令startapp來創建一個名為users的應用程序
(ll_env) yangdongxingdeMacBook-Pro:learning_log yangdongxing$ python3 manage.py startapp users
(ll_env) yangdongxingdeMacBook-Pro:learning_log yangdongxing$ ls
db.sqlite3 learning_logs manage.py
learning_log ll_env users
(ll_env) yangdongxingdeMacBook-Pro:learning_log yangdongxing$ ls users
__init__.py apps.py models.py views.py
admin.py migrations tests.py
(ll_env) yangdongxingdeMacBook-Pro:learning_log yangdongxing$
將users添加到settings.py中
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#我的應用程序
'learning_logs',
'users',
]
修改項目根目錄的urls.py,其中包含我們將應用程序users定義的URL
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('users/',include('users.urls',namespace='users')),
path('', include('learning_logs.urls', namespace='learning_logs')),
]
添加了一行代碼,以包含應用程序users中的文件urls.py。這行代碼與任何以單詞users 打頭的URL(如http://localhost:8000/users/login/)都匹配。還創建了命名空間'users',以便將應用程序learning_logs的URL同應用程序users的URL區分開來
登錄界面,在learning_log/users/中新建urls.py的文件夾
"""為應用程序users定義URL模式"""
from django.conf.urls import url
#導入默認是圖login
from django.contrib.auth.views import login
from . import views
urlpatterns = [
#登錄界面
url(r'^login/$',login,{'template_name':'users/login.html'},name='login'),
]
在learning_log/users/中,創建目錄templates,在里面創建目錄users,建立登錄模版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' %}"></input>
</form>
{% endblock content %}
注:一個應用程序中的模版可繼承另一個應用程序的模版
在base.htmlz紅添加到登錄頁面的鏈接,讓所有頁面都包含它
<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 'users:login' %}">log in</a>
{% endif %}
</p>>
{% block content %}{% endblock content %}
在Django身份驗證系統中,每個人模版都可使用user,這個變量有一個is_authenticated屬性:如果用戶已登錄,該屬性為True,否則為false,可以使用這個屬性向已通過身份驗證的用戶顯示一條信息,而未通過身份驗證的用戶顯示另一條消息
- 使用登錄頁面
訪問地址http://localhost:8000/admin/,如果依然使用的是管理員身份登錄的,右上角退出當前賬戶
訪問時會報錯TemplateDoesNotExist at /users/login/
[圖片上傳失敗...(image-e34764-1526949597206)]
解決辦法,在settings中添加DIRS即可
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
- 注銷
注銷URL,修改users/urls.py
"""為應用程序users定義URL模式"""
from django.conf.urls import url
#導入默認是圖login
from django.contrib.auth.views import login
from . import views
app_name='users'
urlpatterns = [
#登錄界面
url(r'^login/$',login,{'template_name':'users/login.html'},name='login'),
#注銷
url(r'logout/$',views.logout_view,name='logout'),
]
視圖函數logout_view(),修改users/views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.contrib.auth import logout
# Create your views here.
def logout_view(request):
"""注銷用戶"""
logout(request)
return HttpResponseRedirect(reverse('learning_logs:index'))
pass
從django.contrib.auth中導入了函數logout(),將request對象作為參數,然后重新定向到主頁
鏈接到注銷視圖,在base.html中添加這種鏈接,讓每個頁面都包含它,將其放在標簽{% if user.is_authenticated %}中,使得僅當用戶登錄后才看到它
<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' %}">logout out</a>
{% else %}
<a href="{% url 'users:login' %}">log in</a>
{% endif %}
</p>>
{% block content %}{% endblock content %}
- 注冊頁面,使用Django提供的表單UserCreationForm編寫視圖函數和模板
注冊頁面的URL模式
視圖函數register(),在注冊頁面首次被請求時,視圖函數register()需要顯示一個空的注冊表單,并在用戶提交填寫好的注冊表單時對其進行處理,如果注冊成功,這個函數還需要讓用戶自動登錄,在users/views.py中添加視圖
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth import logout,login,authenticate
from django.contrib.auth.forms import UserCreationForm
# Create your views here.
def logout_view(request):
"""注銷用戶"""
logout(request)
return HttpResponseRedirect(reverse('learning_logs:index'))
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 HttpResponseRedirect(reverse('learning_logs:index'))
context = {'form':form}
return render(request,'users/register.html',context)
默認表單 UserCreationForm,在上面檢查是否是POST請求,如果不是,創建UserCreationForm實例,使用form.is_valid判斷輸入的數據是否有效,如果有效調用表單的放大save(),將用戶名和密碼的散列值保存到數據庫中,save()返回新創建的用戶對象,我們將其存儲在new_user中
讓用戶自動登錄,調用authenticate(),并將實參new_user.username和密碼傳遞給它,用戶注冊時,輸入了倆次密碼,倆次密碼相同,所以使用password和password1是一樣的,如果用戶名和密碼是正確的,方法authenticate()將返回一個通過了身份驗證的用戶對象,調用函數login(),并將對象request和authenticated_user傳遞給它,為新用戶創建有效的會話,將用戶重定向到主頁
注冊模版,在login.html所在的目錄中新建register.html
{% extends "learning_logs/base.html" %}
{% block content %}
<form method="post" action="{% url 'users:register' %}">
{% block content %}
{{ form.as_p }}
<button name="submit">register</button>
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}"/>
</form>
{% endblock content %}
方法as_P,讓Django在表單中正確地顯示所有字段,包括錯誤信息
鏈接到注冊頁面
修改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' %}">logout out</a>
{% else %}
<a href="{% url 'users:register' %}">register</a>
<a href="{% url 'users:login' %}">log in</a>
{% endif %}
</p>>
{% block content %}{% endblock content %}
注冊時報錯The User could not be created because the data didn't validate.