《Django by Example》第二章 使用Django高级功能增强你的Blog

在上一个章节,我们已经创建了一个基础的Blog程序。现在我们将使用一些Dajngo高级功能,去实现一个完整的blog网站,比如通过email进行分享,允许用户进行评论,可以添加标签和对帖子进行搜索检索,在这一章,我们将学到如下的内容:

  1、使用Django去发送一封邮件

 2、创建表单并在视图中处理他们

3、从models中创建表单

4、整合集成一些第三方的应用

5、创建复杂的查询

第一:使用Djngo去发送一封邮件

首先,我们将实现让用户通过邮件去分享自己的帖子的功能,在未看指南之前,先自己花点时间想想,你将如何使用上一章节我们学过的URLS\VIEWS\TEMPLATE等知识,来自己设计实现一个发送邮件的功能?现在检查你需要什么去允许你的用户发送通过邮件发送帖子。你将要:

1、创建一个表单,让用户可以去输入 名字、email、接收人和可选的评论

2、在views.py里面创建一个视图,处理帖子的内容并发出邮件

3、在blog程序的url 地址映射表中,将新增加的视图,添加到地址映射表

4、创建一个展示表单的网页模板(Template)

在Django中创建/使用 表单

让我们从创建分享帖子的表单开始,Django有一个内置的表单框架,让你可以用一个简单的方式去创建一个表单。这个表单的框架允许你去定义你表单的字段,标记他们是否必须显示,验证输入的数据是否准确。Django的表单架构还是提供处理数据和渲染表单的一种灵活的方式。

Django有两个基础类去创建表单:

Form  允许你去创建标准的表单

ModelForm  允许你创建表单来创建和更新model实例

首先,我们在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 = froms.Textarea)

这是你的第一个Django表单,让我们再看看code:我们通过基础的Form类,来创建一个表单,我们使用了Django的不同类型的字段,并根据每个字段类型的不同去验证数据的有效性。

注意: 表单可以写在Django项目的任何地方,如写到views里面或者写在文件里面或者写在models里面,但一般来说,大家约定是在每个应用程序中创建一个forms.py,将表单写在这个文件中。

在上面的表单中,name字段是一个CharField类型,这个类型的字段在网页上呈现的是一个<input type='text'>的 Html  元素。每一个字段类似都有一个默认的网页元素widget与之对应。字段的默认网页元素widget属性可以被重写,后面将会介绍到如何重写一个表单字段的默认widget属性。在comments字段中,我们使用widget=forms.Textarea来使用html的Textarea 元素替代默认的字段显示为 <input>元素。意思说,如果我们不在字段属性后面使用 widget=froms.Textarea,那么 comments字段的默认widget是input。

字段校验同样依赖于字段类型。例如,email和to这两个字段的类型是EmailField。这些字段就需要用户输入一个正确的e-mail地址,否则校验的时候会抛出一个forms.ValidationError异常,并且表单不会通过验证。其他的参数同样需要考虑到表单的验证:我们定义了一个最大长度为25个字符的name字段,我们让comments字段含有一个required=False参数来指明comments是一个可选的,不用必须输入。所有这些都需要考虑到字段的校验。上面表单的字段操作知识只是Django 表单字段的一部分,对于所有表单的可用字段列表说明,你可以访问:https://docs.djangoproject.com/en/1.8/ref/forms/fields/.

在视图中处理表单

当表单成功提交后,为了能够处理表单的数据并发送一个e-mail你需要去创建一个新的view。编辑你的blog程序下的views.py文件,将下面的代码添加进去:

from .forms import EmailPostForm

def post_share(request,post_id):

 #Retrive post by id ,通过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并确实检索到的post是published状态

我们仍然用这个view去显示初始化的表单并处理提交的数据.我们对表单提交的方法进行了区分。我们用POST方法去提交表单数据,用get方法去获取表单数据。同时我们需要判断以下,如果我们得到的是一个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.cleaned_date来检索验证后的数据。表单的字段和表单值将以字段的形式组织起来。

注意:假如你的表单数据没有校验 cleaned_date将只包含有效的字段。

现在,你需要去学习,怎么使用Django发送一个email,去把他们整合在一起。

通过Django发送e-mails

通过django发送e-mails是很简单。首先,你需要有个本地的SMTP服务器或者通过在项目的settings.py里面添加下面的步骤定义一个外部SMTP服务器。

