1386 lines
58 KiB
Python
1386 lines
58 KiB
Python
from __future__ import annotations
|
||
|
||
import copy
|
||
import uuid
|
||
from typing import Any
|
||
|
||
from django.core.management.base import BaseCommand
|
||
from django.db import transaction
|
||
|
||
from wagtail.blocks import StreamValue
|
||
from wagtail.models import Locale, Page
|
||
|
||
from mandelstudio.management.commands._agency_content import (
|
||
COMMON_CTA,
|
||
CTA_VARIANTS,
|
||
FOOTER_CONTENT,
|
||
NL_REPLACEMENTS,
|
||
)
|
||
from mandelstudio.management.commands._agency_content import (
|
||
body_for as localized_body_for,
|
||
)
|
||
from mandelstudio.management.commands._agency_content import (
|
||
footer_stream_data as localized_footer_stream_data,
|
||
)
|
||
from mandelstudio.models import LocalizedFooterContent
|
||
|
||
SOURCE_PAGE_IDS = {
|
||
"home": 127,
|
||
"about": 128,
|
||
"services": 129,
|
||
"projects": 130,
|
||
"contact": 131,
|
||
"process": 192,
|
||
"starter": 200,
|
||
"business": 201,
|
||
"webshop": 202,
|
||
"support": 203,
|
||
"ai_search": 199,
|
||
}
|
||
|
||
PAGE_TITLE_MAP = {
|
||
"about": {
|
||
"nl": "Over ons",
|
||
"en": "About us",
|
||
"de": "Über uns",
|
||
"fr": "À propos",
|
||
"es": "Sobre nosotros",
|
||
"it": "Chi siamo",
|
||
"pt": "Sobre nós",
|
||
"ru": "О нас",
|
||
},
|
||
"services": {
|
||
"nl": "Diensten",
|
||
"en": "Services",
|
||
"de": "Dienstleistungen",
|
||
"fr": "Services",
|
||
"es": "Servicios",
|
||
"it": "Servizi",
|
||
"pt": "Serviços",
|
||
"ru": "Услуги",
|
||
},
|
||
"projects": {
|
||
"nl": "Projecten",
|
||
"en": "Projects",
|
||
"de": "Projekte",
|
||
"fr": "Projets",
|
||
"es": "Proyectos",
|
||
"it": "Progetti",
|
||
"pt": "Projetos",
|
||
"ru": "Проекты",
|
||
},
|
||
"contact": {
|
||
"nl": "Contact",
|
||
"en": "Contact",
|
||
"de": "Kontakt",
|
||
"fr": "Contact",
|
||
"es": "Contacto",
|
||
"it": "Contatto",
|
||
"pt": "Contacto",
|
||
"ru": "Контакт",
|
||
},
|
||
"process": {
|
||
"nl": "Werkwijze",
|
||
"en": "How we work",
|
||
"de": "Vorgehensweise",
|
||
"fr": "Méthode de travail",
|
||
"es": "Método de trabajo",
|
||
"it": "Metodo di lavoro",
|
||
"pt": "Método de trabalho",
|
||
"ru": "Как мы работаем",
|
||
},
|
||
"starter": {
|
||
"nl": "Starter-website",
|
||
"en": "Starter website",
|
||
"de": "Starter-Website",
|
||
"fr": "Site de démarrage",
|
||
"es": "Sitio web inicial",
|
||
"it": "Sito starter",
|
||
"pt": "Website inicial",
|
||
"ru": "Стартовый сайт",
|
||
},
|
||
"business": {
|
||
"nl": "Zakelijke website",
|
||
"en": "Business website",
|
||
"de": "Geschäftswebsite",
|
||
"fr": "Site d’entreprise",
|
||
"es": "Sitio web empresarial",
|
||
"it": "Sito business",
|
||
"pt": "Site empresarial",
|
||
"ru": "Бизнес-сайт",
|
||
},
|
||
"webshop": {
|
||
"nl": "Webshop-implementatie",
|
||
"en": "Webshop implementation",
|
||
"de": "Webshop-Implementierung",
|
||
"fr": "Implémentation e-commerce",
|
||
"es": "Implementación webshop",
|
||
"it": "Implementazione webshop",
|
||
"pt": "Implementação de webshop",
|
||
"ru": "Внедрение вебшопа",
|
||
},
|
||
"support": {
|
||
"nl": "Onderhoud & groei",
|
||
"en": "Maintenance & growth",
|
||
"de": "Wartung & Wachstum",
|
||
"fr": "Maintenance & croissance",
|
||
"es": "Mantenimiento y crecimiento",
|
||
"it": "Manutenzione e crescita",
|
||
"pt": "Manutenção & crescimento",
|
||
"ru": "Поддержка и рост",
|
||
},
|
||
"ai_search": {
|
||
"nl": "AI-zoekfunctie",
|
||
"en": "AI search",
|
||
"de": "KI-Suche",
|
||
"fr": "Recherche IA",
|
||
"es": "Búsqueda con IA",
|
||
"it": "Ricerca IA",
|
||
"pt": "Pesquisa com IA",
|
||
"ru": "Поиск с ИИ",
|
||
},
|
||
}
|
||
|
||
|
||
def uid() -> str:
|
||
return str(uuid.uuid4())
|
||
|
||
|
||
def block(block_type: str, value: dict[str, Any]) -> dict[str, Any]:
|
||
return {"type": block_type, "value": value, "id": uid()}
|
||
|
||
|
||
def item(value: dict[str, Any] | str) -> dict[str, Any]:
|
||
return {"type": "item", "value": value, "id": uid()}
|
||
|
||
|
||
def replace_nested(node: Any, replacements: dict[str, str]) -> Any:
|
||
if isinstance(node, dict):
|
||
return {key: replace_nested(value, replacements) for key, value in node.items()}
|
||
if isinstance(node, list):
|
||
return [replace_nested(value, replacements) for value in node]
|
||
if isinstance(node, str):
|
||
for source, target in replacements.items():
|
||
node = node.replace(source, target)
|
||
return node
|
||
return node
|
||
|
||
|
||
def clone(blocks: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||
return copy.deepcopy(blocks)
|
||
|
||
|
||
def footer_stream_data(
|
||
locale: str, links: dict[str, str]
|
||
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
||
cfg = FOOTER_CONTENT[locale]
|
||
footer = [
|
||
block("about_us", {"heading": "MandelBlog Studio", "content": cfg["about"]}),
|
||
block(
|
||
"text",
|
||
{
|
||
"heading": cfg["links_heading"],
|
||
"content": (
|
||
f'<p><a href="{links["about"]}">{PAGE_TITLE_MAP["about"][locale]}</a><br/>'
|
||
f'<a href="{links["services"]}">{PAGE_TITLE_MAP["services"][locale]}</a><br/>'
|
||
f'<a href="{links["projects"]}">{PAGE_TITLE_MAP["projects"][locale]}</a><br/>'
|
||
f'<a href="{links["contact"]}">{PAGE_TITLE_MAP["contact"][locale]}</a></p>'
|
||
),
|
||
},
|
||
),
|
||
block("text", {"heading": cfg["support_heading"], "content": cfg["support"]}),
|
||
]
|
||
mini = [block("text", cfg["mini"])]
|
||
return footer, mini
|
||
|
||
|
||
def nl_home(urls: dict[str, str]) -> list[dict[str, Any]]:
|
||
primary = COMMON_CTA["nl"]["primary"]
|
||
secondary = COMMON_CTA["nl"]["secondary"]
|
||
return [
|
||
block(
|
||
"saas_hero_banner",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "split",
|
||
"badge_text": "MANDELBLOG STUDIO",
|
||
"badge_url": urls["home"],
|
||
"headline": "Websites voor bedrijven die professioneel willen groeien",
|
||
"sub_headline": "<p>MandelBlog ontwikkelt websites die vertrouwen opbouwen, duidelijk sturen op contact en eenvoudig te beheren zijn voor uw team.</p>",
|
||
"primary_cta_text": primary,
|
||
"primary_cta_url": urls["contact"],
|
||
"secondary_cta_text": secondary,
|
||
"secondary_cta_url": urls["services"],
|
||
"hero_image": 1,
|
||
"video_url": "",
|
||
"stats": [
|
||
item({"value": "3", "label": "Heldere stappen"}),
|
||
item({"value": "1", "label": "Vast aanspreekpunt"}),
|
||
item({"value": "8", "label": "Beschikbare talen"}),
|
||
],
|
||
"customer_logos_title": "Gebouwd met Wagtail, Django en beproefde componenten",
|
||
},
|
||
),
|
||
block(
|
||
"saas_features",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "grid",
|
||
"section_title": "Waar MandelBlog op stuurt",
|
||
"section_subtitle": "<p>Geen webshopdemo, maar een zakelijke website die klaar is voor aanvragen, vertrouwen en doorontwikkeling.</p>",
|
||
"features": [
|
||
item(
|
||
{
|
||
"icon": "diagram-3",
|
||
"icon_image": None,
|
||
"title": "Duidelijke structuur",
|
||
"description": "<p>Bezoekers vinden snel de juiste dienst, case of contactroute.</p>",
|
||
"link_text": PAGE_TITLE_MAP["process"]["nl"],
|
||
"link_url": urls["process"],
|
||
"highlight": "featured",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"icon": "pencil-square",
|
||
"icon_image": None,
|
||
"title": "Zelf te beheren",
|
||
"description": "<p>Teksten, beelden en secties beheert u zelf in overzichtelijke blokken.</p>",
|
||
"link_text": secondary,
|
||
"link_url": urls["services"],
|
||
"highlight": "none",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"icon": "shield-check",
|
||
"icon_image": None,
|
||
"title": "Stabiele technische basis",
|
||
"description": "<p>Een schaalbare opzet zonder overbodige complexiteit of template-ruis.</p>",
|
||
"link_text": "Bekijk werkwijze",
|
||
"link_url": urls["process"],
|
||
"highlight": "none",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"icon": "graph-up-arrow",
|
||
"icon_image": None,
|
||
"title": "Klaar voor doorontwikkeling",
|
||
"description": "<p>Later uitbreiden met extra pagina’s, koppelingen of commerce blijft mogelijk.</p>",
|
||
"link_text": "Bekijk projecten",
|
||
"link_url": urls["projects"],
|
||
"highlight": "none",
|
||
}
|
||
),
|
||
],
|
||
"columns": "2",
|
||
},
|
||
),
|
||
block(
|
||
"saas_pricing",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "cards",
|
||
"section_title": "Onze pakketten",
|
||
"section_subtitle": "<p>Elk pakket heeft een duidelijke scope. De exacte invulling stemmen we af in het kennismakingsgesprek.</p>",
|
||
"show_annual_toggle": False,
|
||
"annual_discount_text": "",
|
||
"tiers": [
|
||
item(
|
||
{
|
||
"name": PAGE_TITLE_MAP["starter"]["nl"],
|
||
"description": "Voor ondernemers die professioneel online willen starten",
|
||
"price_monthly": None,
|
||
"price_annual": None,
|
||
"price_suffix": "",
|
||
"custom_price_text": "Op offertebasis",
|
||
"features": [
|
||
item(
|
||
{
|
||
"text": "Kernpagina’s en duidelijke navigatie",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "Editor voor eigen contentbeheer",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "Mobiel sterke presentatie",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
],
|
||
"cta_text": primary,
|
||
"cta_url": urls["contact"],
|
||
"cta_style": "secondary",
|
||
"is_featured": False,
|
||
"featured_label": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"name": PAGE_TITLE_MAP["business"]["nl"],
|
||
"description": "Voor dienstverleners met meerdere proposities of groeiplannen",
|
||
"price_monthly": None,
|
||
"price_annual": None,
|
||
"price_suffix": "",
|
||
"custom_price_text": "Op offertebasis",
|
||
"features": [
|
||
item(
|
||
{
|
||
"text": "Meer ruimte voor diensten en cases",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "Conversiegerichte opbouw",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "SEO-vriendelijke basis",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
],
|
||
"cta_text": primary,
|
||
"cta_url": urls["contact"],
|
||
"cta_style": "primary",
|
||
"is_featured": True,
|
||
"featured_label": "Aanbevolen",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"name": PAGE_TITLE_MAP["webshop"]["nl"],
|
||
"description": "Voor organisaties die een zakelijke site willen uitbreiden met online verkoop",
|
||
"price_monthly": None,
|
||
"price_annual": None,
|
||
"price_suffix": "",
|
||
"custom_price_text": "Op offertebasis",
|
||
"features": [
|
||
item(
|
||
{
|
||
"text": "Productstructuur en checkout",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "Betalingen en orderverwerking",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "Schaalbare commerce-opzet",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
],
|
||
"cta_text": primary,
|
||
"cta_url": urls["contact"],
|
||
"cta_style": "secondary",
|
||
"is_featured": False,
|
||
"featured_label": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"name": PAGE_TITLE_MAP["support"]["nl"],
|
||
"description": "Voor teams die onderhoud, technische rust en doorlopende optimalisatie nodig hebben",
|
||
"price_monthly": None,
|
||
"price_annual": None,
|
||
"price_suffix": "",
|
||
"custom_price_text": "Maandelijks traject",
|
||
"features": [
|
||
item(
|
||
{
|
||
"text": "Updates en onderhoud",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "Monitoring en technische oplossingen",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"text": "Doorlopende verbetering",
|
||
"included": True,
|
||
"tooltip": "",
|
||
}
|
||
),
|
||
],
|
||
"cta_text": primary,
|
||
"cta_url": urls["contact"],
|
||
"cta_style": "secondary",
|
||
"is_featured": False,
|
||
"featured_label": "",
|
||
}
|
||
),
|
||
],
|
||
"footer_text": "<p>We adviseren welk pakket past bij uw fase, team en doelstelling.</p>",
|
||
},
|
||
),
|
||
block(
|
||
"saas_testimonials",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "cards",
|
||
"section_title": "Wat opdrachtgevers waarderen",
|
||
"section_subtitle": "<p>Kleine teams kiezen voor MandelBlog omdat het traject overzichtelijk blijft en de site daarna echt bruikbaar is.</p>",
|
||
"testimonials": [
|
||
item(
|
||
{
|
||
"quote": "<p>We kregen in korte tijd een website die eindelijk past bij onze dienstverlening en die we zelf kunnen onderhouden.</p>",
|
||
"author_name": "Sanne de Vries",
|
||
"author_title": "Studio Nova - eigenaar",
|
||
"author_photo": None,
|
||
"company_logo": None,
|
||
"rating": 0,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"quote": "<p>Het traject was helder, de teksten kregen structuur en onze contactaanvragen lopen nu via één duidelijke route.</p>",
|
||
"author_name": "Mark Jansen",
|
||
"author_title": "Jansen Interieur - medeoprichter",
|
||
"author_photo": None,
|
||
"company_logo": None,
|
||
"rating": 0,
|
||
}
|
||
),
|
||
],
|
||
"customer_logos": [],
|
||
"aggregate_rating": "",
|
||
"aggregate_source": "",
|
||
},
|
||
),
|
||
block(
|
||
"saas_faq",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "accordion",
|
||
"section_title": "Veelgestelde vragen",
|
||
"section_subtitle": "<p>We zijn duidelijk over planning, samenwerking en beheer.</p>",
|
||
"faqs": [
|
||
item(
|
||
{
|
||
"question": "Voor welke bedrijven is MandelBlog geschikt?",
|
||
"answer": "<p>Voor dienstverleners, studio’s en kleine teams die een professionele site nodig hebben zonder zwaar traject.</p>",
|
||
"category": "Algemeen",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"question": "Kunnen we later uitbreiden?",
|
||
"answer": "<p>Ja. We bouwen een structuur waarmee extra pagina’s, talen of koppelingen later logisch aansluiten.</p>",
|
||
"category": "Uitbreiding",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"question": "Beheren we de content zelf?",
|
||
"answer": "<p>Ja. De opzet is juist bedoeld zodat uw team pagina’s en blokken zelfstandig kan aanpassen.</p>",
|
||
"category": "Beheer",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"question": "Wat gebeurt er na livegang?",
|
||
"answer": "<p>Dan kunt u kiezen voor onderhoud en gerichte doorontwikkeling als dat nodig is.</p>",
|
||
"category": "Support",
|
||
}
|
||
),
|
||
],
|
||
"show_contact_cta": "card",
|
||
"contact_cta_text": primary,
|
||
"contact_cta_url": urls["contact"],
|
||
},
|
||
),
|
||
block(
|
||
"saas_cta_footer",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "banner",
|
||
"headline": "Wilt u een website die vertrouwen geeft en werk uit handen neemt?",
|
||
"subheadline": "<p>Plan een kennismakingsgesprek en we laten zien welke opzet past bij uw bedrijf en team.</p>",
|
||
"primary_cta_text": primary,
|
||
"primary_cta_url": urls["contact"],
|
||
"secondary_cta_text": secondary,
|
||
"secondary_cta_url": urls["services"],
|
||
"background_image": 1,
|
||
"side_image": 1,
|
||
"show_no_credit_card": "with-icon",
|
||
"no_credit_card_text": "Volledig vrijblijvend",
|
||
},
|
||
),
|
||
]
|
||
|
||
|
||
def nl_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any]]:
|
||
primary = COMMON_CTA["nl"]["primary"]
|
||
secondary = COMMON_CTA["nl"]["secondary"]
|
||
page_data = {
|
||
"about": {
|
||
"headline": "Wie MandelBlog is en hoe we werken",
|
||
"sub": "<p>MandelBlog helpt kleine bedrijven en dienstverleners aan een website die professioneel oogt, logisch converteert en beheerbaar blijft voor het eigen team.</p>",
|
||
"features_title": "Waar we op letten",
|
||
"features_sub": "<p>We werken het liefst voor organisaties die behoefte hebben aan duidelijkheid, snelheid en inhoudelijke regie.</p>",
|
||
"features": [
|
||
(
|
||
"people",
|
||
"Voor wie we werken",
|
||
"<p>Dienstverleners, studio’s en kleine teams met een duidelijke propositie en een praktische planning.</p>",
|
||
),
|
||
(
|
||
"diagram-3",
|
||
"Onze werkwijze",
|
||
"<p>We starten met scherpte in doel en inhoud, bouwen met vaste blokken en leveren beheersbaar op.</p>",
|
||
),
|
||
(
|
||
"shield-check",
|
||
"Waarom het anders werkt",
|
||
"<p>Geen los template of black box, maar een duidelijke structuur waarmee u zelf verder kunt.</p>",
|
||
),
|
||
(
|
||
"person-badge",
|
||
"Klein team, direct contact",
|
||
"<p>U schakelt direct met de mensen die het werk uitvoeren en keuzes vertalen naar de site.</p>",
|
||
),
|
||
],
|
||
"extra_block": block(
|
||
"saas_animated_stats",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "cards-grid",
|
||
"badge": "Werkwijze",
|
||
"heading": "Onze aanpak in 3 stappen",
|
||
"subheading": "Kort traject, duidelijke keuzes en daarna een site die voor uw team werkt.",
|
||
"stats": [
|
||
item(
|
||
{
|
||
"value": "1",
|
||
"prefix": None,
|
||
"suffix": "",
|
||
"label": "Kennismaking",
|
||
"description": "We bepalen doel, inhoud en prioriteiten.",
|
||
"icon": "chat-square-text",
|
||
"highlight": False,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"value": "2",
|
||
"prefix": None,
|
||
"suffix": "",
|
||
"label": "Uitwerking",
|
||
"description": "We bouwen de pagina’s en stemmen de inhoud af.",
|
||
"icon": "layout-text-window",
|
||
"highlight": False,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"value": "3",
|
||
"prefix": None,
|
||
"suffix": "",
|
||
"label": "Oplevering",
|
||
"description": "U krijgt uitleg, beheer en een duidelijke vervolgstap.",
|
||
"icon": "rocket",
|
||
"highlight": False,
|
||
}
|
||
),
|
||
],
|
||
"animation_duration": 1800,
|
||
"animation_easing": "ease-out",
|
||
"start_on_scroll": True,
|
||
"show_logos": False,
|
||
"logos_heading": "",
|
||
"company_logos": [],
|
||
},
|
||
),
|
||
"cta": "Wilt u weten of onze aanpak past bij uw bedrijf?",
|
||
},
|
||
"services": {
|
||
"headline": "Diensten voor bedrijven die overzicht en kwaliteit willen",
|
||
"sub": "<p>Elke dienst is ingericht rondom duidelijke keuzes, bruikbare content en een technische basis die door kan groeien.</p>",
|
||
"features_title": "Wat we leveren",
|
||
"features_sub": "<p>Geen losse modules, maar een traject dat aansluit op uw fase, team en doelen.</p>",
|
||
"features": [
|
||
(
|
||
"window",
|
||
PAGE_TITLE_MAP["starter"]["nl"],
|
||
"<p>Voor ondernemers die snel professioneel online willen staan met een heldere basis.</p>",
|
||
),
|
||
(
|
||
"briefcase",
|
||
PAGE_TITLE_MAP["business"]["nl"],
|
||
"<p>Voor organisaties met meerdere diensten, cases of een complexere aanbodstructuur.</p>",
|
||
),
|
||
(
|
||
"cart-check",
|
||
PAGE_TITLE_MAP["webshop"]["nl"],
|
||
"<p>Voor teams die online verkoop willen toevoegen zonder de grip op techniek te verliezen.</p>",
|
||
),
|
||
(
|
||
"wrench-adjustable",
|
||
PAGE_TITLE_MAP["support"]["nl"],
|
||
"<p>Voor organisaties die onderhoud, stabiliteit en doorlopende verbetering nodig hebben.</p>",
|
||
),
|
||
],
|
||
"extra_block": None,
|
||
"cta": "Twijfelt u welk pakket past bij uw fase?",
|
||
},
|
||
"projects": {
|
||
"headline": "Projecten waarin structuur, inhoud en techniek samenkomen",
|
||
"sub": "<p>Onze projecten zijn ontworpen om professioneel over te komen, vertrouwen op te bouwen en beheerbaar te blijven na livegang.</p>",
|
||
"features_title": "Wat u in onze projecten terugziet",
|
||
"features_sub": "<p>We sturen niet op oppervlakkige effecten, maar op duidelijkheid en bruikbaarheid.</p>",
|
||
"features": [
|
||
(
|
||
"diagram-3",
|
||
"Heldere pagina-opbouw",
|
||
"<p>Bezoekers begrijpen snel waar ze moeten zijn en welke stap logisch volgt.</p>",
|
||
),
|
||
(
|
||
"pencil-square",
|
||
"Eenvoudig beheer",
|
||
"<p>Teams kunnen teksten, visuals en pagina’s zelf aanpassen zonder omweg.</p>",
|
||
),
|
||
(
|
||
"graph-up-arrow",
|
||
"Gericht op aanvragen",
|
||
"<p>Contact en conversie zijn zichtbaar verwerkt in de structuur en inhoud.</p>",
|
||
),
|
||
],
|
||
"extra_block": None,
|
||
"cta": "Wilt u uw volgende project professioneel neerzetten?",
|
||
},
|
||
"contact": {
|
||
"headline": "Laten we uw volgende stap helder maken",
|
||
"sub": "<p>Deel kort waar u hulp bij nodig heeft. U krijgt een helder antwoord en een concreet voorstel voor de volgende stap.</p>",
|
||
"features_title": "Waarvoor u contact kunt opnemen",
|
||
"features_sub": "<p>Kies de route die past bij uw vraag of traject.</p>",
|
||
"features": [
|
||
(
|
||
"rocket",
|
||
"Nieuw traject",
|
||
"<p>Voor een nieuwe website, herpositionering of complete herbouw.</p>",
|
||
),
|
||
(
|
||
"briefcase",
|
||
"Pakketkeuze",
|
||
"<p>Voor advies over welk pakket of welke structuur het beste past.</p>",
|
||
),
|
||
(
|
||
"tools",
|
||
"Onderhoud of uitbreiding",
|
||
"<p>Voor technische ondersteuning, uitbreidingen of een vervolgfase na livegang.</p>",
|
||
),
|
||
],
|
||
"extra_block": block(
|
||
"saas_demo_request",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "split",
|
||
"section_title": "Start uw volgende project met helderheid",
|
||
"section_subtitle": "<p>We helpen u ontwerpen, bouwen en opschalen - snel, gestructureerd en zonder giswerk.</p>",
|
||
"form_fields": [
|
||
item(
|
||
{
|
||
"field_type": "text",
|
||
"label": "Naam",
|
||
"placeholder": "Uw naam",
|
||
"required": True,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"field_type": "email",
|
||
"label": "E-mail",
|
||
"placeholder": "naam@bedrijf.nl",
|
||
"required": True,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"field_type": "company",
|
||
"label": "Bedrijf",
|
||
"placeholder": "Bedrijfsnaam",
|
||
"required": True,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"field_type": "message",
|
||
"label": "Vraag of project",
|
||
"placeholder": "Waar zoekt u hulp bij?",
|
||
"required": False,
|
||
}
|
||
),
|
||
],
|
||
"submit_button_text": "Plan mijn strategiegesprek",
|
||
"form_action_url": urls["contact"],
|
||
"benefits_title": "Waarom dit gesprek werkt",
|
||
"benefits": [
|
||
item("Reactie binnen 24 uur"),
|
||
item("Vrijblijvend strategiegesprek"),
|
||
item("Praktische vervolgstappen"),
|
||
],
|
||
"side_image": 1,
|
||
"privacy_text": "<p>We gebruiken uw gegevens alleen voor contact over deze aanvraag. Geen verplichtingen.</p>",
|
||
},
|
||
),
|
||
"cta": "Klaar om een eerste stap te zetten?",
|
||
},
|
||
"process": {
|
||
"headline": "Werkwijze met duidelijke stappen en vaste keuzes",
|
||
"sub": "<p>We houden het traject overzichtelijk: u weet wanneer iets gebeurt, wat u moet aanleveren en waar we naartoe werken.</p>",
|
||
"features_title": "Zo werken we samen",
|
||
"features_sub": "<p>Kort, duidelijk en zonder onnodige ruis.</p>",
|
||
"features": [
|
||
(
|
||
"chat-square-text",
|
||
"1. Kennismaking",
|
||
"<p>We bespreken doel, doelgroep, inhoud en wat u intern wilt kunnen beheren.</p>",
|
||
),
|
||
(
|
||
"layout-text-window",
|
||
"2. Uitwerking",
|
||
"<p>We zetten structuur, inhoud en ontwerp om in een duidelijke pagina-opbouw.</p>",
|
||
),
|
||
(
|
||
"rocket",
|
||
"3. Oplevering",
|
||
"<p>Na review gaat de site live en zorgen we voor een beheerbare overdracht.</p>",
|
||
),
|
||
(
|
||
"graph-up-arrow",
|
||
"4. Doorontwikkeling",
|
||
"<p>Wanneer nodig bouwen we verder op basis van gedrag, vragen en nieuwe plannen.</p>",
|
||
),
|
||
],
|
||
"extra_block": None,
|
||
"cta": "Wilt u dit traject ook voor uw website?",
|
||
},
|
||
}
|
||
cfg = page_data[page_key]
|
||
blocks = [
|
||
block(
|
||
"saas_hero_banner",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "split",
|
||
"badge_text": "MANDELBLOG STUDIO"
|
||
if page_key != "services"
|
||
else "DIENSTEN",
|
||
"badge_url": urls[page_key],
|
||
"headline": cfg["headline"],
|
||
"sub_headline": cfg["sub"],
|
||
"primary_cta_text": primary,
|
||
"primary_cta_url": urls["contact"],
|
||
"secondary_cta_text": secondary,
|
||
"secondary_cta_url": urls["services"],
|
||
"hero_image": 1 if page_key != "process" else 24,
|
||
"video_url": "",
|
||
"stats": [],
|
||
"customer_logos_title": "",
|
||
},
|
||
),
|
||
block(
|
||
"saas_features",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "grid",
|
||
"section_title": cfg["features_title"],
|
||
"section_subtitle": cfg["features_sub"],
|
||
"features": [
|
||
item(
|
||
{
|
||
"icon": icon,
|
||
"icon_image": None,
|
||
"title": title,
|
||
"description": desc,
|
||
"link_text": primary
|
||
if page_key in {"contact", "about"}
|
||
else secondary,
|
||
"link_url": urls["contact"]
|
||
if page_key in {"contact", "about"}
|
||
else urls["services"],
|
||
"highlight": "none",
|
||
}
|
||
)
|
||
for icon, title, desc in cfg["features"]
|
||
],
|
||
"columns": "2" if len(cfg["features"]) <= 4 else "3",
|
||
},
|
||
),
|
||
]
|
||
if cfg["extra_block"] is not None:
|
||
blocks.append(cfg["extra_block"])
|
||
blocks.append(
|
||
block(
|
||
"saas_faq",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "accordion",
|
||
"section_title": "Veelgestelde vragen",
|
||
"section_subtitle": "<p>We houden het traject helder en praktisch.</p>",
|
||
"faqs": [
|
||
item(
|
||
{
|
||
"question": "Werken jullie met vaste templates?",
|
||
"answer": "<p>Nee. We gebruiken herbruikbare blokken, maar stemmen inhoud en structuur af op uw bedrijf.</p>",
|
||
"category": "Werkwijze",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"question": "Kunnen we later uitbreiden?",
|
||
"answer": "<p>Ja. De opzet is bedoeld om later door te groeien zonder opnieuw te beginnen.</p>",
|
||
"category": "Uitbreiding",
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"question": "Beheren we de inhoud zelf?",
|
||
"answer": "<p>Ja. Dat is juist een belangrijk uitgangspunt van het platform.</p>",
|
||
"category": "Beheer",
|
||
}
|
||
),
|
||
],
|
||
"show_contact_cta": "card",
|
||
"contact_cta_text": primary,
|
||
"contact_cta_url": urls["contact"],
|
||
},
|
||
)
|
||
)
|
||
blocks.append(
|
||
block(
|
||
"saas_cta_footer",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "banner",
|
||
"headline": cfg["cta"],
|
||
"subheadline": "<p>Plan een kennismakingsgesprek en we laten zien welke route logisch is voor uw bedrijf.</p>",
|
||
"primary_cta_text": primary,
|
||
"primary_cta_url": urls["contact"],
|
||
"secondary_cta_text": secondary,
|
||
"secondary_cta_url": urls["services"],
|
||
"background_image": 1,
|
||
"side_image": 1,
|
||
"show_no_credit_card": "with-icon",
|
||
"no_credit_card_text": "Volledig vrijblijvend",
|
||
},
|
||
)
|
||
)
|
||
return blocks
|
||
|
||
|
||
def nl_service_page(kind: str, urls: dict[str, str]) -> list[dict[str, Any]]:
|
||
primary = COMMON_CTA["nl"]["primary"]
|
||
secondary = COMMON_CTA["nl"]["secondary"]
|
||
config = {
|
||
"starter": {
|
||
"title": "Starter-website",
|
||
"audience": "Voor ondernemers of kleine teams die snel professioneel online willen staan met een duidelijke basis.",
|
||
"what": [
|
||
(
|
||
"layout-text-window",
|
||
"Voor wie is dit?",
|
||
"<p>Voor bedrijven met een helder aanbod die snel een professionele eerste indruk willen neerzetten.</p>",
|
||
),
|
||
(
|
||
"window",
|
||
"Wat krijgt u?",
|
||
"<p>Kernpagina’s, een logische navigatie en een editor waarmee uw team zelf content kan beheren.</p>",
|
||
),
|
||
(
|
||
"graph-up-arrow",
|
||
"Wat levert het op?",
|
||
"<p>Een professionele basis waarmee bezoekers sneller begrijpen wat u doet en hoe ze contact opnemen.</p>",
|
||
),
|
||
],
|
||
"outcomes": [
|
||
(
|
||
"shield-check",
|
||
"Heldere online basis",
|
||
"<p>Geen overbodige onderdelen, wel een site die vertrouwen geeft.</p>",
|
||
),
|
||
(
|
||
"people",
|
||
"Eenvoudig beheer",
|
||
"<p>Uw team kan updates zelf doen zonder afhankelijkheid.</p>",
|
||
),
|
||
(
|
||
"rocket",
|
||
"Snelle livegang",
|
||
"<p>Geschikt als eerste professionele stap of als vervanging van een verouderde site.</p>",
|
||
),
|
||
],
|
||
"choose": [
|
||
"U wilt snel professioneel online staan.",
|
||
"U heeft vooral kernpagina’s en duidelijke navigatie nodig.",
|
||
"U wilt zelf teksten en beelden kunnen aanpassen.",
|
||
],
|
||
"duration": "Gemiddelde oplevering: 2 tot 4 weken",
|
||
},
|
||
"business": {
|
||
"title": "Zakelijke website",
|
||
"audience": "Voor dienstverleners en teams die meerdere proposities, cases of funnelstappen helder willen presenteren.",
|
||
"what": [
|
||
(
|
||
"briefcase",
|
||
"Voor wie is dit?",
|
||
"<p>Voor organisaties die meer structuur, inhoudelijke diepgang en een sterkere aanvraagroute nodig hebben.</p>",
|
||
),
|
||
(
|
||
"layout-text-window",
|
||
"Wat krijgt u?",
|
||
"<p>Meer pagina-opbouw, ruimte voor cases en een SEO-vriendelijke basis die logisch meegroeit.</p>",
|
||
),
|
||
(
|
||
"graph-up-arrow",
|
||
"Wat levert het op?",
|
||
"<p>Een site die uw aanbod beter uitlegt en bezoekers gerichter naar contact of aanvraag leidt.</p>",
|
||
),
|
||
],
|
||
"outcomes": [
|
||
(
|
||
"diagram-3",
|
||
"Meer overzicht",
|
||
"<p>Diensten, cases en expertise krijgen elk hun eigen plek.</p>",
|
||
),
|
||
(
|
||
"search",
|
||
"Betere vindbaarheid",
|
||
"<p>De opbouw is ingericht voor sterke inhoud en een SEO-vriendelijke basis.</p>",
|
||
),
|
||
(
|
||
"people",
|
||
"Sterkere aanvragen",
|
||
"<p>Bezoekers zien sneller welke route en welk aanbod bij hen past.</p>",
|
||
),
|
||
],
|
||
"choose": [
|
||
"U heeft meerdere diensten of doelgroepen.",
|
||
"U wilt cases, expertise en bewijs beter laten zien.",
|
||
"U zoekt meer structuur dan een startsite biedt.",
|
||
],
|
||
"duration": "Gemiddelde oplevering: 2 tot 4 weken",
|
||
},
|
||
"webshop": {
|
||
"title": "Webshop-implementatie",
|
||
"audience": "Voor organisaties die online verkoop willen toevoegen zonder in een standaardshop te belanden.",
|
||
"what": [
|
||
(
|
||
"cart-check",
|
||
"Voor wie is dit?",
|
||
"<p>Voor bedrijven die hun aanbod online willen verkopen met grip op inhoud, checkout en beheer.</p>",
|
||
),
|
||
(
|
||
"credit-card",
|
||
"Wat krijgt u?",
|
||
"<p>Een webshopstructuur met productoverzicht, checkout en een schaalbare basis voor orderverwerking.</p>",
|
||
),
|
||
(
|
||
"graph-up-arrow",
|
||
"Wat levert het op?",
|
||
"<p>Een verkoopomgeving die past bij uw merk en niet voelt als een los demo-sjabloon.</p>",
|
||
),
|
||
],
|
||
"outcomes": [
|
||
(
|
||
"window",
|
||
"Betere presentatie",
|
||
"<p>Producten en categorieën krijgen een zakelijke, duidelijke opbouw.</p>",
|
||
),
|
||
(
|
||
"shield-check",
|
||
"Stabiele techniek",
|
||
"<p>Betalingen en orderverwerking sluiten aan op een beheerbare stack.</p>",
|
||
),
|
||
(
|
||
"rocket",
|
||
"Klaar voor groei",
|
||
"<p>De commerce-opzet kan meegroeien met assortiment en processen.</p>",
|
||
),
|
||
],
|
||
"choose": [
|
||
"U wilt online verkoop combineren met een zakelijke website.",
|
||
"U heeft behoefte aan grip op structuur en techniek.",
|
||
"U zoekt geen standaard thema, maar een doordachte implementatie.",
|
||
],
|
||
"duration": "Gemiddelde oplevering: 3 tot 6 weken",
|
||
},
|
||
"support": {
|
||
"title": "Onderhoud & groei",
|
||
"audience": "Voor teams die hun website of webshop stabiel willen houden en gericht willen doorontwikkelen.",
|
||
"what": [
|
||
(
|
||
"tools",
|
||
"Voor wie is dit?",
|
||
"<p>Voor organisaties die niet zelf alle techniek willen monitoren, oplossen en plannen.</p>",
|
||
),
|
||
(
|
||
"shield-check",
|
||
"Wat krijgt u?",
|
||
"<p>Onderhoud, updates, monitoring en technische oplossingen binnen een vast werkritme.</p>",
|
||
),
|
||
(
|
||
"graph-up-arrow",
|
||
"Wat levert het op?",
|
||
"<p>Meer rust, minder technische verrassingen en ruimte om gericht te verbeteren.</p>",
|
||
),
|
||
],
|
||
"outcomes": [
|
||
(
|
||
"activity",
|
||
"Minder verstoringen",
|
||
"<p>Technische issues worden sneller gesignaleerd en opgelost.</p>",
|
||
),
|
||
(
|
||
"clipboard-data",
|
||
"Doorlopende verbetering",
|
||
"<p>We werken stap voor stap aan performance, inhoud en conversie.</p>",
|
||
),
|
||
(
|
||
"people",
|
||
"Vast ritme",
|
||
"<p>U weet wanneer onderhoud gebeurt en waar prioriteit ligt.</p>",
|
||
),
|
||
],
|
||
"choose": [
|
||
"U wilt een vaste partner voor technisch onderhoud.",
|
||
"Uw site vraagt om kleine verbeteringen in plaats van een volledige herbouw.",
|
||
"U wilt sneller kunnen schakelen bij issues of uitbreidingen.",
|
||
],
|
||
"duration": "Reactie binnen 24 uur",
|
||
},
|
||
}[kind]
|
||
return [
|
||
block(
|
||
"saas_hero_banner",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "split",
|
||
"badge_text": "PAKKET",
|
||
"badge_url": urls[kind],
|
||
"headline": config["title"],
|
||
"sub_headline": f"<p>{config['audience']}</p>",
|
||
"primary_cta_text": primary,
|
||
"primary_cta_url": urls["contact"],
|
||
"secondary_cta_text": secondary,
|
||
"secondary_cta_url": urls["services"],
|
||
"hero_image": 23,
|
||
"video_url": "",
|
||
"stats": [
|
||
item({"value": config["duration"], "label": "Doorlooptijd"}),
|
||
item({"value": "Reactie binnen 24 uur", "label": "Communicatie"}),
|
||
item({"value": "Volledig vrijblijvend", "label": "Kennismaking"}),
|
||
],
|
||
"customer_logos_title": "",
|
||
},
|
||
),
|
||
block(
|
||
"saas_features",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "grid",
|
||
"section_title": "Wat krijgt u?",
|
||
"section_subtitle": "<p>Elk pakket is opgebouwd rond duidelijke keuzes, beheerbaarheid en inhoud die past bij uw bedrijf.</p>",
|
||
"features": [
|
||
item(
|
||
{
|
||
"icon": icon,
|
||
"icon_image": None,
|
||
"title": title,
|
||
"description": desc,
|
||
"link_text": secondary,
|
||
"link_url": urls["services"],
|
||
"highlight": "none",
|
||
}
|
||
)
|
||
for icon, title, desc in config["what"]
|
||
],
|
||
"columns": "3",
|
||
},
|
||
),
|
||
block(
|
||
"saas_features",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "grid",
|
||
"section_title": "Wat levert het op?",
|
||
"section_subtitle": "<p>De waarde zit niet in losse effecten, maar in duidelijkere communicatie en een beter werkende site.</p>",
|
||
"features": [
|
||
item(
|
||
{
|
||
"icon": icon,
|
||
"icon_image": None,
|
||
"title": title,
|
||
"description": desc,
|
||
"link_text": primary,
|
||
"link_url": urls["contact"],
|
||
"highlight": "none",
|
||
}
|
||
)
|
||
for icon, title, desc in config["outcomes"]
|
||
],
|
||
"columns": "3",
|
||
},
|
||
),
|
||
block(
|
||
"saas_demo_request",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "split",
|
||
"section_title": "Wanneer kiest u dit pakket?",
|
||
"section_subtitle": "<p>We adviseren dit pakket wanneer onderstaande punten aansluiten op uw situatie.</p>",
|
||
"form_fields": [
|
||
item(
|
||
{
|
||
"field_type": "text",
|
||
"label": "Naam",
|
||
"placeholder": "Uw naam",
|
||
"required": True,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"field_type": "email",
|
||
"label": "E-mail",
|
||
"placeholder": "naam@bedrijf.nl",
|
||
"required": True,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"field_type": "company",
|
||
"label": "Bedrijf",
|
||
"placeholder": "Bedrijfsnaam",
|
||
"required": True,
|
||
}
|
||
),
|
||
item(
|
||
{
|
||
"field_type": "message",
|
||
"label": "Vraag of context",
|
||
"placeholder": "Vertel kort waar u nu staat",
|
||
"required": False,
|
||
}
|
||
),
|
||
],
|
||
"submit_button_text": primary,
|
||
"form_action_url": urls["contact"],
|
||
"benefits_title": "Dit pakket past wanneer",
|
||
"benefits": [item(text) for text in config["choose"]],
|
||
"side_image": 23,
|
||
"privacy_text": "<p>We gebruiken uw gegevens alleen voor een reactie op uw aanvraag.</p>",
|
||
},
|
||
),
|
||
block(
|
||
"saas_cta_footer",
|
||
{
|
||
"layout_width": "container",
|
||
"background_style": "light",
|
||
"layout": "banner",
|
||
"headline": f"Wilt u weten of {config['title'].lower()} past bij uw situatie?",
|
||
"subheadline": "<p>Plan een kennismakingsgesprek en we adviseren eerlijk welk pakket logisch is.</p>",
|
||
"primary_cta_text": primary,
|
||
"primary_cta_url": urls["contact"],
|
||
"secondary_cta_text": secondary,
|
||
"secondary_cta_url": urls["services"],
|
||
"background_image": 1,
|
||
"side_image": 1,
|
||
"show_no_credit_card": "with-icon",
|
||
"no_credit_card_text": "Volledig vrijblijvend",
|
||
},
|
||
),
|
||
]
|
||
|
||
|
||
def nl_body_for(page_key: str, urls: dict[str, str]) -> list[dict[str, Any]]:
|
||
if page_key == "home":
|
||
return nl_home(urls)
|
||
if page_key in {"about", "services", "projects", "contact", "process"}:
|
||
return nl_standard_page(page_key, urls)
|
||
if page_key in {"starter", "business", "webshop", "support"}:
|
||
return nl_service_page(page_key, urls)
|
||
raise KeyError(page_key)
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = "Apply MandelBlog agency cleanup content to the main site tree"
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument(
|
||
"--apply", action="store_true", help="Persist and publish changes"
|
||
)
|
||
|
||
def handle(self, *args, **options):
|
||
apply_changes = options["apply"]
|
||
with transaction.atomic():
|
||
for locale in Locale.objects.all().order_by("language_code"):
|
||
language_code = locale.language_code
|
||
translated_pages = {}
|
||
for key, source_id in SOURCE_PAGE_IDS.items():
|
||
source = Page.objects.get(id=source_id)
|
||
translated = (
|
||
Page.objects.filter(
|
||
translation_key=source.translation_key, locale=locale
|
||
)
|
||
.specific()
|
||
.first()
|
||
)
|
||
if translated:
|
||
translated_pages[key] = translated
|
||
urls = {
|
||
key: page.url
|
||
for key, page in translated_pages.items()
|
||
if getattr(page, "url", None)
|
||
}
|
||
if "home" not in urls:
|
||
continue
|
||
|
||
for key, page in translated_pages.items():
|
||
changed = False
|
||
title_map = PAGE_TITLE_MAP.get(key)
|
||
if (
|
||
title_map
|
||
and title_map.get(language_code)
|
||
and page.title != title_map[language_code]
|
||
):
|
||
page.title = title_map[language_code]
|
||
changed = True
|
||
|
||
if key == "ai_search":
|
||
if changed and apply_changes:
|
||
rev = page.save_revision()
|
||
rev.publish()
|
||
continue
|
||
|
||
if hasattr(page, "body"):
|
||
raw_data = list(page.body.raw_data)
|
||
if language_code in COMMON_CTA and key in {
|
||
"home",
|
||
"about",
|
||
"services",
|
||
"projects",
|
||
"contact",
|
||
"process",
|
||
"starter",
|
||
"business",
|
||
"webshop",
|
||
"support",
|
||
}:
|
||
page.body = StreamValue(
|
||
page.body.stream_block,
|
||
localized_body_for(
|
||
language_code, key, urls, PAGE_TITLE_MAP
|
||
),
|
||
is_lazy=True,
|
||
)
|
||
changed = True
|
||
else:
|
||
replacements = {}
|
||
replacements.update(
|
||
NL_REPLACEMENTS if language_code == "nl" else {}
|
||
)
|
||
if language_code in COMMON_CTA:
|
||
primary = COMMON_CTA[language_code]["primary"]
|
||
secondary = COMMON_CTA[language_code]["secondary"]
|
||
for variant in CTA_VARIANTS.get("nl", []):
|
||
replacements[variant] = primary
|
||
replacements["Bekijk diensten"] = secondary
|
||
replacements["Bekijk alle diensten"] = secondary
|
||
replacements["Plan kennismaking"] = primary
|
||
updated_raw_data = replace_nested(
|
||
clone(raw_data), replacements
|
||
)
|
||
if updated_raw_data != raw_data:
|
||
page.body = StreamValue(
|
||
page.body.stream_block,
|
||
updated_raw_data,
|
||
is_lazy=True,
|
||
)
|
||
changed = True
|
||
|
||
if changed:
|
||
self.stdout.write(
|
||
f"{language_code}: update {key} -> {page.title}"
|
||
)
|
||
if apply_changes:
|
||
rev = page.save_revision()
|
||
rev.publish()
|
||
|
||
footer_cfg = FOOTER_CONTENT.get(language_code)
|
||
footer_obj = LocalizedFooterContent.objects.filter(
|
||
locale=locale
|
||
).first()
|
||
if footer_obj and footer_cfg:
|
||
link_urls = {
|
||
"about": urls.get("about", "/"),
|
||
"services": urls.get("services", "/"),
|
||
"projects": urls.get("projects", "/"),
|
||
"contact": urls.get("contact", "/"),
|
||
}
|
||
footer_data, mini_data = localized_footer_stream_data(
|
||
language_code, link_urls, PAGE_TITLE_MAP
|
||
)
|
||
footer_obj.footer = StreamValue(
|
||
footer_obj.footer.stream_block, footer_data, is_lazy=True
|
||
)
|
||
footer_obj.mini_footer = StreamValue(
|
||
footer_obj.mini_footer.stream_block, mini_data, is_lazy=True
|
||
)
|
||
self.stdout.write(f"{language_code}: update footer")
|
||
if apply_changes:
|
||
footer_obj.save()
|
||
|
||
if not apply_changes:
|
||
transaction.set_rollback(True)
|
||
self.stdout.write(
|
||
self.style.WARNING("Dry run complete; no changes saved.")
|
||
)
|
||
else:
|
||
self.stdout.write(self.style.SUCCESS("Agency website refresh applied."))
|