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.