Guide: Model-less views¶
SeoViewMixin works for any class-based view, including TemplateView and
custom views that have no associated model object. Use class attributes for
static pages or override get_seo_metadata for dynamic ones.
Static pages with class attributes¶
# pages/views.py
from django.views.generic import TemplateView
from seo_suite.mixins import SeoViewMixin
class AboutView(SeoViewMixin, TemplateView):
template_name = "pages/about.html"
seo_title = "About Us"
seo_description = "Learn who we are and what we do."
seo_canonical = "/about/"
class PrivacyPolicyView(SeoViewMixin, TemplateView):
template_name = "pages/privacy.html"
seo_title = "Privacy Policy"
seo_robots = "noindex"
All seo_* class attributes are picked up automatically at priority 50 (the
view layer). Global defaults from SEO_SUITE["DEFAULTS"] still apply below
them, so robots, title_suffix, and og:type are inherited without
repetition.
Dynamic titles from the request or URL kwargs¶
Override get_seo_title for titles that depend on runtime values:
class SearchResultsView(SeoViewMixin, TemplateView):
template_name = "search/results.html"
def get_seo_title(self, context=None):
query = self.request.GET.get("q", "")
if query:
return f'Search results for "{query}"'
return "Search"
def get_seo_canonical(self, context=None):
# Canonical strips the query string; all searches share one canonical.
return "/search/"
Fully dynamic metadata with get_seo_metadata¶
For complete control, override get_seo_metadata and return a
SeoMetadata.partial(...):
from seo_suite.mixins import SeoViewMixin
from seo_suite.metadata import SeoMetadata
from django.views.generic import TemplateView
class UserDashboardView(SeoViewMixin, TemplateView):
template_name = "dashboard/index.html"
def get_seo_metadata(self, context=None):
user = self.request.user
return SeoMetadata.partial(
title=f"{user.first_name}'s Dashboard",
robots="noindex,nofollow", # personal pages should not be indexed
)
SeoMetadata.partial(**kwargs) only sets the fields you name; everything else
remains UNSET so global defaults fill the gaps.
Function-based views¶
For function-based views, use resolve_seo directly and pass the result into
the template context:
from seo_suite.context import resolve_seo
from django.shortcuts import render
def contact_view(request):
seo = resolve_seo(request)
# Without a view or object, the context processor's lazy resolution is used
# automatically. You can also construct a SeoMetadata and attach it manually
# by passing a lightweight wrapper.
return render(request, "pages/contact.html", {"seo": seo})
Alternatively, rely on the seo context processor entirely. Because the
context processor is lazy, the seo variable is available in every template
that uses {% seo_head %}, including those rendered by function-based views.
You just cannot inject view-layer overrides without using the mixin.
Accessing h1 in the template¶
The resolved h1 field is available in any template as {{ seo.h1 }}. When
it is set:
Or, since seo.h1 is None when unset, use the default filter: