用户账户

用户表单

添加主题

创建基于表单的页面的方法几乎与前面创建网页一样:定义一个url,编写一个视图函数并编写一个模板。

用于添加主题的表单

用户输入信息,我们需要验证,确认提供的信息是正确的类型,然后再对有效的信息进行处理,并将其存储到数据库的地方。

Django中创建表单最简单的方式是使用ModelForm,会自动创建表单。创建一个forms.py,将其存储到models.py所在目录中

forms.py

1
2
3
4
5
6
7
8
from django import forms
from .models import Topic

class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['Text']
labels = {'Text':''}

注:首先导入了模块forms以及要使用的模型Topic,后定义了一个TopicForm的类,继承了forms.ModelForm

URL 模式

新建一个网页new_topic,能够通过https://localhost:8000/mew_topic来访问到,将其添加到`learning_logs/urls.py`中

1
2
3
4
5
6
7
8
9
10
# 定义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'), # 新增
]

注:url将请求交给视图函数的new_topic()

编写new_topic视图函数

新的视图函数需要处理两种情况:刚进入new_topic网页(这时应显示空表单);对提交的表单数据进行处理,将用户重定向到网页topics:

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from django.shortcuts import render
from .models import Topic

from django.http import HttpResponse # new
from django.core.urlresolvers import reverse # new
from .forms import TopicForm # new


# Create your views here.
def index(request):
"""学习笔记的主页"""
return render(request,'learning_logs/index.html')
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:
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)

GET和POST请求

对于只是从服务器中读取数据的页面,使用GET请求;

在需要提交表单信息时,使用POST请求。

注1:如果请求是GET,则创建了一个TopicForm实例,将其存储在form中,在通过上下文字典将这个表单发送给模板,由于TopicForm实例没有实参,django将创建一个可供用户填写的空表单。

注2:如果请求时POST,则执行else代码,对提交的表单数据进行处理,用户输入的数据创建一个TopicForm实例;这样的form包含用户提交的信息

注3:要将提交的信息保存到数据库,必须先通过检查确定他们是不是有效的,函数is_valid()核实用户填写了所有必不可少的字段,且输入的数据与要求的字段类型一致;如果所有字段都有效,就调用save()将表单中的数据写入数据库,保存数据后离开页面,用reverse()获取页面topics中的url,并且将其传递给HttpResponseRedirect(),后者将用户的浏览器重定向到页面topics

new_topic模板

1
2
3
4
5
6
7
8
9
10
11
12
13
{% raw %}    <!--由于博客渲染问题,特加raw标注-->
{% 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 %}
{% endraw %}

注1:继承了base.html;定义了一个表单form,实参告诉服务器将提交的表单数据发送到哪里

注2:django使用模板标签{百分号 csrf_token 百分号}来防止攻击者利用表单来获得对服务器未经授权的访问(称为跨站请求伪造);修饰符as_p让Django以段落格式渲染所以表单元素

链接页面

在页面topics中添加一个到页面new_topic的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% raw %}    <!--由于博客渲染问题,特加raw标注-->
{% 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 }}</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 %}
{% endraw %}

添加新条目

添加新条目的表单

创建一个与模型Entry相关联的表单

forms.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django import forms
from .models import Topic
from .models import Entry # new

class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text':''}


# 定义新条目表单
class EntryForm(forms.ModelForm): # new
class Meta:
model = Entry
fields = ['text']
labels = {'text':''}
widgets = {'text':forms.Textarea(attr={'cols':80})}

注:Meta类指出了表单基于的模型以及要在表单中包含的字段;widgets小部件是一个HTML表单元素,如单行文本框,多行文本区域或下拉列表,通过设置属性widgets可覆盖django选择的默认小部件;

url模式new_entry

在用于添加新条目的页面的url模式中,需要包含实参topic_id,因为条目必须与特定的主题相关联

。在该模式下,添加到learning_logs/urls.py

1
2
3
4
5
6
7
8
9
10
11
# 定义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'),
url(r'^new_entry/(?P<topic_id>\d+)/$',views.new_entry,name='new_entry'), # new
]

注:这个模式与形式为https://localhost:8001/new_entry/id/的url匹配,其中id是一个与主题ID匹配的数字。(?P<topic_id>\d+)代码捕获一个数字值,并将其存储在变量topic_id中,请求的url与这个模式匹配时,django将请求与主题发送给函数new_entry

编写视图函数new_entry()

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from django.shortcuts import render
from .models import Topic

from django.http import HttpResponseRedirect
from django.urls import reverse
from .forms import TopicForm,EntryForm # 新增EntryForm


# Create your views here.
# 编写视图new_entry
def new_entry(request,topic_id): # 新增视图new_entry函数
topic = Topic.objects.get(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)

模板new_entry.html

1
2
3
4
5
6
7
8
9
10
11
12
13
{% raw %}    <!--由于博客渲染问题,特加raw标注-->
{% extends 'learning_logs/base.html' %}
{% block content %}
<p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a></p>