EMAIL_HOST:SMTP服务器的host。默认是本地的

EMAIL_POST:SMTP服务器的端口,默认是25

EMAIL_HOST_USER:SMTP服务器的用户名

EMAIL_HOST_PASSWORD:SMTP服务器的密码

EMAIL_USE_TLS:是否去使用TLS安全连接

EMAIL_USE_SSL:是否去使用隐藏TLS安全连接

假如你没有本地服务器,你可以使用你的email提供商的SMTP服务器。下面是一个通过使用GMAIL服务器发送邮件的例子。

EMAL_HOST ='smtp.gmail.com'

EMAIL_HOST_USER='your_account@gmail'

EMAIL_HOST_PASSWORD='your password'

EMAIL_post=587

EMAIL_USE_TLS=True

在命令行运行python manage.py命令打开python的shell,像下面这样发送一个e-mail试试:

>>>from django.core.mail import send_mail

>>>send_mail('Django mail','this e-mail was send with django.','your_account@gmail',['your_account@gmail.com'],fail_silently=False)

send_mail() 使用 主题、消息、发送者、收件人列表做为参数.通过设置可选参数fail_silently=False,我们告诉服务器,当e-mail不能被正确投送时,会抛出一个异常,如果你看到值为1,这个时候你的email被发送成功。如果你通过前面配置的gmail服务器,你需要去允许访问低安全度在https://www.google.com/settings/security/lesssecureapps.

现在,我们开始把这些添加到我们的视图中,在blog程序的views.py中编辑post_share视图,让他像下面这样:

from django.core.mail import send_mail

def post_share(request,post_id):

#retrieve post by id

post = get_object_or_404(Post,id=post_id,status='published')

sent = False

if request.method == 'POST':

#form was submitted

from = EmailPostForm(request.POST)

if form.is_valid():

cd = form.cleaned_date

post_url = request.build_absolute_url(post.get_absolute_url())

subject='{}({})recomends you reading "{}"'.format(cd['name'],cd['email'],post.title)

message='Read "{}" at {}\b\n{}\'s comments:{}'.format(post.title,post_url,cd['name'],cd['comments'])

send_mail(subject,message,'admin@myblog.com',[cd['to']])

sent =True

else:

form = EmailPostForm()

return render(request,'blog/post/share.html',{'post':post,'form':form,'sent':sent})

注意,我们在发送post的时候声明了一个sent变量并将其设置为True。当表单成功提交后,我们将在模板中使用这个变量,去显示一个成功的信息。因为我们必须在电子邮件中加入一个post连接,我们得用get_absolute_url()来检索post的绝对路径。

我们使用这个路径做为request.build_absolute_uri()的输入,去构建一个完整的URL链接,在这个URL链接中包含HTTP模式和域名,我们通过使用已验证过的、被清洗后的表单数据构建邮件的标题和邮件正文,并最终发送邮件到表单里面e-mail字段的 地址中。

现在你的视图已经完成,记住需要为它添加一个新的url表达式。打开blog程序下的urls.py文件,添加post_share url表达式,完成后如下:

urlpatterns =[

#.....

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

]


在网页模板中渲染表单

在创建了模板,编写了view的代码,并在urls 地址路由表中添加了url表达式,我们就只缺少构建一个该视图的网页模板了。在blog/templates/blog/post/目录下添加一个名字叫做share.html的新文件,并加入如下的代码:

{% entends "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}}

{% crst_token %}

<input type="submit" value = "send e-mail">

</form>

{% endif %}

{% endblock %}

这个模板用来去显示表单或者当他被发送时显示发送成功的信息。正如你所看到的,我们创建这个HTML表单元素去标明 ,表单必须通过POST方法去提交。

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

然后我们包含了这个实际的表单实例,我们告诉Django在HTML段落 <p>元素中,用表单的as_p方法去渲染表单的字段,我们还可以通过调用as_ul渲染表单为一个无序列表或者调用as_table渲染成为一个html 表。如果我们想渲染每一个字段,我们还可以通过遍历字段。向下面的例子:

{% for field in form %}

<div>

{{field.errors}}

{{field.label_tag}}{{field}}

</div>

{% endfor %}

{% csrf_token %}模板标签 引入一个隐藏字段,带有一个自动生成的token,去规避 CROSS-Site Request Forgery(CSRF)攻击。这些攻击由一些恶意的网站或者程序组成,去执行一些你的网站用户不想发生的有害的行为。

在https://en.wikipedia.org/wiki/Cross-site_request_forgery你可以发现更多的关于CSRF信息。

前面说的这个标签生成一个隐藏的字段,像下面这里:

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

注意:默认的,Django在所有的POST请求中检查CSRF token。记住在所有的表单中包含csrf_token标签并通过POST渠道去提交。

编辑你的 blog/post/detail.html模板,添加下述链接到分享帖子的url中,在{{post.body|linebreaks}}变量后。

<p>

<a href ="{% url "blog:post_share" post.id %}">share this post</a>

<p>

记住,我们正通过Django提供的 {% url %}模板标签去动态构建url链接。我们通过名字回调blog并把url命名为 post_share,我们通过传递post id参数去创建绝对路径。

现在,使用python manage.py runserver命令启动开发服务器,在浏览器打开http://127.0.0.1:8000/blog/,点击任意一个帖子标题进入详情页。在正文的下面,你将看到我们刚刚添加的链接,如下图所示:

XXXXX图片。。

点击 Share this post ,你将看到一个包含通过email去分享这个post表单的页面,如下图所示:

表单的CSS样式表包含在 例子代码的 static/css/blog.css文件中,当你点击Send e-mail按钮,这个表单将提交并进行验证,如果所有的字段都是有效的数据,你将得到一个如下图所示的发送成功的消息。

如果你输入了错误的数据,你将看到,表单被在再次渲染,并包含所有的校验错误信息。

创建一个评论系统

现在,我们开始为这个blog程序开发一个评论系统,在里面用户可以对某个帖子发表评论。去创建这样一个评论系统,你将要做:

-创建保存评论的数据模型;

-创建一个表单去提交评论并验证输入的数据正确性;

-添加一个处理表单并保存评论记录到数据库中的视图;

-编辑帖子的detail模板,增加评论列表在详情页的显示,并含有让用户进行评论的表单单元;

首先,让我们创建一个保存评论的数据库模型。打开你的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数据模型.它包含一个外键,用来让评论关联到唯一一个帖子上。这是在Commnet数据模型中定义的一个多对一的数据关系,因为每个评论都是在帖子上,并且每个帖子都可能有多个评论。post的 related_name属性,允许我们通过定义这个属性来让关联的对象反向引用。在定义这个后,我们就可以通过使用comment.post获取到当前帖子的一条评论对象或者通过post.comments.all()获取到当前post的所有评论。如果你不定义related_name这个属性,Django将使用 undercase name of 数据模型后面跟随的_set连接器(在本例中是 comment_set)去命名关联对象反向引用的管理器。

[翻译不好,引用解释:如果模型有一个ForeignKey,那么该ForeignKey 所指的模型实例可以通过一个管理器返回前一个模型的所有实例。默认情况下,这个管理器的名字为foo_set,其中foo是源模型的小写名称。该管理器返回的查询集可以用上一节提到的方式进行过滤和操作。]

你可以在https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/学习到更多关于多对一关系的资料。中文版:http://python.usyiyi.cn/documents/django_182/topics/db/queries.html#backwards-related-objects

http://python.usyiyi.cn/translate/django_182/topics/db/queries.html#following-relationships-backward

[这个地方介绍的比较好

我们包含一个active的布尔类型字段,这个可以用来对comments进行手动的审核。我们用created字段对评论进行默认时间顺序排序。

你刚才新创建的Comment模型还没有同步到数据库中。运行下面的命令,去生成一个新的迁移前准备。

python manage.py makemigrations blog

你将看到下面的输出:

Migrations for 'blog':

0002_comment.py:

-create model comment

Django在blog程序的 migrations目录下生成了一个0002_comment.py的文件,现在你需要去创建关联数据库模式并把更新应用到数据库上。运行下面的命令去应用已存在的迁移:

python manage.py migrate

你在下面会看到如下的输出信息:

Applying blog.0002_comment... OK

我们刚才创建的迁移就这样被应用了,在数据库你就能看到一个新的blgo_comment的数据表存在。

现在我们可以把我们的新数据模型添加到管理后台页面中,通过简单的交互界面去管理评论。打开blog程序里的admin.py文件,添加对Comment的调用和下面的ModelAdmin:

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')#在管理后台,可以通过name、email、body进行搜索

admin.site.register(Comment,CommentAdmin)

用python manage.py runserver启动开发环境的服务器,并用浏览器打开 http://127.0.0.1:8000/admin/,你将看到在blog的段落中,已经包含了这个新的comment,如下面截图所示:

XXXXXX

我们的数据模型现在被注册进入到管理页面,这让我们可以通过简单的交互界面去管理Comment实例。

从数据模型创建表单

我们还需要去创建一个表单让我们用户能在blog帖子里面进行评论。还记得之前我们说过,Django支持两种基础类去创建表单:From和ModelForm。在前面的例子中,我们已经使用了第一种类去创建表单,让用户可以通过e-mail去分享帖子。在当前的例子中,你将使用ModelForm类去创建表单,因为你必须从你的Comment数据模型中创建一个动态表态。编辑blog程序中的forms.py并添加下面的代码:

from .models import Comment

class CommentForm(forms.ModelForm):

  class Meta:

model =Comment

fields =('name','email','body')

从数据模型中创建一个表单,我们只需要在Meta元组中去指明哪个模型🏆用来创建表单。Django 会自己反查数据模型并为我们创建一个动态的表单。每个表单的字段都有一个相应的默认表单字段类型。我们定义我们模型字段的方法是考虑到表单验证的。默认的,对于每个Django的form字段都在数据模型中包含了。然而,你可以通过使用fields_list方法来明确的告诉框架,那个字段是你想在form中使用的,或者使用exclude一个字段列表来定义表单会包含那些你想要包含的字段。在这个CommentForm中,我们只在表单中用了name,email,body字段,因为这些是我们的用户可以去填写的。

在views里面处理ModelForm

为了让表单比较简单,我们将使用帖子已有的detail视图去实例化并处理表单。编辑models.py文件,添加Comment数据模型和CommentFrom表单的引用,并编辑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,publist__month=month,publish__day=day)

#List of active comments for this post

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

if request.method = 'POST':

#A comment was posted

comment_form = CommentForm(data = request.POST)

if comment_form.is_valid():

#create comment object bug don't save to database yet

new_comment = comment_form.save(commit=False)

#assign the current post to the comment

new_comment.post=post

#save the comment to the database

new_comment.save()

else:

comment_form =CommentFrom()

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

我们回顾一下我们刚才添加到视图中的信息。我们使用post_detail视图去显示帖子和他的评论,我们添加了一个QuerySet去检索当前帖子的所有活跃的评论

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

我们从post对象创建这个QuerySet。我们用之前在comment数据模型里面定义反向数据查询管理器related_name=comments来通过post关系反向查询comments。

在这个视图中我们还需要去实现用户提交新的评论功能。因此如果调用视图的是一个GET请求,我们使用comment_form=CommentFrom()创建一个form实例,如果调用视图的请求是一个POST请求,我们使用用户提交的数据实例化表单,并用is_valid()方法来校验数据有效性。如果表单是无效的,我们用校验错误信息渲染模板,如果表单数据有效的,我们采取后续行动:

1、我们调用form.save()方法创建一个新的Comment对象,像下面这样:

new_comment = comment_form.save(commit=False)

save()方法创建一个当前表单所连接的模型的实例,并保存他到数据库,假如你用commit=False调用他,你将创建模型的实例,但并没有将它保存到数据库。当你在接下来的下一步,想在最终保存数据前,对数据进行一些修改,这是很方便的。save()方法对ModelForm是可用的,但对Form实例是不可用的,因为Form实例没有关联到任何的模型上。

2.我们把当前的帖子指定到刚创建的comment上。

new_comment.post=post

通过这样,我们清晰的描述了,新评论属于给定的帖子。

3.最终,我们用下面的代码将新评论保存到数据库中。

new_comment.save()

现在我们的view已经可以去处理和显示新的评论了。

添加评论到帖子的详情模板中

我们已经创建帖子的评论管理功能。现在我们需要去让我们的post_detail.html模板去适配做这些:

>显示当天帖子的评论总数

>显示评论列表

>显示用户添加新评论的表单,让用户可以再帖子添加新评论

首先,我们将添加评论总数。打开blog_detail.html模板,并在content模块里添加下面的代码:

{% with comments.counts as total_comments %}

<h2>

{{total_comments}}  comments {{total_comments|pluralize}}

</h2>

{% endwith %}

我们在模板中使用Django ORM,执行查询comments.count().记住django模板语言不用使用圆括号去调用方法。如上面我们使用的comments.counts.{% with %}标签允许我们去给一个新的变量赋值,这个变量可以使用到 {% endwith %}标签处。

注意:{% with %}模板标签是一个需要规避多次访问数据库数据或使用比较耗时的方法时的有用方法。

我们使用pluralize模板过滤器,根据total_comments值去显示comment的复数后缀.模板过滤器 用已被使用的变量的值做为输入并返回一个计算过的值。在第三章《扩展Blog程序》我们将讨论模板过滤器。

如果值是和1不同的,pluralize模板过滤器将显示一个"s"。前面的文字将被渲染成 0 comments,1 comment, 或者N comments。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 %}模板标签去循环所有的评论。当comments列表是空的时候,我们显示一个默认的消息,告诉用户当前帖子还没有评论。我们使用 {{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对象存在,我们显示一个成功的信息,因为评论已被成功的创建了。否则,我们为每个字段使用一个段落元素去渲染表单,并且对于POST请求我们需要添加一个CSRF token的验证码。在浏览器打开http://127.0.0.1:8000/blog/,点击帖子的标题查看详情页,如下图所示:

XXXX

用表单添加几个评论,他们应该按照时间顺序显示在你的帖子后面。像下图所示

XXXXXXX

在浏览器打开http://127.0.0.1:8000/admin/blog/comment/,在管理页面,你将看到你创建的评论列表,选一个点击并编辑,不选Active复选框,并点击保存按钮。我们将重新得到一个评论列表,并且在Active列上面,当前评论将显示一个不活跃的图标。像下面的截图中,第一个评论已经被标记成inactive.

XXXX

这时候,假如你返回到帖子详情页,你将注意到,刚才被删除的评论在任何地方都没有再显示。无论是创建的评论总数还是评论详情。幸亏有active字段,你可以在你的帖子中设置一些评论无效并避免显示他们。

添加标签功能

在实现了我们的评论系统后,我们将开始去创建一个标记我们文章的方法,我们计划在我们项目中整和一个第三方的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提供的TabgableManager管理器到Post模型中

from taggit.managers import TaggableManager

class Post(models.Model):

#...

tags = TaggableManager()

tags管理器允许你从post对象中去添加、检索、删除标签。运行下面的命令给模型的改变创建一个迁移事件。

python manage.py makemigrations blog

我们将得到如下的输出:

Migrations for 'blog':

0003_post_tags.py

-Add field tags to post

现在,运行下面的命令,为django-taggit模型创建需要的数据库表,并同步模型的变更到数据库中。

python manage.py migrate

你将看到一个输出指示当前的迁移已经被应用了,如下图所示:

Applying taggit.0001_initial.....ok

Applying taggit.0002_auto_20150616_2121.....ok

Applying blog.0003_post_tags....ok

现在数据库已经可以去试用django-taggit模型,试用python manage.py shell命令打开客户端,我们来学学如何使用tags管理器。首先,我们检索出来一个帖子,(用帖子ID)

>>>from blog.models import Post

>>> post = Post.objects.get(id=1)

然后我们在post对象里面添加一些标签并将这些标签再检索出来,验证这些标签被成功添加了:

>>post.tags.add('music','jazz','django')

>>>post.tags.all()

[<tag:jazz>,<tag:django>,[tag:musci]]

最终,删除一个标签并再检查标签列表:

>>>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应用程序,里面有标签对象的列表。

跳转到http://127.0.0.1:8000/admin/blog/post/,点击一条帖子并编辑他,你可以看到在帖子里面已经包含了一个新的Tags的字段,像下图所示,你可以很轻松的去编辑标签信息。

XXXXXXXX

现在我们打算去编辑我们的blog帖子,并显示这些标签,打开blog/post/list.html模板,在html的帖子标题下面添加下面代码:

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

上面的  join模板过滤器的工作机制和python里面的 字符 join()方法相同,用来连接给定的字符元素。打开http://127.0.0.1:8000/bolg/,在每个帖子标题下面你就能看到一条标签列表了,如下图:

XXXXXXXX

现在,我们开始去编辑我们的post_list视图,让用户可以通过一个给定的标签去列出符合这个标签的所有帖子。打开你的blog程序中的views.py文件,从django-taggit中加载Tag模板,像下面这样把post_list视图换成posts的tag选择过滤器。

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])

