Stores Domain - Serializers Documentation
Source: api/nextango/apps/stores/serializers.py
Last Updated: 2026-07-09
Overview
The Stores serializers transform between Django model fields and Sanity's camelCase nested payloads. The API surface is snake_case with a handful of computed camelCase blocks (settings, regulatedCompliance) that mirror the Sanity schema.
Serializers in this module:
- TaxJurisdictionSerializer: tax jurisdiction data with camelCase mapping
- SiteSerializer: Django-owned Site REST surface (no Sanity webhook feed)
- StoreSerializer: conditional webhook processing, settings flattening, compliance-profile bridging
- ZoneSerializer, ComplianceCheckSerializer, PlanogramSerializer: lightweight model serializers
TaxJurisdictionSerializer
Model: TaxJurisdiction
Source: serializers.py:6-47
| Field | Access |
|---|---|
id | read-only |
name | writable |
tax_rates_data | writable |
total_rate | read-only (recalculated by the model on save) |
effective_date | writable |
notes | writable |
is_exempt | writable |
sanity_id | writable |
to_internal_value accepts both shapes: the Sanity webhook camelCase keys (taxRates to tax_rates_data, effectiveDate to effective_date, isExempt to is_exempt) and the direct snake_case keys used by DRF API clients (name, notes, is_exempt, tax_rates_data, effective_date). Only keys present in the payload are processed; everything else keeps its current value.
SiteSerializer
Model: Site
Source: serializers.py:49-115
Serves the Django-owned /sites/ REST surface (SiteViewSet: branding and config lookup by domain). Site no longer syncs to or from Sanity; the camelCase handling in to_internal_value (siteCode, isActive, isDefault, plus nested address/branding/seo objects) only tolerates legacy payload shapes and is not fed by a Sanity webhook.
| Field | Access |
|---|---|
site_code, name, description, is_active, is_default | writable |
address_data, branding_data, domain, seo_data | writable |
domain_verified, ssl_status, nginx_status | writable |
id, sanity_id, sync metadata (last_synced_at, sync_enabled, sync_status), created_at, updated_at | read-only |
update() (serializers.py:103-115) enforces the single-default invariant: when a site becomes the default, is_default is cleared on every other site via a queryset .update() (which deliberately does not fire post_save signals on the others).
StoreSerializer
Model: Store
Source: serializers.py:118-694
Handles both API clients and inbound Sanity webhook payloads. Its design principles: process only the fields present in the payload (partial updates preserve everything else), flatten the Sanity settings object into individual model fields where one exists, resolve Sanity references to Django FKs, and bridge the regulated-compliance block to the store's compliance profile.
Fields
| Field | Access | Notes |
|---|---|---|
store_code, name, address_data, phone | writable | |
slug | read-only | Django-derived on save, never client-authored (serializers.py:384-386) |
site | writable | Dormant C4 seam; not populated from Sanity payloads |
default_currency, is_active, is_default, timezone, accepts_online_returns, requires_appointments | writable | |
operating_hours | writable | validated array |
manager_ids | computed on read | Sanity user refs resolved to Django User UUID strings (get_manager_ids, serializers.py:324-344); unresolved refs are dropped with a warning |
settings | computed on read | see below |
settings_data | write-only | carries the full inbound settings object |
regulatedCompliance | computed on read | see below |
regulated_compliance_data | write-only | carries the inbound regulatedCompliance block |
tax_jurisdiction | writable | FK by sanity_id |
tax_jurisdiction_name, total_tax_rate, tax_rate | read-only | pulled from the related jurisdiction |
id, sanity_id, sync metadata, created_at, updated_at | read-only |
The computed settings object
get_settings (serializers.py:299-322) returns the stored settings JSONField with the moved compliance keys stripped, plus computed defaults (isActive, timezone, defaultCurrency, acceptsOnlineReturns, requiresAppointments) and a taxJurisdiction reference object when a jurisdiction is assigned. The individual model fields are the source of truth; settings output reconstructs the Sanity shape.
Regulated compliance handling
The regulated-retail policy block (age gate, credential types, permits) is authored in Sanity at top-level store.regulatedCompliance, off the agnostic storeSettings object.
- Read:
get_regulatedCompliance(serializers.py:231-246) sources the block from the store's compliance profile and returns{'requireAgeGate', 'requiredCredentialTypes', 'secondaryReviewCredentialTypes', 'permits'}, orNonefor a store with no profile (non-regulated vertical). - Write:
create()andupdate()extract compliance updates by dual-read (_extract_compliance_updates,serializers.py:248-278): the newregulatedComplianceblock is preferred, with a legacy fallback to the same keys undersettingsso existing Sanity content is never orphaned. Extracted updates are written to the profile viaget_active_vertical_provider().upsert_store_profile(), not to the Store row. - Scrub: the four moved keys (
_MOVED_COMPLIANCE_KEYS,serializers.py:283-288) are stripped fromsettingsbefore storage and before output, so they never ridesettingspost-move. - Validation:
_validate_credential_policy_fields(serializers.py:150-170) rejects non-list or non-string-item credential-type payloads._validate_permits(serializers.py:174-229) rejects malformed permit arrays: each entry must be an object with a string_key, apermitType, astatusin{active, suspended, revoked}, and a payload that passes the compliance permit validators.
to_internal_value transformations
Source: serializers.py:456-574
Only keys present in the payload are transformed:
- Top-level camelCase mappings:
storeCode,managerIds,operatingHours,isDefault. - Direct fields:
name,phone,is_default. addressobject: recursive camelCase-to-snake_case transformation intoaddress_data.settingsobject: validated (credential policy + permits), stored whole viasettings_data, and flattened to the individual fieldstimezone,default_currency,accepts_online_returns,requires_appointments,is_active.settings.taxJurisdiction._ref: resolved through the shared serializer reference resolver to aTaxJurisdictionand assigned bysanity_id. An unresolvable reference logs a warning and leaves the field unset.regulatedComplianceobject: validated and stashed inregulated_compliance_data.slugis not accepted from inbound payloads;sitereferences are not resolved from Sanity (sync severed).
Validation methods
validate_addressrequiresstreet,city,state,country, and a postal code under eitherpostalCodeorpostal_code.validate_operating_hoursrequires an array of objects each carryingday,open, andclose.validate_manager_idsrequires an array of strings or{'_ref': ...}reference objects.
update() default-store invariant
Source: serializers.py:604-640
When a store becomes the default, is_default is cleared on every other default store using per-instance .save(update_fields=['is_default']) so post_save fires and the change syncs outbound to Sanity. Compare SiteSerializer.update, which deliberately suppresses signals for the same operation.
ZoneSerializer
Source: serializers.py:696-701
Exposes zones.Zone with fields id, sanity_id, store_location, store_name (read-only, from store_location.name), zone_name, zone_type, is_shown. Note: the stores ViewSets use the zones app's own ZoneSerializer (stores/views.py:14); this local one is not wired into the stores views.
ComplianceCheckSerializer
Source: serializers.py:703-711
Exposes ComplianceCheck with zone_name (from zone.zone_name) and employee_name (from employee.name) as read-only computed fields. Writable: check_id, zone, employee, status, checklist_results, notes, photos. Read-only: timestamp.
PlanogramSerializer
Source: serializers.py:713-716
Exposes zones.Planogram with fields id, sanity_id, planogram_name, version. As with ZoneSerializer, the stores views import the zones app's PlanogramSerializer instead; this local one is not wired into the stores views.
Related Documentation
- Models
- Views
- Signals
- Integrations for the outbound sync counterpart (
store_sync)