第十九章 用戶交互
一泼菌、添加新主題
創(chuàng)建基于表單的頁(yè)面的方法幾乎與前面創(chuàng)建網(wǎng)頁(yè)一樣:定義URL;編寫一個(gè)視圖函數(shù)并編寫模板琅关,一個(gè)主要差別是煮岁,需要導(dǎo)入包含表單的模塊。
from django import forms
from .models import Topic,Entry
#新增主題form
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text':''}
#新增條目form
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text':''}
widgets = {'text':forms.Textarea(attrs={'cols':80})}
最簡(jiǎn)單的ModelForm版本只包含一個(gè)內(nèi)嵌的Meta類涣易,它告訴Django根所哪個(gè)模型創(chuàng)建表單画机,以及在表單中包含哪些字段。
視圖函數(shù)
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from .forms import TopicForm,EntryForm
def new_topic(request):
if request.method != 'POST':
form = TopicForm()
else:
form = TopicForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('learning_logs/topics.html'))
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)
GET:從服務(wù)器讀取數(shù)據(jù)的頁(yè)面新症,POST:用戶提交表單信息步氏;is_valid():核對(duì)是否填寫了所有必不可少的字段,且輸入的數(shù)據(jù)與要求的字段類型是否一致账劲;save():將表單數(shù)據(jù)寫入數(shù)據(jù)庫(kù)戳护,
commit=False
讓Django創(chuàng)建一個(gè)新條目,reverse():把實(shí)參轉(zhuǎn)換成URL瀑焦,列表args腌且,包含URL中的所有實(shí)參;HttpResponseRedirect():將瀏覽器重定向
創(chuàng)建新增主題模板
{% extends "learning_logs/base.html" %}
{% block content %}
<p>Add New Topic:</p>
<form action="{% url 'learning_logs:new_topic' %}" method='post'>
{% csrf_token %}
{{ form.as_p}}
<button name="submit">add topic</button>
</form>
</ul>
{% endblock content%}
{% csrf_token %}:防止攻擊者利用表單來(lái)獲得對(duì)服務(wù)器未經(jīng)過(guò)授權(quán)的訪問(wèn)(這種攻擊被稱跨站請(qǐng)求) ;{{ form.as_p}}:讓Django自動(dòng)創(chuàng)建顯示表單的全部字段榛瓮,
as_p
讓Django以段落格式渲染所有表單元素
二铺董、編輯條目
def edit_entry(request,entry_id):
entry = Entry.objects.get(id=entry_id)
if request.method != 'POST':
form = EntryForm(instance=entry)
else:
form = EntryForm(instance=entry,data=request.POST)
if form.is_valid():
form.save(commit=False)
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)
instance=entry:創(chuàng)建一個(gè)表單,并使用既有對(duì)象中的數(shù)據(jù) 填充;POST時(shí)精续,
instance=entry
和data=request.POST
使Django根據(jù)即有對(duì)像創(chuàng)建表單實(shí)例坝锰,并根所request.POST
中的相關(guān)數(shù)據(jù)進(jìn)行修改
編輯模板
{% 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>
</ul>
{% endblock content%}
三、建立應(yīng)用程users
1重付、建立應(yīng)用:
python manage.py startapp users
2顷级、在settings.py增加users
3、在learning_log下urls.py增加
path(r'^users/',include(('users.urls','users'),namespace='users')),
4确垫、在users目錄下建立urls.py
from django.conf.urls import url
from django.contrib.auth.views import login
from . import views
urlpatterns = [
#匹配 http://localhost:8000/users/login/
url(r'^login/$',login,{'template_name':'users/login.html'}),name='login'),
]
URL中users讓Django在users/urls.py中查找弓颈,login將請(qǐng)求發(fā)送給默認(rèn)視圖login,{'template_name':'users/login.html'})告訴Django去哪里查找模板
5删掀、建立login.html模板
在目錄users\templates\users
下新建login.html
{% extends "learning_logs/base.html" %}
{% block content %}
{% if form.errors %}
<p>Your name 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">login</button>
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
</form>
{% endblock content %}
login.html
繼承base.html
翔冀,一個(gè)應(yīng)用程序中的模板可繼承另一個(gè)應(yīng)用程序中的模板。
form.errors:表示表單是否有錯(cuò)誤
<input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
:表示登錄成功后要跳轉(zhuǎn)的網(wǎng)頁(yè)
6披泪、登出logout纤子, 在ulrs.py
中加入url(r'^logout/$', views.logout_view, name='logout'),
,在view.py
中加入
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth import logout
def logout_view(request):
logout(request)
return HttpResponseRedirect(reverse('learning_logs:index'))
7款票、注冊(cè)頁(yè)面控硼,在urls.py
中加url(r'^resgister/$',views.resgister,name='resgister'),
,在view.py
增加resgister()
def resgister(request):
if request.method != 'POST':
form = UserCreationForm()
else:
form = UserCreationForm(request.POST)
if form.is_valid():
new_user = form.save()
authenticated_user = authenticate(username=new_user.username,
password=request.POST['password1'])
login(requst,authenticated_user)
return HttpResponseRedirect(reverse('learning_logs:index'))
context = {'form':form}
return render(request,'user/resgister.html',context)
authenticate()*:接收用戶名和密碼徽职,返一個(gè)通過(guò)身份驗(yàn)證的對(duì)象authenticated_user
login():接收requst
和authenticated_user
象颖,為新用戶創(chuàng)建有效會(huì)話
8佩厚、注冊(cè)模板resgister.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 %}
9姆钉、讓用戶擁有自己的數(shù)據(jù)
裝飾器(decorator)是放在函數(shù)定義前面的指令,Python在函數(shù)運(yùn)行前抄瓦,根據(jù)它來(lái)修改函數(shù)的行為潮瓶。
@login_required:只允許已登錄的用戶訪問(wèn)。
10钙姊、將數(shù)據(jù)與用戶關(guān)聯(lián)
查看用戶
python manage.py shell
>>>from django.contrib.auth.models import User
>>>User.objects.all()
<QuerySet [<User:admin>,<User:jimmy>]>
遷移數(shù)據(jù)庫(kù)
python manage.py makemigration learning_logs
You are trying to add a non-nullable field 'owner' to topic without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
- Provide a one-off default now (will be set on all existing rows)
提供默認(rèn)值
- Quit, and let me add a default in models.py
退出在Model中添加默認(rèn)值
Select an option: 1
Please enter the default value now, as valid Python The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>>1
Migrations for 'learning_logs':
0003_topic_owner.py:
Add field owner to topic
應(yīng)用遷移python manage.py migrate
你可重置數(shù)據(jù)庫(kù)而不是遷移毯辅,但是這樣做,會(huì)丟失所有數(shù)據(jù)煞额。如果需要一個(gè)全新的數(shù)據(jù)庫(kù)思恐,可以使用命令python manage.py flush重建數(shù)據(jù)庫(kù)結(jié)構(gòu)。
10膊毁、限制用戶行為
#views.py
#def topics(request):
#只顯示當(dāng)前用戶主題
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
#def topic(request胀莹,topic_id):
#保護(hù)用主題,因?yàn)槿魏我训卿浀挠脩舳伎梢暂斎險(xiǎn)RL(http://localhost:8000/topics/1/)地址來(lái)訪問(wèn)頁(yè)面
if topic.owner != request.user:
raise Http404
#保護(hù)編輯條目頁(yè)面
#def edit_entry(request,entry_id):
if topic.owner != request.user:
raise Http404