#...

视图的工作流程如下:

1、在视图我们取了一个可选的tag_slug参数,并赋值为None,这个参数将通过URL取得。

2.在view里面,我们创建最初的检索,检索出所有的已发表的帖子,假如当前帖子有 tag_slug,我们通过已知的slug使用 get_object_or_404()快捷功能区获取Tag对象。

3.然后我们根据帖子是否包含已给的tag来过滤出来帖子列表。因为这是一个多对多的关系,我们必须以 标签包含在给定的标签列表来过滤帖子。在我们的例子里面,只有一个元素符合。

请记住,Querysets是惰性的【像Entry.Objects.all(),这些操作返回的是一个QuerySet对象,这个对象比较特别,并不是执行Objects.all(),或者filter之后就会与数据库交互,而是执行比如打印具体entry某个参数,才会去进行数据库查询,这个就是django querysets惰性的解释】,在渲染模板的时候当我们循环完帖子列表Querysets才会去执行检索帖子的操作。

最后,修改view底部的render()功能,将tag变量值传给模板,最终view视图如下:

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 = Paginarot(object_list,3) #每页显示3条帖子

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'),

添加下面的新urlm模式去支持 通过标签筛选帖子列表:

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

像看到那样,两个url的模式都是指向同一个view,但是我们把它命名不同的名字,第一个模式命名为post_list,没有任何的参数项;第二个url模式,需要使用一个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 %},{% enfif %}

