让用户拥有自己的数据
用户应该能够以输入其专有的数据,因此我们将创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据
Ⅰ、使用@login_required限制访问
Django提供的装饰器@login_required
,让你能够轻松地实现这样的目标:对于某些页面,只允许已经登录的用户访问它们,装饰器是放在函数定义前面的指令,Python在函数运行前,根据它来修改函数代码的行为。下面我们来看一个实例:
① 限制对topics页面的访问
每个主题都归特定用户所有,因此应只允许已登录的用户请求topics
页面,为此,在learning_logs/views.py
中添加以下代码:
# --snip--
# from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from .models import Topic, Entry
# --snip--
@login_required
# def topic(request):
"""显示所有的主题"""
# --snip--
login_required()
的代码检查用户是否已登录,仅当用户已登录时,Django才运行topics()
的代码,如果用户未登录,就重定向到登录页面,为实现这种重定向,我们需要修改settings.py
,让Django知道到哪里去查找登录页面:
# """项目learning_logd的Django设置"""
# --snip--
# 我的设置
LOGIN_URL = '/users/login/'
现在如果未登录的用户请求装饰器@login_required
的保护页面,Django将重定向到settings.py
中的LOGIN_URL
指定的URL。要测试这个设置,可注销并进入主页,然后单击链接Topics
,这将重定向到登录页面,接下来使用你的账户登录,并再次点击主页中的Topics
链接,你将看到topics
页面。
② 全面限制对项目“学习笔记”的访问
最好先确定项目的哪些页面不需要保护,再限制对其他所有页面的访问。在项目“学习笔记”中,我们将不再限制对主页、注册页面和注销页面的访问,并限制对其他所有页面的访问,打开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--
如果你在未登陆的情况下尝试访问这些页面,都将被重定向到登录页面,另外你还不能单击到new_topic
等页面的链接,对于所有与私有用户数据相关的URL,都应限制对它们的访问
Ⅱ、将数据关联到用户
现在我们需要将数据关联到提交它们的用户,我们只需将最高层的数据关联到用户,这样更低层的数据将自动关联到用户,下面来修改模型Topic
,在其中添加一个关联到用户的外键,这样做后,我们必须对数据库进行迁移,最后我们必须对有些视图进行修改,使其只显示与当前登录用户相关联的数据。
① 修改模型Topic
对models.py的修改只涉及到两行代码:
# from django.db import models
from django.contrib.auth.mosels 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
中的模型User
,然后在Topic
添加了字段owner
,它建立到模型User
的外键关系。
② 确定当前有哪些用户
为了执行数据库的迁移,Django需要知道该将各个既有的主题关联到哪个用户,最简单的方法就是将既有主题都关联到一个用户,如超级用户,为此我们需要知道该用户的ID,下面来查看已创建的所有用户的ID,启动一个Django shell 会话
,并执行以下代码:
(venv)learning_log$ python manage.py shell
>>> from django.contrib.auth.models import User
>>> User.object.all()
[<User: ll_admin>, <User:eric>, <User: willie>]
>>> for user in User.object.all():
... print(user.username, user.id)
...
ll_admin 1
eric 2
willie 3
>>>
我们在shell会话
中导入了模型User
,然后我们查看到目前为止都创建了哪些用户,输出中列出了三个用户:ll_admin
、eric
和willie
,最后我们遍历用户列表并打印每位用户的用户名和ID,当Django询问要将既有主题关联到哪个用户时,我们将指定其中的一个ID值。
③ 迁移数据库
知道用户ID之后,就可以迁移数据库了:
(venv)learning_log$ python manage.py makemigrations learning_logs
You are trying to add a non-nullable field 'owner' to topic without a default
we can't do that (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)
2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The detatime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>> 1
Migrations for 'learning_logs':
0003_topic_owner.py:
- Add field owner to topic
我们首先执行了命令了makemigrations
,在代码的第2行Django指出我们试图给既有模型Topic
添加一个必不可少(不可为空)的字段,而该字段没有默认值,在代码的第4行Django给我们提供了两种选择:要么现在提供默认值,要么退出并在models.py
中添加默认值,在代码的第6行,我们选择了第一个选项,因此Django让我们输入默认值。为将所有既有主题都关联到管理用户ll_admin
,我们在代码的第10行输入了用户ID值1,并非必须使用超级用户,而可使用已创建的任何用户的ID。
接下来,Django使用这个值来迁移数据库,并生成了迁移文件0003_topic_owner.py
,它在模型Topic
中添加字段owner
,为此,我们在活动的虚拟环境下执行以下代码:
(venv)learning_log$ python manage.py migrate
为验证迁移是否符合预期,我们可在shell会话
中执行以下代码验证:
>>> from learning_logs.models import Topic
>>> for topic in Topic.objects.all():
... print(topic, topic.owner)
...
Chicken ll_admin # Chicken是我之前手动添加的主题
LOL ll_admin # LOL是我之前手动添加的主题
我们从learning_logs.models
中导入Topic
,再遍历所有既有的主题,并打印每个主题及其所属用户,正如预期,现在每个主题都属于用户ll_admin
注意:如果你确实想要一个全新的数据库,可执行命令
python manage.py flush
,这将重建数据库的结构,如果你这样做,就必须重新创建超级用户,且原来的所有数据都将丢失,所以建议亲还是小心谨慎点好!!!
Ⅲ、只允许用户访问自己的主题
当前,不管你以哪个用户的身份登录,都能够看到所有的主题,我们来改变这种情况,只向用户显示属于自己的主题,打开views.py
,对函数topics()
做如下修改:
# --snip--
# @login——required
# def topics(request):
# """显示所有的主题"""
topics = Topic.objects.filter(owner=request.user).order_by('date_added')
# context = {'topics': topics}
# return render(request, 'learning_logs/topics.html',context)
# --snip--
代码Topic.objects.filter(owner=request.user)
让Django只从数据库中获取owner
属性为当前用户的Topic
对象,要查看结果,以所有既有主题关联到的用户的身份登录,并访问topics
页面,你将看到所有的主题,然后注销并以另一个用户的身份登录,topics
页面将不会列出任何主题。
Ⅳ、保护用户的主题
打开views.py
,我们在视图函数topic()
获取请求的条目前执行检查:
# from django.shortcuts import render
from django.http import HttpResponseRedirect, Http404
# from django.core.urlresolvers import reverse
# --snip--
# @login——required
# def topics(request,topic_id):
# """显示单个主题及其所有的条目"""
# topics = Topic.objects.get(id=topic_id)
# 确认请求的主题属于当前用户
if topic.owner != request.user:
raise Http404
# entries = topic.entry_set.order_by('-date_added')
# context = {'topics': topics, 'entries':entries}
# return render(request, 'learning_logs/topics.html',context)
# --snip--
服务器上没有请求的资源时,标准的做法是返回404响应
,收到主题请求后,我们在渲染网页前检查该主题是否属于当前登录的用户,如果请求的主题不归当前用户所有,我们就引发Http404异常
,让Django返回一个404
错误页面。现在,如果你试图查看其他用户的主题条目,将看到Django发送的消息Page Not Found
。
Ⅴ、保护页面edit_entry
页面edit_entry
的URL为http://localhost:8000/edit_entry/entry_id/
,其中entry_id是一个数字,下面来保护这个页面,禁止用户通过输入类似于前面的URL来访问其他用户的条目,打开views.py
:
# --snip--
# @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--
我们获取指定的条目以及与之相关联的主题,然后检查主题的所有者是都是当前登录的用户,如果不是,就引发Http404异常
。
Ⅵ、将新主题关联到当前用户
当前,用于添加新主题的页面存在问题,因此它没有将新主题关联到特定用户,如果尝试添加新主题,将看到错误消息IntegrityError
,指出learning_logs_topic.user_id
不能为NULL
,Django的意思是,创建新主题时,你必须指定其owner
字段的值。由于我们可以通过request
对象获悉当前用户,因此存在一个修复这种问题的简单方法,请打开views.py
并添加以下代码,将新主题关联到当前用户:
# --snip--
# @login——required
# def new_topic(request):
# """添加新主题"""
# if request.method != ''POST:
# 未提交数据:创建一个新表单
# form = TopicForm()
# else:
# POST提交的数据,对数据进行处理
# form = TopicForm(request.POST)
# if form.is_valid():
new_topic = form.save(commit=False)
new_topic.owner = request.user
new_topic.save()
# return HttpResponseRedirect(reserve('learning_logs:topics'))
# context = {'form':form}
# return render(request, 'learning_logs/new_topic.html', context)
# --snip--
我们首先调用form.save()
,并传递实参commit=False
,这是因为我们先修改新主题,再将其保存到数据库中,接下来将新主题的owner
属性设置为当前用户,最后对刚定义的主题实例调用save()
,现在主题包含所有必不可少的数据,都将被成功地保存。现在,这个项目允许任何用户注册,而每个用户想添加多少新主题都可以,每个用户都只能访问自己的数据,无论是查看数据、输入数据还是修改旧数据时都如此。
至此,我们创建了一个完整的功能齐全的项目,它运行在本地计算机上,功夫不负有心人,如果你真的是一步一步跟着我的话,现在已经具备少许项目经验啦,哈哈哈,但我们都知道代码的道路上是永无止境的,我们还是得虚心求教,脚踏实地地学习好任何知识,哪怕知识一个小小的知识点。希望你有所付出有所收获,不枉费你的所有时间。在后面的学习分享中,我们将对整个项目进行样式设计,让它成为一个更加可视化的网站,我们甚至还可以将其部署在服务器上,让任何人都可通过交互式互联网来注册并创建账户一起使用ta,今天的那个就到这里啦,感谢您的阅读嗷💞💞💞