《Python编程:从入门到实践》第19章笔记:用户/用户注册/身份验证
接上篇django最基本的一些日常用法,这是第19章笔记,希望在做“动手试一试”的时候可以让自己方便参考。
这一章实现了两个功能:
1、让用户能够添加主题Topic和条目Entry,以及编辑既有的条目。
2、建立一个用户注册和身份验证系统,让用户能够注册账户,进而登录和注销。
让用户能够输入数据
一、让用户能够添加新主题(Topic)
关于表单
让用户输入并提交信息的页面都是表单。用户输入时,我们需要进行验证,确认提供的信息是正确的数据类型,而不是恶意的信息。然后我们再对这些有效信息进行处理,并将其保存到数据库的合适地方。
在Django中,可使用ModelForm创建表单
在models.py所在目录创建forms.py文件。
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''}
其中内嵌的Meta类告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。
URL
在learning_logs/urls.py中
path('new_topic/', views.new_topic, name='new_topic'),
视图
views.py中加入如下内容
from django.http import HttpResponseRedirect
from django.urls import reverse
from .forms import TopicForm
def new_topic(request):
# 如果未提交数据,创建一个新表单
if request.method != 'POST':
form = TopicForm()
# 否则对数据进行处理
else:
# 用户输入的数据存在request.POST中
form = TopicForm(request.POST)
# 检查是否有效,有效就保存
if form.is_valid():
form.save()
#保存后就可离开这个页面了,用reverse()获取也页面topics的URL
return HttpResponseRedirect(reverse('learning_logs:topics'))
context = {'form': form}
return render(request, 'learning_logs/new_topic.html', context)
-
导入部分
- 导入HttpResponseRedirect类,用户提交主题后使用这个类将用户重定向到网页topics。
- 函数reverse()根据指定的URL模型确定URL。
-
关于GET请求和POST请求
从服务器读取数据的页面,使用GET请求;需要通过表单提交信息时,通常使用POST请求。
函数new_topic()将请求对象作为参数。用户初次请求该网页时,其浏览器将发送GET请求;用户填写并提交表单时,其浏览器将发送POST请求。根据请求的类型,我们可以确定用户请求的是空表单(GET请求)还是要求对填写好的表单进行处理(POST请求)
模板
创建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 %}来防止攻击者利用表单来获取对服务器未经授权的访问(跨站请求伪造)
- 修饰符as_p让Django以段落格式渲染所有表单元素。
链接
在页面topics中添加一个到页面new_topic的链接
加入
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic:</a>
二、让用户能够添加新条目(Entry)
表单
form.py
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text': ''}
widgets = {'text': forms.Textarea(attrs={'cols': 80})}
其中设置属性widgets可以覆盖Django选择的默认小部件。这里让Django使用forms.Textarea,定制来字段'text'的输入小部件,将文本区域的宽度设置为80列。
URL
在learning_logs/urls.py中加入:
path('new_entry/<int:topic_id>', views.new_entry, name='new_entry'),
视图
views.py
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)
这个与前面new_topic()类似,区别是
- 调用save()时,传递来实参commit=False,让Django创建一个新的Entry对象,并将其存储到new_entry中,但不将它保存到数据库中。
- 调用reverse()时,列表args,其中包含在URL中的所有实参,这里列表中只有一个元素topic_id。
模板
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 %}
其中,表单的实参action包含URL中的topic_id,让视图函数能够将新entry关联到正确的主题。
链接
在topic.html中加入
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">add new entry</a>
</p>
三、让用户能够编辑既有条目
URL
path('edit_entry/<int:entry_id>', views.edit_entry, name='edit_entry'),
视图
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
{% 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 %}
链接
在topic.html中加入
<p>
<a href="{% url 'learning_logs:edit_entry' entry.id %}">edit entry</a>
</p>
用户注册和身份验证系统
一、创建用户账户
创建应用程序users
输入命令:python manage.py startapp users
将users添加到settings.py中:在INSTALLED_APPS加入 'users'
应用程序users的URL:在项目根目录的urls.py 加入path('users/', include('users.urls')),
登录页面
在users中新建一个urls.py
from django.urls import path
from django.contrib.auth.views import LoginView
from . import views
app_name = 'users'
urlpatterns = [
path('login/', LoginView.as_view(template_name='users/login.html'),
name='login'),
]
这里书上是旧版,前面引用的是from django.contrib.auth.views import login
,而新版的Django中内置登录视图不再是函数了,而是类。类视图有个as_view方法,template_name是类视图中的一个变量,默认值是"registration/login.html"。
模板
在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' %}"/>
</form>
{% endblock content %}
链接
在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}}.
{% else %}
<a href="{% url 'users:login' %}">log in</a>
{% endif%}
</p>
用户注销
让用户只需单击一个链接就能注销并返回到主页。
URL
users/urls.py中:path('logout/', views.logout_view, name='logout'),
视图
users/views.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'))
链接
在在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' %}">log out</a>
{% else %}
<a href="{% url 'users:login' %}">log in</a>
{% endif%}
</p>
注册界面
url:path('register/', views.register, name='register'),
视图
views.py变成这样
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth import login, logout, 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)
模板
register.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 %}
链接
base.html加入<a href="{% url 'users:register' %}">register</a>
二、让用户拥有自己的数据
在这里要确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。
使用@login_required限制访问
在函数前面加上这个装饰器。它的代码会检查用户是否已经登录,仅当登录时,才运行下面函数的代码。如果未登录就重定向到登录界面。
为了实现重定向,修改settings.py,在末尾添加:LOGIN_URL = '/users/login/'
然后把learning_logs/views.py中除了index的每个函数都加上这个装饰器。
将数据关联到用户
修改models.py中的Topic
from django.contrib.auth.models import User
再在Topic类中加上字段owner:owner = models.ForeignKey(User)
可以通过shell查看现在有哪些用户:
迁移数据库
python manage.py makemigrations learning_logs
出现提示,选1
It is impossible to add a non-nullable field 'owner' to topic without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Select an option:
下面输入的值是将主题(Topic)关联到的用户id号,这里1,关联到eisen
Please enter the default value as valid Python.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
>>> 1
然后python manage.py migrate
然后可通过shell看到每个Topic所属的User了,都是eisen。
只允许用户访问自己的主题
经过上面操作,虽然......
但是登录了eisen后,复制这个页面的URLlocalhost:8000/topics/2
,登录JX帐号后再输入这个URL也看到下面的内容了。
做点修改即可,views.py中topics函数中,加个fliter,让Django只从数据库中获取owner属性为当前用户的Topic对象。
_topics = Topic.objects.filter(owner=request.user).order_by('date_added')
再限制用户对单个主题的页面的访问。在topic函数中,加个判断,如果不属于这个用户就404
@login_required
def topic(request, topic_id):
_topic = Topic.objects.get(id=topic_id)
if _topic.owner != request.user:
raise Http404
entries = _topic.entry_set.order_by('-date_added')
context = {'topic': _topic, 'entries': entries}
return render(request, 'learning_logs/topic.html', context)
edit_entry中也一样加入这个判断if _topic.owner != request.user:raise Http404
新主题创建时也应关联到当前用户
new_topic函数中,判断有效后,先调用form.save并传递实参commit=False,先修该新主题,在将其保存到数据库中。再将owner属性设置为当前用户。
if form.is_valid():
_new_topic = form.save(commit=False)
_new_topic.owner = request.user
_new_topic.save()
return HttpResponseRedirect(reverse('learning_logs:topics'))
于是就成功了。
最后贴几张图:
本文来自博客园,作者:EisenJi,转载请注明原文链接:https://www.cnblogs.com/eisenji/p/16526726.html