Adding H-Card Support to django-blog
by Aaron Crowder on
I just merged PR #21 into django-blog, adding first-class h-card support across the app. This is part of my slow march toward more semantic, IndieWeb-friendly markup without bolting on heavy third-party tooling.
This change is mostly invisible from a UI perspective, but it adds a lot of structure under the hood.
What’s an H-Card?
An h-card is a microformat for representing a person or organization in HTML. Think of it as a lightweight, standards-based alternative to things like JSON-LD for basic identity metadata.
At a minimum, it can represent things like:
- name
- photo
- URL
- social profiles
- organization info
And it does all of that directly in HTML via class names.
The Data Model
This PR introduces a flexible HCard model that supports all core h-card properties while requiring none of them.
Example (simplified):
class HCard(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="hcards",
null=True,
blank=True,
on_delete=models.CASCADE,
)
name = models.CharField(max_length=255, blank=True)
nickname = models.CharField(max_length=255, blank=True)
photo = models.URLField(blank=True)
note = models.TextField(blank=True)
def __str__(self):
return self.name or "H-Card"
Supporting fields like emails, URLs, and categories are broken out into related models so each h-card can have many of them:
class HCardEmail(models.Model):
hcard = models.ForeignKey(HCard, related_name="emails", on_delete=models.CASCADE)
email = models.EmailField()
This mirrors the microformat spec while staying idiomatic Django.
Admin UX
One goal here was making h-cards easy to manage without touching code.
In Django admin, h-cards show up with inline editors for all related fields:
class HCardEmailInline(admin.TabularInline):
model = HCardEmail
extra = 1
@admin.register(HCard)
class HCardAdmin(admin.ModelAdmin):
inlines = [HCardEmailInline]
That lets you attach multiple emails, URLs, or categories directly to an h-card from one screen.
You can also assign h-cards to users, which makes author metadata reusable across the site.
Making It Available in Templates
To avoid threading author identity through every view manually, this PR adds a context processor that exposes a site-level h-card globally:
def site_hcard(request):
return {
"site_hcard": HCard.objects.filter(user__is_superuser=True).first()
}
Once registered, templates can safely assume a site_hcard exists and render it where needed.
Template Markup
This is where the payoff happens.
Blog templates now emit proper microformat markup using h-entry and h-card classes.
Example from a post list:
<article class="h-entry">
<header>
<a class="u-url" href="{{ post.get_absolute_url }}">
<h2 class="p-name">{{ post.title }}</h2>
</a>
<p>
by
<span class="p-author h-card">
<span class="p-name">{{ site_hcard.name }}</span>
</span>
</p>
</header>
</article>
And for richer h-cards:
<div class="h-card">
<img class="u-photo" src="{{ site_hcard.photo }}" alt="" />
<a class="u-url p-name" href="{{ site_hcard.url }}">
{{ site_hcard.name }}
</a>
{% for email in site_hcard.emails.all %}
<a class="u-email" href="mailto:{{ email.email }}">
{{ email.email }}
</a>
{% endfor %}
</div>
No JavaScript. No JSON blobs. Just HTML.
Performance Notes
Anywhere author metadata is used heavily, queries now prefetch related h-card data:
Post.objects.select_related("author").prefetch_related(
"author__hcards",
"author__hcards__emails",
)
So this stays cheap even as metadata grows.
Why This Matters
This PR doesn’t radically change how the blog looks, but it does change how it communicates.
- author identity is now structured, not implied
- markup is machine-readable without extra APIs
- h-cards can be reused anywhere: posts, notes, profile pages, feeds
It’s a small step toward a more semantic web, and it fits nicely into Django’s strengths.