Stores Domain - Views & API Endpoints Documentation
Source: api/nextango/apps/stores/views.py and api/nextango/apps/stores/viewsets/tax_jurisdiction_viewset.py
Last Updated: 2026-07-09
Overview
The Stores endpoints are served by root-router registrations in the root URLconf (api/nextango/urls.py:60-63):
| Route | ViewSet |
|---|---|
/sites/ | SiteViewSet |
/stores/ | StoreViewSet |
/tax-jurisdictions/ | TaxJurisdictionViewSet |
/compliance-checks/ | ComplianceCheckViewSet |
The app-level router in stores/urls.py is not included in the root URLconf, so the paths above are the only live Stores routes. Zone and planogram endpoints are served by the zones app at /zones/ and /planograms/ (root router, nextango/urls.py:66-72), not by the stores-namespace viewsets.
None of these ViewSets declares permission_classes, so all resolve to the platform default: IsActiveUser in production, AllowAny when DEBUG is on (settings/base.py:251-253).
SiteViewSet
Purpose: CRUD for the Django-owned Site model; the storefront's branding/config lookup by domain.
Route: /sites/
Source: views.py:18-36
- Queryset is filtered to
is_active=Trueand ordered default-first (-is_default,name), so inactive sites are not served. - Filterable via
?domain=,?is_active=,?is_default=(filterset_fields). - Used by the Next.js storefront's site resolution at SSR time, e.g.
GET /sites/?domain=downtown.myshop.com, and by internal management.
Standard ModelViewSet actions apply (list, retrieve by pk, create, update, partial update, destroy). Setting is_default: true on one site auto-clears it on the others (serializer invariant).
StoreViewSet
Purpose: Store location CRUD for dashboard administration, POS terminal store lookup, and the storefront store selector.
Route: /stores/
Source: views.py:39-151
Queryset and lookup
- List queryset:
select_related('tax_jurisdiction'), filtered tois_active=True, ordered-is_default,name. Inactive stores never appear in the list. lookup_field = 'store_code', with multi-lookup inget_object()(views.py:73-106): a UUID lookup value is tried first as the Djangoid, then assanity_id; a non-UUID value resolves bystore_code. Single-object resolution uses the unfiltered queryset, so retrieving an inactive-but-existing store returns the object (withis_active: false) rather than 404.
List (cached)
GET /stores/
Cached for 600 seconds per request path and query params; invalidated on any Store save or delete (see Signals). Returns the paginated StoreSerializer representation.
Retrieve (summary shape)
GET /stores/{store_code}/
Source: views.py:112-126
Overridden: returns StoreService.get_store_summary() instead of the serializer representation, so the retrieve response is the summary shape (identity fields plus employee_count, active_zones_count, total_zones_count, recent_compliance_checks_count), not the full Store object. The summary is cached 300 seconds per store. 404 with {"error": "Store not found"} when the store disappears between lookup and summary.
See Services for the exact response fields.
Dashboard home
GET /stores/{store_code}/dashboard_home/
Source: views.py:128-151
Custom detail action returning today's KPIs, top products, low-stock alerts, and recent transactions for the store. Delegates to DashboardService.get_dashboard_home() (60-second cache). Returns 500 with {"error": "Failed to load dashboard data"} on aggregation failure. Full response schema: Dashboard Home.
Create / Update / Delete
POST /stores/
PUT /stores/{store_code}/
PATCH /stores/{store_code}/
DELETE /stores/{store_code}/
Standard ModelViewSet behavior through StoreSerializer: partial updates only touch fields present in the payload; setting is_default: true clears the flag on other stores; slug is server-derived and rejected as input. Deletion can fail on FK constraints from dependent records and does not remove the Sanity document (outbound deletion is disabled; see Signals).
TaxJurisdictionViewSet
Purpose: CRUD for tax jurisdictions.
Route: /tax-jurisdictions/
Source: viewsets/tax_jurisdiction_viewset.py:9-28
lookup_field = 'sanity_id': detail routes address jurisdictions by their Sanity document ID, e.g.GET /tax-jurisdictions/taxJurisdiction-franklin-oh/.list()is overridden to serialize the full queryset directly and returns a bare array, bypassing the platform's page-number pagination.- Creating or updating with
tax_rates_datatriggers the model's automatictotal_raterecalculation. DELETEfails while any store references the jurisdiction (PROTECT).
GET /tax-jurisdictions/
POST /tax-jurisdictions/
GET /tax-jurisdictions/{sanity_id}/
PUT /tax-jurisdictions/{sanity_id}/
PATCH /tax-jurisdictions/{sanity_id}/
DELETE /tax-jurisdictions/{sanity_id}/
ComplianceCheckViewSet
Purpose: CRUD for compliance audit records.
Route: /compliance-checks/
Source: views.py:175-195
- Queryset prefetches
zoneandemployee(select_related). lookup_field = 'check_id': detail routes use the business identifier, e.g.GET /compliance-checks/CHK-2026-001/.timestampis read-only; a created check syncs outbound to Sanity asynchronously (see Signals).
GET /compliance-checks/
POST /compliance-checks/
GET /compliance-checks/{check_id}/
PUT /compliance-checks/{check_id}/
PATCH /compliance-checks/{check_id}/
DELETE /compliance-checks/{check_id}/
Stores-namespace Zone and Planogram ViewSets
views.py:153-217 defines lightweight ZoneViewSet and PlanogramViewSet classes registered only on the app-level router in stores/urls.py. Because that router is not included in the root URLconf, these viewsets are not reachable; the live /zones/ and /planograms/ routes are the zones app's full-featured viewsets. Documented here only to explain why the classes exist in this module.
Related Documentation
- Models
- Serializers
- Services
- Dashboard Home
- Zones for the live zone and planogram endpoints