<p>Add a mew 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 %}
{% endraw %}

注:在页面顶端显示主题,用户可以知道是在哪个主题中添加条目,该主题名是一个链接,可用于返回该主题的主页面

链接到页面new_entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{% draw %}
{% extends 'learning_logs/base.html' %}

{% block content %}
<p>Topic:{{ topic }}</p>
<p>Entries:</p>
<a href="{% url 'learning_logs:new_entry' topic_id %}">Add new entry</a> <!--新增-->
<ul>
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:'M d,Y H:i' }}</p>
<p>{{ entry.date.text|linebreaks }}</p>
</li>
{% empty %}
<li>There are no entries for this topic yet</li>
{% endfor %}
</ul>
{% endblock content %}
{% endraw %}

编辑条目

url模式

这个页面的url需要传递要编辑的条目的ID,修改后的learning_logs/urls.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 定义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'),
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'), # 新增
]

在urlhttps://localhost/8001/edit_entry/1/中传递的ID存储在形参`entry`中,这个url模式将预期匹配的请求发送给视图函数`edit_entry()`

视图函数edit_entry()

页面edit_entry收到GET请求时,edit_entry()将返回一个表单,使得能够编辑条目,该页面收到POST请求时,他将修改后的文本保存到数据库中:

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.shortcuts import render
from .models import Topic, Entry # 新增Entry

from django.http import HttpResponseRedirect
from django.urls import reverse
from .forms import TopicForm,EntryForm


# Create your views here.

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)

模板edit_entry.html

新建edit_entry.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% draw %}
{% extends 'learning_logs/base.html' %}

{% block content %}
<p><a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</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 change</button>
</form>
{% endblock content %}
{% endraw %}

注:实参action将表单发回给函数edit_entry()进行处理,在标签{百分号 url 百分号}中,将条目ID作为一个实参,让视图能够修改正确的条目对象;

链接到页面edit_entry

现在在显示特定主题的页面中,需要给每个条目添加到页面edit_entry的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{% draw %}
{% extends 'learning_logs/base.html' %}

{% block content %}
<p>Topic:{{ topic }}</p>
<p>Entries:</p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a>
<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 %}" methods="post">edit entry</a>
</p> <!--新增-->
</li>
{% empty %}
<li>There are no entries for this topic yet</li>
{% endfor %}
</ul>
{% endblock content %}
{% endraw %}

创建用户账号

也就是建立一个身份验证系统,用户能够注册账号,进而登录和注册;首先创建一个新的应用程序APP,其中包含处理用户账户相关的所有功能

1
python manage.py startapp users

将应用程序添加到settings.py

1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'learning_logs', # 新增的app应用
'users', # 用户注册app # 新增
]

包含应用程序usersurl

也就是修改项目根目录中urls

1
2
3
4
5
6
7
8
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
path(r'admin/', admin.site.urls),
path(r'^users/',include('users.urls',name='users')), # new
path(r'',include(('learning_logs.urls','learning_logs'),namespace='learning_logs')),
]

注:添加了一行代码,以包含应用程序users中的urls.py,这行代码与任何以单词users打头的URL(https://localhost:8001/users/login/)都匹配,还创建了命名空间’users‘,以便将应用程序learning_logs中的url与应用程序users中的url区分开

登录页面

在目录learning_log/users/中,新建名为urls.py

1
2
3
4
5
6
7
8
9
10
"""为应用程序users定义url模式"""
from django.conf.urls import url
from django.contrib.auth.views import LoginView

from . import views

urlpatterns = [
# login
url(r'^login/$',LoginView.as_view(template_name='users/login.html'),name='login'),
]

注:登录页面的url模式与url https://localhost:8001/users/login 匹配,单词login让它将请求发送给django魔人视图auth_login

模板login.html

login.html存储在learning_log/users/templates/users/中,templates为新建文件夹,在此文件夹下再创建users文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% draw %}
{% 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 %}
{% endraw %}

注:一个应用程序的模板可以继承另一个应用程序的模板;如果表单中的errors属性被设置,我们就显示一条错误信息,指出输入的密码或账号不匹配;让登陆页面处理表单,将实参action设置为登录页面的url,登录视图将发送一个表单到模板;隐藏的表单元素,next,其中的实参value告诉django在用户成功登录后将重定向到什么地方--这里是主页

链接到登录页面

base.html中添加到登录页面的链接,让所有页面都包含它。用户已登录时,不显示这个链接,因此使用一个if判断

1
2
3
4
5
6
7
8
9
10
11
12
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a> - <!--创建一个包含项目名的段落,也是一个到主页的链接-->
<a href="{% url 'learning_logs:topics' %}">Topics</a>

{% if user.is_authenticated %} # new
Hello,{{ user.username }}
{% else %}
<a href="{% url 'users:login' %}">log in</a>
{% endif %}

</p>
{% block content %}{% endblock content %}

注:在身份验证系统中,每个模板都可使用变量user,这个变量有一个is_authenticated属性,如果已经登录属性为True,否则的话为False