Mezzanine Blog -> Automad import complete

2026-10-27 — Thomas Raschbacher (LordVan)

So I finally managed to finish importing my Blogs from mezzanine (Disclaimer: text only, no images -- also an older version already, so for newest probably need to adapt - in particular the tags part) to automad (with a little help from gemini).

First was the export from mezzanine using the shell ( python manage.py shell ):

import json
from mezzanine.blog.models import BlogPost

posts_data = []

for post in BlogPost.objects.all():
    # Access tags through the keywords relation on older Mezzanine builds
    try:
        tags = [k.keyword.title for k in post.keywords.all()]
    except AttributeError:
        tags = []

    posts_data.append({
        'title': post.title,
        'date': post.publish_date.strftime('%Y-%m-%d %H:%M:%S') if post.publish_date else '',
        'slug': post.slug,
        'tags': tags, # newer mezzanine should be something like this 'tags': [t.name for t in post.tags.all()],
        'content': post.content,
    })

with open('mezzanine_blog_export.json', 'w', encoding='utf-8') as f:
    json.dump(posts_data, f, ensure_ascii=False, indent=2)

print(f"Exported {len(posts_data)} posts successfully!")

Then next comes the page generation - which is pretty straightforward if one doesn't mess up something like putting metadata outside of "published" by accident XD

import json
import os
import uuid
from datetime import datetime

# Load Mezzanine export
with open('mezzanine_blog_export.json', 'r', encoding='utf-8') as f:
    posts = json.load(f)

output_base = './automad_pages_blog'

# Current execution time with local timezone offset (without microseconds)
now_iso = datetime.now().astimezone().isoformat(timespec='seconds')

for post in posts:
    # Use clean slug as folder name
    slug = post['slug'].split('/')[-1]
    dir_path = os.path.join(output_base, slug)
    os.makedirs(dir_path, exist_ok=True)

    # Handle post publication/creation date formatting
    raw_date = post.get('date')
    if raw_date:
        try:
            # Parse 'YYYY-MM-DD HH:MM:SS' and apply local timezone offset
            dt_obj = datetime.strptime(raw_date, "%Y-%m-%d %H:%M:%S").astimezone()
            created_iso = dt_obj.isoformat(timespec='seconds')
            # Short date format for the 'date' field (YYYY-MM-DDTHH:MM)
            date_field = dt_obj.strftime("%Y-%m-%dT%H:%M")
        except ValueError:
            # Fallback if string format differs slightly
            created_iso = now_iso
            date_field = now_iso[:16]
    else:
        created_iso = now_iso
        date_field = now_iso[:16]

    block_id = str(uuid.uuid4())[:8]

    # Complete Automad 2 page structure
    automad_data = {
        "draft": [],
        "published": {
            "+main": {
                "blocks": [
                    {
                        "id": block_id,
                        "type": "paragraph",
                        "data": {
                            "large": False,
                            "text": post['content']
                        },
                        "tunes": []
                    }
                ]
            },
            "date": date_field,
            "slug": slug,
            "title": post['title'],
            "tags": ", ".join(post['tags']) if post.get('tags') else "",
            "template": "page_full_width_centered",
            "private": False,
            ":created": created_iso,
            ":automadVersion": "2.0.0-beta.68",
            ":lastModified": now_iso,
            ":lastPublished": now_iso
        }
    }

    # Write directly to 'data' (no extension)
    file_path = os.path.join(dir_path, 'data')
    with open(file_path, 'w', encoding='utf-8') as f:
        json.dump(automad_data, f, ensure_ascii=False, indent=2)

print(f'Generated {len(posts)} pages')

Don't forget to disable cache or clear/purge it!

Tryton templates caveat A little useful addition for ssh login (from putty) to my .bashrc / .bash_profile