Port shopper accounts, reviews, tracking, and seed_demo into FEATURE_SHOP / FEATURE_SHIPPING #9

Open
opened 2026-09-07 04:42:36 -07:00 by westfarn · 1 comment
Owner

Port Print Forge shopper accounts, slim contact form, purchase reviews, Stripe-hosted cards, and shipment tracking into this template. These belong to catalog features, not the always-on CRM portal:

Capability Feature flag / app
Customer register/login/profile, order history, reviews, Stripe customer ids FEATURE_SHOPshop (+ accounts customer URLs only when shop is installed)
Password reset for shoppers same (FEATURE_SHOP)
Tracking status, scan events, EasyPost tracker webhook, paste-in Pirate Ship / carrier numbers FEATURE_SHIPPINGshipping
Slim public contact form (email + interest + message) always-on public, but keep CRM Contact name/phone/address for portal import

Do not copy Print Forge branding, copy, SKUs, or demo addresses. Keep template-neutral labels ("Shop", "Account", generic product names in seed).

Reference implementation: print_forge#7 / print_forge#8 (feature/7-customer-accounts-reviews). Print Forge also ships seed_demo; this template currently has no seed_demo command — port it and extend it (see §9).


Why

Print Forge added a guest-checkout shop with staff-only /accounts/login/. Buyers had no way to see orders, save a shipping address, or review products. Cards must stay on Stripe. Shipping labels already go through EasyPost (and Pirate Ship CSV); customers need tracking feedback without a second Shippo integration.


1. Slim public contact form (always-on public)

Current template (site/public/forms.py): first_name, last_name, email, phone, full address, interest, message.

Target

  • Keep: email (required), interest, message, optional reCAPTCHA.
  • Remove from the public form only: name, phone, address.
  • upsert_contact(..., merge_phone_address=False) so the form matches on email only.
  • Email consent only (no SMS/postcard consent from this form).
  • Admin notify email: email + message (drop name/phone/address blocks).
  • CRM Contact model unchanged — portal create/import still collect name/phone/address.
  • Update ContactFormMergeTests: merge-on-email; extra POST fields ignored.

Print Forge files to port (adapt interest choices back to template: quote/support/other, not toy-specific):

  • site/public/forms.py
  • site/public/views.py (contact)
  • site/public/templates/public/contact.html
  • site/public/notifications.py
  • site/public/templates/emails/contact_email.html + .txt
  • site/contacts/tests_merge.py

2. Customer accounts (gate on FEATURE_SHOP)

Staff portal login stays at /accounts/login/ (RealtorProfile). Shoppers are non-staff Django Users.

Model

accounts.CustomerProfile (OneToOne User):

  • phone
  • shipping_address JSON (same shape as Contact.make_postal_address)
  • stripe_customer_id (blank OK; never store PAN/CVC/payment method numbers)

URLs — only include when apps.is_installed("shop")

Mount /account/ from accounts.customer_urls in the project urls.py inside the existing if apps.is_installed("shop") block (do not always-on include it).

Path Name Purpose
/account/ account:home redirect to orders
/account/login/ account:login email + password, public base.html
/account/logout/ account:logout
/account/register/ account:register email as username
/account/profile/ account:profile name, phone, shipping
/account/password-reset/ see §3
/account/orders/ account:orders
/account/orders/<uuid>/ account:order_detail

Auth behavior

  • Register: username = email.lower(), create CustomerProfile, claim_orders_for_user (attach guest orders with matching email).
  • PortalLoginView: non-staff success → account:home.
  • PortalStaffMiddleware: authenticated non-staff hitting /portal/ (except /webhooks/) → account:home. Must exempt /portal/shop/webhooks/stripe/ and /portal/shipping/webhooks/easypost/.
  • Public nav: Sign in / Account only when shop enabled; Client portal link stays for staff.
  • UnderConstructionMiddleware: exempt /account/login, /account/logout, /account/register, /account/password-reset.
  • robots.txt: Disallow: /account/.
  • Portal tests that login() to /portal/ must create users with is_staff=True.

Stripe (no card storage)

