from __future__ import annotations from django.core.management.base import BaseCommand from django.db import transaction from django.db.models import Q from wagtail.blocks import StreamValue from wagtail.models import Page from mandelstudio.content_hygiene import BLOCKED_DEMO_PAGE_SLUGS, DEMO_MARKERS from mandelstudio.idea_marketplace import seed_idea_marketplace_products HOME_COPY = { "nl": { "badge": "IDEA MARKETPLACE", "headline": "Premium ideeën die je direct kunt uitvoeren", "sub_headline": "

Ontdek bewezen plannen, koop de strategie en ontgrendel het volledige implementatieplan.

", "features_title": "Idea Marketplace", "features_subtitle": "

Preview eerst. Koop alleen wat past. Ontgrendel daarna de complete blueprint.

", "footer_headline": "Klaar om een premium idee te ontgrendelen?", "footer_subheadline": "

Kies een plan, rond checkout af en krijg direct toegang tot de volledige strategie.

", "cta_explore": "Explore Ideas", "cta_buy": "Buy Strategy", "cta_unlock": "Unlock Full Plan", }, "en": { "badge": "IDEA MARKETPLACE", "headline": "Premium ideas you can execute immediately", "sub_headline": "

Explore proven plans, buy the strategy, and unlock the full implementation blueprint.

", "features_title": "Idea Marketplace", "features_subtitle": "

Preview first. Buy what fits. Unlock complete execution plans after checkout.

", "footer_headline": "Ready to unlock a premium idea?", "footer_subheadline": "

Select a plan, complete checkout, and get full strategy access instantly.

", "cta_explore": "Explore Ideas", "cta_buy": "Buy Strategy", "cta_unlock": "Unlock Full Plan", }, } SUPPORTED_LANGUAGES = {"nl", "en", "de", "fr", "es", "it", "pt", "ru"} def _copy_for(language_code: str) -> dict[str, str]: normalized = (language_code or "nl").split("-")[0].lower() if normalized not in SUPPORTED_LANGUAGES: normalized = "nl" return HOME_COPY["en"] if normalized != "nl" else HOME_COPY["nl"] def _shop_url_for(language_code: str) -> str: normalized = (language_code or "nl").split("-")[0].lower() if normalized == "nl": return "/shop/" return f"/{normalized}/shop/" def _update_homepage_stream(page) -> bool: if not hasattr(page, "body"): return False body = page.body if not body: return False copy = _copy_for(getattr(page.locale, "language_code", "nl")) shop_url = _shop_url_for(getattr(page.locale, "language_code", "nl")) stream_data = list(body.stream_data) changed = False for block in stream_data: block_type = block.get("type") value = block.get("value", {}) if not isinstance(value, dict): continue if block_type == "saas_hero_banner": updates = { "badge_text": copy["badge"], "headline": copy["headline"], "sub_headline": copy["sub_headline"], "primary_cta_text": copy["cta_explore"], "primary_cta_url": shop_url, "secondary_cta_text": copy["cta_buy"], "secondary_cta_url": shop_url, } for key, new_value in updates.items(): if value.get(key) != new_value: value[key] = new_value changed = True if block_type == "saas_features": updates = { "section_title": copy["features_title"], "section_subtitle": copy["features_subtitle"], } for key, new_value in updates.items(): if value.get(key) != new_value: value[key] = new_value changed = True if block_type == "saas_cta_footer": updates = { "headline": copy["footer_headline"], "subheadline": copy["footer_subheadline"], "primary_cta_text": copy["cta_unlock"], "primary_cta_url": shop_url, "secondary_cta_text": copy["cta_explore"], "secondary_cta_url": shop_url, } for key, new_value in updates.items(): if value.get(key) != new_value: value[key] = new_value changed = True if not changed: return False page.body = StreamValue(page.body.stream_block, stream_data, is_lazy=True) page.search_description = "Idea marketplace with premium plans. Preview each strategy and unlock full implementation after purchase." page.save() return True def _purge_demo_pages() -> int: marker_filter = Q() for marker in DEMO_MARKERS: marker_filter |= ( Q(title__icontains=marker) | Q(slug__icontains=marker) | Q(search_description__icontains=marker) ) candidate_ids = set( Page.objects.exclude(depth__lte=2) .filter(marker_filter) .values_list("id", flat=True) ) candidate_ids.update( Page.objects.exclude(depth__lte=2) .filter(slug__in=BLOCKED_DEMO_PAGE_SLUGS) .values_list("id", flat=True) ) candidates = Page.objects.filter(id__in=candidate_ids).specific() deleted = 0 for page in candidates: page.delete() deleted += 1 return deleted def _update_homepages() -> int: updated = 0 # In this architecture localized homepages are expected at depth=2. for page in Page.objects.filter(depth=2).specific(): if _update_homepage_stream(page): updated += 1 return updated class Command(BaseCommand): help = ( "Prepare production idea marketplace launch: seed idea products, purge obvious demo pages, " "and refresh homepage sections/CTAs to marketplace messaging." ) def add_arguments(self, parser): parser.add_argument( "--no-seed", action="store_true", help="Skip idea product seeding.", ) parser.add_argument( "--purge-demo-pages", action="store_true", help="Delete pages with obvious demo/lorem/sample markers.", ) parser.add_argument( "--skip-purge-demo-pages", action="store_true", help="Skip deleting obvious demo pages (enabled by default).", ) parser.add_argument( "--apply-homepage-copy", action="store_true", help="Update homepage stream blocks to idea marketplace messaging and CTAs.", ) parser.add_argument( "--skip-apply-homepage-copy", action="store_true", help="Skip homepage marketplace copy refresh (enabled by default).", ) @transaction.atomic def handle(self, *args, **options): if not options["no_seed"]: seed_stats = seed_idea_marketplace_products( purge_demo_products=True, retire_non_idea_products=True, ) self.stdout.write( self.style.SUCCESS( "Seeded idea products: " f"created={seed_stats['created']}, " f"updated={seed_stats['updated']}, " f"deleted_demo_products={seed_stats['deleted_demo']}, " f"retired_non_idea_products={seed_stats['retired_non_idea']}" ) ) should_purge_demo_pages = ( options["purge_demo_pages"] or not options["skip_purge_demo_pages"] ) if should_purge_demo_pages: deleted_pages = _purge_demo_pages() self.stdout.write( self.style.SUCCESS(f"Deleted demo pages: {deleted_pages}") ) should_apply_homepage_copy = ( options["apply_homepage_copy"] or not options["skip_apply_homepage_copy"] ) if should_apply_homepage_copy: updated_pages = _update_homepages() self.stdout.write( self.style.SUCCESS( f"Updated homepages with marketplace copy: {updated_pages}" ) )