{% endfor %}

</p>

现在,我们循环通过帖子显示的所有标签,并通过tag过滤帖子。我们用 {%url "blog:post_list_by_tag" tag.slug%}动态创建url,并用url名字和标签slug作为参数,我们通过逗号分开这些标签。

打开 http;//127.0.0.1:8000/blog/,点击标签连接,你可以看到通过标签过滤出来的帖子列表,如下图:

XXXXXXX


关联检索

现在,我们已经为我们的博客贴上了标签,做完这个,我们就可以围绕标签做很多有意思的事情。

通过标签,我们可以把我们的博客进行很好的分类。类似主题的文章通常会有好几个标签,我们计划去创建一个功能,显示被分享的文章中,相关标签的数量。这样,当一个用户读一个文章时,我们就可以建议他再读其他的相关的文章。

为实现通过具体文章来进行关联检索,我们需要:

检索当前文章的所有标签

获取所有使用当前文章标签中任意一个标签标记上的文章

通过分享文章中标签的数量,对结果进行排序;

万一有两个或者多个文件,都有相同数量的标签,那么推荐最近发布的。

将查询限制为要推荐的文章数量

上面这些步骤被一个复杂的查询数据集实现,计划在我们的post_view视图包含这个查询。打开blog程序的views.py文件,在文件头部添加下面的import信息:

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__inpost_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 查询组将返回一个值的元组列表,values_list()查询组将返回定值的字段的值的元组数据。我们通过传递flat=True去获取一个平坦单调的列表,[1,2,3,....].

2.我们获取所有包含任意一个上文标签的文章并将当前文章自己从列表中去掉;

3.我们使用Count聚合函数去生成一个统计字段same_tags,包含所有需要统计的标签的总数

4.我们通过共享标签的数量,对结果进行排序,并且当不同的文章含有相同的标签数量时,我们使用最近发布的文章,我们取当前结果最开头的四篇文章作为和当前文章关联最紧密的文章去显示;

添加sililar_post对象到render()函数的上下文字典字典中,如下图:

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

现在 编辑 blog/post/detail.html模板,并在文章评论列前面添加下面的代码:

<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 %}

也建议 和我们在文章列表模板里面做的一样 ,再添加标签列表到你的文章详情模板。现在你的文章详情页看起来应该像下面这个样子:


XXXXXX

现在你可以成功的推荐相关文章给你的用户了,django-taggit同样包含一个similar_objects()管理器,你可以使用这个管理器通过共享标签检索对象,你可以通过http://django-taggit.readthedocs.org/en/latest/api.html来查看全部的django-taggit相关技术知识。

总结:

在本章节,我们学习了如何使用Django 的froms和models froms。我们创建一个通过邮件分享网站内容的系统,我们给自己的博客创建了一个评论系统,我们给我们的博客文章添加了标签,整合一个第三方可重用的程序,让我们可以创建关联的查询组,去查询关联对象。

在下一章,我们会学习如何创建一个 自定义的标签和过滤器。我们还会去创建一个自定义站点地图并填充到你的博客文章中,并在我们的程序中聚合一个高级搜索引擎。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容