Django入门3
19.2.4 注册页面
下面来创建一个让新用户能够注册的页面。我们将使用 Django 提供的表单 UserCreationForm,但编写自己的视图函数和模板。
1.注册页面的 URL 模式
# learning_log/users/urls.py
--snip--
urlpatterns = [
--snip--
# 注册
url(r'^register/$', views.register, name='register'),
]
2.视图函数 registerer()
#users/views.py
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.views import login
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth import logout, authenticate
def logout_view(request):
--snip--
def register(request):
"""注册新用户"""
# 如果不是 POST 请求,就先创建一个 UserCreationForm实例
# UserCreationForm:默认表单
if request.method != 'POST':
# 显示空的注册表单
form = UserCreationForm()
else:
# 处理填写好的表单
form = UserCreationForm(data=request.POST)
if form.is_valid():
# 如果提交的数据有效,我们调用表单的 save(),将用户名和密码的散列值保存到数据库。
# 方法 save() 返回新创建的用户对象,我们将其存储到 new_user 中。
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)
-
1.函数 login() 和函数 authenticate()可以让用户注册成功后自动登录。
-
2.在注册的时候,如果用户提交的注册表单数据有效,调用方法 save() 用户名和密码的散列值保存到数据库,并返回新创建的用户对象,存储到 new_user中。
-
3.自动登录过程中,调用 authenticate()并把实参 new_user.username和密码(因为表单有效,两个密码相同,所以我们使用第一个密码'password1')传给它。
-
4.方法 authenticate() 返回一个通过身份验证的用户对象,我们将其保存在 authenticate_user中。接下来调用函数 login(),并把对象 request 和 authenticate_user 传给他。
-
5.最后,我们将重定向到主页,其页眉显示一个问候语,让用户知道注册成功了。
3.注册模板
rehister.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 %}
{#as_p,让 Django 在表单中正确的显示所有的字段,包括错误信息。#}
4.链接到注册页面
<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 %}
现在,已登录的用户看到的是个性化的问候语和注销链接,未登录的用户看到的是注册链接和登录链接。尝试使用注册页面创建几个用户各不同的用户账号,接下来我们将对一些用户进行限制,仅让已登录的用户访问它们,我们还将确保每个主题属于特定的用户。
19.3 让用户拥有自己的数据
19.3.1 使用 @login_required 限制访问
1.限制对 topics 页面的访问
#learning_logs/views.py
--snip--
from django.contrib.auth.decorators import login_required
--snip--
# 使用 @login_required 限制访问。
# 加上装饰器( @login_required )之后,python会在运行 topics()的代码前先运行 login_required()的代码。
# login_required()会检查用户是否已登录,仅当用户已登录时,django才运行topics()。
# 如果未登录,就重定向到登录页面。 为实现重定向,需要修改 setting.py。
@login_required
def topics(request):
--snip--
修改 setting.py
# 我的设置
# 现在,如果为登录的用户请求装饰器 @login_required 的保护页面, Django 将重定向到 setting.py 中 LOGIN_URL 指定的 URL。
LOGIN_URL = '/users/login/'
2.全面限制对项目的访问
现在,我们不限制对主页、注册、注销页面的访问,而限制对其他所有页面的访问。
在 learning_logs/views.py 中,除了index()意外,每个视图都加上 @login_required。
--snip--
@login_required
def topics(request):
--snip--
@login_required
def topic(request, topic_id):
--snip--
@login_required
def new_topic(request):
--snip--
@login_required
def new_entry(request, topic_id):
--snip--
@login_required
def edit_entry(request, entry_id):
--snip--
19.3.2 将数据关联到用户
1.修改模型Topic
修改 learning_logs/models.py
from django.db import models
from django.contrib.auth.models import User
class Topic(models.Model):
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User)
def __str__(self):
return self.text
class Entry(models.Model):
--snip--
我们首先导入了 django.contrib.auth.models import User ,然后在 Topic 中添加了字段 owner,它建立到模型 User 的外键关系。
2.确定有哪些用户
我们迁移数据库时,Django 将对数据库进行修改,使其能够存储主题和用户之间的关联。为执行迁移,Django 需要知道该将各个既有主题关联到那个账户。最简单的方法是,将主题都关联到一个账户。
下面先查看已创建的索引账户的ID。
启动一个 Django shell 会话
(11_env) C:\learning_log>python manage.py shell
我们遍历用户列表,打印每位用户的用户名和 ID。Django 询问要将既有主题关联懂啊哪个账户时,我们将指定其中的一个 ID 值。
3.迁移数据库
- 1.数据迁移
知道 ID 后就可以迁移数据库了。
- 2.应用数据迁移
- 3.验证结果
19.3.3 只允许用户访问自己的主题
当前不管哪个用户登录,都能看到所有主题,现在我们让用户只能看到自己的主题。
修改 learning_logs/views.py
#--snip--
@login_required
def topics(request):
'''显示所有主题'''
# 用户登录后,request 对象会有一个 user属性,其存储了有关该用户的所有信息。
# filter() 用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
# 在这里我们用这个函数从数据库中只获取 owner 属性为当前用户的 Topic 对象。
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
context = {'topics': topics}
return render(request, 'learning_logs/topics.html', context)
#--snip--
19.3.4 保护用户的主题
在此之前,已登录的用户可输入 URL http://127.0.0.1:8000/topics/1/来访问显示相应主题的页面。
为修复这种问题,我们在视图函数 topic() 获取请求的条目前执行检查。
from django.shortcuts import render
from django.http import HttpResponseRedirect, Http404
from django.core.urlresolvers import reverse
#--snip--
@login_required
def topic(request, topic_id):
'''显示单个主题以其所有条目'''
topic = Topic.objects.get(id=topic_id)
# 确认请求的主题属于当前用户
if topic.name != request.user:
raise Http404
entries = topic.entry_set.order_by('-date_added')
context = {'topics': topics, 'entries': entries}
return render(request, 'learning_logs/topic.html', context
#--snip--
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':
--snip--
渲染网页前检查该主题是否属于当前登录的用户,如果不是,则 raise Http404 。
19.3.6 将新主题关联到当前用户
#--snip--
@login_required
def new_topic(request):
'''添加新主题'''
# 确定 GET or POST,如果不为 Post,返回一个空表单。
if request.method != 'POST':
# 未提交数据:创建一个新表单
form = TopicForm()
else:
# POST提交的数据,对数据进行处理,数据在 request.POST 中。
form = TopicForm(request.POST)
# 检查是否填写了所有必不可少的字段。(是否有效)
if form.is_valid():
# form.save()
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)
#--snip--
首先调用 form.save(commit=False) 是为了我们先修改新主题,再将其保存到数据库中(暂时不保存到数据库)。接下来将新主题的 owner 属性设置为当前用户后,对刚定义的主题实例调用 save(),最后在保存提交到数据库中。