Django¶
The Django adapter reads its configuration from the EASEO setting and renders
tags from templates.
Simple: settings and a template tag¶
# settings.py
EASEO = {
"canonical_host": "example.com",
"public_base_url": "https://example.com",
"site_name": "Example",
"title_template": "{title} - Example",
}
# models.py
class Post(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
{# templates/post.html #}
{% load easeo_tags %}
<!doctype html>
<html>
<head>
{% easeo_head post request.path %}
</head>
<body><h1>{{ post.title }}</h1></body>
</html>
The adapter reads entity_type (defaulting to page), title, and
description or excerpt from the object it is given.
Simple: granular tags¶
Complex: full settings¶
Every SEOConfig field is supported. default_og_image accepts a URL string
or a mapping; url_policy, default_robots, and search_robots accept
mappings.
# settings.py
EASEO = {
"canonical_host": "shop.example.com",
"public_base_url": "https://shop.example.com",
"site_name": "Example Shop",
"title_template": "{title} - Example Shop",
"url_policy": {
"enforce_https": True,
"trailing_slash": "never",
"allowed_query_params": ["page", "q"],
},
"default_robots": {"index": True, "follow": True},
"search_robots": {"index": False, "follow": True},
"default_og_image": {
"url": "https://shop.example.com/assets/og-image.png",
"width": 1200,
"height": 630,
"alt": "Example Shop",
},
"publisher_name": "Example Shop",
"locale": "en_US",
"twitter_site": "@exampleshop",
"auto_generate_schema": True,
"search_url_template": "https://shop.example.com/search?q={search_term_string}",
}
Complex: map a model to an entity¶
For product pages you want the schema fields, so build an SEOEntity rather
than relying on attribute defaults:
# views.py
from django.shortcuts import render
from easeo import SEOEntity, build_seo_payload
from .models import Product
def entity_from_product(product: Product) -> SEOEntity:
return SEOEntity(
entity_type="product",
title=product.name,
excerpt=product.short_description,
featured_image=product.image_url,
sku=product.sku,
price=str(product.price),
price_currency=product.currency,
availability="InStock" if product.in_stock else "OutOfStock",
updated_at=product.updated_at.isoformat(),
breadcrumbs=[
# Breadcrumb(name=..., url=...) entries
],
)
def product_detail(request, slug):
product = Product.objects.get(slug=slug)
payload = build_seo_payload(entity_from_product(product), request.path, config_from_settings())
return render(request, "product.html", {"product": product, "seo_html": payload.render_html()})
Complex: an ETag response¶
Wrap the payload in a conditional response so repeat visitors get a 304:
from django.http import HttpResponseNotModified, JsonResponse
from easeo import build_seo_payload
def product_json(request, slug):
product = Product.objects.get(slug=slug)
payload = build_seo_payload(entity_from_product(product), request.path, config_from_settings())
etag = payload.etag()
if request.headers.get("If-None-Match") == etag:
return HttpResponseNotModified()
response = JsonResponse(payload.to_dict())
response.headers["ETag"] = etag
response.headers["Cache-Control"] = "public, max-age=300"
return response
Notes¶
- If
EASEOis missing, the adapter warns and falls back tolocalhost. - Tags return
mark_safeoutput because the payload escapes its values. - The function API
seo_head(entity, route, config=None)bypasses settings when you pass a config.