generated from westfarn/web_django_template
Ship Print Forge shop site (colors, photos, 3D viewer) (#2)
## Summary - Rebrand the Django client template as Print Forge (`client_site` → `print_forge`) with shop + shipping enabled. - Product listings support multiple photos, color variants (shared price/description/STL, per-color stock and photos), and a photo-first / 3D-second gallery. - Public pages use 3D printer / printed-toy photography instead of leftover t-shirt mockups; beta CI deploys on `master`. Closes #1 Infra: [server-infra#27](ai_ml_operations/server-infra#27) (easy deploy beta). ## Test plan - [ ] Product page shows photo first, 3D model second; color swatches swap photos and stock - [ ] Portal can upload multiple photos and per-color qty/images; STL stays shared - [ ] Public home/about/gallery have no t-shirt mockups - [ ] `manage.py test` passes - [ ] After server-infra#27: beta deploy to `print-forge-preview.aimloperations.com` Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@ GROUP_ORDER = {
|
||||
"Outreach": 20,
|
||||
"Content": 30,
|
||||
"Social": 40,
|
||||
"Retail": 45,
|
||||
"Billing": 50,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,10 @@ 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("shop_portal:sales").startswith("/portal/shop/sales/"))
|
||||
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/"))
|
||||
|
||||
Reference in New Issue
Block a user