Guide: Third-party models (seoobject)¶
When the model you need to add SEO to belongs to a third-party package (for
example a Product model from django-oscar, a Post from a CMS package, or
any other model where you cannot add a mixin), the seoobject contrib app lets
editors manage SEO through a separate linked table using Django's generic
relations.
When to use seoobject¶
Use seoobject when:
- The model is in a third-party package you cannot modify.
- You want admin-editable per-object SEO without patching the model.
- You want the same admin-editable approach but your own model already has
SeoModelFieldsMixin. In that case use the inline columns directly instead.
For models you own, prefer SeoModelMixin (automatic, zero columns) or
SeoModelFieldsMixin (editor-editable columns). seoobject adds a
ContentTypes query to every resolution of an allowlisted object.
Installation¶
Add both apps to INSTALLED_APPS:
INSTALLED_APPS = [
"django.contrib.contenttypes", # usually already present
"seo_suite",
"seo_suite.contrib.seoobject",
]
Run migrations:
Allowlist the model¶
The seoobject provider only acts on models you explicitly allowlist. This
prevents a ContentTypes query on every object resolution; unlisted models
are skipped with no database hit.
# settings.py
SEO_SUITE = {
"OBJECT_MODELS": [
"catalogue.product", # app_label.model_name, lowercase
"blog.post",
],
}
The format is "app_label.model_name" (case-insensitive).
Managing SEO in Django Admin¶
Once the app is installed and a model is allowlisted, editors can create and
edit SeoObject rows from the Django Admin at
SEO Suite — Object rules → SEO object rules.
Each row stores:
| Field | Purpose |
|---|---|
| Content type | Which model this row applies to |
| Object ID | The primary key of the specific object |
| Site ID | Optional: restrict to a specific site |
| Language | Optional: restrict to a specific language |
| Title | Overrides the page title |
| Description | Overrides the meta description |
| Keywords | Overrides meta keywords |
| Robots | Overrides the robots directive |
| Canonical | Overrides the canonical URL |
| OG image | Overrides the social sharing image |
| Extra JSON-LD | Additional JSON-LD objects (accumulated, not replaced) |
Leave any field blank to defer to lower-priority defaults.
How resolution works¶
When the resolver processes a request for an allowlisted object, it queries
the SeoObject table for rows matching the object's content type and primary
key. The specificity rules are:
- A row with a matching
site_idandlanguagewins. - A row with a matching
site_idbut no language is next. - A row with no
site_idand no language (blank both) applies to all sites and languages.
The winning row is merged as the object layer (priority 40). An object that
also defines get_seo_metadata() (i.e. has SeoModelMixin) has its output
merged at the same layer. The SeoObject row takes precedence because it is
applied after the mixin call.
Note
If the object is not in OBJECT_MODELS, no SeoObject lookup is
performed. The model's own get_seo_metadata() (if any) is still called
as normal.
Example: django-oscar Product¶
# yourapp/views.py
from django.views.generic import DetailView
from seo_suite.mixins import SeoViewMixin
from oscar.apps.catalogue.models import Product
class ProductDetailView(SeoViewMixin, DetailView):
model = Product
template_name = "catalogue/product_detail.html"
The view passes the Product instance as the context object. The resolver
checks the allowlist, finds catalogue.product, queries SeoObject, and
merges any matching row. If no row exists, resolution falls back to global
defaults.