make_post_sell/docs/tickets/mps-14.md

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 environment column to mps_shop (BigInteger, server_default="0", NOT NULL)
  • Idempotent guard with _column_exists

Exclusion Points

Non-production shops (environment != 0) must be excluded from:

  1. Search resultsviews/shop.py:129 search() — filter query to shop.environment == 0
  2. Discovery ringmodels/shop.py:656 _build_discovery_ring() — already scoped to shop's own products, but ring should not be reforged for non-production shops
  3. RSS/Atom/Sitemapviews/feeds.py:204,222,242 — skip non-production shops entirely (return empty feed or 404)
  4. Subscription digestsmodels/shop_subscription.py query helpers — filter by shop.environment == 0
  5. 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.py POST /s/new): if environment != 0 and allowance <= 0, flash error and reject
  • Environment change (views/shop.py settings 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