本系列是对入门书籍《Python编程:从入门到实践》的笔记整理,属于初级内容。标题顺序采用书中标题。
本篇记录如何创建用户注册系统,如何实现用户输入自己的数据。
1. 前言
在本篇中,我们将:
实现一个身份验证系统。
2. 让用户能够输入数据
2.1 添加新主题
和之前创建网页的步骤一样:定义URL,编写视图函数,编写模板。主要区别是,这里需要一个包含表单的模块forms.py
2.1.1 创建forms.py模块
用户输入信息时,需要进行验证,确保提交的信息是正确的数据类型,且不是恶意信息,如中断服务器的代码。然后再处理信息,并保存到数据库中。当然,这些工作很多都由Django自动完成。
在models.py所在的目录中新建forms.py模块。创建表单的最简单方法是继承Django的ModelForm类:
最简单的ModelForm版本只包含一个内嵌的Meta类,它告诉Django根据哪个模型创建表单,以及在表单中包含哪些字段。第6行,我们根据Topic创建一个表单,该表单只包含字段text(第7行),并不为该字段生成标签(第8行)。
2.1.2 URL模式new_topic
当用户要添加新主题时,将切换到http://localhost:8000/new_topic/ 。在learning_logs/urls.py中添加如下代码:
2.1.3 视图函数new_topic()
该函数需要处理两种情形:①刚进入new_topic网页,显示一个空表单;②对提交的表单数据进行处理,并将用户重定向到网页topics。修改views.py文件:
2.1.4 GET请求和POST请求
创建Web应用程序时,将用到两种主要数据请求类型:GET请求和POST请求。从这俩英文单词可以看出,如果只从服务器读取数据页面,则使用GET请求;如果要提交用户填写的表单,通常使用POST请求。当然还有一些其他的请求类型,但这个项目中没有使用。本项目中处理表单都使用POST方法。
request.method存储了请求的类型(第7行代码)。
当不是POST请求时,我们生成一个空表单传递给模板new_topic.html,然后返回给用户;当请求是POST时,我们从request.POST这个变量中获取用户提交的数据,并暂存到form变量中。
通过is_valid()方法验证表单数据是否满足要求:用户是否填写了所有必不可少的字段(表单字段默认都是必填的),且输入的数据与字段类型是否一致。当然这些验证都是Django自动进行的。如果表单有效,在通过form的save()方法存储到数据库,然后通过reverse()函数获取页面topics的URL,并将其传递给HTTPResponseRedirect()以重定向到topics页面。如果表单无效,把这些数据重新传回给用户。
2.1.5 模板new_topic.html
模板继承了base.html,因此其基本结构和项目中的其他页面相同。第6行中,参数action告诉服务器将提交的表单数据送到什么位置去处理,参数method让浏览器以POST请求的方式提交数据。
Django显示表单非常方便:只需要使用模板变量{{ form.as_p }},修饰符as_p让Django以段落格式渲染所有表单元素,这是一种整洁地显示表单的简单方法。
Django不自动创建提交表单的按钮,需自行创建。
2.1.6 链接到页面new_topic
在页面topics.html中添加一个到页面new_topic的链接:
2.1.7 效果
以下是实际效果图:
通过这个页面,随意添加几个主题,如下:
2.2 添加新条目
和前面的步骤相似:创建条目表单,添加URL,添加视图,添加模板,链接到页面
2.2.1 创建条目表单
创建一个与模型Entry相关联的表单,但这个表单的自定义程度比TopicForm要高些,依然是在刚才创建的forms.py中添加:
代码中定义了属性widgets。小部件(widget)是一个HTML表单元素,如单行文本框、多行文本框或下拉列表。通过设置属性widgets可以覆盖Django选择的默认小部件。通过Django的forms.Textarea定制字段“text”的输入小部件,将文本框的宽度设置为80列,而不是默认的40列。
2.2.2 添加URL模式new_entry
修改learning_logs/urls.py:
该URL模式与形式为http://localhost:8000/new_entry/topi_id/ 的URL匹配,其中topic_id是主题的ID。
2.2.3 视图函数new_entry()
与函数new_topic()很像:
new_entry()的定义包含形参topic_id,用于存储从URL中获得的值。
在调用save()时传递了参数commit=False(第14行),它让Django创建一个新的条目对象,但并不立刻提交数据库,而是暂时存储在变量new_entry中,待为这个新条目对象添加了属性topic之后再提交数据库。
在重定向时,reverse()函数中传递了两个参数,URL模式的名称以及列表args,args包含要包含在URL中的所有参数。
2.2.4 模板new_entry.html
类似于new_topic:
注意第4行代码,改行代码返回到特定主题页面。
2.2.5 链接到页面new_entry
在显示特定主题的页面中添加到页面new_entry的链接,修改topic.html:
2.2.6 效果
下图是实际效果,请随意添加一些条目:
2.3.1 URL模式edit_entry
修改learning_logs/urls.py:
2.3.2 视图函数edit_entry()
首先获取要被修改的entry以及与该条目相关的主题。处理GET请求时,通过参数instance=entry创建EntryForm实例,该参数让Django创建一个表单,并使用既有条目对象中的信息填充它。处理POST请求时,还传入了data=request.POST参数,Django根据POST中的相关数据对entry进行修改。
2.3.3 模板edit_entry.html
2.3.4 链接到页面edit_entry.html
在显示特定主题的页面中,需要给每个条目添加到页面edit_entry.html的链接,为此,修改topic.html:
2.3.5 效果
以下是实际效果图:
3. 创建用户账户
现在开始建立一个用户注册和身份验证系统。为此将创建一个新的应用程序,其中包含处理用户账户相关的所有功能。对Topic模型也要做稍许修改,让每个主题都归属于特定用户。
3.1 创建应用程序users
希望大家还记得如何使用startapp命令还创建APP:
将APP添加到settings.py中
在APP根目录下创建urls.py文件,并添加命名空间:
为APP定义URL,修改项目根目录中的urls.py:
3.2 登陆页面
使用Django提供的默认登陆视图,URL模式会有所不同。在users中的urls.py中添加如下代码:
代码中,我们使用Django自带的login视图函数(注意,参数是login,而不是views.login)。从之前的例子可以看出,我们渲染模板的代码都是在自己写的视图函数中。但这里使用了自带的视图函数,无法自行编写进行渲染的代码。所以,我们还传了一个字典给path,告诉Django到哪里查找我们要用到的模板。注意,该模板在users中,而不是在learning_logs中。
3.2.1 新建模板login.html
在learning_log/users/templates/users中创建login.html:
如果表单的errors属性被设置,则显示一条提示账号密码错误的信息。
3.2.2 链接到登陆页面
在base.html中添加到登陆页面的链接,让所有页面都包含它。将这个链接嵌套在一个 if 标签中,用户已登录时隐藏掉该链接:
在Django身份验证系统中,每个模板都可使用变量user,这个变量有一个is_authenticated属性:如果用户已登录,该属性将为True,否则为False。
3.2.3 使用登陆页面
首先访问localhost:8000/admin注销超级用户,再访问localhost:8000/users/login/,得到如下页面:
3.3 注销
并不为注销创建单独的页面,而是让用户单击一个连接用于注销并返回主页。因此,需要做如下工作:注销URL模式,新建视图,链接到注销视图。
在users/urls.py中添加与http://localhost:8000/users/logout/ 匹配的URL模式:
编写视图函数logout_view(),其中,直接调用Django自带的logout()函数,该函数要求request作为参数:
在base.html中添加注销链接:
3.4 注册页面
使用Django提供的表单UserCreationFrom,但编写自己的视图函数和模板。URL->view->template->link。
首先,创建注册页面的URL模式,修改users/urls.py:
其次,创建视图register():
以上代码在用户成功创建了用户后会自动登陆,该功能由login()函数实现。该函数将会为通过了身份验证的用户对象创建会话(session)。最后上述代码重定向到主页。
然后,编写注册页面的模板register.html:
最后,在页面中显示注册链接,修改base.html,在用户没有登录时显示注册链接:
下面是实际效果:
这是直接点register按钮时的反馈,不过这里有点疑惑,从上面的register.html中看到,其实代码很简单,但这里有个浮动效果,而且在注册模板中并没有像前面那样的{% form.errors %}模板标签,但依然有未注册成功时的反应,而且注册的视图函数也是自己写的,并不是用的自带的注册函数,所以不知道是不是和form.as_p有关。之后再慢慢研究吧,
4. 让用户拥有自己的数据
用户应该能够输入其专有的数据,所以应该创建一个系统,确定各项数据所属的用户,再限制对页面的访问,使得用户只能使用自己的数据,即访问控制。
4.1 使用@login_required限制访问
Django提供了装饰器@login_required,使得能轻松实现用户只能访问自己能访问的页面。
限制对topics.html的访问:
每个主题都归特定用户所有,所以需要加限制,修改learning_logs/views.py:
装饰器也是一个函数,python在运行topics()前会先运行login_required()的代码。
login_required()函数检查用户是否登录,仅当用户已登录时,Django才运行topics()函数,若未登录,就重定向到登陆界面。而为了实现这个重定向,还需要修改项目的settings.py文件,在该文件中添加这样一个常量(其实也是变量),一般在文件末尾添加:
全面限制对项目“学习笔记”的访问 :
Django能轻松地限制对页面的访问,但得自己设计需要限制哪些页面。一般先确定哪些页面不需要保护,再限制对其他页面的访问。在该项目中,我们不限制对主页、注册页面和注销链接的访问,其他页面均限制。在learning_logs/views.py中,除了index()外,每个视图函数都加上@login_required。
4.2 将数据关联到用户
为了禁止用户访问其他用户的数据,需要将用户与数据关联。只需要将最高层的数据关联到用户,这样更低层的数据将自动关联到用户。下面修改Topic模型和相关视图:
修改模型后,还需要迁移数据库。此时,需要将主题与用户关联。这里并没有通过代码进行关联,我们在迁移数据库时手动进行关联。为此,我们需要先知道有哪些用户,以及这些用户的ID。我们通过Django shell查询用户信息,当然也可以直接查看数据库,这里不再演示。我们将主题都关联到超级用户ll_admin上,它的ID是1。现在我们执行数据迁移:
Django指出试图给既有模型Topic添加一个必不可少(不可为空)的字段,而该字段没有默认值,需要我们采取措施:要么现在提供默认值,要么退出并在models.py中添加默认值。我们选择了直接输入默认值。接下来,Django使用这个值来迁移数据库,并生成了迁移文件0003_topic_owner.py,它在模型Topic中添加字段owner。
现在执行迁移命令:
执行后可以在Django shell中验证是否迁移成功,这里不再验证。
4.3 只允许用户访问自己的主题
目前不管以哪个用户身份登录,都能看到所有主题。现在我们添加限制,让用户只能看到自己的主题。在views.py中,对topics()做如下修改:
用户登录后,request对象将有一个user属性,这个属性存储了有关该用户的信息。第5行代码让Django只从数据库中读取特定用户的数据。
4.4 保护用户的主题
上述代码做到了登录后只显示相应用户的数据,但是,如果登录后直接通过URL访问,如直接输入http://localhost:8000/topics/1/ ,依然可以访问不属于自己的特定主题页面。下面修改views.py中的topic()函数来加以限制:
4.5 保护页面edit_entry
此时用户也可以像上面一样,登陆后直接通过URL来访问edit_entry.html,现在我们对这个页面也加以限制:
4.6 最后一步:将新主题关联到当前用户
当前用于添加新主题的页面存在问题,因为它没有将新主题关联到特定用户。如果此时尝试添加新主题,将看到错误信息IntegrityError,指出learning_logs_topic.user_id不能为NULL,下面修改new_topic()函数:
现在,这个项目允许任何用户注册,而每个用户想添加多少新主题都可以,每个用户只能访问自己的数据,无论是查看数据、输入新数据还是修改旧数据时都是如此。
5. 小结
閱讀更多 VPointer701 的文章