In shop.services.create_checkout_session:

  • If order.user: ensure_stripe_customer → Checkout Session customer= + payment_intent_data.setup_future_usage="on_session".
  • Else: customer_email= as today.
  • Webhook checkout.session.completed: mark_paid(..., stripe_customer_id=session.customer) and persist id on CustomerProfile.
  • Profile copy: "Cards stay on Stripe. We never store card numbers."

Order model

shop.Order.user nullable FK SET_NULL, related_name="shop_orders". Checkout attaches the logged-in user and prefills profile shipping/name/email.

Print Forge files:

  • site/accounts/models.py (CustomerProfile)
  • site/accounts/forms.py, views.py, services.py, middleware.py, customer_urls.py
  • site/accounts/templates/accounts/{account_base,customer_login,register,profile}.html
  • site/shop/models.py (Order.user)
  • site/shop/services.py (create_order_from_cart(user=), ensure_stripe_customer, remember_stripe_customer)
  • site/shop/views.py (checkout prefill, account order views)
  • site/shop/templates/shop/account/, checkout.html, success.html
  • site/print_forge/urls.py → equivalent in template project urls: shop-gated include

3. Shopper password reset (required; not optional)

Print Forge register/login shipped without reset. This template port must include it so buyers are not locked out.

Use Django auth_views.PasswordResetView / PasswordResetDoneView / PasswordResetConfirmView / PasswordResetCompleteView under /account/, public base.html templates, email looking up User.email (same as username).

  • Login page: "Forgot password?" → account:password_reset
  • Tokens expire per Django defaults
  • Tests: POST reset for a shopper → mail.outbox has a link; confirm view sets a new password; staff portal login is unchanged
  • Dev: console/locmem email; prod: existing SMTP backend + DEFAULT_FROM_EMAIL

4. Purchase-gated reviews (FEATURE_SHOP)

shop.ProductReview:

  • FK product, user, order
  • rating 1–5 (MinValueValidator/MaxValueValidator + CheckConstraint)
  • optional title, body
  • UniqueConstraint(user, product) — one review per buyer per product

qualifying_order_for_review: paid or fulfilled order containing that product, matched by user or email.

Product detail: average + list; form only if logged-in buyer who has not reviewed; POST shop:review.

Print Forge: shop/models.py ProductReview, shop/services.py helpers, shop/views.py product_review, shop/templates/shop/detail.html, shop/public_urls.py.


5. Tracking (FEATURE_SHIPPING only)

Do not add Shippo as a second label buyer. EasyPost remains the shipping API. Pirate Ship stays CSV export. Tracking lookup uses EasyPost Tracker API, which works for labels bought in EasyPost and tracking numbers pasted from Pirate Ship / the carrier (USPS, UPS, FedEx, DHL).

shipping.Shipment fields

  • tracking_status (unknown, pre_transit, in_transit, out_for_delivery, delivered, available_for_pickup, return_to_sender, failure, cancelled, error)
  • tracking_url, tracker_id, tracking_events JSON, last_tracked_at

Services

  • public_tracking_url(carrier, number) fallback links
  • apply_tracker_payload / refresh_tracking (POST https://api.easypost.com/v2/trackers when EASYPOST_API_KEY set; stub pre_transit otherwise)
  • attach_tracking for portal paste-in
  • sync_open_tracking registered on register_dispatcher so dispatch_due refreshes labeled, not-yet-delivered rows
  • After buy_label, call refresh_tracking

Portal + customer UI

  • Shipment detail: paste tracking + carrier, refresh, scan table
  • CSRF-exempt POST /portal/shipping/webhooks/easypost/ (EASYPOST_WEBHOOK_SECRET optional header/?token=)
  • Customer order detail: show carrier/status/events; refresh if stale (>15 min) and not delivered
  • Portal order detail: tracking column

Settings / env

  • EASYPOST_WEBHOOK_SECRET= in .env.example and .env.prod.example (optional; do not add to required validate-env.sh unless shipping is on and you want it required)

Print Forge: site/shipping/models.py, services.py, views.py, urls.py, hooks.py, templates/shipping/detail.html.

If FEATURE_SHIPPING is false: no tracking UI, no webhook, no dispatcher. Orders still exist under shop.


6. seed_demomust ship in this template

This repo has no seed_demo today (site/core/management/commands/ is only dispatch_due). Print Forge added a tagged demo seeder (DEMO- / demo+) for client walkthroughs. Port that command (neutral copy, not Print Forge SKUs) and extend it for this ticket so a FEATURE_SHOP=true demo shows accounts/reviews and a FEATURE_SHIPPING=true demo shows tracking.

Print Forge source: site/core/management/commands/seed_demo.py + site/core/tests.py SeedDemoTests. After this ticket, Print Forge seed will also create shoppers/reviews/tracking — copy that extended behavior, not the old catalog-only seed.

Rules (keep from Print Forge)

  • Refuse DJANGO_ENV=prod.
  • Allowed on dev and beta.
  • Idempotent keys: DEMO- SKUs/order numbers, demo+…@example.com users/contacts, [demo-seed] notes.
  • --reset deletes only tagged demo rows (including demo shopper users), then reseeds.
  • No live Stripe, EasyPost, or outbound mail.
  • Every extra block behind apps.is_installed(...) so clients without shop/shipping still seed contacts/leads.

New rows when FEATURE_SHOP

  • Non-staff Users for a subset of demo contacts (e.g. demo+jordan@example.com), password documented in command help / stdout (strong enough for AUTH_PASSWORD_VALIDATORS, e.g. Demo-buyer-pass-123).
  • CustomerProfile with phone + shipping JSON copied from the matching Contact.
  • Order.user set on paid/fulfilled demo orders; leave at least one guest paid/open order (user=NULL) so claim-on-register can be demoed.
  • ProductReview rows (1–5) only for users who purchased that product.
  • --reset must delete User where username matches demo+*@example.com (profiles/reviews cascade).

New rows when FEATURE_SHIPPING

  • At least two labeled demo shipments:
    • Delivered: tracking number, tracking_status=delivered, tracking_url, 2+ tracking_events (facility → delivered).
    • In transit: tracking_status=in_transit, events, last_tracked_at.
  • Stub numbers only (no EasyPost API). Notes tagged [demo-seed].
  • Re-runs update tracking fields on existing demo shipments (not only defaults= on create).

Tests (SeedDemoTests)

  • Prod refuse; beta allowed; idempotent second run; --reset rebuilds.
  • If shop installed: demo shopper can login on account:login; order history shows linked DEMO-ORD-*; product detail shows seeded review; guest order still unlinked.
  • If shipping installed: demo shipment has tracking_status and events.
  • If shop not installed: command still succeeds; no /account/ routes required.

7. Tests (non-seed)

Port/adapt from Print Forge:

  • accounts/tests.py — register, profile, claim orders, customer cannot open portal
  • Password reset — see §3
  • shop — checkout attaches user + Stripe customer mock; review requires purchase; order history isolation
  • shipping — stub buy → pre_transit; attach_tracking; EasyPost webhook JSON → in_transit
  • Contact form — §1
  • Staff portal tests: is_staff=True

8. Out of scope

  • Custom AUTH_USER_MODEL
  • Storing cards or Stripe PaymentMethod ids locally
  • Shippo (or other) as a label provider
  • Wallet / Apple Pay UI beyond hosted Checkout
  • Changing staff RealtorProfile / /accounts/login/ purpose

9. Suggested implementation order

  1. Slim contact form + tests.
  2. CustomerProfile + shop-gated /account/ + staff middleware + portal test is_staff.
  3. Password reset.
  4. Order.user, Stripe customer on checkout, order history UI.
  5. ProductReview + product page.
  6. Shipment tracking fields, EasyPost tracker, webhook, customer/portal UI (shipping flag).
  7. Port + extend seed_demo + tests.
  8. Manual: shop off → no Sign in / /account/; shipping off → orders without tracking.

10. Acceptance

  • Shop off: no customer account URLs, no reviews, contact form still slim.
  • Shop on: register, login, reset password via email, profile shipping, order history, Stripe customer id only, 1–5 review after purchase.
  • Shipping on: EasyPost or pasted tracking appears on customer order; webhook + dispatch_due update status.
  • seed_demo (dev/beta) creates shoppers, reviews, tracking when those apps are installed; --reset is safe; prod refused.
Port Print Forge shopper accounts, slim contact form, purchase reviews, Stripe-hosted cards, and shipment tracking into this template. These belong to **catalog features**, not the always-on CRM portal: | Capability | Feature flag / app | |---|---| | Customer register/login/profile, order history, reviews, Stripe customer ids | `FEATURE_SHOP` → `shop` (+ `accounts` customer URLs only when shop is installed) | | Password reset for shoppers | same (`FEATURE_SHOP`) | | Tracking status, scan events, EasyPost tracker webhook, paste-in Pirate Ship / carrier numbers | `FEATURE_SHIPPING` → `shipping` | | Slim public contact form (email + interest + message) | always-on `public`, but keep CRM `Contact` name/phone/address for portal import | **Do not copy Print Forge branding, copy, SKUs, or demo addresses.** Keep template-neutral labels ("Shop", "Account", generic product names in seed). Reference implementation: [print_forge#7](https://git.aimloperations.com/ai_ml_operations/print_forge/issues/7) / [print_forge#8](https://git.aimloperations.com/ai_ml_operations/print_forge/pulls/8) (`feature/7-customer-accounts-reviews`). Print Forge also ships `seed_demo`; this template currently has **no** `seed_demo` command — port it and extend it (see §9). --- ## Why Print Forge added a guest-checkout shop with staff-only `/accounts/login/`. Buyers had no way to see orders, save a shipping address, or review products. Cards must stay on Stripe. Shipping labels already go through EasyPost (and Pirate Ship CSV); customers need tracking feedback without a second Shippo integration. --- ## 1. Slim public contact form (always-on `public`) **Current template** (`site/public/forms.py`): `first_name`, `last_name`, `email`, `phone`, full address, `interest`, `message`. **Target** - Keep: `email` (required), `interest`, `message`, optional reCAPTCHA. - Remove from the **public form only**: name, phone, address. - `upsert_contact(..., merge_phone_address=False)` so the form matches on email only. - Email consent only (no SMS/postcard consent from this form). - Admin notify email: email + message (drop name/phone/address blocks). - CRM `Contact` model **unchanged** — portal create/import still collect name/phone/address. - Update `ContactFormMergeTests`: merge-on-email; extra POST fields ignored. Print Forge files to port (adapt interest choices back to template: quote/support/other, not toy-specific): - `site/public/forms.py` - `site/public/views.py` (`contact`) - `site/public/templates/public/contact.html` - `site/public/notifications.py` - `site/public/templates/emails/contact_email.html` + `.txt` - `site/contacts/tests_merge.py` --- ## 2. Customer accounts (gate on `FEATURE_SHOP`) Staff portal login stays at `/accounts/login/` (`RealtorProfile`). Shoppers are **non-staff** Django `User`s. ### Model `accounts.CustomerProfile` (OneToOne `User`): - `phone` - `shipping_address` JSON (same shape as `Contact.make_postal_address`) - `stripe_customer_id` (blank OK; **never** store PAN/CVC/payment method numbers) ### URLs — only include when `apps.is_installed("shop")` Mount `/account/` from `accounts.customer_urls` in the project `urls.py` **inside** the existing `if apps.is_installed("shop")` block (do not always-on include it). | Path | Name | Purpose | |---|---|---| | `/account/` | `account:home` | redirect to orders | | `/account/login/` | `account:login` | email + password, public `base.html` | | `/account/logout/` | `account:logout` | | | `/account/register/` | `account:register` | email as username | | `/account/profile/` | `account:profile` | name, phone, shipping | | `/account/password-reset/` … | see §3 | | | `/account/orders/` | `account:orders` | | | `/account/orders/<uuid>/` | `account:order_detail` | | ### Auth behavior - Register: `username = email.lower()`, create `CustomerProfile`, `claim_orders_for_user` (attach guest orders with matching email). - `PortalLoginView`: non-staff success → `account:home`. - `PortalStaffMiddleware`: authenticated non-staff hitting `/portal/` (except `/webhooks/`) → `account:home`. **Must exempt** `/portal/shop/webhooks/stripe/` and `/portal/shipping/webhooks/easypost/`. - Public nav: Sign in / Account only when shop enabled; Client portal link stays for staff. - `UnderConstructionMiddleware`: exempt `/account/login`, `/account/logout`, `/account/register`, `/account/password-reset`. - `robots.txt`: `Disallow: /account/`. - Portal tests that `login()` to `/portal/` must create users with `is_staff=True`. ### Stripe (no card storage) In `shop.services.create_checkout_session`: - If `order.user`: `ensure_stripe_customer` → Checkout Session `customer=` + `payment_intent_data.setup_future_usage="on_session"`. - Else: `customer_email=` as today. - Webhook `checkout.session.completed`: `mark_paid(..., stripe_customer_id=session.customer)` and persist id on `CustomerProfile`. - Profile copy: "Cards stay on Stripe. We never store card numbers." ### Order model `shop.Order.user` nullable FK `SET_NULL`, `related_name="shop_orders"`. Checkout attaches the logged-in user and prefills profile shipping/name/email. Print Forge files: - `site/accounts/models.py` (`CustomerProfile`) - `site/accounts/forms.py`, `views.py`, `services.py`, `middleware.py`, `customer_urls.py` - `site/accounts/templates/accounts/{account_base,customer_login,register,profile}.html` - `site/shop/models.py` (`Order.user`) - `site/shop/services.py` (`create_order_from_cart(user=)`, `ensure_stripe_customer`, `remember_stripe_customer`) - `site/shop/views.py` (checkout prefill, account order views) - `site/shop/templates/shop/account/`, `checkout.html`, `success.html` - `site/print_forge/urls.py` → equivalent in template project urls: **shop-gated** include --- ## 3. Shopper password reset (required; not optional) Print Forge register/login shipped without reset. **This template port must include it** so buyers are not locked out. Use Django `auth_views.PasswordResetView` / `PasswordResetDoneView` / `PasswordResetConfirmView` / `PasswordResetCompleteView` under `/account/`, public `base.html` templates, email looking up `User.email` (same as username). - Login page: "Forgot password?" → `account:password_reset` - Tokens expire per Django defaults - Tests: POST reset for a shopper → `mail.outbox` has a link; confirm view sets a new password; staff portal login is unchanged - Dev: console/locmem email; prod: existing SMTP backend + `DEFAULT_FROM_EMAIL` --- ## 4. Purchase-gated reviews (`FEATURE_SHOP`) `shop.ProductReview`: - FK product, user, order - `rating` 1–5 (`MinValueValidator`/`MaxValueValidator` + `CheckConstraint`) - optional `title`, `body` - `UniqueConstraint(user, product)` — one review per buyer per product `qualifying_order_for_review`: paid or fulfilled order containing that product, matched by `user` or email. Product detail: average + list; form only if logged-in buyer who has not reviewed; POST `shop:review`. Print Forge: `shop/models.py` `ProductReview`, `shop/services.py` helpers, `shop/views.py` `product_review`, `shop/templates/shop/detail.html`, `shop/public_urls.py`. --- ## 5. Tracking (`FEATURE_SHIPPING` only) Do **not** add Shippo as a second label buyer. EasyPost remains the shipping API. Pirate Ship stays CSV export. Tracking lookup uses EasyPost **Tracker** API, which works for labels bought in EasyPost **and** tracking numbers pasted from Pirate Ship / the carrier (USPS, UPS, FedEx, DHL). ### `shipping.Shipment` fields - `tracking_status` (unknown, pre_transit, in_transit, out_for_delivery, delivered, available_for_pickup, return_to_sender, failure, cancelled, error) - `tracking_url`, `tracker_id`, `tracking_events` JSON, `last_tracked_at` ### Services - `public_tracking_url(carrier, number)` fallback links - `apply_tracker_payload` / `refresh_tracking` (POST `https://api.easypost.com/v2/trackers` when `EASYPOST_API_KEY` set; stub `pre_transit` otherwise) - `attach_tracking` for portal paste-in - `sync_open_tracking` registered on `register_dispatcher` so `dispatch_due` refreshes labeled, not-yet-delivered rows - After `buy_label`, call `refresh_tracking` ### Portal + customer UI - Shipment detail: paste tracking + carrier, refresh, scan table - CSRF-exempt `POST /portal/shipping/webhooks/easypost/` (`EASYPOST_WEBHOOK_SECRET` optional header/`?token=`) - Customer order detail: show carrier/status/events; refresh if stale (>15 min) and not delivered - Portal order detail: tracking column ### Settings / env - `EASYPOST_WEBHOOK_SECRET=` in `.env.example` and `.env.prod.example` (optional; do not add to required `validate-env.sh` unless shipping is on and you want it required) Print Forge: `site/shipping/models.py`, `services.py`, `views.py`, `urls.py`, `hooks.py`, `templates/shipping/detail.html`. If `FEATURE_SHIPPING` is false: no tracking UI, no webhook, no dispatcher. Orders still exist under shop. --- ## 6. `seed_demo` — **must ship in this template** This repo has **no** `seed_demo` today (`site/core/management/commands/` is only `dispatch_due`). Print Forge added a tagged demo seeder (`DEMO-` / `demo+`) for client walkthroughs. **Port that command** (neutral copy, not Print Forge SKUs) **and** extend it for this ticket so a `FEATURE_SHOP=true` demo shows accounts/reviews and a `FEATURE_SHIPPING=true` demo shows tracking. Print Forge source: `site/core/management/commands/seed_demo.py` + `site/core/tests.py` `SeedDemoTests`. After this ticket, Print Forge seed will also create shoppers/reviews/tracking — copy that extended behavior, not the old catalog-only seed. ### Rules (keep from Print Forge) - Refuse `DJANGO_ENV=prod`. - Allowed on `dev` and `beta`. - Idempotent keys: `DEMO-` SKUs/order numbers, `demo+…@example.com` users/contacts, `[demo-seed]` notes. - `--reset` deletes only tagged demo rows (including demo shopper users), then reseeds. - **No live Stripe, EasyPost, or outbound mail.** - Every extra block behind `apps.is_installed(...)` so clients without shop/shipping still seed contacts/leads. ### New rows when `FEATURE_SHOP` - Non-staff `User`s for a subset of demo contacts (e.g. `demo+jordan@example.com`), password documented in command help / stdout (strong enough for `AUTH_PASSWORD_VALIDATORS`, e.g. `Demo-buyer-pass-123`). - `CustomerProfile` with phone + shipping JSON copied from the matching `Contact`. - `Order.user` set on paid/fulfilled demo orders; leave at least one **guest** paid/open order (`user=NULL`) so claim-on-register can be demoed. - `ProductReview` rows (1–5) only for users who purchased that product. - `--reset` must delete `User` where `username` matches `demo+*@example.com` (profiles/reviews cascade). ### New rows when `FEATURE_SHIPPING` - At least two labeled demo shipments: - **Delivered**: tracking number, `tracking_status=delivered`, `tracking_url`, 2+ `tracking_events` (facility → delivered). - **In transit**: `tracking_status=in_transit`, events, `last_tracked_at`. - Stub numbers only (no EasyPost API). Notes tagged `[demo-seed]`. - Re-runs update tracking fields on existing demo shipments (not only `defaults=` on create). ### Tests (`SeedDemoTests`) - Prod refuse; beta allowed; idempotent second run; `--reset` rebuilds. - If shop installed: demo shopper can `login` on `account:login`; order history shows linked `DEMO-ORD-*`; product detail shows seeded review; guest order still unlinked. - If shipping installed: demo shipment has `tracking_status` and events. - If shop **not** installed: command still succeeds; no `/account/` routes required. --- ## 7. Tests (non-seed) Port/adapt from Print Forge: - `accounts/tests.py` — register, profile, claim orders, customer cannot open portal - Password reset — see §3 - `shop` — checkout attaches user + Stripe customer mock; review requires purchase; order history isolation - `shipping` — stub buy → `pre_transit`; `attach_tracking`; EasyPost webhook JSON → `in_transit` - Contact form — §1 - Staff portal tests: `is_staff=True` --- ## 8. Out of scope - Custom `AUTH_USER_MODEL` - Storing cards or Stripe PaymentMethod ids locally - Shippo (or other) as a label provider - Wallet / Apple Pay UI beyond hosted Checkout - Changing staff `RealtorProfile` / `/accounts/login/` purpose --- ## 9. Suggested implementation order 1. Slim contact form + tests. 2. `CustomerProfile` + shop-gated `/account/` + staff middleware + portal test `is_staff`. 3. Password reset. 4. `Order.user`, Stripe customer on checkout, order history UI. 5. `ProductReview` + product page. 6. Shipment tracking fields, EasyPost tracker, webhook, customer/portal UI (shipping flag). 7. Port + extend `seed_demo` + tests. 8. Manual: shop off → no Sign in / `/account/`; shipping off → orders without tracking. ## 10. Acceptance - [ ] Shop off: no customer account URLs, no reviews, contact form still slim. - [ ] Shop on: register, login, reset password via email, profile shipping, order history, Stripe customer id only, 1–5 review after purchase. - [ ] Shipping on: EasyPost or pasted tracking appears on customer order; webhook + `dispatch_due` update status. - [ ] `seed_demo` (dev/beta) creates shoppers, reviews, tracking when those apps are installed; `--reset` is safe; prod refused.
westfarn self-assigned this 2026-09-07 04:42:36 -07:00
Author
Owner

Print Forge source of truth for the extended seed_demo is now on feature/7-customer-accounts-reviews (PR ai_ml_operations/print_forge#8), not the older catalog-only seeder.

Port that command (neutral catalog/copy — do not copy Print Forge SKUs or toy names) plus:

Shoppers (FEATURE_SHOP)

Email Password Linked orders Review
demo+<slug>@example.com (subset of demo contacts) documented in command help / stdout; Print Forge uses Demo-buyer-pass-123 paid/fulfilled orders get Order.user 1–5 stars on a purchased SKU, body prefixed [demo-seed]
at least one guest contact with an OPEN/CANCELLED order no User user=NULL none — claim-on-register demo

--reset must delete non-staff Users whose username is demo+…@example.com after demo invoices (do not snapshot created_by before reset; prefer staff created_by, never a demo shopper).

Tracking (FEATURE_SHIPPING)

  • One delivered labeled shipment with 2+ tracking_events and a stub tracking number.
  • One in_transit labeled shipment with events + last_tracked_at.
  • Re-runs update fields on existing rows (not create-only defaults).
  • Notes tagged [demo-seed]. No EasyPost/Stripe/mail calls.

Password reset

Shopper reset is required on this ticket (§3). Print Forge now has /account/password-reset/ (Django auth views, Forgot password? on login). Copy those templates + tests when porting accounts.

Print Forge source of truth for the extended `seed_demo` is now on `feature/7-customer-accounts-reviews` (PR https://git.aimloperations.com/ai_ml_operations/print_forge/pulls/8), not the older catalog-only seeder. Port **that** command (neutral catalog/copy — do not copy Print Forge SKUs or toy names) plus: ### Shoppers (`FEATURE_SHOP`) | Email | Password | Linked orders | Review | |---|---|---|---| | `demo+<slug>@example.com` (subset of demo contacts) | documented in command help / stdout; Print Forge uses `Demo-buyer-pass-123` | paid/fulfilled orders get `Order.user` | 1–5 stars on a purchased SKU, body prefixed `[demo-seed]` | | at least one guest contact with an OPEN/CANCELLED order | no User | `user=NULL` | none — claim-on-register demo | `--reset` must delete non-staff `User`s whose username is `demo+…@example.com` **after** demo invoices (do not snapshot `created_by` before reset; prefer staff `created_by`, never a demo shopper). ### Tracking (`FEATURE_SHIPPING`) - One **delivered** labeled shipment with 2+ `tracking_events` and a stub tracking number. - One **in_transit** labeled shipment with events + `last_tracked_at`. - Re-runs update fields on existing rows (not create-only defaults). - Notes tagged `[demo-seed]`. No EasyPost/Stripe/mail calls. ### Password reset Shopper reset is required on this ticket (§3). Print Forge now has `/account/password-reset/` (Django auth views, `Forgot password?` on login). Copy those templates + tests when porting accounts.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: westfarn/web_django_template#9