130 lines
3.9 KiB
Python
130 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Iterable
|
|
|
|
from django.core.management.base import BaseCommand
|
|
from django.db.models import Q
|
|
|
|
from wagtail.models import Page
|
|
|
|
from oscar.core.loading import get_model
|
|
|
|
IDEA_PRODUCT_TITLES = {
|
|
"B2B Webshop Starter Blueprint",
|
|
"AI Product Description System",
|
|
"High-Converting Landing Page Framework",
|
|
"Subscription-Based Service Website Model",
|
|
"Marketplace Platform Architecture (Django)",
|
|
}
|
|
|
|
DEMO_PAGE_SLUGS = {
|
|
"starter-website-2",
|
|
"business-website-2",
|
|
"starter-website",
|
|
"business-website",
|
|
}
|
|
|
|
DEMO_MARKERS = (
|
|
"demo",
|
|
"dummy",
|
|
"sample",
|
|
"placeholder",
|
|
"starter website",
|
|
"business website",
|
|
"lorem ipsum",
|
|
)
|
|
|
|
|
|
def _build_demo_text_filter(fields: Iterable[str]) -> Q:
|
|
query = Q()
|
|
for field in fields:
|
|
for marker in DEMO_MARKERS:
|
|
query |= Q(**{f"{field}__icontains": marker})
|
|
return query
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = (
|
|
"Remove demo content from Wagtail pages and Oscar catalogue. "
|
|
"Use --keep-only-idea-products to retain only the five launch idea products."
|
|
)
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
default=False,
|
|
help="Show what would be deleted without applying changes.",
|
|
)
|
|
parser.add_argument(
|
|
"--keep-only-idea-products",
|
|
action="store_true",
|
|
default=False,
|
|
help="Delete every top-level product except the five launch idea products.",
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
dry_run: bool = options["dry_run"]
|
|
keep_only_ideas: bool = options["keep_only_idea_products"]
|
|
|
|
Product = get_model("catalogue", "Product")
|
|
|
|
product_filter = _build_demo_text_filter(("title",))
|
|
product_filter |= Q(slug__in=DEMO_PAGE_SLUGS)
|
|
|
|
top_level_products = Product.objects.filter(parent__isnull=True)
|
|
if keep_only_ideas:
|
|
products_to_delete = top_level_products.exclude(
|
|
title__in=IDEA_PRODUCT_TITLES
|
|
)
|
|
else:
|
|
products_to_delete = top_level_products.filter(product_filter).exclude(
|
|
title__in=IDEA_PRODUCT_TITLES
|
|
)
|
|
|
|
pages_to_delete = (
|
|
Page.objects.live()
|
|
.public()
|
|
.filter(depth__gt=2)
|
|
.filter(
|
|
Q(slug__in=DEMO_PAGE_SLUGS) | _build_demo_text_filter(("title", "slug"))
|
|
)
|
|
)
|
|
|
|
product_preview = list(products_to_delete.values_list("id", "title")[:30])
|
|
page_preview = list(pages_to_delete.values_list("id", "slug", "title")[:30])
|
|
|
|
self.stdout.write(
|
|
f"Products matched for deletion: {products_to_delete.count()}"
|
|
)
|
|
for item in product_preview:
|
|
self.stdout.write(f" - product#{item[0]}: {item[1]}")
|
|
if products_to_delete.count() > len(product_preview):
|
|
self.stdout.write(" - ...")
|
|
|
|
self.stdout.write(f"Pages matched for deletion: {pages_to_delete.count()}")
|
|
for item in page_preview:
|
|
self.stdout.write(f" - page#{item[0]}: /{item[1]}/ ({item[2]})")
|
|
if pages_to_delete.count() > len(page_preview):
|
|
self.stdout.write(" - ...")
|
|
|
|
if dry_run:
|
|
self.stdout.write(
|
|
self.style.WARNING("Dry run completed. No data was deleted.")
|
|
)
|
|
return
|
|
|
|
deleted_products = products_to_delete.count()
|
|
deleted_pages = pages_to_delete.count()
|
|
|
|
products_to_delete.delete()
|
|
for page in pages_to_delete:
|
|
# Use Wagtail's delete to remove descendants and revisions safely.
|
|
page.delete()
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"Demo purge complete. Deleted products={deleted_products}, pages={deleted_pages}."
|
|
)
|
|
)
|