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属性后,保存到数据库中。