4 KiB
MPS-14: Shop Environment — Dev & Stage Shops
Summary
Add an environment column to Shop so owners can create development and staging
shops for practicing thumbnails, videos, product staging, and testing checkout
flows. Non-production shops are fully independent (no sync to production) and
invisible to the public.
Every paid production shop seat includes 2 free dev/stage shops.
Model Changes
Shop model (models/shop.py)
Add column:
environment = Column(BigInteger, default=0)
# 0 = production (default)
# 1 = staging
# 2 = development
Add properties:
@property
def is_production(self):
return self.environment == 0
@property
def is_staging(self):
return self.environment == 1
@property
def is_development(self):
return self.environment == 2
@property
def is_non_production(self):
return self.environment != 0
@property
def environment_label(self):
return {0: "Production", 1: "Staging", 2: "Development"}.get(self.environment, "Production")
Migration
- Add
environmentcolumn tomps_shop(BigInteger, server_default="0", NOT NULL) - Idempotent guard with
_column_exists
Exclusion Points
Non-production shops (environment != 0) must be excluded from:
- Search results —
views/shop.py:129search()— filter query toshop.environment == 0 - Discovery ring —
models/shop.py:656_build_discovery_ring()— already scoped to shop's own products, but ring should not be reforged for non-production shops - RSS/Atom/Sitemap —
views/feeds.py:204,222,242— skip non-production shops entirely (return empty feed or 404) - Subscription digests —
models/shop_subscription.pyquery helpers — filter byshop.environment == 0 - Public shop listings — any place shops are listed publicly
Non-production shops still fully function for the owner: product upload, cart, checkout, settings, analytics — all work normally.
Banner
Display a persistent environment banner for non-production shops, similar to
the ribbon pattern. In base.j2 or snippets/ribbon.j2:
{% if request.shop and request.shop.is_non_production %}
<div class="environment-banner environment-{{ request.shop.environment_label|lower }}">
{{ request.shop.environment_label }} Shop
</div>
{% endif %}
CSS in common.css:
- Staging banner: amber/yellow background
- Development banner: blue/purple background
- Always visible, not dismissible
Settings UI
Add environment selector to shop settings. New form section environment-settings
or add to existing shop-settings section.
Radio buttons or select:
- Production (default)
- Staging
- Development
Changing from non-production to production should warn: "This shop will become publicly visible."
Shop Creation Flow
On /s/new, add an optional environment selector (default: production).
This lets users create dev/stage shops directly during onboarding.
Enforcement: 2 Free Dev/Stage Per Production Shop
Each paid production shop seat entitles the user to 2 free non-production shops.
Counting logic
def non_production_shop_allowance(user):
production_count = sum(1 for s in user.shops if s.is_production)
allowed_non_production = production_count * 2
current_non_production = sum(1 for s in user.shops if s.is_non_production)
return allowed_non_production - current_non_production
Enforcement points
- Shop creation (
views/shop.pyPOST/s/new): if environment != 0 and allowance <= 0, flash error and reject - Environment change (
views/shop.pysettings POST): if changing to non-production and allowance <= 0, reject; if changing to production, always allow
Tests
- Unit:
test_models.py— environment properties, environment_label - Integration:
test_integration.py— non-production shop excluded from discovery ring query, allowance counting - Functional:
test_functional.py— create dev shop, verify search excludes it, verify banner appears, verify allowance enforcement