指南:列表页¶
列表视图(文章归档、分类页、搜索结果)需要能感知分页的 canonical 和标题。SeoListViewMixin 同时处理这两项。
基本列表视图¶
# blog/views.py
from django.views.generic import ListView
from seo_suite.mixins import SeoListViewMixin
from .models import Article
class ArticleList(SeoListViewMixin, ListView):
model = Article
queryset = Article.objects.filter(published=True).order_by("-published_at")
paginate_by = 20
seo_title = "All Articles"
seo_description = "Browse the full archive."
感知分页的标题¶
请求中带有 ?page=2 时,标题会自动变为 "All Articles – Page 2"。第 1 页渲染为 "All Articles"。
这一行为由 SeoListViewMixin.get_seo_title() 提供,不需要额外代码。
感知分页的 canonical¶
默认情况下,SeoListViewMixin 把 canonical URL 设置为:
- 第 1 页:
/articles/ - 第 2 页:
/articles/?page=2,第 3 页:/articles/?page=3,依此类推
这是推荐的做法:它告诉搜索引擎每个分页页面都有自己的 canonical URL,而不是全部指向第 1 页。
要在 canonical 中去掉页码(所有页面都指向 /articles/):
同时作为列表页的分类页¶
Category 模型既可以是提供 SEO 元数据的对象,也可以是展示条目列表的页面。两个 mixin 一起用:
# blog/models.py
from django.db import models
from seo_suite.mixins import SeoModelMixin
class Category(SeoModelMixin, models.Model):
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return f"/categories/{self.slug}/"
# blog/views.py
from django.views.generic import ListView
from seo_suite.mixins import SeoListViewMixin
from .models import Article, Category
class CategoryDetail(SeoListViewMixin, ListView):
model = Article
template_name = "blog/category_detail.html"
paginate_by = 20
def get_queryset(self):
self.category = Category.objects.get(slug=self.kwargs["slug"])
return Article.objects.filter(category=self.category, published=True)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["category"] = self.category
return ctx
def get_seo_title(self, context=None):
# Delegate to the category object for the base title; the parent
# class appends " – Page N" automatically.
if hasattr(self, "category"):
return self.category.name
return None
def get_seo_canonical(self, context=None):
# Let the parent build the paginated canonical from the request path.
return super().get_seo_canonical(context)
Note
视图通过 get_seo_title 提供标题时,它以优先级 50(视图层)生效,会覆盖对象层(优先级 40)。分类对象的 description 等其他字段不会自动经由视图带入。要包含分类对象的完整元数据,可以把它作为 obj 传给 attach_seo,或者重写 get_context_data 并调用 resolve_seo(request,
obj=self.category)。对于大多数列表页,直接在视图上设置 seo_title 和 seo_description 最简单。
纯列表视图(无模型)¶
对于首页或静态归档页这类页面,直接设置属性即可:
class HomepageView(SeoListViewMixin, ListView):
model = Article
template_name = "home.html"
queryset = Article.objects.filter(published=True)[:5]
seo_title = "My Blog"
seo_description = "Thoughts on Python, Django, and the web."
独立于 title 的 h1¶
<title> 标签和页面上可见的 <h1> 标题经常不一样。设置 seo_h1 可以单独控制页面标题:
class ArticleList(SeoListViewMixin, ListView):
model = Article
seo_title = "Articles | My Blog" # browser tab / search result
seo_h1 = "All Articles" # visible page heading
在模板中这样使用: