注册页面
下面就需要创建一个让新用户能够注册的页面。接下来将使用Django提供的表单UserCreationForm,并且需要编写自己的视图函数和模版。
注册页面的URL模式
【资料图】
在users/urls.py中添加定义了注册页面的URL模式,如下:
"""为应用users定义URL模式"""fromdjango.conf.urlsimporturlfromdjango.urlsimportpathfromdjango.contrib.authimportloginfromdjango.contrib.auth.viewsimportLoginViewfrom.importviewsurlpatterns=[#登陆页面url(r"^login/$",LoginView.as_view(template_name="users/login.html"),name="login"),#注销页面url(r"^logout/$",views.logout_view,name="logout"),#注册页面url(r"^register/$",views.register,name="register"),]
这个添加的模式与URLhttp://localhost:8000/users/register/匹配。
视图函数register
在users/views.py中添加对应的视图函数register,如下:fromdjango.shortcutsimportrenderfromdjango.httpimportHttpResponseRedirectfromdjango.urlsimportreversefromdjango.contrib.authimportlogout,login,authenticatefromdjango.contrib.auth.formsimportUserCreationForm#Createyourviewshere.deflogout_view(request):"""注销用户"""logout(request)returnHttpResponseRedirect(reverse("learning_logs:index"))defregister(request):"""注册新用户"""ifrequest.method!="POST":"""显示空的注册表"""form=UserCreationFormelse:#处理填写好的表单form=UserCreationForm(data=request.POST)ifform.is_valid:new_user=form.save#让用户自动登录authenticated_user=authenticated(username=new_user.username,password=request.POST["password"])login(request,authenticated_user)returnHttpResponseRedirect(reverse("learning_logs:index"))context={"form":form}returnrender(request,"users/register.html",context)
注册模版
对应就需要在login.html的同级目录下新建register.html模版:
{%extends"learning_logs/base.html"%}{%blockcontent%}
链接到注册页面
接下来就需要在用户没有登陆时提供新用户注册的链接,所以在base.html中添加注册页面的链接:
LearningLogTopics{%ifuser.is_authenticated%}Hello,{{user.username}}.logout{%else%}registerlogin{%endif%}
运行
让用户拥有自己的数据
用户应该能够输入其专用的数据,所以需要创建一个系统,确定各项数据所属的用户,然后限制对页面的访问,让用户只能使用自己的数据。在这里,将修改模型Topic,使得每个主题都归属于特定用户。这也将影响条目,所以每个条目都属于特定的主题,因此,需要先限制一些页面的访问。
使用@login_required限制访问
Django提供了装饰器@login_required,能够轻松实现这样的目的:对于某些页面,只允许已登录的用户访问它们。
装饰器(decorator)是放在函数定义前面的指令,Python在函数运行钱,根据它来修改函数代码的行为。
限制对topics页面的访问
每个主题都归特定用户所有,因此应只允许已登录的用户请求topics页面。为此,在learning_logs/views.py中添加如下代码:
fromdjango.contrib.auth.decoratorsimportlogin_required...@login_requireddeftopics(request):"""显示所有的主题"""topics=Topic.objects.order_by("date_added")context={"topics":topics}returnrender(request,"learning_logs/topics.html",context)...
首先导入函数login_required。将该函数作为装饰器用于视图函数topics中。在login_required前面加上@符号后放置到函数声明前面,会使得Python在运行topics的代码前先运行login_required的代码。而login_required的代码是用于检查用户是否处于登录状态的。
login_required将检查用户是否已登录,仅当用户已登录时,Django才运行topics的代码。如果用户未登录,则会重定向到登录页面。
为了实现这种重定向,我们需要修改settings.py,让Django知道到哪里去查找登录页面。所以可以在settings.py末尾添加如下:
#Staticfiles(CSS,JavaScript,Images)#https://docs.djangoproject.com/en/3.1/howto/static-files/STATIC_URL="/static/"LOGIN_URL="/users/login/"
全面限制对项目“学习笔记”的访问
Django使得能够轻松地限制对页面的访问,但是必须针对要保护哪些页面做出决定。最好先确定项目的哪些页面不需要保护,再限制对其他所有页面的访问。这样可以轻松修改过于严格的访问限制,其风险比不限制对敏感页面的访问更低。
fromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportUser#Createyourmodelshere.classTopic(models.Model):"""用户学习的主题"""text=models.CharField(max_length=200)date_added=models.DateTimeField(auto_now_add=True)#添加归属用户owner=models.ForeignKey(User,on_delete=models.CASCADE,)def__str__(self):"""返回模型的字符串表示"""returnself.textclassEntry(models.Model):"""学到的有关主题的具体知识"""topic=models.ForeignKey(Topic,on_delete=models.CASCADE)text=models.TextFielddate_added=models.DateTimeField(auto_now_add=True)classMeta:verbose_name_plural="entries"def__str__(self):"""返回模型的字符串表示"""returnself.text[:50]+"..."
首先,导入django.contrib.auth.models中的模型User,然后在Topic中添加字段owner,建立到模型User的外键关系。
确定当前有哪些用户
需要迁移数据库,Django将对数据库进行修改,使得其能够存储主题和用户之间的关联。为执行迁移,Django需要知道该将各个既有主题关联到哪个用户。最简单的办法就是将既有主题都关联到同一用户(如:超级用户)。为此,需要知道该用户的ID。
查看已创建的所有用户ID,启动Djangoshell,如下:
这里,我们遍历打印了User模型中的用户名和ID,其中得到4个用户,其中11_admin1是超级用户。
迁移数据库
当知道用户ID后,就可以执行操作迁移数据库了:
<.../Scripts>py.exe.\manage.pymakemigrationslearning_logsSystemcheckidentifiedsomeissues:WARNINGS:?:(2_0.W001)YourURLpattern"^edit_entry/(?P
现在可以执行迁移,如下:
<...\Scripts>py.exe.\manage.pymigrateSystemcheckidentifiedsomeissues:WARNINGS:?:(2_0.W001)YourURLpattern"^edit_entry/(?P
Django应用新的迁移,结果一切顺利。为了验证迁移符合预期,可以使用Djangoshell打印如下输入:
从learning_logs.models中导入Topic,再遍历所有的既有主题,并打印每个主题及其所属的用户。所以之前的所有主题现在都属于了11_admin这个用户了。
只允许用户访问自己的主题
当前,不管你以哪个用户的身份登陆,都能够看到所有的主题。现在就来改变这样的情况,只允许用户访问并显示属于自己的主题。
在views.py中,对函数topics做如下修改:
@login_requireddeftopics(request):"""显示所有的主题"""#topics=Topic.objects.order_by("date_added")topics=Topic.objects.filter(owner=request.user).order_by("date_added")context={"topics":topics}returnrender(request,"learning_logs/topics.html",context)
保护用户的主题
我们还没有限制对显示单个主题的页面的访问,因此,目前所有的已登录的用户都可以输入类似于http://localhost:8000/topics/1/的URL,来访问显示相应主题的页面。
为了修复这个问题,在视图函数topic获取请求的条目前执行检查:
fromdjango.httpimportHttpResponseRedirect,Http404@login_requireddeftopic(request,topic_id):"""显示单个主题的所有内容"""topic=Topic.objects.get(id=topic_id)#确认请求的主题属于当前用户iftopic.owner!=request.user:raiseHttp404entries=topic.entry_set.order_by("-date_added")context={"topic":topic,"entries":entries}returnrender(request,"learning_logs/topic.html",context)
服务器上没用请求的资源时,标准的做法就是返回404响应。所以,在这里,如果当前用户不是这个主题的所属用户,则返回404页面。
保护页面edit_entry
页面edit_entry的URL为http://localhost:8000/edit_entry/entry_id/,其中entry_id是一个数字,下面需要来保护这个页面,禁止用户通过输入类似前面的特定URL访问其他用户的条目:
@login_requireddefedit_entry(request,entry_id):"""编辑已有条目"""entry=Entry.objects.get(id=entry_id)topic=entry.topic#检查当前用户iftopic.owner!=request.user:raiseHttp404ifrequest.method!="POST":"""初次请求,使用当前条目填充表单"""form=EntryForm(instance=entry)else:"""POST提交的数据,对数据进行处理"""form=EntryForm(instance=entry,data=request.POST)ifform.is_valid:form.savereturnHttpResponseRedirect(reverse("learning_logs:topic",args=[topic.id]))context={"entry":entry,"topic":topic,"form":form}returnrender(request,"learning_logs/edit_entry.html",context)
将新主题关联到当前用户
当前,用于添加新主题的页面是存在问题的,因为新创建的新主题是无法关联到自己的。所以需要修改new_topic视图函数:
@login_requireddefnew_topic(request):"""添加新主题"""ifrequest.method!="POST":"""未提交数据:创建空表单"""form=TopicFormelse:"""POST提交数据,对数据进行处理"""form=TopicForm(request.POST)ifform.is_valid:new_topic=form.save(commit=False)new_topic.owner=request.usernew_topic.save#form.savereturnHttpResponseRedirect(reverse("learning_logs:topics"))context={"form":form}returnrender(request,"learning_logs/new_topic.html",context)