Guide: Detail views

SeoViewMixin on a DetailView gives you object-derived metadata by default, with an easy path to override specific fields at the view level.


Default behaviour

When SeoViewMixin is mixed into a DetailView, the resolver receives both the view and self.object. The object's get_seo_metadata() runs at priority 40; any seo_* attributes or get_seo_metadata() override on the view runs at priority 50.

from django.views.generic import DetailView
from seo_suite.mixins import SeoViewMixin

from .models import Article


class ArticleDetail(SeoViewMixin, DetailView):
    model = Article
    slug_field = "slug"
    slug_url_kwarg = "slug"

This is all that is required. The article's title, description, canonical URL, and any other fields resolved by SeoModelMixin are automatically used.


Overriding individual fields

Set seo_* class attributes to override specific fields without touching the model:

class ArticleDetail(SeoViewMixin, DetailView):
    model = Article
    slug_field = "slug"
    seo_robots = "noindex"  # drafts, paywalled content, etc.

Available class attributes (all default to None, meaning "no opinion"):

Attribute Metadata field
seo_title title
seo_title_suffix title_suffix
seo_h1 h1
seo_description meta_description
seo_keywords meta_keywords
seo_robots robots
seo_canonical canonical_url
seo_og_image og_image

Dynamic overrides with get_seo_metadata

For values that depend on request state, override get_seo_metadata:

class ArticleDetail(SeoViewMixin, DetailView):
    model = Article

    def get_seo_metadata(self, context=None):
        from seo_suite.metadata import SeoMetadata

        # Only suppress indexing for unpublished content.
        if not self.object.published:
            return SeoMetadata.partial(robots="noindex,nofollow")
        # Return UNSET for all fields to defer entirely to the object layer.
        return SeoMetadata.partial()

SeoMetadata.partial(**kwargs) constructs a metadata object with only the named fields set; all others remain UNSET so lower-priority layers supply them.


View title overrides the object, but canonical survives

A common pattern: the view sets a branded title while keeping the object's canonical URL:

class ArticleDetail(SeoViewMixin, DetailView):
    model = Article
    seo_title = "Read Our Latest"  # overrides article.title at priority 50
    # canonical_url is not set here, so article.get_absolute_url() survives

Because seo_canonical is None on the view (no opinion), the object's canonical_url from get_absolute_url() is used unchanged.


Adding JSON-LD at the view level

Use seo_schema_profiles to enable schema profiles at the view level. This is useful when the profile depends on request context (e.g. adding a WebSite profile only on the homepage):

class ArticleDetail(SeoViewMixin, DetailView):
    model = Article
    seo_schema_profiles = ["WebPage"]

When both the model and the view declare profiles, both sets of JSON-LD blocks are emitted (accumulation rule). See JSON-LD structured data.


Using SeoViewMixin without DetailView

SeoViewMixin works with any class-based view that has get_context_data. The object attribute is optional; if present, it is passed to the resolver.

from django.views.generic.base import View
from django.shortcuts import get_object_or_404
from seo_suite.mixins import SeoViewMixin
from seo_suite.context import attach_seo


class MyCustomView(SeoViewMixin, View):
    seo_title = "Custom Page"

    def get(self, request, pk):
        self.object = get_object_or_404(Article, pk=pk)
        seo = attach_seo(request, view=self, obj=self.object)
        # ... render response ...