Stores Domain - Signals Documentation

Source: api/nextango/apps/stores/signals.py Last Updated: 2026-07-09

Overview

Stores signals cover four concerns: slug derivation before save, outbound Sanity sync for Store, TaxJurisdiction, and ComplianceCheck, response-cache invalidation, and inventory backfill for newly created stores.

There is no Site signal. The Site outbound sync signal was removed in the store-as-url minimal reversal; Site is Django-owned and does not sync to Sanity in either direction (signals.py:201-205).

derive_store_slug_on_save

Trigger: pre_save on Store. Synchronous.

Source: signals.py:25-52

Guarantees every store has a URL-routable slug before it is saved. When slug is empty, derives one from the store name (falling back to store_code, then a short unique token) via stores.slug_utils.derive_store_slug, checking uniqueness against the live table; the DB unique constraint is the backstop under concurrency. An already-set slug is respected, so the derivation only fills missing values.

Because this runs on pre_save, it covers every creation path that goes through Model.save(): ORM, admin, inbound Sanity sync via serializer.save(), and data migrations using the real model. Bulk paths that bypass save() (bulk_create, QuerySet.update) are not covered, but no store creation path uses them.

sync_store_to_sanity

Trigger: post_save on Store. Synchronous (the sync call itself runs in-request through the sync orchestrator).

Source: signals.py:55-132

Pushes Store changes to Sanity in real time through the integrations sync framework (SyncOrchestrator with the store sync handler). Layered loop-prevention guards ensure a save that originated from an inbound Sanity webhook is not echoed back to Sanity: the thread-local sync context, an instance-level sync-source marker, webhook-processing and in-progress-sync flags, and rapid-successive-save detection. Load-testing mode (LOAD_TESTING_MODE=true) disables the sync entirely. Sync failures are logged and never block the save.

What the outbound document contains, including the top-level regulatedCompliance block sourced from the store's compliance profile, is defined by the store sync handler; see below.

Outbound compliance block

The outbound store document always emits regulatedCompliance with four agnostic keys, sourced from store.compliance_profile (integrations/sync/store_sync.py:538-552):

{
  "requireAgeGate": false,
  "requiredCredentialTypes": [],
  "secondaryReviewCredentialTypes": [],
  "permits": []
}

The block is always present (defaults when no profile exists) so Sanity patch-set semantics clear stale values when a profile is removed. The store.site reference and slug are intentionally not emitted (store_sync.py:554-555).

delete_store_from_sanity

Trigger: post_delete on Store. Synchronous.

Source: signals.py:135-165

Outbound Sanity deletion is disabled by design (P13 deletion-semantics standard): deleting a Store in Django logs the event but does not delete the Sanity document. Store lifecycle deletion is expected to originate in Sanity, not Django.

sync_taxjurisdiction_to_sanity

Trigger: post_save on TaxJurisdiction. Synchronous.

Source: signals.py:168-198

TaxJurisdiction has no standalone outbound document sync; it is referenced from Store. When a jurisdiction changes (for example, rates updated), the handler re-fires post_save for every store using that jurisdiction, causing each store to re-sync to Sanity with the updated tax information. The same webhook-origin guard applies, so inbound jurisdiction updates do not trigger the cascade.

invalidate_store_cache

Trigger: post_save and post_delete on Store. Synchronous.

Source: signals.py:214-219

Invalidates the cached /stores/ list responses whenever a store is created, updated, or deleted, so the 600-second response cache never serves stale store data after a change. See Core - Caching.

sync_compliance_check_to_sanity

Trigger: post_save on ComplianceCheck. Asynchronous: defers via transaction.on_commit, then dispatches a Celery task.

Source: signals.py:226-256

Syncs the check outbound to Sanity as a complianceCheck document. Deferring to after commit avoids partial-write races; the actual push happens in a background task, so API writes are not blocked by Sanity latency. Webhook-originated saves are skipped by the sync-context guard.

reconcile_new_store_inventory_on_create

Trigger: post_save on Store, create only. Asynchronous: defers via transaction.on_commit, then dispatches a Celery task.

Source: signals.py:263-286

When a store is created, queues a chunked backfill of InventoryLevel records for available-everywhere product variants, so a store created after products were published is correctly stocked instead of appearing empty. Runs only on create; updates never re-stock. Deferring to after commit ensures the task never races the uncommitted Store row.

Was this page helpful?