Stand up Print Forge as a 3D-printed toy shop with color variants and printer photography.

Replaces the client_site template branding, adds shop/shipping, and points beta CI at master for easy deploy.

Closes #1
This commit is contained in:
2026-09-06 08:28:04 -05:00
parent 8a97e3fbe2
commit b2029b6c0f
336 changed files with 9241 additions and 2537 deletions
@@ -0,0 +1,27 @@
# Generated by Django 6.1
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="storedfile",
name="kind",
field=models.CharField(
choices=[
("campaign_image", "Campaign image"),
("social_image", "Social image"),
("social_video", "Social video"),
("invoice_pdf", "Invoice PDF"),
("product_image", "Product image"),
],
default="campaign_image",
max_length=32,
),
),
]
@@ -0,0 +1,28 @@
# Generated by Django 6.1
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0002_storedfile_product_image"),
]
operations = [
migrations.AlterField(
model_name="storedfile",
name="kind",
field=models.CharField(
choices=[
("campaign_image", "Campaign image"),
("social_image", "Social image"),
("social_video", "Social video"),
("invoice_pdf", "Invoice PDF"),
("product_image", "Product image"),
("product_stl", "Product STL"),
],
default="campaign_image",
max_length=32,
),
),
]
+2
View File
@@ -29,6 +29,8 @@ class StoredFile(UUIDPrimaryKeyModel, TimeStampedModel):
SOCIAL_IMAGE = "social_image", "Social image"
SOCIAL_VIDEO = "social_video", "Social video"
INVOICE_PDF = "invoice_pdf", "Invoice PDF"
PRODUCT_IMAGE = "product_image", "Product image"
PRODUCT_STL = "product_stl", "Product STL"
kind = models.CharField(
max_length=32, choices=Kind.choices, default=Kind.CAMPAIGN_IMAGE
+1
View File
@@ -22,6 +22,7 @@ GROUP_ORDER = {
"Outreach": 20,
"Content": 30,
"Social": 40,
"Retail": 45,
"Billing": 50,
}
+39 -4
View File
@@ -10,7 +10,7 @@ from django.urls import reverse
def _read_settings_source() -> str:
return (Path(__file__).resolve().parent.parent / "client_site/settings/base.py").read_text()
return (Path(__file__).resolve().parent.parent / "print_forge/settings/base.py").read_text()
class FeatureFlagSettingsTests(SimpleTestCase):
@@ -22,16 +22,30 @@ class FeatureFlagSettingsTests(SimpleTestCase):
self.assertIn("FEATURE_PAYMENTS", src)
self.assertIn("FEATURE_SOCIAL", src)
self.assertIn("FEATURE_SOCIAL_AI", src)
self.assertIn("FEATURE_SHOP", src)
self.assertIn("FEATURE_POS_SYNC", src)
self.assertIn("FEATURE_EVENTS", src)
self.assertIn("FEATURE_SHIPPING", src)
self.assertIn("email_sms.apps.EmailSmsConfig", src)
self.assertIn("directmail.apps.DirectmailConfig", src)
self.assertIn("blog.apps.BlogConfig", src)
self.assertIn("payments.apps.PaymentsConfig", src)
self.assertIn("social_ai.apps.SocialAiConfig", src)
self.assertIn("shop.apps.ShopConfig", src)
self.assertIn("pos_sync.apps.PosSyncConfig", src)
self.assertIn("events.apps.EventsConfig", src)
self.assertIn("shipping.apps.ShippingConfig", src)
def test_payments_requires_email_sms_in_source(self):
src = _read_settings_source()
self.assertIn("FEATURE_PAYMENTS requires FEATURE_EMAIL_SMS", src)
self.assertIn("FEATURE_SOCIAL_AI requires FEATURE_SOCIAL", src)
self.assertIn("FEATURE_SHOP requires FEATURE_EMAIL_SMS", src)
self.assertIn("FEATURE_SHOP requires FEATURE_PAYMENTS", src)
self.assertIn("FEATURE_POS_SYNC requires FEATURE_SHOP", src)
self.assertIn("FEATURE_EVENTS requires FEATURE_EMAIL_SMS", src)
self.assertIn("FEATURE_EVENTS requires FEATURE_PAYMENTS", src)
self.assertIn("FEATURE_SHIPPING requires FEATURE_SHOP", src)
class AlwaysOnImportIsolationTests(SimpleTestCase):
@@ -44,6 +58,10 @@ class AlwaysOnImportIsolationTests(SimpleTestCase):
"payments",
"social",
"social_ai",
"shop",
"pos_sync",
"events",
"shipping",
}
ALWAYS_ON = [
@@ -52,7 +70,7 @@ class AlwaysOnImportIsolationTests(SimpleTestCase):
"core/management/commands/dispatch_due.py",
"core/shortener.py",
"core/campaign_utm.py",
"client_site/urls.py",
"print_forge/urls.py",
]
def test_no_module_level_optional_imports(self):
@@ -78,11 +96,22 @@ class AlwaysOnImportIsolationTests(SimpleTestCase):
class InstalledOptionalAppsTests(TestCase):
"""Dev defaults install optional apps so the template is a working seed."""
"""Dev defaults install optional apps so the catalog seed still tests."""
def test_dev_installs_catalog_apps(self):
labels = {c.label for c in apps.get_app_configs()}
for label in ("email_sms", "directmail", "blog", "payments", "social", "social_ai"):
for label in (
"email_sms",
"directmail",
"blog",
"payments",
"social",
"social_ai",
"shop",
"pos_sync",
"events",
"shipping",
):
self.assertIn(label, labels)
def test_optional_urls_resolve_when_installed(self):
@@ -92,3 +121,9 @@ class InstalledOptionalAppsTests(TestCase):
self.assertTrue(reverse("payments:invoice_list").startswith("/portal/payments/"))
self.assertTrue(reverse("social:composer").startswith("/portal/social/"))
self.assertTrue(reverse("social_ai:generate").startswith("/portal/social/api/generate/"))
self.assertTrue(reverse("shop:list").startswith("/shop/"))
self.assertTrue(reverse("shop_portal:product_list").startswith("/portal/shop/"))
self.assertTrue(reverse("pos_sync:event_list").startswith("/portal/pos/"))
self.assertTrue(reverse("events:list").startswith("/events/"))
self.assertTrue(reverse("events_portal:event_list").startswith("/portal/events/"))
self.assertTrue(reverse("shipping:shipment_list").startswith("/portal/shipping/"))