Skip to content

Commit 9b3fbd1

Browse files
author
zmrenwu
committed
Step26: rss feed
1 parent bef24bd commit 9b3fbd1

File tree

7 files changed

+102
-55
lines changed

7 files changed

+102
-55
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ tutorial 分支为项目的主分支,每一篇教程的代码都和历史提
146146
23. [通过 Django Pagination 实现简单分页](https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/81/)
147147
24. [稳定易用的 Django 分页库,完善分页功能](https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/82/)
148148
25. [统计各个分类和标签下的文章数](https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/83/)
149+
26. [开启 Django 博客的 RSS 功能](https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/84/)
149150

150151
## 公众号
151152
<p align="center">

Diff for: blog/feeds.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from django.contrib.syndication.views import Feed
2+
3+
from .models import Post
4+
5+
6+
class AllPostsRssFeed(Feed):
7+
# 显示在聚合阅读器上的标题
8+
title = "HelloDjango-blog-tutorial"
9+
10+
# 通过聚合阅读器跳转到网站的地址
11+
link = "/"
12+
13+
# 显示在聚合阅读器上的描述信息
14+
description = "HelloDjango-blog-tutorial 全部文章"
15+
16+
# 需要显示的内容条目
17+
def items(self):
18+
return Post.objects.all()
19+
20+
# 聚合器中显示的内容条目的标题
21+
def item_title(self, item):
22+
return "[%s] %s" % (item.category, item.title)
23+
24+
# 聚合器中显示的内容条目的描述
25+
def item_description(self, item):
26+
return item.body_html

Diff for: blog/models.py

+53-20
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
1+
import re
2+
13
import markdown
24
from django.contrib.auth.models import User
35
from django.db import models
46
from django.urls import reverse
57
from django.utils import timezone
8+
from django.utils.functional import cached_property
69
from django.utils.html import strip_tags
10+
from django.utils.text import slugify
11+
from markdown.extensions.toc import TocExtension
12+
13+
14+
def generate_rich_content(value):
15+
md = markdown.Markdown(
16+
extensions=[
17+
"markdown.extensions.extra",
18+
"markdown.extensions.codehilite",
19+
# 记得在顶部引入 TocExtension 和 slugify
20+
TocExtension(slugify=slugify),
21+
]
22+
)
23+
content = md.convert(value)
24+
m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
25+
toc = m.group(1) if m is not None else ""
26+
return {"content": content, "toc": toc}
727

828

929
class Category(models.Model):
@@ -16,10 +36,11 @@ class Category(models.Model):
1636
Django 内置的全部类型可查看文档:
1737
https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-types
1838
"""
19-
name = models.CharField('分类名', max_length=100)
39+
40+
name = models.CharField("分类名", max_length=100)
2041

2142
class Meta:
22-
verbose_name = '分类'
43+
verbose_name = "分类"
2344
verbose_name_plural = verbose_name
2445

2546
def __str__(self):
@@ -31,10 +52,11 @@ class Tag(models.Model):
3152
标签 Tag 也比较简单,和 Category 一样。
3253
再次强调一定要继承 models.Model 类!
3354
"""
34-
name = models.CharField('标签名', max_length=100)
55+
56+
name = models.CharField("标签名", max_length=100)
3557

3658
class Meta:
37-
verbose_name = '标签'
59+
verbose_name = "标签"
3860
verbose_name_plural = verbose_name
3961

4062
def __str__(self):
@@ -47,19 +69,19 @@ class Post(models.Model):
4769
"""
4870

4971
# 文章标题
50-
title = models.CharField('标题', max_length=70)
72+
title = models.CharField("标题", max_length=70)
5173

5274
# 文章正文,我们使用了 TextField。
5375
# 存储比较短的字符串可以使用 CharField,但对于文章的正文来说可能会是一大段文本,因此使用 TextField 来存储大段文本。
54-
body = models.TextField('正文')
76+
body = models.TextField("正文")
5577

5678
# 这两个列分别表示文章的创建时间和最后一次修改时间,存储时间的字段用 DateTimeField 类型。
57-
created_time = models.DateTimeField('创建时间', default=timezone.now)
58-
modified_time = models.DateTimeField('修改时间')
79+
created_time = models.DateTimeField("创建时间", default=timezone.now)
80+
modified_time = models.DateTimeField("修改时间")
5981

6082
# 文章摘要,可以没有文章摘要,但默认情况下 CharField 要求我们必须存入数据,否则就会报错。
6183
# 指定 CharField 的 blank=True 参数值后就可以允许空值了。
62-
excerpt = models.CharField('摘要', max_length=200, blank=True)
84+
excerpt = models.CharField("摘要", max_length=200, blank=True)
6385

6486
# 这是分类与标签,分类与标签的模型我们已经定义在上面。
6587
# 我们在这里把文章对应的数据库表和分类、标签对应的数据库表关联了起来,但是关联形式稍微有点不同。
@@ -70,22 +92,22 @@ class Post(models.Model):
7092
# 同时我们规定文章可以没有标签,因此为标签 tags 指定了 blank=True。
7193
# 如果你对 ForeignKey、ManyToManyField 不了解,请看教程中的解释,亦可参考官方文档:
7294
# https://docs.djangoproject.com/en/2.2/topics/db/models/#relationships
73-
category = models.ForeignKey(Category, verbose_name='分类', on_delete=models.CASCADE)
74-
tags = models.ManyToManyField(Tag, verbose_name='标签', blank=True)
95+
category = models.ForeignKey(Category, verbose_name="分类", on_delete=models.CASCADE)
96+
tags = models.ManyToManyField(Tag, verbose_name="标签", blank=True)
7597

7698
# 文章作者,这里 User 是从 django.contrib.auth.models 导入的。
7799
# django.contrib.auth 是 Django 内置的应用,专门用于处理网站用户的注册、登录等流程,User 是 Django 为我们已经写好的用户模型。
78100
# 这里我们通过 ForeignKey 把文章和 User 关联了起来。
79101
# 因为我们规定一篇文章只能有一个作者,而一个作者可能会写多篇文章,因此这是一对多的关联关系,和 Category 类似。
80-
author = models.ForeignKey(User, verbose_name='作者', on_delete=models.CASCADE)
102+
author = models.ForeignKey(User, verbose_name="作者", on_delete=models.CASCADE)
81103

82104
# 新增 views 字段记录阅读量
83105
views = models.PositiveIntegerField(default=0, editable=False)
84106

85107
class Meta:
86-
verbose_name = '文章'
108+
verbose_name = "文章"
87109
verbose_name_plural = verbose_name
88-
ordering = ['-created_time']
110+
ordering = ["-created_time"]
89111

90112
def __str__(self):
91113
return self.title
@@ -95,10 +117,9 @@ def save(self, *args, **kwargs):
95117

96118
# 首先实例化一个 Markdown 类,用于渲染 body 的文本。
97119
# 由于摘要并不需要生成文章目录,所以去掉了目录拓展。
98-
md = markdown.Markdown(extensions=[
99-
'markdown.extensions.extra',
100-
'markdown.extensions.codehilite',
101-
])
120+
md = markdown.Markdown(
121+
extensions=["markdown.extensions.extra", "markdown.extensions.codehilite",]
122+
)
102123

103124
# 先将 Markdown 文本渲染成 HTML 文本
104125
# strip_tags 去掉 HTML 文本的全部 HTML 标签
@@ -110,8 +131,20 @@ def save(self, *args, **kwargs):
110131
# 自定义 get_absolute_url 方法
111132
# 记得从 django.urls 中导入 reverse 函数
112133
def get_absolute_url(self):
113-
return reverse('blog:detail', kwargs={'pk': self.pk})
134+
return reverse("blog:detail", kwargs={"pk": self.pk})
114135

115136
def increase_views(self):
116137
self.views += 1
117-
self.save(update_fields=['views'])
138+
self.save(update_fields=["views"])
139+
140+
@property
141+
def toc(self):
142+
return self.rich_content.get("toc", "")
143+
144+
@property
145+
def body_html(self):
146+
return self.rich_content.get("content", "")
147+
148+
@cached_property
149+
def rich_content(self):
150+
return generate_rich_content(self.body)

Diff for: blog/views.py

+15-33
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,47 @@
1-
import re
2-
3-
import markdown
4-
from django.shortcuts import get_object_or_404, render
5-
from django.utils.text import slugify
1+
from django.shortcuts import get_object_or_404
62
from django.views.generic import DetailView, ListView
7-
from markdown.extensions.toc import TocExtension
3+
84
from pure_pagination.mixins import PaginationMixin
95

106
from .models import Category, Post, Tag
117

128

139
class IndexView(PaginationMixin, ListView):
1410
model = Post
15-
template_name = 'blog/index.html'
16-
context_object_name = 'post_list'
11+
template_name = "blog/index.html"
12+
context_object_name = "post_list"
1713
paginate_by = 10
1814

1915

2016
class CategoryView(IndexView):
21-
2217
def get_queryset(self):
23-
cate = get_object_or_404(Category, pk=self.kwargs.get('pk'))
18+
cate = get_object_or_404(Category, pk=self.kwargs.get("pk"))
2419
return super().get_queryset().filter(category=cate)
2520

2621

2722
class ArchiveView(IndexView):
2823
def get_queryset(self):
29-
year = self.kwargs.get('year')
30-
month = self.kwargs.get('month')
31-
return super().get_queryset().filter(created_time__year=year,
32-
created_time__month=month)
24+
year = self.kwargs.get("year")
25+
month = self.kwargs.get("month")
26+
return (
27+
super()
28+
.get_queryset()
29+
.filter(created_time__year=year, created_time__month=month)
30+
)
3331

3432

3533
class TagView(IndexView):
3634
def get_queryset(self):
37-
t = get_object_or_404(Tag, pk=self.kwargs.get('pk'))
35+
t = get_object_or_404(Tag, pk=self.kwargs.get("pk"))
3836
return super().get_queryset().filter(tags=t)
3937

4038

4139
# 记得在顶部导入 DetailView
4240
class PostDetailView(DetailView):
4341
# 这些属性的含义和 ListView 是一样的
4442
model = Post
45-
template_name = 'blog/detail.html'
46-
context_object_name = 'post'
43+
template_name = "blog/detail.html"
44+
context_object_name = "post"
4745

4846
def get(self, request, *args, **kwargs):
4947
# 覆写 get 方法的目的是因为每当文章被访问一次,就得将文章阅读量 +1
@@ -58,19 +56,3 @@ def get(self, request, *args, **kwargs):
5856

5957
# 视图必须返回一个 HttpResponse 对象
6058
return response
61-
62-
def get_object(self, queryset=None):
63-
# 覆写 get_object 方法的目的是因为需要对 post 的 body 值进行渲染
64-
post = super().get_object(queryset=None)
65-
md = markdown.Markdown(extensions=[
66-
'markdown.extensions.extra',
67-
'markdown.extensions.codehilite',
68-
# 记得在顶部引入 TocExtension 和 slugify
69-
TocExtension(slugify=slugify),
70-
])
71-
post.body = md.convert(post.body)
72-
73-
m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
74-
post.toc = m.group(1) if m is not None else ''
75-
76-
return post

Diff for: blogproject/urls.py

+5
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616
from django.contrib import admin
1717
from django.urls import path, include
1818

19+
from blog.feeds import AllPostsRssFeed
20+
1921
urlpatterns = [
2022
path('admin/', admin.site.urls),
2123
path('', include('blog.urls')),
2224
path('', include('comments.urls')),
25+
26+
# 记得在顶部引入 AllPostsRssFeed
27+
path('all/rss/', AllPostsRssFeed(), name='rss'),
2328
]

Diff for: templates/base.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ <h1><a href="{% url 'blog:index' %}"><b>Black</b> &amp; White</a></h1>
124124
{% show_tags %}
125125

126126
<div class="rss">
127-
<a href=""><span class="ion-social-rss-outline"></span> RSS 订阅</a>
127+
<a href="{% url 'rss' %}"><span class="ion-social-rss-outline"></span> RSS 订阅</a>
128128
</div>
129129
</aside>
130130
</div>

Diff for: templates/blog/detail.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ <h1 class="entry-title">{{ post.title }}</h1>
1515
</div>
1616
</header>
1717
<div class="entry-content clearfix">
18-
{{ post.body|safe }}
18+
{{ post.body_html|safe }}
1919
</div>
2020
</article>
2121
<section class="comment-area" id="comment-area">

0 commit comments

Comments
 (0)