django 实现用户账户功能
本篇使用表单让用户添加新主题、添加新条目和编辑既有条目,建立简单的用户身份验证和注册系统。
教程来自 Python Crash Course.
主要内容:创建表单,实现用户身份验证系统。
设置成中文界面:将setting.py
中的LANGUAGE_CODE
设置成zh-hans
。(默认是en-us
)
1. 让用户能够输入数据
当前只有超级用户能够通过管理网站输入数据,为了让普通用户能够输入数据,需要使用表单创建工具,让用户能够添加新主题、新条目、编辑既有条目。
1.1 添加新主题
1.1.1 用于添加主题的表单
新建文件forms.py
,使用辅助类ModelForm来创建一个Form类。
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
model = Topic # 根据Topic模型创建表单
fields = [\'text\'] # 只包含字段text
labels = {\'text\': \'\'} # 不生成标签
1.1.2 URL模式
# ...
urlpatterns = [
# ...
path(\'new_topic/\', views.new_topic, name=\'new_topic\'),
]
1.1.3 视图 new_topic
from django.shortcuts import render, redirect
from .models import Topic
from .forms import TopicForm
# ...
def new_topic(request):
# 添加新主题
if request.method != \'POST\':
# 刚进入new_topic页面,GET方法,未提交数据,则创建一个空表单
form = TopicForm()
else:
# POST提交了数据,对表单数据进行处理,并将用户重定向到topics页面
form = TopicForm(data=request.POST)
# 检查表单是否有效
if form.is_valid():
form.save()
return redirect(\'learning_logs:topics\')
# 不是POST请求,显示空表单或无效表单
context = {\'form\':form}
return render(request, \'learning_logs/new_topic.html\', context)
1.1.4 新主题模板
new_topic.html
,as_p
让Django以段落格式渲染所有表单元素。
{% extends "learning_logs/base.html" %}
{% block content %}
<p>添加新主题</p>
<form action="{% url \'learning_logs:new_topic\' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">添加主题</button>
</form>
{% endblock content %}
1.1.5 从topics链接到新主题页面
topics.html
...
</ul>
<a href="{% url \'learning_logs:new_topic\' %}">添加一个新主题</a>
{% endblock content %}
1.1.6 页面效果
1.2 添加新条目
1.2.1 用于添加新条目的表单
# forms.py
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
class Meta:
model = Topic # 根据Topic模型创建表单
fields = [\'text\'] # 只包含字段text
labels = {\'text\': \'\'} # 不生成标签
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = [\'text\']
labels = {\'text\': \'\'}
# 定义text字段的widgets小部件,将文本区域的宽度设置为80列
widgets = {\'text\': forms.Textarea(attrs={\'cols\': 80})}
1.2.2 URL模式
# ...
urlpatterns = [
# ...
path(\'new_entry/<int:topic_id>/\', views.new_entry, name=\'new_entry\'),
]
1.2.3 视图new_entry
from django.shortcuts import render, redirect
from .models import Topic
from .forms import TopicForm, EntryForm
# ...
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,需要添加topic属性,所以commit=False表示不提交
new_entry = form.save(commit=False)
# 设置对应主题
new_entry.topic = topic
# 存入数据库
new_entry.save()
return redirect(\'learning_logs:topic\', topic_id=topic_id)
context = {\'topic\': topic, \'form\': form}
return render(request, \'learning_logs/new_entry.html\', context)
1.2.4 新条目模板
new_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p><a href="{% url \'learning_logs:topic\' topic.id %}">{{ topic }}</a></p>
<p>添加新条目:</p>
<form action="{% url \'learning_logs:new_entry\' topic.id %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">添加条目</button>
</form>
{% endblock content %}
1.2.5 从topic链接到新条目页面
topic.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>主题: {{ topic }}</p>
<p>条目: </p>
<p>
<a href="{% url \'learning_logs:new_entry\' topic.id %}">添加新条目</a>
</p>
<ul>
...
1.2.6 页面效果
1.3 编辑条目
1.3.1 URL模式
# ...
urlpatterns = [
# ...
path(\'edit_entry/<int:entry_id>/\', views.edit_entry, name=\'edit_entry\'),
]
1.3.2 视图edit_entry
from django.shortcuts import render, redirect
from .models import Topic, Entry
from .forms import TopicForm, EntryForm
# ...
def edit_entry(request, entry_id):
# 编辑已经存在的条目
# 根据entry_id获取entry和相应topic
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != \'POST\':
# 初次请求,使用当前条目填充表单
form = EntryForm(instance=entry)
else:
# POST请求,根据既有条目对象创建一个表单实例,并根据request.POST对它进行修改
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
# 表单有效,则保存
form.save()
# 重定向到该主题页面
return redirect(\'learning_logs:topic\', topic_id=topic_id)
context = {\'entry\': entry, \'topic\': topic, \'form\': form}
return render(request, \'learning_logs/edit_entry.html\', context)
1.3.3 编辑条目模板
edit_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p><a href="{% url \'learning_logs:topic\' topic.id %}">{{ topic }}</a></p>
<p>编辑条目:</p>
<form action="{% url \'learning_logs:edit_entry\' entry.id %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">保存改动</button>
</form>
{% endblock content %}
1.3.4 从topic链接到新条目页面
topic.html
...
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:\'Y-m-d H:m:s\' }}</p>
<p>{{ entry.text|linebreaks }}</p>
<p>
<a href="{% url \'learning_logs:edit_entry\' entry.id %}">编辑条目</a>
</p>
</li>
...
1.3.5 页面效果
2. 创建用户账户
建立用户注册和身份验证系统,让用户能够注册、登录和注销。
创建一个应用程序,其中包含与处理用户账户相关的所有功能。
对Topic进行修改,让每个主题都归属于特定用户。
2.1 应用程序users
python manage.py startapp users
加入INSTALLED_APPS
,注意顺序!!!users
要在admin
之前
# mysite/settings.py
INSTALLED_APPS = [
\'polls.apps.PollsConfig\',
\'learning_logs\',
\'users\',
\'django.contrib.admin\',
\'django.contrib.auth\',
\'django.contrib.contenttypes\',
\'django.contrib.sessions\',
\'django.contrib.messages\',
\'django.contrib.staticfiles\',
]
# mysite/urls.py
urlpatterns = [
path(\'admin/\', admin.site.urls),
path(\'users/\', include(\'users.urls\')),
path(\'polls/\', include(\'polls.urls\')),
path(\'learning_logs/\', include(\'learning_logs.urls\')),
]
2.2 登录页面
使用django自带的认证系统django.contrib.auth
# users/urls.py
"""为应用程序users定义URL模式"""
from django.urls import path, include
app_name = \'users\'
urlpatterns = [
path(\'\', include(\'django.contrib.auth.urls\')),
]
2.2.1 登录模板
用户请求登录页面时,Django会使用默认视图,但是仍需要提供模板login.html
。
默认的认证视图会在templates下的registration文件夹中查找模板,
users/templates/registration/login.html
{% extends "learning_logs/base.html" %}
{% block content %}
{% if form.errors %}
<p>用户名或密码错误,请重试</p>
{% endif %}
<form method="post" action="{% url \'users:login\' %}">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">登录</button>
<input type="hidden" name="next"
value="{% url \'learning_logs:index\' %}" />
</form>
{% endblock content %}
2.2.2 链接到登录页面
在base.html中添加到登录页面的链接
<p>
<a href="{% url \'learning_logs:index\' %}">学习笔记</a> -
<a href="{% url \'learning_logs:topics\' %}">主题</a> -
{% if user.is_authenticated %}
你好,{{ user.username }}
{% else %}
<a href="{% url \'users:login\' %}">登录</a>
{% endif %}
</p>
{% block content %}{% endblock content %}
2.2.3 使用登录页面
登录URL:http://localhost:8000/users/login/
2.3 注销
2.3.1 链接到注销链接
base.html
...
{% if user.is_authenticated %}
你好,{{ user.username }}
<a href="{% url \'users:logout\' %}">注销</a>
{% else %}
...
2.3.2 注销模板
users/templates/registration/logged_out.html
{% extends "learning_logs/base.html" %}
{% block content %}
<p>你已注销成功,感谢你的访问!</p>
{% endblock content %}
2.3.3 注销页面
2.4 注册页面
使用Django自带的UserCreationForm
创建新用户注册页面,但编写自己的视图函数和模板。
2.4.1 注册URL模式
# ...
from . import views
#...
urlpatterns = [
# ...
# 注册页面
path(\'register/\', views.register, name=\'register\'),
]
2.4.2 视图 register
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm
def register(request):
"""注册新用户"""
if request.method != \'POST\':
# 显示空白注册表单
form = UserCreationForm()
else:
# 处理填写好的表单
form = UserCreationForm(data=request.POST)
if form.is_valid():
new_user = form.save()
# 用户自动登录,重定向到主页
login(request, new_user)
return redirect(\'learning_logs:index\')
# 显示空白或无效表单
context = {\'form\': form}
return render(request, \'registration/register.html\', context)
2.4.3 注册模板
register.html
{% extends "learning_logs/base.html" %}
{% block content %}
<form method="post" action="{% url \'users:register\' %}">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">注册</button>
<input type="hidden" name="next" value="{% url \'learning_logs:index\' %}" />
</form>
{% endblock content %}
2.4.4 链接到注册页面
base.html
...
<a href="{% url \'users:logout\' %}">注销</a>
{% else %}
<a href="{% url \'users:register\' %}">注册</a> -
<a href="{% url \'users:login\' %}">登录</a>
...
2.4.5 页面显示
3. 让用户拥有自己的数据
创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据。
修改模型Topic,让每个主题都归属于特定用户,条目都属于特定主题,也需要限制访问。
3.1 使用@login_required限制访问
装饰器@login_required只允许已登录的用户访问。
3.1.1 限制对主题Topics页的访问
@login_required检查用户是否已登录,仅当用户已登录时,运行topics函数;否则,重定向到登录页面。
# learning_Logs/views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
#...
@login_required
def topics(request):
#...
Django会重定向到settings.py
中LOGIN_URL
指定的URL:
# My settings for @login_required
LOGIN_URL = \'users:login\'
3.1.2 全面限制对项目“学习笔记”的访问
限制所有与私有用户数据(主题、条目)相关的URL访问
# ...
@login_required
def topics(request):
#...
@login_required
def topic(request, topic_id):
#...
@login_required
def new_topic(request):
#...
@login_required
def new_entry(request, topic_id):
#...
@login_required
def edit_entry(request, entry_id):
#...
3.2 将数据关联到用户
项目“学习笔记”中,应用程序的最高层数据是主题,所有条目都与特定主题相关联,只要每个主题都归属特定用户,就能确定数据库中每个条目的所有者。
修改模型Topic,在其中添加一个关联到用户的外键,迁移数据库。
修改视图,使其只显示与当前登录用户相关联的数据。
3.2.1 修改模型Topic
在Topic中添加onwer字段,通过外键关联到User。
删除用户时,所有关联主题也会被删除。
# models.py
from django.db import models
# new
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)
# new
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.text
3.2.2 确定当前用户名单
为了保存当前已输入的主题数据,迁移数据库前要确认用户id:
3.2.3 迁移数据库
makemigrations
时,Django指出owner是非空字段,但没有默认值,需要修复。
方式1修复直接指定用户id,迁移文件0004(如果之前没做额外模型修改,应该是0003)给主题topic添加字段owner。
验证迁移成功:
3.2.4 关于重置数据库
重置数据库,既有数据包括超级用户等都会丢失,最好是迁移数据库的同时确保用户数据的完整性。
python manage.py flush
3.3 只允许用户访问自己的主题
当前所有用户都可以看到所有主题,需要修改视图,只向用户显示属于自己的主题。
Topic.objects.filter(owner=request.user)
只获取owner属性为当前用户的对象。
# views.py
@login_required
def topics(request):
"""显示所有主题"""
# modified
topics = Topic.objects.filter(owner=request.user).order_by(\'date_added\')
context = {\'topics\': topics}
return render(request, \'learning_logs/topics.html\', context)
3.4 保护用户的主题
限制对显示单个主题页面的访问(当前可以通过输入主题URL如http://localhost:8000/topics/1/ 访问其他用户的主题)
修改topic视图,在获取请求的条目前执行检查,如果主题不属于当前用户,返回404错误。
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import Http404
# ...
@login_required
def topic(request, topic_id):
"""显示单个主题和它的条目"""
topic = Topic.objects.get(id=topic_id)
# 确保主题属于当前用户
if topic.owner != request.user:
raise Http404
# ...
3.5 保护页面edit_entry
edit_entry页面的URL为http://localhost:8000/edit_entry/entry_id/,限制其他用户访问的方法:获取当前条目的主题,判断主题所有者是否为当前用户,不是则返回404错误。
#...
@login_required
def edit_entry(request, entry_id):
# 编辑已经存在的条目
# 根据entry_id获取entry和相应topic
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
# 确保条目对应主题属于当前用户
if topic.owner != request.user:
raise Http404
#...
3.6 将新主题关联到当前用户
当前新主题没有关联到任何用户,即添加新主题没有指定owner字段的值,需要修改new_topic视图。
@login_required
def new_topic(request):
# 添加新主题
if request.method != \'POST\':
# 刚进入new_topic页面,GET方法,未提交数据,则创建一个空表单
form = TopicForm()
else:
# POST提交了数据,对表单数据进行处理,并将用户重定向到topics页面
form = TopicForm(data=request.POST)
# 检查表单是否有效
if form.is_valid():
new_topic = form.save(commit=False)
new_topic.owner = request.user
new_topic.save()
return redirect(\'learning_logs:topics\')
# 不是POST请求,显示空表单或无效表单
context = {\'form\':form}
return render(request, \'learning_logs/new_topic.html\', context)
先将表单信息保存到new_topic中,为其添加owner属性后,保存到数据库中。