Django by Example·第二章

2023-10-26

Django by Example·第二章|Enhancing Your Blog with Advanced Features(为博客系统添加高级功能)@笔记

这本书的结构确实很不错,如果能够坚持看下去,那么Django框架的各种用法也就掌握的七七八八了。之前写过一篇这本书的第一章,看完第一章就算是入门了,但是还是继续写下去,看完这本书。


部分内容引用自原书,如果大家对这本书感兴趣
请支持原版Django by Example·Antonio Melé


目录

第一章:建立一个博客系统


前言

第一章其实就是使用Django创建了一个博客系统,实现了一个博客系统最基本的功能。那么第二章内容是以第一章为基础的,您将把您的应用程序变成一个功能齐全的博客,具有高级功能,如通过电子邮件共享帖子、添加评论、标记帖子以及按相似度检索帖子。如果您对第一章的内容很熟悉,那么您可以直接看第二章的内容。第二章Antonio Melé主要讲了以下内容:

  • Sending e-mails with Django(使用Django发送电子邮件)
  • Creating forms and handling them in views(创建表单并在视图中处理它们)
  • Creating forms from models(通过模型创建表单)
  • Integrating third-party applications(集成第三方应用程序)
  • Building complex QuerySets(构建复杂的查询集)

** 正式开始之前

*** 用邮件的方式分享帖子

首先,我们将允许用户通过电子邮件共享帖子。花一点时间思考如何使用视图、URL和模板,使用上一章中所学的内容创建此功能。现在,检查允许用户通过电子邮件发送帖子所需的内容。您需要:

  • 创建一个表单,供用户填写姓名和电子邮件、电子邮件收件人和可选备注。
  • 在views.py文件中创建一个视图,用于处理发布的数据并发送电子邮件。
  • 在blog应用下的urls.py中为新创建的视图添加一个URL模式。
  • 在blog应用的template下创建一个模板文件(HTML),用于展示相关的表单。

1 使用Django发送电子邮件

1.1 使用Django创建Form表单

让我们从构建共享帖子的表单开始。Django内置了一个表单框架允许您以简单的方式创建表单。表单框架允许您定义表单的字段,指定它们的显示方式,并指示它们如何验证输入数据。Django表单框架还提供了一种灵活的方式来呈现表单和处理数据。
Django提供了两个基类来构建表单:

  • Form: 允许您创建标准表单。
  • ModelForm: 允许您使用已创建的表单去创建或更新模型实例。

首先,在blog应用程序的目录中创建一个forms.py文件,并添加以下代码:

from django import forms

class EmailPostForm(forms.Form):
	name = forms.CharField(max_length=25)
	email = forms.EmailField()
    to = forms.EmailField()
    comments = forms.CharField(required=False, widget=forms.Textarea)

这是您的第一个Django表单。看看代码:我们通过继承基类Form创建了一个表单。我们为Django使用不同的字段类型来分别对应不同的前端HTML元素。

*** 表单可以位于Django项目中的任何位置,但惯例是将它们放置在每个应用程序的forms.py文件中。

以下是上述代码中涉及到的相关字段的解释:

  • name字段是CharField。这种类型的字段呈现为<input type=“text”>HTML元素。每个字段类型都有一个默认参数widget,用于确定字段在HTML中的显示方式。在comments字段中,我们将widget参数赋值为forms.Textarea,这样它将作为< textarea>HTML元素显示,而不是默认的<input>元素。
  • 字段验证还取决于字段类型。例如,电子邮件和收件人字段是EmailField。这两个字段都需要有效的电子邮件地址,否则将抛出forms.ValidationError异常。表单验证还考虑了其他参数:我们为name字段定义了25个字符的最大长度,并将comments字段设置为可选的(required=False)。所有这些也被考虑到现场验证。此表单中使用的字段类型只是Django表单字段的一部分。有关所有可用表单字段的列表,您可以访问Django官网Form表单字段介绍.

1.2 在视图函数中处理form表单

您必须创建一个新视图来处理表单,并在表单成功提交后发送电子邮件。编辑blog应用程序的views.py文件,并向其中添加以下代码:

from .forms import EmailPostForm

def post_share(request, post_id):
	# Retrieve post by id
    post = get_object_or_404(Post, id=post_id, status='published')
    # 提交表单数据
    if request.method == 'POST':
    	# Form was submitted
    	form = EmailPostForm(request.POST)
    	
    	if form.is_valid():
    		# Form fields passed validation 
    		cd = form.cleaned_data
    		# ... send email
    # 创建一个空白表单
    else:
    	form = EmailPostForm()
    
    return render(request, 'blog/post/share.html', {'post': post, 'form': form})

对该视图函数的一些解释:

  • 我们定义了post_share视图,该视图将请求对象request和post_id作为参数。
  • 我们使用get_object_or_404()方法按ID检索帖子,并确保检索到的是已发布的帖子。
  • 我们使用相同的视图来显示初始表单和处理提交的数据。我们根据请求方法区分表单是否已提交。我们将使用POST提交表格。我们假设,如果收到get请求,则必须显示一个空表单;如果收到POST请求,则表单已提交并需要处理。因此,我们使用request.method=='POST’来区分这两种情况。

以下是显示和处理表单的过程:

  1. 当视图最初加载GET请求时,我们将创建一个新的表单实例,用于在模板中显示空表单:form = EmailPostForm()

  2. 用户填写表单并通过POST提交。然后,我们使用request.POST中包含的提交数据创建一个表单实例:

    if request.method == 'POST':
    	# Form was submitted
    	form = EmailPostForm(request.POST)
    
  3. 之后,我们使用表单的is_valid()方法验证提交的数据。此方法验证表单中引入的数据,如果所有字段都包含有效数据,则返回True。如果任何字段包含无效数据,则is_valid()返回False。您可以看到验证错误列表通过访问form.errors。

  4. 如果表单验证无效,我们将使用提交的数据再次在模板中呈现表单。我们将在模板中显示验证错误。

  5. 如果表单验证有效,我们将检索访问form.clean_data的有效数据。此属性是表单字段及其值的字典。

    如果表单数据未验证,cleaned_data将仅包含有效字段。

现在,您需要学习如何使用Django发送电子邮件,以将所有内容整合在一起。

1.3 使用Django发送电子邮件

用Django发送电子邮件非常简单。首先,您需要有一个本地SMTP服务器,或者通过在项目的settings.py文件中添加以下设置来定义外部SMTP服务器的配置:

  • EMAIL_HOST:SMTP服务器主机。默认localhost。
  • EMAIL_PORT:SMTP端口默认值25。
  • EMAIL_HOST_USER:SMTP服务器的用户名。
  • EMAIL_HOST_PASSWORD:SMTP服务器的密码。
  • EMAIL_USE_TLS:是否使用TLS安全连接。
  • EMAIL_USE_SSL:是否使用隐式TLS安全连接。

如果没有本地SMTP服务器,则可能可以使用电子邮件提供商的SMTP服务器。以下示例配置适用于使用腾讯QQ邮箱服务器发送电子邮件:

EMAIL_HOST = "smtp.qq.com"  # SMTP服务器主机,这个不用改
EMAIL_PORT = 587             # 端口,千万要写587端口,否则容易出错
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST_USER = "********@qq.com"       # 邮箱地址
EMAIL_HOST_PASSWORD = "************"    # 授权码
EMAIL_USE_TLS= True
EMAIL_FROM = "***********@qq.com"    

运行命令python manage.py shell打开python shell并发送邮件:

>>> from django.core.mail import send_mail
>>> send_mail(
	'Django mail', # 主题
	'This e-mail was sent with Django.', # 消息
	'your_account@gmail.com', # 发件人
	['your_account@gmail.com'], # 收件人列表
	fail_ silently=False
)

send_mail()将主题、消息、发件人和收件人列表作为必需的参数。通过设置可选参数fail_silently=False,我们告诉它如果无法正确发送电子邮件,将引发异常。如果您看到的输出为1,则表明您的电子邮件已成功发送。

现在,我们将把这个方法应用到我们的视图中。编辑博客应用程序views.py文件中的post_share视图,在其中添加以下代码:

from django.core.mail import send_mail

def post_share(request, post_id):
	# 获取帖子
	post = get_object_or_404(Post, id=post_id, status='published')     
	sent = False
	
	# 表单被提交
	if request.method == 'POST':
		form = EmailPostForm(request.POST) 
		# 表单内容通过验证
		if form.is_valid():
			# 获取表单内容
			cd = form.cleaned_data
			# 获取帖子的url
			post_url = request.build_absolute_uri(post.get_absolute_url())
			# 获取邮件主题和消息内容
			subject = '{} ({}) recommends you reading "{}"'.format(cd['name'], cd['email'], post.title)
			message = 'Read "{}" at {}\n\n{}\'s comments: {}'.format(post.title, post_url, cd['name'], cd['comments'])
			# 发送邮件
			send_mail(subject, message, '发件人邮箱', [cd['to']])
			sent = True
	# 加载表单
	else:
		form = EmailPostForm()
	
	return render(
		request,
		'blog/post/share.html',
		{'post': post, 'form': form, 'sent': sent}
	)

注意,我们声明了一个sent变量,并在发送邮件成功后将其设置为True。我们稍后将在模板中将使用该变量,以在表单成功提交时显示成功消息。由于我们必须在电子邮件中包含到文章的链接,因此我们使用其get_absolute_url()方法检索文章的绝对路径。我们使用此路径作为request.build_absolute_uri()的输入,以构建包含HTTP模式和主机名的完整URL。我们使用经过验证的表单的数据构建电子邮件的主题和邮件正文,最后将电子邮件发送到表单的收件人字段中包含的电子邮件地址。

现在通过邮件分享帖子的视图已经完成,请记住为它添加一个新的URL模式。打开blog应用程序的urls.py文件并为post_share 视图添加URL模式:

urlpatterns = [
	# ...
	url(r'^(?P<post_id>\d+)/share/$', views.post_share, name='post_share'),
]

1.4 创建模板文件用以渲染数据

在创建表单、编程视图并添加URL模式后,我们只缺少此视图的模板。在blog/templates/blog/post/目录中创建一个新文件,并将其命名为share.html。向其中添加以下代码:

{% extends "blog/base.html" %}

{% block title %}Share a post{% endblock %}

{% block content %}
{% if sent %}
	<h1>E-mail successfully sent</h1>
	<p>"{{ post.title }}" was successfully sent to {{ cd.to }}.</p>
{% else %}
	<h1>Share "{{ post.title }}" by e-mail</h1>
	<form action="." method="post">
		{{ form.as_p }}
		{% csrf_token %}
		<input type="submit" value="Send e-mail">
	</form>
{% endif %}
{% endblock %}

这是发送表单时显示表单或成功消息的模板。如您所见,我们创建了HTML表单元素,指示必须通过POST方法提交:

<form action="." method="post">

然后我们开始处理从后端传回的form表单实例。我们告诉Django使用as_p方法在HTML<p>标签中呈现其字段。我们还可以使用as_ul将表单呈现为HTML无序列表,或者使用as_table将表单呈现成HTML表。我们还可以用下面的方法迭代form表单实例:

{% for field in form %}
	<div>
		{{ field.errors }}
    	{{ field.label_tag }} {{ field }}
    </div>
{% endfor %}

{%csrf_token%}模板标记引入了带有django中间件自动生成token,以避免跨站点请求伪造(csrf)攻击。这些攻击包括恶意网站或程序对您网站上的用户执行不必要的操作。有关此的详细信息,请访问https://en.wikipedia.org/wiki/Cross-site_request_forgery

前面的标记生成了的隐藏字段,如下所示:

<input type='hidden' name='csrfmiddlewaretoken' value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />

默认情况下,Django在所有POST请求中检查CSRF令牌。记住在通过POST提交的所有表单中都包含csrf_token标记。

编辑您的博客/post/detail.html模板,并在{{post.body|linebreaks}}变量后添加用来分享帖子的链接:

<p>
	<a href="{% url "blog:post_share" post.id %}">分享</a>
</p>

请记住,我们使用Django提供的{%URL%}模板标记动态构建URL。我们使用名为blog的命名空间和名为post_share的URL,并传递postID作为参数来构建绝对URL。

现在,使用命令python manage.py runserver启动开发服务器,然后打开http://127.0.0.1:8000/blog/在浏览器中。单击任何文章标题以查看详细信息页面。在贴体下,您应该看到刚才添加的链接,如下图所示:
在这里插入图片描述
单击“共享”,您将看到包含通过电子邮件共享此帖子的表单的页面。它必须如下所示:
在这里插入图片描述

表单的CSS样式包含在static/CSS/blog中的示例代码中。单击“发送电子邮件”按钮时,将提交并验证表单。如果所有字段都包含有效数据,您将收到一条成功消息,如以下内容:
在这里插入图片描述
如果您输入了无效数据,您将看到表单再次呈现,包括所有验证错误:
在这里插入图片描述


2 创建一个评论系统

现在我们将为博客构建一个评论系统,用户可以在其中对帖子进行评论。要构建评论系统,您需要:

  • 创建模型以保存评论
  • 创建一个表单用于提交评论以及验证输入的数据
  • 添加一个视图处理从表单提交的评论
  • 编辑帖子详细信息模板以显示评论列表以及添加评论的表单

2.1 创建模型

首先,让我们构建一个模型用来保存评论。打开blog应用程序的models.py文件并添加以下代码:

class Comment(models.Model):
	post = models.ForeignKey(Post, related_name='comments')     name = models.CharField(max_length=80)
    email = models.EmailField()
    body = models.TextField()
    created = models.DateTimeField(auto_now_add=True)     updated = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=True)

	class Meta:
		ordering = ('created',)
	
	def __str__(self):
		return 'Comment by {} on {}'.format(self.name, self.post)

这是我们的Comment模型。它包含一个ForeignKey,用于将评论与对应的帖子相关联。这种多对一的关系是在Comment模型(多的一方)中定义的,因为每个评论都将在一篇文章上发表,并且每个文章可能有多条评论。related_name属性允许我们将用于从相关对象返回到此对象的关系的属性命名。在定义了这个属性之后,我们可以使用comment.post检索一个评论对象(comment)的帖子(post),并使用post.comments.all()检索一个帖子(post)的所有注释。如果您没有定义related_name属性,Django将使用模型的底层名称,后跟_set(即comment_set)将相关对象的管理器命名回此对象。

您可以通过这个链接学到更多关于一对多关系的知识一对多关系。

我们已经包含了名为active的布尔字段,我们将使用它来手动屏蔽不适当的评论。默认情况下,我们使用created字段按时间顺序对注释进行排序。

刚刚创建的新Comment模型尚未同步到数据库中。运行以下命令生成迁移文件:

python manage.py makemigrations blog

然后执行迁移,Django将在数据库中创建对应的数据库表:

python manage.py migrate

我们刚刚创建的迁移已经应用,现在数据库中存在一个blog_comment表。

现在,我们可以将新模型添加到管理站点,以便通过一个简单的界面管理评论。打开blog应用程序的admin.py文件,添加以下代码:

from .models import Post, Comment

# 创建评论管理模型
class CommentAdmin(admin.ModelAdmin):
	list_display = ('name', 'email', 'post', 'created', 'active')
	list_filter = ('active', 'created', 'updated')
    search_fields = ('name', 'email', 'body')

# 注册模型
admin.site.register(Comment, CommentAdmin)

使用命令python manage.py runserver启动开发服务器并打开http://127.0.0.1:8000/admin/在浏览器中。您应该可以在Blog部分看到新添加的评论模型,如下图所示:
在这里插入图片描述
我们的模型现在已注册到管理站点,我们可以使用一个简单的界面来管理Comment实例。

2.2 创建Comment模型对应的表单

我们仍然需要构建一个表单,让用户可以对博客发表评论。记住Django有两个基类来构建表单:Form和ModelForm。您之前使用了Form,让您的用户通过电子邮件共享帖子。在本例中,您需要使用ModelForm(相对Form使用更简单),因为您必须从Comment模型动态构建表单。编辑blog应用程序的forms.py并添加以下代码:

from .models import Comment

class CommentForm(forms.ModelForm):
	class Meta:
		model = Comment
		fields = ('name', 'email', 'body')

要通过模型创建表单,我们只需要在表单的Meta类中指明要使用哪个模型来构建表单。Django会对模型进行内省,并为我们动态构建表单。每个模型字段类型都有一个相应的默认表单字段类型。表单验证考虑了我们定义模型字段的方式。默认情况下,Django为模型中包含的每个字段构建一个表单字段。但是,您可以使用字段列表明确告诉框架要在表单中包含哪些字段,或者使用字段排除列表定义要排除哪些字段。对于我们的CommentForm,我们将只使用表单的名称、电子邮件和正文字段,因为这些是我们的用户能够填写的唯一字段。

2.3 在视图中处理ModelForm

因为帖子和评论是一对多关系,所以我们将使用post_detail视图来实例化评论的表单。编辑blog应用下的models.py文件,为Comment模型和CommentForm表单添加导入,并修改post_detail视图,使其看起来如下所示:

from .models import Post, Comment
from .forms import EmailPostForm, CommentForm

def post_detail(request, year, month, day, post):
    post = get_object_or_404(Post, slug=post, status='published', publish__year=year, publish__month=month, publish__day=day)

	# 获取当前帖子的评论
	comments = post.comments.filter(active=True)
	
	# 提交评论	
	if request.method == 'POST':
		# 实例化表单对象
		comment_form = CommentForm(data=request.POST)
		if comment_form.is_valid():
			new_comment = comment_form.save(commit=False)
			# 关联帖子与评论
			new_comment.post = post
			# 将提交的评论保存到数据库
			new_comment.save()
	# 加载评论
	else:
		comment_form = CommentForm()
	
	return render(
		request,
		'blog/post/detail.html',
		{
			'post': post,
			'comments': comments,
			'comment_form': comment_form
		}
	)
    

让我们看一下我们在视图中添加的内容。我们使用post_detail视图来显示文章及其评论。我们添加一个QuerySet来检索此帖子的所有为被屏蔽的评论:

comments = post.comments.filter(active=True)

我们还使用相同的视图让用户添加新的评论。因此,如果视图被GET请求调用,我们将使用comment_form=CommentForm()构建一个表单实例。如果请求是通过POST完成的,我们将使用提交的数据实例化表单,并使用is_valid()方法验证它。如果表单无效,我们将呈现带有验证错误的模板。如果表格有效,我们将采取以下措施:

  1. 我们通过调用表单的save()方法来创建一个新的Comment对象:

    new_comment = comment_form.save(commit=False)
    

    通过save()方法可以创建表单对象链接到的模型的实例,并将其保存到数据库中。如果使用commit=False调用它,则创建模型实例,但不将其保存到数据库中。当您想要在最终保存之前修改对象时,这非常方便,这是我们接下来要做的。save()方法适用于ModelForm,但不适用于Form实例,因为它们未链接到任何模型。

  2. 我们将当前帖子和刚刚创建的评论对象关联起来:

    new_comment.post = post
    
  3. 最后,我们使用以下代码将新注释保存到数据库中:

    new_comment.save()
    

我们的视图现在可以显示和处理新的注释了。

2.4 为帖子详情页模板添加显示和提交评论的区域

我们已经创建了管理帖子评论的功能(即视图函数)。现在我们需要调整post_detail.html模板以执行以下操作:

  • 显示帖子的评论总数
  • 显示评论列表
  • 显示用户添加新评论的表单

首先,我们将添加评论总数。打开blog_detail.html模板并在内容块中附加以下代码:

{% with comments.count as total_comments %}
<h2>
	{{ total_comments }} comment{{ total_comments|pluralize }}
</h2>
{% endwith %}

我们使用的是Django的模板语言,可能不同后端语言对应框架的模板语言都是不一样的。需要注意的是,Django模板语言不使用括号来调用方法,你可以使用类似变量名.count来得到一个容器包含的元素数量。{%with%}标记允许我们将一个值分配给一个新变量,该变量将在{%endwith%}}标记之前可用。

{%with%}模板标记有助于避免多次访问数据库或访问昂贵的方法。

我们使用复数模板过滤器根据total_comments值显示单词注释的复数后缀。模板过滤器将其应用的变量的值作为输入,并返回计算值。我们将在第3章“扩展博客应用程序”中讨论模板过滤器。
如果值不同于1,则复数模板过滤器显示“s”。前面的文本将呈现为0条注释、1条注释或N条注释。Django包含大量的模板标签和过滤器,可以帮助您以您想要的方式显示信息。

现在,让我们列出评论列表。在前面的代码后将以下行附加到模板:

{% for comment in comments %}
	<div class="comment">
		<p class="info">
			Comment {{ forloop.counter }} by {{ comment.name }}{{ comment.created }}
		</p>
		{{ comment.body|linebreaks }}
	</div>
{% empty %}
	<p>There are no comments yet.</p>
{% endfor %}

我们使用{%for%}模板标记循环遍历注释。如果评论列表为空,我们将显示一条默认消息,告诉我们的用户还没有对此文章的评论。我们使用{{forloop.counter}}变量枚举注释,该变量包含每个迭代中的循环计数器。然后,我们显示发布评论的用户的姓名、日期和评论正文。

最后,您需要呈现表单或在成功提交表单时显示成功消息。在上述代码的正下方添加以下行:

{% if new_comment %}
	<h2>Your comment has been added.</h2>
{% else %}
	<h2>Add a new comment</h2>
	<form action="." method="post">
		{{ comment_form.as_p }}
		{% csrf_token %}
		<p><input type="submit" value="Add comment"></p>
	</form>
{% endif %}

代码非常简单:如果new_comment对象存在,我们将显示一条成功消息,因为注释已成功创建。否则,我们为每个字段呈现带有段落<p>元素的表单,并包含POST请求所需的CSRF令牌。

打开http://127.0.0.1:8000/blog/在浏览器中,单击文章标题以查看其详细信息页面。您将看到以下内容:
在这里插入图片描述
使用表单添加一些评论。它们应该按时间顺序显示在您的帖子下面,如下所示:
在这里插入图片描述
打开http://127.0.0.1:8000/admin/blog/comment/在浏览器中。您将看到带有您创建的评论列表的管理页面。单击其中一个按钮进行编辑,取消选中“活动”复选框,然后单击“保存”按钮。您将再次重定向到评论列表,“活动”列将显示该评论的非活动图标。它应该像下面截图中的第一条评论:
在这里插入图片描述

如果返回到文章详细信息视图,您将注意到删除的评论不再显示;它也不计入评论总数。由于活动字段,您可以停用不适当的评论,并避免在帖子中显示它们。


3 使用第三方插件 为帖子自动添加标签

在实现注释系统之后,我们将新增一个功能来为我们的帖子添加标签。我们将通过在项目中集成第三方Django标记应用程序来实现这一点。django-taggit是一个可重用的应用程序,它主要为您提供一个Tag模型和一个管理器,可以轻松地向任何模型添加标记。您可以在https://github.com/alex/django-taggit.学到更多关于此插件的使用方法。

首先,您需要通过pip安装django-taggit,运行以下命令:

pip install django-taggit==0.17.1

然后打开mysite项目的settings.py文件,将taggit添加到INSTALLED_APPS列表中,如下所示:

INSTALLED_APPS = (
	# ...
	'blog',
	'taggit',
)

打开blog应用程序的models.py文件,使用以下代码将django-taggit提供的TagableManager()管理器添加到Post模型中:

from taggit.managers import TaggableManager 

class Post(models.Model):
	# ...
    tags = TaggableManager()

TaggableManager()管理器将允许您添加、检索和删除Post对象中的标签。

按顺序运行以下命令为模型生成迁移文件并执行迁移:

python manage.py makemigrations blog
python manage.py migrate

您的数据库现在可以使用django-taggit模型了。使用命令python manage.py shell打开终端,学习如何使用标记管理器。首先,我们检索一篇帖子(ID为1的帖子):

>>> from blog.models import Post
>>> post = Post.objects.get(id=1)

然后向其添加一些标签,并检索其标签以检查是否已成功添加:

>>> post.tags.add('music', 'jazz', 'django')
>>> post.tags.all()
[<Tag: jazz>, <Tag: django>, <Tag: music>]

Finally, remove a tag and check the list of tags again:
最后,删除标记并再次检查标记列表:

>>> post.tags.remove('django')
>>> post.tags.all()
[<Tag: jazz>, <Tag: music>]

这很容易,对吧?运行命令python manage.py runserver以再次启动开发服务器并打开http://127.0.0.1:8000/admin/taggit/tag/在浏览器中。您将看到带有taggit应用程序的Tag对象列表的管理页面:

在这里插入图片描述
进入到http://127.0.0.1:8000/admin/blog/post/然后单击一篇文章进行编辑。您将看到文章现在包含一个新的标签字段,如下所示,您可以在其中轻松编辑标签:
在这里插入图片描述
现在,我们将编辑博客帖子模板文件以显示标签。打开blog/post/list.html模板,并在文章标题下方添加以下html代码:

<p class="tags">Tags: {{ post.tags.all|join:", " }}</p>

连接模板过滤器用作Python字符串join()方法,将元素与给定字符串连接起来。打开http://127.0.0.1:8000/blog/在浏览器中。您应该看到每个帖子标题下的标签列表:
在这里插入图片描述
现在,我们将编辑post_list视图,添加标签过滤功能,可以让用户筛选出特定标签的所有帖子。打开blog应用程序的views.py文件,导入标记模型表单django-taggit,添加以下代码:

from taggit.models import Tag

def post_list(request, tag_slug=None):
	object_list = Post.published.all()
	tag = None
    
    if tag_slug:
    	tag = get_object_or_404(Tag, slug=tag_slug)
    	object_list = object_list.filter(tags__in=[tag])
    	# ...

该视图现在的工作方式如下:

  • 视图采用一个可选的tag_slug参数,该参数具有None默认值。此参数将出现在URL中。
  • 在视图中,我们构建初始QuerySet,检索所有已发布的帖子,如果有给定的标记段,我们使用get_object_or_404()快捷方式获取带有给定段的tag对象。
  • 然后,我们根据包含给定标记的帖子过滤帖子列表。由于这是一个多对多的关系,我们必须根据给定列表中包含的标记进行过滤,在我们的例子中,该列表只包含一个元素。

请记住,Queryset是惰性查询的。只有当我们在呈现模板时循环帖子列表时,才会评估检索帖子的QuerySet。

最后,修改视图底部的render()函数,将标记变量传递给模板。视图最终应该如下所示:

def post_list(request, tag_slug=None):
	object_list = Post.published.all()
	tag = None
    
    if tag_slug:
    	tag = get_object_or_404(Tag, slug=tag_slug)
    	object_list = object_list.filter(tags__in=[tag])
    
    paginator = Paginator(object_list, 3) # 3 posts in each page
    page = request.GET.get('page')
    try:
    	posts = paginator.page(page)
    except PageNotAnInteger:
		# If page is not an integer deliver the first page
		posts = paginator.page(1)
    except EmptyPage:
    	# If page is out of range deliver last page of results
    	posts = paginator.page(paginator.num_pages)
    
    return render(
    	request,
    	'blog/post/list.html',
    	{
    		'page': page,
    		'posts': posts,
    		'tag': tag
    	}
    )

打开blog应用程序的urls.py文件,注释掉基于类的PostListView URL模式,并取消注释post_list视图,如下所示:

url(r'^$', views.post_list, name='post_list'),
# url(r'^$', views.PostListView.as_view(), name='post_list'),

添加以下附加URL模式以按标签筛选帖子:

url(r'^tag/(?P<tag_slug>[-\w]+)/$', views.post_list, name='post_list_by_tag'), 

正如您所看到的,这两种模式都指向相同的视图,但我们对它们的命名不同。第一个模式将在没有任何可选参数的情况下调用post_list视图,而第二个模式将使用tag_slug参数调用视图。

由于我们使用的是post_list视图,因此编辑blog/post/list.html模板并修改分页以使用posts对象,如下所示:

{% include "pagination.html" with page=posts %}

在{%for%}循环上方添加以下行:

{% if tag %}
  <h2>Posts tagged with "{{ tag.name }}"</h2> 
{% endif %}

如果用户正在访问博客,他将看到所有帖子的列表。如果他通过带有特定标签的帖子进行过滤,他将看到这些信息。现在,将标记的显示方式更改为:

<p class="tags">
	Tags: 
	{% for tag in post.tags.all %}
		<a href="{% url "blog:post_list_by_tag" tag.slug %}">
			{{ tag.name }}
   		</a>
    	{% if not forloop.last %}, {% endif %}
    {% endfor %}
</p>

现在,我们循环浏览一个帖子的所有标签,显示一个指向URL的自定义链接,以根据该标签过滤帖子。我们使用{%URL“blog:post_list_by_tag”tag.slug%}构建URL,使用URL名称和标记slug作为参数。我们用逗号分隔标签。

打开http://127.0.0.1:8000/blog/在浏览器中,单击任何标记链接。您将看到该标签下的帖子列表,如下所示:

在这里插入图片描述


4 按相似性检索帖子

现在我们已经为博客文章添加了标签,我们可以用它们做很多有趣的事情。使用标签,我们可以很好地分类我们的博客文章。关于类似主题的帖子将有几个共同的标签。我们将构建一个功能,通过它们共享的标签数量来显示类似的帖子。这样,当用户阅读帖子时,我们可以建议他们阅读其他相关帖子。

为了检索与特定帖子类似的帖子,我们需要:

  • 检索当前帖子的所有标签。
  • 获取所有带有这些标签的帖子。
  • 从该列表中排除当前帖子,以避免推荐同一帖子。
  • 根据与当前帖子共享的标签数量排序结果。
  • 如果两个或多个帖子具有相同数量的标签,则推荐最近的帖子。
  • 将查询限制为我们希望推荐的帖子数量。

这些步骤被转换为一个复杂的QuerySet,我们将在post_detail视图中包含它。打开blog应用程序的views.py文件,并在其顶部添加以下导入:

from django.db.models import Count

这是Django ORM的Count聚合函数。此函数将允许我们执行汇总计数。然后在post_detail视图中的render()函数之前添加以下行:

# List of similar posts
post_tags_ids = post.tags.values_list('id', flat=True)
similar_posts = Post.published.filter(tags__in=post_tags_ids).exclude(id=post.id)
similar_posts = similar_posts.annotate(same_tags=Count('tags')).order_by('-same_tags','-publish')[:4]

上述代码含义如下:

  1. 我们检索当前帖子的标签的ID列表。values_list()返回带有给定字段值的QuerySet。我们将其传递为flat=True,以获得类似[1,2,3,…]的展开列表。
  2. 我们得到所有包含这些标签的帖子,不包括当前帖子。
  3. 我们使用Count聚合函数生成计算字段samettags,包含与查询的所有标记共享的标记数。
  4. 我们根据共享标签的数量(后代顺序)和发布对结果进行排序,以首先显示具有相同数量共享标签的帖子的最近帖子。我们对结果进行切片,以仅检索前四个帖子。

将similar_posts对象添加到render()函数的上下文字典中,如下所示:

return render(
	request,
	'blog/post/detail.html', 
	{
		'post': post,
		'comments': comments,
		'comment_form': comment_form,
		'similar_posts': similar_posts
	}
)

现在,编辑blog/post/detail.html模板,并在post注释列表之前添加以下代码:

<h2>Similar posts</h2>
{% for post in similar_posts %}
	<p>
		<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
	</p>
{% empty %}
	There are no similar posts yet.
{% endfor %}

还建议您将标签列表添加到帖子详细信息模板中,方法与我们在帖子列表模板中所做的相同。现在,您的文章详细信息页面应该如下所示:
在这里插入图片描述
您成功地向用户推荐了类似的帖子。django-taggit还包括一个类似的objects()管理器,您可以使用它通过共享标记检索对象。您可以在http://django-taggit.readthedocs.org/en/latest/api.html


总结

在本章中,您学习了如何使用Django表单和模型表单。您创建了一个通过电子邮件共享网站内容的系统,并为您的博客创建了评论系统。您在博客文章中添加了标记,集成了一个可重用的应用程序,并构建了复杂的QuerySet以按相似性检索对象。

在下一章中,您将学习如何创建自定义模板标记和过滤器。您还将为您的博客文章构建自定义站点地图和提要,并将高级搜索引擎集成到应用程序中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Django by Example·第二章 的相关文章

随机推荐