第二章内容:
利用django发送邮件
创建表单并在视图中加以处理
从模型中创建表单
整合第三方应用程序
构建复杂的QuerySet
2.1通过电子邮件共享帖子
针对帖子的邮件发送功能,需要执行以下操作:
- 创建用户表单,并填写名字,邮件,收件人,可选的备注功能。
- 在views.py文件中创建视图,并处理数据以及发送邮件.
- 在blog应用程序的urls.py文件中,针对新视图添加url路径。
- ’ 创建模板并显示表单。
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)
setting中配置邮件参数:
# 发送邮件配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# smpt服务地址
EMAIL_HOST = 'smtp.qq.com'
EMAIL_PORT = 25 # 端口默认都是25不需要修改
# 发送邮件的邮箱,需要配置开通SMTP
EMAIL_HOST_USER = '[email protected]'
# 在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'xxx'
# 收件人看到的发件人
EMAIL_FROM = '带签名的<[email protected]>'
views.py中处理表单:
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
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, '[email protected]', [cd['to']])
sent = True
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html', {"post": post, "form": form, 'sent': sent})
配置blog/urls.py:
path('<int:post_id>/share/', views.post_share, name='post_share'),
在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 {{ form.cleaned_data.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 %}
然后运行程序就可以发送邮件分享了,注意setting和view中的发送邮箱[email protected]要一致。
感觉做的有点累,但是加油,我们才刚开始。。。继续努力。
2.2构建评论系统
第一步创建模型,blog中models.py文件中写代码:
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE,
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)
运行命令行进行迁移
python manage.py makemigrations blog
python manage.py migrate
在blog/admin.py中导入comment模型:
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'post', 'created', 'active')
list_filter = ('active', 'created', 'updated')
search_fields = ('name', 'email', 'body')
创建模型中的表单,这里使用ModelForm:
from blog.models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
修改blog/views.py中post_detail函数:
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)
new_comment = None
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,
'new_comment': new_comment,
'comment_form': comment_form})
修改post/detail.html
- 显示全部评论数量
- 显示评论列表
- 向用户显示一个表单,并可添加新的评论内容。
{% extends "blog/base.html" %}
{% block title %}{{ post.title }} {% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|linebreaks }}
<p>
<a href="{% url 'blog:post_share' post.id %}">
Share this post</a>
</p>
{% with comments.count as total_comments %}
<h2>
{{ total_comments}} comment{{ total_comments|pluralize }}
</h2>
{% endwith %}
{% 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 %}
{% 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 %}
{% endblock %}
访问网址,可以看到下面内容,提交评论测试成功。
2.3 添加标签功能
第三方插件django-taggit模块,书上是0.22.2版本,我安装的是1.2.0版本。
pip install django_taggit==0.22.2
setting.py中注册
INSTALLED_APPS = [
...
'taggit',
]
在models.py中的Post函数中实例化。
class Post(models.Model): # 继承models.Model
...
tags = TaggableManager() # 添加标签
然后数据库迁移两步操作。
python manage.py makemigrations blog
python manage.py migrate
运行项目,打开http://127.0.0.1:8000/admin/taggit/tag/
添加标签。给每个文章添加标签。
在blog/post/list.html中帖子标题h2下加入p标签,使用逗号join:
<p class="tags">Tags:{{ post.tags.all|join:"," }}</p>
修改blog/views.py,这里又使用了函数视图,完整代码如下:
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) # 每页显示三个
page = request.GET.get("page")
try:
posts = paginator.page(page)
except PageNotAnInteger: # 不是整数,返回第一个页面
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request, 'blog/post/list.html',
{'page': page, 'posts': posts, 'tag': tag})
注意修改url,我把此时的blog/urls.py整个贴出来:
from django.urls import path
from . import views
urlpatterns = [
path('', views.post_list, name='post_list'),
# path('', views.PostListView.as_view(), name='post_list'),
path('<int:year>/<int:month>/<int:day>/<slug:post>/',
views.post_detail,
name='post_detail'),
path('<int:post_id>/share/', views.post_share, name='post_share'),
path('tag/<slug:tag_slug>/', views.post_list, name='post_list_by_tag'),
]
这里的两个路径指向了同一个视图,但采用了不同的name,第一种不含参数,第二个使用tag_slug参数,使用slug路径转化器。
修改blog/post/list.html,我把全部代码贴出来:
{% extends "blog/base.html" %}
{% block title %}{% endblock %}
{% block content %}
<h1>My blog</h1>
{% if tag %}
<h2>Posts tagged with "{{ tag.name }}"</h2>
{% endif %}
{% for post in posts %}
<h2>
<a href="{{ post.get_absolute_url }}">
{{ post.title }}
</a>
</h2>
<!--修改标签的显示方式-->
<p class="tags">Tags:
<!--{{ post.tags.all|join:"," }}-->
{% 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>
<p>
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|truncatewords:30|linebreaks }}
{% endfor %}
{% include "pagination.html" with page=posts %}
<!--{% include "pagination.html" with page=page_obj %}-->
{% endblock %}
访问链接,测试查看,需要提前在admin中给文章添加标签。
http://127.0.0.1:8000/blog/
点击tag访问。
2.4根据相似性检索帖子。
通过共有的标签号显示相似的帖子,通过这个方式,当用户访问某个帖子时,可向其推荐阅读相关帖子。
步骤:
- 针对当前帖子检索全部标签,
- 获取包含特定标签的全部帖子,
- 从结果列表中排除当前帖子,以避免推荐相同帖子,
- 通过当前帖子的标签号,对结果进行排序,
- 如果具有相同标签号的两个或者多个帖子,推荐使用最近发布的帖子,
- 将查询限制为希望推荐的帖子数量
视图函数中增加:
from taggit.models import Tag
def post_detail(request, year, month, day, post):
...
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]
return render(request,
'blog/post/detail.html',
{...
'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 %}
第二章完,继续努力。