Django by Example·第二章|Enhancing Your Blog with Advanced Features@笔记

news2024/12/23 10:08:04

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以按相似性检索对象。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147439.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

acwing差分

题目&#xff1a;输入一个长度为 n 的整数序列。接下来输入 m 个操作&#xff0c;每个操作包含三个整数 l,r,c&#xff0c;表示将序列中 [l,r] 之间的每个数加上 c。请你输出进行完所有操作后的序列。输入格式第一行包含两个整数 n 和 m。第二行包含 n 个整数&#xff0c;表示整…

【C++高阶数据结构】跳表(skiplist)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

第十一章Thymeleaf学习

文章目录什么是Thymeleaf什么是模板引擎Thymeleaf的同行Thymeleaf优势一个实例来认识大概过程导入对应的jar包配置对应的xml文件对应的ViewBaseServlet编写——对应的模板引擎写对应的Servlet类并且继承ViewBaseServlet对应index.html资源——对应的模板Thymeleaf的基础语法th名…

337. 打家劫舍 III

目录题目思路代码题目 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”…

笔试强训(11)

第一题:二进制插入二进制插入__牛客网 给定32位整数n和m&#xff0c;同时我们指定i和j&#xff0c;将m的二进制位数插入到n的二进制位数的j到i位&#xff0c;我们保证n的j到i位均是等于0的&#xff0c;况且m的二进制位数小于等于i-j1&#xff0c;其中二进制的位数从0开始从低到…

js设计模式(八)-总体感受一下设计模式

前言 首先&#xff0c;不得不说我们是站在巨人的肩膀上写代码&#xff0c;前辈们已经很合理的帮助我们总结出来了23种设计模式&#xff0c;虽然有些已经被语言直接使用Api实现了&#xff0c;感谢走在前沿的攻城狮。 但是真真正正的看一遍所有的设计模式还是很有必要的&#x…

MyBatis查询数据库

1.MyBatis 是什么&#xff1f; MyBatis 是⼀款优秀的持久层框架&#xff0c;它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO&#xf…

计算机基础——计算机分类

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会讲解计算机分类应用领域以及发展趋势 一.计算机分类 计算机并非只有日常所…

并行计算 Clion配置使用OpenMP

文章目录配置CMakeList.txt文件OpenMP之HelloWorld数据共享属性shared子句private子句default子句default(shared)default(none)配置CMakeList.txt文件 文件底部加入以下内容&#xff0c;即可支持OpenMP FIND_PACKAGE(OpenMP REQUIRED) if (OPENMP_FOUND)message("OPENM…

STM32MP157驱动开发——Linux DAC驱动

STM32MP157驱动开发——Linux DAC驱动0.前言一、DAC 简介二、驱动源码分析1.设备树下的 DAC 节点2.驱动源码分析1&#xff09;stm32_dac 结构体2&#xff09;stm32_adc_probe 函数3&#xff09;stm32_dac_iio_info 结构体三、驱动开发1.修改设备树2.使能DAC驱动四、 运行测试0.…

读书笔记 -公司改造 和 紧迫感

读书笔记 -公司改造 - 三枝匡 读书笔记 -公司改造 - 三枝匡 2022 年夏天的时候在微信读书上读了这本书&#xff0c;这是我们 CSDN 的创始人蒋涛推荐的&#xff0c;当时记了一些笔记如下。 总结&#xff1a; 每个有一定的历史&#xff0c;比较成功、或者尚未非常成功的公司遇…

基于Java+SpringBoot+vue+element实现毕业就业招聘系统

基于JavaSpringBootvueelement实现毕业就业招聘系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…

用最简单的案例带你掌握C++中各种指针

1、前言 指针&#xff0c;作为C/C中最神秘、功能最强大的语法&#xff0c;着实是难以理解 、难以掌握、难以运用。&#x1f625; 但是&#xff0c;能灵活的使用指针&#xff0c;对利用C/C开发程序将有很大的帮助&#xff0c;让我们一起来了解了解吧。 2、啥是指针&#xff1f…

参加《2022 中国开发者影响力盛典》我的 4 重收获!

感谢 CSDN 邀请&#xff0c;西红柿有幸参加了 2022 中国开发者影响力盛典暨 CSDN 企业生态汇&#xff0c;让我有了一个不虚此行的下午&#xff0c;也跟大家分享一下我在会上的 4 重收获吧~第一重收获&#xff1a;互联网圈大佬 会议聚焦开发者生态建设主题&#xff0c;分享了 CS…

分布式基础篇4 —— 基础篇完结

分类维护一、三级分类后端实现准备工作跨域问题关闭 ESLint 检查前端实现二、分类删除前端完善分类列表后端实现——删除配置发送请求代码片段前端实现——删除三、分类增加前端实现四、分类修改五、拖拽菜单拖拽效果实现拖拽数据收集拖拽功能完成拖拽功能完善六、批量删除品牌…

JS知识补充-JS原型链

概述JS原型链别名&#xff1a;隐式原型链作用&#xff1a;根据一定路径查找属性&#xff08;方法&#xff09;作用举例&#xff1a;我们定义一个构造函数Fn&#xff0c;使用此构造函数创建一个对象fn1&#xff0c;接着使用创建的对象fn1去调用toString方法并打印&#xff0c;我…

【阶段三】Python机器学习03篇:机器学习中的函数、机器学习中的梯度下降、机器学习的数据结构:张量与机器学习概率与统计基础

本篇的思维导图: 机器学习中的函数 函数描述了输入与输出的关系。在函数中,一个事物(输出)随着另一个(或一组)事物(输入)的变化而变化,如下图所示。 输入与输出的关系一般情况下,用x(或x1,x2,x3,…)表示输入,用y表示输出,并把它们叫作变量,…

Java设计模式中的设计原则/开闭原则、里氏代换原则和依赖倒转原则又是什么,怎么用

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 3.设计原则 3.1 目的 提高软件系统可维护性与可复用性增加软件可扩展性与灵活性节约开发成本与维护成本 3.2 开闭原则 3.2.1 特点 对扩展开放&#xff0c;对修…

实战干货|自研数据存储迁移MySQL实战

背景 最近公司内部在做某自研数据存储的下线工作&#xff0c;这里我们暂且化名其为DistributeSQL&#xff0c;由于DistributeSQL不再进行服务支持&#xff0c;需要迁移项目中使用到该存储到其他数据存储中。 本篇来聊聊这次在数据存储迁移过程中的方案设计思路、实现的大致细节…

中老年服装电商小程序开发

近年来&#xff0c;随着网络的发展&#xff0c;中老年服装电商小程序开发有了很大的进步。这种平台不仅可以方便用户购买到最新最时尚的产品&#xff0c;而且还能帮助商家提高销售业绩。 1&#xff1a;中老年服装电商小程序开发的优势 中老年人对商品信息需求大、容易接受新鲜…