在上一个章节,我们已经创建了一个基础的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类型,这个类型的字段在网页上呈现的是一个的 Html 元素。每一个字段类似都有一个默认的网页元素widget与之对应。字段的默认网页元素widget属性可以被重写,后面将会介绍到如何重写一个表单字段的默认widget属性。在comments字段中,我们使用widget=forms.Textarea来使用html的Textarea 元素替代默认的字段显示为 元素。意思说,如果我们不在字段属性后面使用 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',['[email protected]'],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,'[email protected]',[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
]
在网页模板中渲染表单
在创建了模板,编写了view的代码,并在urls 地址路由表中添加了url表达式,我们就只缺少构建一个该视图的网页模板了。在blog/templates/blog/post/目录下添加一个名字叫做share.html的新文件,并加入如下的代码:
{% entends "blog/base.html" %}
{% block title %} Share a post {% endblock %}
{% block content %}
{% if sent %}
E-mail successfully sent
"{{post.title}}" was successfully sent to {{cd.to}}
{% else %}
Share "{{post.title}}"by e-mail
{% endif %}
{% endblock %}
这个模板用来去显示表单或者当他被发送时显示发送成功的信息。正如你所看到的,我们创建这个HTML表单元素去标明 ,表单必须通过POST方法去提交。
{% 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()
[
最终,删除一个标签并再检查标签列表:
>>>post.tags.remove('django')
>>>post.tags.all()
[
很简单,对不对?运行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的帖子标题下面添加下面代码:
上面的 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
像看到那样,两个url的模式都是指向同一个view,但是我们把它命名不同的名字,第一个模式命名为post_list,没有任何的参数项;第二个url模式,需要使用一个tag_slug参数来调用视图。
以后我们用post_list视图,我们去编辑blog/post/list.html模板,并编辑分页使用posts对象:
{% include "pagination.html" with page=posts %}
在{% for %}循环上添加下面的行:
{% if tag %}
Posts tagged with "{{tag.name}}"
{% endif %}
加入用户访问泊客,他将看到帖子列,如果他通过一个给定的标签去过滤帖子,他将看到这些信息。更改标签的显示方式:
Tags:
{% for tag in post.tags.all %}
{% if not forloop.last %},{% enfif %}
{% endfor %}
现在,我们循环通过帖子显示的所有标签,并通过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模板,并在文章评论列前面添加下面的代码:
Similar posts
{% for post in similar_posts %}
{% 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。我们创建一个通过邮件分享网站内容的系统,我们给自己的博客创建了一个评论系统,我们给我们的博客文章添加了标签,整合一个第三方可重用的程序,让我们可以创建关联的查询组,去查询关联对象。
在下一章,我们会学习如何创建一个 自定义的标签和过滤器。我们还会去创建一个自定义站点地图并填充到你的博客文章中,并在我们的程序中聚合一个高级搜索引擎。