Django个人博客系统(6-10)
在上篇中,我们已经学会了Django的一些基本操作,本篇在其基础上进一步完善。
6.登录注册与重置密码
用户的登录注册是大部分网站的基本功能,而Django非常贴心地内置了用户管理模型——User
,利用这个内置模型可以满足绝大多数网站的需求,但是这里由于需要用到用户头像等User
中没有的字段,因此我们将用自定义的用户模型UserProfile
来覆盖User
。
首先新建一个userprofile
的应用:
然后在settings.py
文件的INSTALLED_APPS
中添加应用的名称:
最后将其路由添加到项目的urls.py
中:
以上就是注册一个app的基本流程。接下来我们在userprofile
应用中新建一个用户模型UserProfile
:
我们自定义的用户模型继承自AbstractUser
,事实上Django提供的User
也是继承自AbstractUser
。而AbstractUser
还有一个父类AbstractBaseUser
,区别在于前者已经定义了很多字段、实现了登录登出等基本功能,也就是说其实AbstractBaseUser
才是真正的"抽象类"。因此,我们自定义的UserProfile
其实已经继承了很多基本字段,我们只需添加头像字段即可。
而头像字段使用到了ImageField
字段类型,在执行makemigrations
前需要安装依赖包:pillow。在Pycharm的Terminal终端窗口执行安装命令:
而想要真正使用自定义的认证模型UserProfile
,还需要在setting.py
中添加下面内容,才能替换默认的User
模型。
最后执行如下命令来生成数据表:
注意:使用这种方式创建自定义用户模型时,如果之前创建过用户或相应的数据表,在执行数据库迁移命令之前需要清空原数据,否则会报错。具体做法是删除所有应用下的migrations
文件夹下除__init__.py
外的所有文件,而博主在踩过坑后发现还需要删除db.sqlite3
才能彻底清空原数据,一定要在删除干净后再执行数据库迁移命令!
模型创建成功后,接下来开始真正实现用户的登录与注册。
首先创建表单:
用户登录不需要对数据库进行任何改动,因此直接继承forms.Form
就可以了。forms.Form
需要手动配置每个字段,它适用于不与数据库进行直接交互的功能。
然后创建视图与模板:
Form
对象的主要任务就是验证数据,is_valid()
是Form
实例的一个方法,用来做字段验证,当输入字段值合法时,它将返回True
,同时将表单的数据存放到cleaned_data
属性中。authenticate()
方法验证用户名称和密码是否匹配,如果是,则将这个用户数据返回。login()
方法实现用户登录,将用户数据保存在session中。
注意:调用login()
之前必须调用authenticate()
成功认证登录用户。
之所以用这么几行代码就实现了用户登录功能,是因为我们自定义的用户模型继承自AbstractUser
,所以在功能上其实和Django内置的User
是一样的。
模板文件的核心代码如下:
最后在urls.py
文件中加入该视图的路由即可:
我们在header.html
文件中加入登录的链接:
user.is_authenticated
用来判断用户是否登录,如果登录了则显示用户头像,并用下拉框显示其他功能,如果没有则显示登录链接。
登出功能的实现非常简单,只需定义视图:
然后添加路由即可:
注册功能的实现方法其实和登录功能差不多,整体流程都是先写表单,然后写视图和模板,最后添加路由。
注册表单如下:
注册表单需要对数据库进行操作,因此应该继承forms.ModelForm
,可以自动生成模型中已有的字段。
这里我们覆写了password
字段,因为通常在注册时需要重复输入password
来确保用户没有将密码输入错误,所以覆写掉它以便我们自己进行数据的验证工作。def clean_password2()
中的内容便是在验证密码是否一致了。def clean_[字段]
这种写法Django会自动调用,来对单个字段的数据进行验证清洗。
覆写某字段之后,内部类class Meta
中的定义对这个字段就没有效果了,所以fields
不用包含password
。
需要注意:
- 验证密码一致性方法不能写
def clean_password()
,因为如果你不定义def clean_password2()
方法,会导致password2
中的数据被Django判定为无效数据从而清洗掉,从而password2
属性不存在。最终导致两次密码输入始终会不一致,并且很难判断出错误原因。 - 从
POST
中取值用的data.get('password')
是一种稳妥的写法,即使用户没有输入密码也不会导致程序错误而跳出。前面章节提取POST
数据我们用了data['password']
,这种取值方式如果data
中不包含password
,Django会报错。另一种防止用户不输入密码就提交的方式是在表单中插入required
属性。
注册的视图函数如下:
注册的模板核心如下:
忘记密码是很多用户经常遇到的问题,因此很多网站都会在登陆页面添加一个找回密码的功能,我们这里也实现通过邮件来找回密码的功能。Django内置其实已经实现了通过邮件来找回密码的功能,其主要步骤如下:
- 向用户邮箱发送包含重置密码地址的邮件。邮件的地址需要动态生成,防止不怀好意的用户从中捣乱;
- 向网站用户展示一条发送邮件成功的信息;
- 用户点击邮箱中的地址后,转入重置密码的页面;
- 向用户展示一条重置成功的信息。
其上四个流程分别由PasswordResetView
、PasswordResetDoneView
、PasswordResetConfirmView
、PasswordResetCompleteView
四个视图完成,因此我们要做的其实就是为它们配置路由罢了。在项目的urls.py中添加如下内容:
为什么要在每个视图后都跟着as_view()
?这是因为它们都是基于类的视图,也就是说它们的本质是class
,而括号内的template_name
、email_template_name
其实都是传递给class
的参数,具体可查看源码。事实上每一个视图对应的模板其实都有自带的(查看路径venv/Lib/site-packages/django/contrib/admin/templates/registration/
,我们自定义的模板其实是根据自带模板改编的),也就是说配置完路由其实这个功能就已经实现了。我们之所以要自己编写模板,其实是为了和自己网站的风格相适应,而且最不可忍受的是自带模板竟然还有Django标志。我们自己编写的模板放在template
的userprofile
文件夹下,每个模板的内容如下:
password_reset_form.html
password_reset_email.html
password_reset_done.html
password_reset_confirm.html
password_reset_complete.html
至此,密码找回功能就算基本完成了。
注意:如果要使用Django内置的通过邮箱来找回密码的功能(如上文),则路由配置一定要写在项目目录的urls.py
中,而模板文件则没有要求。如果路由写在app中则会报错,博主踩过这个坑并且试了很多办法都没解决(本来想放在userprofile
中,最后屈服了)。因此,切记路由要放在项目目录下,则基本没有什么问题,只要改改模板文件就可以了。
7.文章增改与个人中心
在上一篇中,我们已经实现了文章列表和详情页面,但当时我们调试用的数据是直接从后台输入的,因此本节我们继续完善文章的创作、修改、删除等功能。
我们首先增加一个文章创作功能。到目前为止,想必我们对于增加功能的流程已经非常熟悉了,其实就是编写视图和模板(根据情况,有时需要编写表单和创建模型。一般来说,需要和数据库交互的都需要通过表单),然后添加路由就可以了。
文章创作视图函数如下:
首先,我们对于文章创作要求用户必须登录,参数login_url
指明了登录链接,当用户未登录时会自动跳转到登录页面。其次,当文章发布成功后,我们将重定向到首页,即文章列表页面,展示在第一位的就是刚刚发布的文章,这是因为我们创建模型时定义的排序方式是按照创建时间倒序排列。
文章创作模板如下:
然后添加路由就实现了文章创作功能。
接下来我们实现文章修改、删除功能。我们可以在现有的文章详情页面添加文章修改、删除功能,我们先看修改后的文章详情模板文件:
可以看到我们在模板中用if
语句添加了几行代码,在if
语句中我们判断的是文章作者与当前用户的username
是否一致,从而决定用户是否有权修改这篇文章。只有当用户就是作者本人时,删除和修改链接才会显示出来。当删除文章时,我们会弹出一个确认框以提醒用户是否确认删除文章,从而防止用户手抖误删。
文章修改、删除的视图函数如下:
可以看到,在视图中我们再次确认了用户是否有权修改或删除文章,虽然在模板中我们已经初步确认了用户权限,但是出于安全考虑在后端再次进行确认还是很有必要的。
文章修改的模板如下:
不难看出,文章修改模板其实和文章创作模板差不多,区别就在于文章修改模板预填了原来的文章内容。
最后将以上功能添加到路由中即可:
至此,关于文章的基本功能就算完成了,更多功能(分页、搜索、点赞、评论等)我们后面慢慢完善。
在上一节中,我们已经实现了登录注册功能,这一节我们在此基础上添加个人中心功能。个人中心目前设计的主要功能就是展示一些信息以及更换头像。其中,主要功能我认为是更换头像,虽然用户注册时会使用默认头像,但是默认头像显然不能满足个性化需求。
由于需要修改用户头像,因此我们要先建一个表单,如下:
个人中心的视图函数如下:
GET
请求其实就是展示一些数据,主要是用户发表过的文章,而用户的一些基本信息其实不必传递,因为用户一旦登录这些基本信息就保存在session
中了,模板页面中可以直接通过user
访问。POST
请求其实就是更换用户头像,表单上传的文件通过request.FILES
进行访问。
个人中心的模板如下:
内容看起来很多,其实一大部分都是css
代码(我更改了base.html
,增加了style
块用于子模板添加独有的样式),用来实现鼠标移到头像上则显示遮罩层过渡动画与更换头像的链接。通过js
代码不难看出,其实更换头像的本质是通过隐藏的表单来实现的。
注意:form
表单要上传文件,必须设置enctype="multipart/form-data"
,否则文件无法上传且不会报错,难以察觉。
上面很多功能没有写添加到header.html
中,当然这部分也不难,这里就不再赘述了。
个人中心的效果图如下:
8.文章分页与搜索排序
对于绝大多数网站而言,分页都是必须的操作,因为大量的结果展示在同一页面,不仅造成页面冗长不便于阅读,而且并不美观,博客网站同样如此。我们采用Django内置的分页模块——Paginator
来实现博客文章分页功能(自己实现还是很困难的,emmm...)。
我们修改文章列表的视图:
从视图函数中可以看出,要实现完整的分页功能,我们至少需要传递page
参数以获取对应的页码。这里我们介绍一种通过url
地址传递参数的方法:即在url
的末尾附上?key=value
的键值对,视图中就可以通过request.GET.get('key')
来查询value
的值。
我们在模板list.html
中添加分页控制按钮:
在上述模板中,articles
是视图函数传递过去的Paginator
对象,has_previous
、has_next
等是对象的方法名,其含义不难理解。widthratio
是Django模板中的一种用于运算的标签,它需要三个参数,其返回结果是参数1/参数2*参数3,利用它可以巧妙实现乘除法,文中就是利用它将articles.number
变成负数,然后和articles.paginator.num_pages
相加,从而获取当前页面后面的剩余页面数量。
接下来我们实现文章的搜索和排序(最新、最热)功能,最热文章的排序就是根据浏览量进行排序,为此我们需要先修改ArticlePost
模型:
然后执行数据库迁移命令,这里就不多介绍数据库迁移命令的写法了,到现在为止想必各位已经熟悉了。有了浏览量字段后就需要在模板中展示出来,这也不多介绍了。
我们对浏览量计数的方法很简单,就是每调用一次article_detail
方法就给对应文章的浏览量加一。
update_fields=[]
指定了数据库只更新total_views
字段,优化了执行效率。
文章的搜索、排序的实现方法其实和分页功能差不多,其实都是通过url
地址传递参数到视图函数中以获取对应的内容,多个参数用&
连接。修改后的视图函数如下:
文章搜索功能是通过Model.objects.filter(**kwargs)
来实现的,它可以返回与给定参数匹配的部分对象。而需要联合查询时就要用到Q
对象,例如Q(title__icontains=search)
意思就是在查询模型的title
字段时返回包含search
(不区分大小写)的对象。多个Q
对象用管道符|
隔开,就达到了联合查询的目的。
注意:当用户没有搜索内容时要返回search = ''
,因为如果用户没有搜索操作,则search = request.GET.get('search')
会使得search = None
,而这个值传递到模板中会错误地转换成"None"字符串!等同于用户在搜索“None”关键字,这明显是错误的。
排序功能是通过order_by()
实现的,该方法指定对象如何进行排序(我们创建的模型默认按照时间倒序排列,因此最新文章的排序不需要进行任何操作)。修改后的模型中有total_views
这个整数字段,因此‘total_views’为正序,‘-total_views’为逆序。之所以把order
也传递到模板中,是因为文章需要翻页,而order
就是给模板一个标识,提醒模板下一页应该如何排序。
搜索功能的模板我们放在header.html
中,更加醒目。
注意:我们是通过GET
请求的url
来传递参数,因此form
中不能加method="POST"
,其默认方法为GET
。
最新、最热排序的模板我们加在list.html
中,即文章列表页面。
分页功能的href
也需要修改,需要添加search
和order
两个参数,如下例所示:
9.文章目录与发表评论
在上篇中,我们已经为博文支持了Markdown
语法,现在我们为其添加目录功能。
修改文章详情视图:
我们仅仅是将markdown.extensions.toc
扩展添加了进去。为了将目录插入到页面的任何一个位置,我们先将Markdown
类赋值给一个临时变量md
,然后用convert()
方法将正文渲染为html
页面,然后通过md.toc
将目录传递给模板。
修改文章详情模板:
我们重新布局了页面内容,将博客正文放到col-9
的容器中,将目录放到右侧col-3
的容器中。
注意:toc
需要|safe
标签才能正确渲染,具体原因在上篇添加Markdown
支持的时候阐述过。
评论功能是一个独立的模块,我们首先要为其新建一个应用:
然后在settings.py
中注册应用:
最后注册到根路由中:
以上就是新建一个app
的流程。下面我们实现评论模块的核心功能。
首先编写评论的模型:
该模型有两个外键,分别是ArticlePost
、UserProfile
,这使我想起了学数据库时的学生选课表(emmm...)。
注意:每次新建、修改模型后,都必须执行数据库迁移才能生效。
用户提交评论需要用到表单,因此我们新建一个表单类:
然后我们新建路由文件
再编写视图:
get_object_or_404()
和Model.objects.get()
的功能基本是相同的,区别是在生产环境下,如果用户请求一个不存在的对象时,后者会返回Error 500
(服务器内部错误),而前者会返回Error 404
。相比之下,返回404错误更加的准确。redirect()
返回到一个适当的url
中:即用户发送评论后,重新定向到文章详情页面。当其参数是一个Model
对象时,会自动调用这个Model
对象的get_absolute_url()
方法。因此我们接下来马上修改ArticlePost
模型:
评论模块需要在文章详情页面展示,因此接下来修改文章详情的视图和模板。
首先修改文章详情视图:
filter()
可以取出多个满足条件的对象,而get()
只能取出1个,注意区分使用。
然后修改文章详情模板:
comments.count
是模板对象中内置的方法,对包含的元素进行计数。|date:"Y-m-d H:i :s"
管道符你已经很熟悉了,用于给对象“粘贴”某些属性或功能。这里用于格式化日期的显示方式。
10.文章栏目标签标题图
文章栏目既方便博主对文章进行分类归档,也方便用户有针对性的阅读。要实现栏目功能其实不难,无非就是新建一个栏目模型,再以外键形式关联到文章模型。
文章标签其实和文章栏目差不多,不同点在于一篇文章可以有多个标签,但只能有一个栏目。这里我们采用一个实现了标签功能的优秀的三方库:django-taggit
(具体安装不再赘述,安装完记得在settings.py
中注册app——taggit
),利用该库进行快速开发。
标题图的添加是考虑到有时一图胜千言,通过图片能够快速了解文章内容。前面我们已经介绍过用户头像了,标题图其实也差不多,只是我们增加了对图片进行缩放等处理。
首先修改article/modles.py
文件:
首先我们增加了一个栏目模型——ArticleColumn
,该模型的字段很简单,因此不过多介绍。对于文章模型,我们不仅添加了三个字段(tags
有点特殊:因为标签引用的不是内置字段,而是库中的TaggableManager
,它是处理多对多关系的管理器),还定义了save()
方法。
save()
是model
内置的方法,它会在model
实例每次保存时调用。我们这里改写它,将处理图片的逻辑加入进去。super(ArticlePost, self).save(*args, **kwargs)
的作用是调用父类中原有的save()
方法,即将model
中的字段数据保存到数据库中。因为图片处理是基于已经保存的图片的,所以这句一定要在处理图片之前执行,否则会得到找不到原始图片的错误。not kwargs.get('update_fields')
是为了排除掉统计浏览量调用的save()
,免得每次用户进入文章详情页面都要处理标题图,因为我们在article_detail()
视图中为了统计浏览量而调用了save(update_fields=['total_views'])
。Pillow
库负责处理图片,将新图片的宽高设置为(400,225)
,最后用新图片将原始图片覆盖掉。Image.ANTIALIAS
表示缩放采用平滑滤波。
模型修改完毕,记住要执行数据迁移才能生效。
然后我们在article/admin.py
中将栏目模型注册到后台,并在后台添加几个栏目,然后随机找几篇文章设置不同的栏目以便后续测试。
既然我们已经在文章模型中添加了新字段,那么接下来文章创作和文章修改这两个功能也要做些更改,要将这几个新字段添加进去。
由于新文章是通过表单上传到数据库中的,因此我们先修改文章创作的表单类:
我们在表单中增加了tags
和avatar
两个字段。
然后我们修改文章创作视图:
修改之处主要有以下几点:
GET
中增加了栏目的上下文,以便模板使用,用户只需在下框中选择即可。- 标题图是文件,应该在
request.FILES
里获取它,而不是request.POST
。 - 对文章栏目和标题图进行判断,通过
save_m2m()
保存文章和标签的关系。
最后我们来看文章创作的模板:
- 为了上传标题图,我们需要对
form
添加enctype="multipart/form-data"
属性,该属性的含义是表单提交时不对字符编码。 <select>
是表单的下拉框选择组件,在这个组件中循环列出所有的栏目数据,我们将value
属性设置为栏目的id
值。
文章修改其实和文章创作差不多,主要就是需要将原数据返回到表单中方便修改。
其视图函数如下:
tags.set()
是库提供的接口,用于更新标签数据。
文章修改的模板文件如下:
与之前不同的是,我们在表单中判断了column.id
与article.column.id
是否相等,如果相等则将其设置为默认值。而对于tags
,由于视图传递过来的是一个set
,因此我们通过|join:","
将元素用英文逗号连接成字符串。
至此,文章创作和文章修改的变更就差不多了。接下来就是展示文章标题图和栏目标签了。
对于标题图,我们将其展示在文章列表页面,修改后的模板如下:
对于栏目和标签,我们将其展示在文章详情页面,修改后的模板如下:
下面我们实现按照栏目和标签进行搜索的功能。
首先修改文章列表的视图:
然后修改模板中的分页按钮链接:
__EOF__

本文链接:https://www.cnblogs.com/marvin-wen/p/14988744.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix