Match banner brand, print splash, optional crop, and demo seed (#6)
Deploy Beta / unit-tests (push) Successful in 32s
Deploy Beta / docker (push) Successful in 32s
Deploy Beta / deploy-beta (push) Successful in 2m23s

## Summary
- Rebrand public site, portal, and emails from Ecoprint orange to the PRINTFORGE banner palette (navy / cyan / lime).
- Replace the loading splash with a CSS 3D-print animation; product uploads get a default-on smart crop + background filter checkbox.
- Add `manage.py seed_demo` for client walkthroughs on dev/beta only (refuses prod).

Closes #5.

## Test plan
- [ ] Public pages and buttons are cyan/lime, not orange
- [ ] Splash shows a printer building a model (or a static print if reduced motion)
- [ ] Product edit: smart-crop checked by default; uncheck stores the original photo
- [ ] `uv run python manage.py seed_demo` fills shop, contacts, leads, orders, campaigns
- [ ] `DJANGO_ENV=prod` seed_demo exits with an error

Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
2026-09-07 04:06:52 -07:00
parent 04e75ec7a3
commit 23a6035ba8
19 changed files with 34722 additions and 65 deletions
+42
View File
@@ -414,6 +414,8 @@ class ShopPortalTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Shop card preview")
self.assertContains(response, 'name="images"')
self.assertContains(response, 'name="smart_crop"')
self.assertContains(response, "Smart crop and background filter")
self.assertContains(response, 'name="stl"')
self.assertContains(response, "Add color")
@@ -512,6 +514,32 @@ class ShopPortalTests(TestCase):
b"".join(fetch.streaming_content), bytes(product.image.data)
)
def test_create_product_skips_smart_crop_when_unchecked(self):
raw = _tiny_png()
upload = SimpleUploadedFile("keep.png", raw, content_type="image/png")
with patch("shop.imaging.cutout_subject") as cut:
response = self.client.post(
reverse("shop_portal:product_new"),
{
"name": "Raw photo toy",
"sku": "RAW-1",
"price": "10.00",
"stock_qty": "1",
"fulfillment": "stocked",
"track_inventory": "on",
"smart_crop": "off",
"images": upload,
},
)
self.assertEqual(response.status_code, 302)
cut.assert_not_called()
product = Product.objects.get(sku="RAW-1")
stored = product.images.get().file
self.assertEqual(bytes(stored.data), raw)
self.assertEqual(stored.filename, "keep.png")
framed = Image.open(BytesIO(bytes(stored.data)))
self.assertEqual(framed.size, (8, 8))
def test_create_product_with_color_photos_and_shared_stl(self):
catalog = SimpleUploadedFile("card.png", _tiny_png(), content_type="image/png")
red_photo = SimpleUploadedFile("red.png", _tiny_png(), content_type="image/png")
@@ -582,6 +610,20 @@ class ShopPortalTests(TestCase):
self.assertTrue(bytes(stored.data))
self.assertEqual(stored.kind, StoredFile.Kind.PRODUCT_IMAGE)
def test_store_product_image_can_skip_smart_crop(self):
raw = _tiny_png()
with TemporaryUploadedFile("dot.png", "image/png", 0, "utf-8") as tmp:
tmp.write(raw)
tmp.seek(0)
with patch("shop.imaging.cutout_subject") as cut:
stored = store_product_image(
upload=tmp, user=self.user, smart_crop=False
)
cut.assert_not_called()
self.assertEqual(bytes(stored.data), raw)
self.assertEqual(stored.content_type, "image/png")
self.assertEqual(stored.filename, "dot.png")
def test_temporary_stl_upload_copied_into_database_then_unlinked(self):
with TemporaryUploadedFile(
"toy.stl", "application/octet-stream", 0, "utf-8"